Migrations
June 2, 2026 · View on GitHub
v16.x to v17.0.0
Agent assistant (PXI)
Phoenix v17 ships PXI, an in-app assistant that helps you investigate traces, iterate on prompts, and navigate Phoenix without leaving the page you're on.
To turn PXI off for an entire deployment, set:
PHOENIX_DISABLE_AGENT_ASSISTANT=true
v15.x to v16.0.0
Sandbox provider allowlist (PHOENIX_ALLOWED_SANDBOX_PROVIDERS)
A new optional environment variable, PHOENIX_ALLOWED_SANDBOX_PROVIDERS, restricts which sandbox provider families are available for code-evaluator execution.
When unset, all providers remain available. Set to NONE to disable all sandbox providers.
To restrict the set of usable sandboxes, set the variable to a comma-separated list of family names:
PHOENIX_ALLOWED_SANDBOX_PROVIDERS=WASM,DENO
Accepted values: WASM, E2B, DAYTONA, VERCEL, DENO, MODAL (case-insensitive). Listing a family covers all of its language variants — for example, VERCEL covers both VERCEL_PYTHON and VERCEL_TYPESCRIPT.
Sandbox and code-evaluator permissions
v16.0.0 introduces sandbox configuration and code evaluators as new API surfaces. When authentication is enabled, access to these surfaces is governed by user role:
| API Surface | Operation | Admin | Member | Viewer |
|---|---|---|---|---|
| View code evaluator source | Read | ✅ | ✅ | ✅ |
| View code evaluator identity | Read | ✅ | ✅ | ✅ |
| View safe sandbox identity | Read | ✅ | ✅ | ✅ |
| View backend capability metadata | Read | ✅ | ✅ | ✅ |
| View sandbox config values | Read | ✅ | ✅ | ✅ |
| View provider config values | Read | ✅ | ✅ | ✅ |
| Create sandbox config | Write | ✅ | ❌ | ❌ |
| Update sandbox config | Write | ✅ | ❌ | ❌ |
| Delete sandbox config | Write | ✅ | ❌ | ❌ |
| Update sandbox provider | Write | ✅ | ❌ | ❌ |
| Create code evaluator | Write | ✅ | ✅ | ❌ |
| Patch code evaluator / rebind sandbox | Write | ✅ | ✅ | ❌ |
| Append code evaluator version | Write | ✅ | ✅ | ❌ |
| Create / update dataset code evaluator | Write | ✅ | ✅ | ❌ |
| Preview sandbox-backed code evaluator | Write | ✅ | ✅ | ❌ |
Sandbox configuration is admin-only. Code-evaluator authoring and preview are available to members and admins but not viewers. All read surfaces are unrestricted. When authentication is disabled, no role checks apply.
v14.x to v15.0.0
No action is required to upgrade from v14.x to v15.0.0.
v13.x to v14.0.0
Phoenix server CLI (phoenix / python -m phoenix.server.main)
The CLI is now subcommand-first: you choose serve or db, then pass options for that command. In v13.x, many flags could appear before the subcommand (for example --dev and --dev-vite-port before serve); those must now come after the subcommand.
Before:
python -m phoenix.server.main --dev --dev-vite-port 5173 serve
phoenix --host 0.0.0.0 --port 6006 serve
After:
python -m phoenix.server.main serve --dev --dev-vite-port 5173
phoenix serve --host 0.0.0.0 --port 6006
Pass --database-url on the subcommand that needs a database (or rely on PHOENIX_SQL_DATABASE_URL / the default).
python -m phoenix.server.main serve --database-url "postgresql://..."
python -m phoenix.server.main db migrate --database-url "postgresql://..."
db migrate is unchanged as a two-word subcommand: python -m phoenix.server.main db migrate (or phoenix db migrate).
Top-level --help only shows global usage; use phoenix serve --help, or phoenix db migrate --help for subcommand options.
PostgreSQL Driver: psycopg Removed
The psycopg driver has been removed. Phoenix now uses asyncpg as the sole PostgreSQL driver for both runtime queries and migrations. If you have psycopg installed only for Phoenix, it can be uninstalled.
The pg extra no longer includes psycopg:
pip install arize-phoenix[pg] # only installs asyncpg
No configuration changes are needed — PHOENIX_SQL_DATABASE_URL continues to work with the same postgresql:// connection strings.
Legacy Client Removed
The legacy phoenix.session.client.Client (accessed via px.Client()) has been removed. All client interactions now go through the arize-phoenix-client package.
pip install arize-phoenix-client
Before:
import phoenix as px
client = px.Client(endpoint="http://localhost:6006")
After:
from phoenix.client import Client
client = Client(base_url="http://localhost:6006")
The constructor parameter endpoint has been renamed to base_url. If omitted, it falls back to environment variables or http://localhost:6006. Attempting to import phoenix.session.client will raise an ImportError with migration guidance.
Client Method Changes
The new client organizes methods under resource namespaces (.spans, .datasets, .experiments) instead of flat methods on the client object. Return types have also changed — the new client uses TypedDicts generated from the OpenAPI schema rather than custom dataclasses.
Spans and Traces
Legacy (px.Client()) | New (phoenix.client.Client()) |
|---|---|
client.get_spans_dataframe() | client.spans.get_spans_dataframe() |
client.query_spans(query) | client.spans.get_spans_dataframe(query=query) |
client.get_evaluations() | client.spans.get_span_annotations() |
client.log_evaluations(evals) | client.spans.log_span_annotations(...) |
client.log_traces(trace_dataset) | client.spans.log_spans(...) |
The query_spans method accepted SpanQuery objects as positional args and could return either a single DataFrame or a list. The new get_spans_dataframe takes a single query keyword argument and always returns a single DataFrame. If you previously passed multiple queries, call get_spans_dataframe once per query and join the results with pandas.
Datasets
Legacy (px.Client()) | New (phoenix.client.Client()) |
|---|---|
client.get_dataset(id=..., name=...) | client.datasets.get_dataset(...) |
client.get_dataset_versions(dataset_id) | client.datasets.get_dataset_versions(...) |
client.upload_dataset(...) | client.datasets.create_dataset(...) |
The legacy get_dataset returned a phoenix.experiments.types.Dataset dataclass. The new client returns a Dataset object with the same conceptual fields (.id, .examples, .version_id) but backed by TypedDicts from the generated API schema.
Evaluations to Annotations
The concept formerly called "evaluations" is now called "annotations" throughout the new client. SpanEvaluations and log_evaluations are replaced:
Before:
from phoenix.trace import SpanEvaluations
px.Client().log_evaluations(
SpanEvaluations(eval_name="Hallucination", dataframe=results_df)
)
After:
from phoenix.client import Client
Client().spans.log_span_annotations_dataframe(
dataframe=results_df,
annotation_name="Hallucination",
annotator_kind="LLM",
)
Experiments
The experiments API has moved from phoenix.experiments to phoenix.client.experiments. The Example type used in task functions now comes from the generated API types.
Before:
from phoenix.experiments.types import Example
from phoenix.experiments.evaluators import create_evaluator
After:
from phoenix.client.__generated__.v1 import DatasetExample as Example
from phoenix.client.experiments import create_evaluator
run_experiment and evaluate_experiment now require keyword arguments for dataset, task, and experiment:
from phoenix.client.experiments import run_experiment, evaluate_experiment
experiment = run_experiment(dataset=dataset, task=task, evaluators=[...])
experiment = evaluate_experiment(experiment=experiment, evaluators=[...])
Removed Helper Functions
The pre-defined query helpers get_retrieved_documents, get_qa_with_reference, and get_called_tools (from phoenix.trace.dsl.helpers) have been removed. Use SpanQuery with client.spans.get_spans_dataframe(query=...) directly instead. The documentation for extracting data from spans has updated examples.
Removed Top-Level Convenience Functions
px.Client— usefrom phoenix.client import Clientinsteadpx.log_evaluations(...)— useclient.spans.log_span_annotations(...)insteadsession.query_spans(...)— useclient.spans.get_spans_dataframe(...)insteadsession.get_evaluations(...)— useclient.spans.get_span_annotations(...)instead
/v1/evaluations Endpoint Removed
The POST /v1/evaluations and GET /v1/evaluations REST endpoints have been removed. Use the annotations API instead, choosing the replacement by evaluation kind:
Ingestion (POST /v1/evaluations replacements)
| Evaluation kind | SDK replacement | REST replacement |
|---|---|---|
| span | client.spans.log_span_annotations_dataframe(...) | POST /v1/span_annotations |
| trace | client.traces.log_trace_annotations_dataframe(...) | POST /v1/trace_annotations |
| document | client.spans.log_document_annotations_dataframe(...) | POST /v1/document_annotations |
Retrieval (GET /v1/evaluations replacement)
The old GET /v1/evaluations only returned span annotations. Its replacement is client.spans.get_span_annotations(...).
Note: Trace annotation retrieval is available via
GET /projects/{id}/trace_annotations, but this was not part of the old evaluations endpoint — it is a new capability, not a direct replacement.
Before:
from phoenix.trace import SpanEvaluations
import phoenix as px
px.Client().log_evaluations(
SpanEvaluations(eval_name="Hallucination", dataframe=results_df)
)
After (span annotations):
from phoenix.client import Client
Client().spans.log_span_annotations_dataframe(
dataframe=results_df,
annotation_name="Hallucination",
annotator_kind="LLM",
)
After (trace annotations):
from phoenix.client import Client
Client().traces.log_trace_annotations_dataframe(
dataframe=results_df,
annotation_name="Hallucination",
annotator_kind="LLM",
)
After (document annotations):
from phoenix.client import Client
Client().spans.log_document_annotations_dataframe(
dataframe=results_df,
annotation_name="Relevance",
annotator_kind="LLM",
)
Removed dependencies: protobuf is no longer a direct dependency of the Phoenix server (it remains a transitive dependency via OpenTelemetry gRPC packages).
v12.x to v13.0.0
DB Index for Session ID
A partial index on spans.attributes for session id is added by migration. Migration run time is estimated at approximately 200 seconds per 100 GiB on a MacBook Pro. Cloud environments may take longer depending on instance size and I/O throughput.
Rolling deployments: If an existing Phoenix instance is still serving traffic while a new instance starts and runs migrations, the default CREATE INDEX acquires a table lock that blocks writes from the old instance. To avoid this, set the following environment variable before starting the new instance:
PHOENIX_MIGRATE_INDEX_CONCURRENTLY=true
This uses CREATE INDEX CONCURRENTLY, which avoids the table lock but is roughly 2-3x slower. The new instance still blocks on startup until the index build completes.
Large PostgreSQL databases (hundreds of GiB+): For very large spans tables, even CONCURRENTLY can take hours. To make the migration instant, pre-create a no-op index with the same name before upgrading (while the old version is still running):
Step 1 — Create a no-op index (instant, no table scan):
CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_spans_session_id
ON spans ((attributes #>> '{session,id}'))
WHERE false;
Step 2 — Upgrade Phoenix. The migration's IF NOT EXISTS sees the index name and skips.
Step 3 — Backfill the real index at your convenience (while the app is running):
DROP INDEX CONCURRENTLY IF EXISTS ix_spans_session_id;
CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_spans_session_id
ON spans (((attributes #>> '{session,id}')::varchar))
WHERE ((attributes #>> '{session,id}')::varchar) IS NOT NULL;
Note: On PostgreSQL, the index uses the #>> path operator (e.g., attributes #>> '{session,id}'). Queries using chained arrow operators (attributes -> 'session' ->> 'id') will not match the index. Phoenix's built-in query layer always uses the #>> form, so this only affects custom SQL queries run directly against the database.
Azure OpenAI v1 API
Azure OpenAI integration now uses the OpenAI v1 API, which simplifies configuration by eliminating explicit API versioning. The api_version parameter is no longer required—versioning is now handled implicitly by the v1 API endpoint.
This change requires openai>=2.14.0.
References:
AWS Bedrock Async Client
AWS Bedrock integration now uses aioboto3 instead of boto3 for fully async client operations. If you have boto3 installed for Bedrock support, you should install aioboto3 instead:
pip install aioboto3
Inferences, dimensions, embeddings, and pointcloud (UMAP)
Breaking change: Model inferences, dimensions, embeddings, and the pointcloud (UMAP) visualization have been removed from Phoenix, along with their GraphQL and REST APIs. The UI no longer includes the /model, /dimensions, or /embeddings routes.
v11.0.0 to v12.0.0
Instrumentation helpers are being moved to openinference-instrumentation.
Before:
from phoenix.trace import using_project
with using_project(project_name="change-project"):
...
After:
# openinference-instrumentation>=0.1.38
from openinference.instrumentation import dangerously_using_project
with dangerously_using_project(project_name="change-project"):
...
PostgreSQL Connection Environment Variables
Breaking Change: Specifying port numbers in PHOENIX_POSTGRES_HOST is no longer supported.
Before:
export PHOENIX_POSTGRES_HOST=localhost:5432
After:
export PHOENIX_POSTGRES_HOST=localhost
export PHOENIX_POSTGRES_PORT=5432
Impact: If you were setting PHOENIX_POSTGRES_HOST with a port (e.g., localhost:5432), you must now separate the host and port into their respective environment variables.
v10.0.0 to v11.0.0
This release is entirely encapsulated in a set of new tables. Have a nice release!
v9.x to v10.0.0
This release updates the users table in the database. Migration is expected to be quick.
No other breaking changes are included in this release.
v8.x to v9.0.0
This release migrates all annotations on spans and traces to a structure that supports multiple annotation values per entity (trace, span). This migration also changes the constraints for the tables. Because it operates on existing data, it may take a bit of time for the records to be fully migrated over. Phoenix migrates your data at boot so you may experience some slowness in the server coming up (depending on the amount of data you have). Please deploy v9.0 when your services can account for small amount of downtime.
Phoenix 9.0 also contains project-level retention policies. By default your pre-existing projects will point to a default retention policy of infinite retention so your data will no be affected.
Caution
This version bump migrates all your annotations to a new format. Do not restart the server while the migration is running. Ensure that the migration is complete. Restarting the server mid-migration could put the DB in a state that will require manual intervention.
v6.x to v7.0.0
Python Script to Populate Database Table For Sessions
Option I. Run the script via the installed module
This assumes the database up migration has been applied by the Phoenix application, i.e. the new table for sessions has been created. See Option II for how to manually apply the up migration.
Note
If you are using a PostgreSQL database, you will have to have the postgres extras installed via pip install arize-phoenix[pg].
python -m phoenix.db.migrations.data_migration_scripts.populate_project_sessions
Option II. Run the script from the repository (and apply the up migration manually).
Step 1. Clone the Phoenix repository.
git clone git@github.com:Arize-ai/phoenix.git
Step 2. Change directory to where alembic.ini is located.
cd phoenix/src/phoenix/db/
Step 3. Run alembic for database up migration. This creates the new table for sessions.
alembic upgrade head
Step 4. Run script to populate sessions table from spans.
python migrations/data_migration_scripts/populate_project_sessions.py
Environment Variables Used by the Script
SQLite example
export PHOENIX_SQL_DATABASE_URL=sqlite:////phoenix.db
PostgreSQL example
export PHOENIX_SQL_DATABASE_URL=postgresql://localhost:5432/postgres?username=postgres&password=postgres
Optionally for PostgreSQL, you can set the schema via the environment variable PHOENIX_SQL_DATABASE_SCHEMA.
v4.x to v5.0.0
Phoenix 5 introduces authentication. By default authentication is disabled and Phoenix will operate exactly as previous versions. Phoenix's authentication is designed to be as flexible as possible and can be adopted incrementally.
With authentication enabled, all API and UI access will be gated with credentials or API keys. Because of this, you will encounter some down time so please plan accordingly.
Phoenix 5 also fully de-couples instrumentation from the Phoenix package. All instrumentation should be installed and run via the OpenInference package. This allows for more flexibility in instrumentation and allows Phoenix to focus on its core functionality.
Enabling Authentication
To get started, simply set two environment variables for your deployment:
export PHOENIX_ENABLE_AUTH=True
export PHOENIX_SECRET=a-sufficiently-long-secret
Once these environment variables are set, Phoenix scaffold and admin login and the entire server will be protected. Log in as the admin user and create a system key to use with your application(s). All API keys should be added as headers to your requests via the Authorization header using the Bearer scheme.
For more details, please see the authentication setup guide.
Migrating to OpenInference
If you are using Phoenix's phoenix.trace modules for LlamaIndex, LangChain, or OpenAI, you will need to migrate to OpenInference. OpenInference is a separate set of packages that provides instrumentation for Phoenix. Phoenix 5 no longer supports LlamaIndex or LangChain instrumentation from the phoenix.trace module.
Phoenix now includes a phoenix.otel module that provides simplified setup for OpenTelemetry. See the phoenix.otel documentation for more details.
Before
from phoenix.trace.openai import OpenAIInstrumentor
OpenAIInstrumentor().instrument()
After
from openinference.instrumentation.openai import OpenAIInstrumentor
OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)
For an extensive list of supported instrumentation, please see the OpenInference
v3.x to v4.0.0
Migrating from legacy phoenix.Dataset to phoenix.Inferences
phoenix.Datasethas been renamed tophoenix.Inferencesphoenix.ExampleDatasethas been renamed tophoenix.ExampleInferences- All other methods and related functions and classes remain under the
phoenixnamespace
Old
from phoenix import Dataset, ExampleDataset
New
from phoenix import Inferences, ExampleInferences
Migrating from phoenix.experimental.evals to phoenix.evals
- Phoenix has now moved promoted the
evalsmodule out of experimental and can be installed as a separate extra.
Installing and Using the evals module
Old
pip install arize-phoenix[experimental]
from phoenix.experimental.evals import OpenAIModel
from phoenix.experimental.evals import llm_classify
model = OpenAIModel()
New
pip install arize-phoenix[evals]
from phoenix.evals import OpenAIModel
from phoenix.evals import llm_classify
Token counting has been removed evals
Old
from phoenix.experimental.evals import OpenAIModel
from phoenix.experimental.evals import processing # no longer supported in phoenix.evals
model = OpenAIModel()
model.max_context_size # no longer supported in phoenix.evals
model.get_token_count_from_messages(...) # no longer supported in phoenix.evals
model.get_tokens_from_text(...) # no longer supported in phoenix.evals
model.get_text_from_tokens(...) # no longer supported in phoenix.evals
BaseEvalModel has been renamed to BaseModel
When implementing a custom model wrapper for use with Phoenix, the base class has been renamed.
Old
from phoenix.experimental.evals.models import BaseEvalModel # renamed to BaseModel
New
from phoenix.evals.models import BaseModel
Some modules in phoenix.evals have been relocated and renamed
Old
from phoenix.experimental.evals.functions import classify, generate
from phoenix.experimental.evals.templates import default_templates, template
New
from phoenix.evals import classify, generate
from phoenix.evals import default_templates, templates
v2.x to v3.0.0
- v3.0.0 - Phoenix now exclusively uses OpenInference for instrumentation. OpenInference uses OpenTelemetry Protocol as the means for sending traces to a collector.
OpenAI Tracing
phoenix.trace.tracer.Tracer is defunct and should be removed.
Old (v2.x)
from phoenix.trace.exporter import HttpExporter # no longer necessary
from phoenix.trace.openai import OpenAIInstrumentor
from phoenix.trace.tracer import Tracer # no longer supported
tracer = Tracer(exporter=HttpExporter()) # no longer supported
OpenAIInstrumentor(tracer).instrument() # tracer argument is no longer supported
New (v3.0.0)
from phoenix.trace.openai import OpenAIInstrumentor
OpenAIInstrumentor().instrument()
Endpoint should be configured via environment variables PHOENIX_HOST, PHOENIX_PORT, or PHOENIX_COLLECTOR_ENDPOINT.
Old (v2.x)
from phoenix.trace.exporter import HttpExporter # no longer necessary
from phoenix.trace.openai import OpenAIInstrumentor
from phoenix.trace.tracer import Tracer # no longer supported
tracer = Tracer(exporter=HttpExporter(port=12345)) # no longer supported
OpenAIInstrumentor(tracer).instrument() # tracer argument is no longer supported
New (v3.0.0)
import os
from phoenix.trace.openai import OpenAIInstrumentor
os.environ["PHOENIX_PORT"] = "12345"
OpenAIInstrumentor().instrument()
Calling .get_spans() on a tracer is no longer supported. Use px.Client() to get the spans as a dataframe from Phoenix.
Old (v2.x)
from phoenix.trace.trace_dataset import TraceDataset # no longer necessary
from phoenix.trace.tracer import Tracer # no longer supported
tracer = Tracer() # no longer supported
TraceDataset.from_spans(tracer.get_spans()) # no longer supported
New (v3.0.0)
import phoenix as px
px.Client().get_spans_dataframe()
LlamaIndex Tracing
The standard way of instrumenting your LlamaIndex application remains the same between 2.x and 3.x:
from llama_index import set_global_handler
set_global_handler("arize_phoenix")
User should not pass Phoenix handler to a callback manager. Use the set_global_handler method above.
from llama_index.callbacks import CallbackManager # no longer necessary
from phoenix.trace.llama_index import OpenInferenceTraceCallbackHandler # no longer supported
callback_handler = OpenInferenceTraceCallbackHandler() # no longer supported
CallbackManager(handlers=[callback_handler]) # no longer supported
Endpoint should be configured via environment variables PHOENIX_HOST, PHOENIX_PORT, or PHOENIX_COLLECTOR_ENDPOINT.
Old (v2.x)
from llama_index import set_global_handler
from phoenix.trace.exporter import HttpExporter # no longer necessary
exporter = HttpExporter(host="127.0.0.1", port=6007) # no longer supported
set_global_handler("arize_phoenix", exporter=exporter)
New (v3.0.0)
import os
from llama_index import set_global_handler
os.environ["PHOENIX_HOST"] = "127.0.0.1"
os.environ["PHOENIX_PORT"] = "6007"
set_global_handler("arize_phoenix")
Calling .get_spans() on a handler is no longer supported. Use px.Client() to get the spans as a dataframe from Phoenix.
Old (v2.x)
from phoenix.trace.trace_dataset import TraceDataset # no longer necessary
from phoenix.trace.llama_index import OpenInferenceTraceCallbackHandler # no longer supported
handler = OpenInferenceTraceCallbackHandler() # no longer supported
TraceDataset.from_spans(handler.get_spans()) # .get_spans() no longer supported
New (v3.0.0)
import phoenix as px
px.Client().get_spans_dataframe()
LangChain Tracing
phoenix.trace.langchain.OpenInferenceTracer is defunct and should be removed.
Old (v2.x)
from phoenix.trace.langchain import LangChainInstrumentor, OpenInferenceTracer
tracer = OpenInferenceTracer() # no longer supported
LangChainInstrumentor(tracer).instrument() # tracer argument is no longer supported
New (v3.0.0)
from phoenix.trace.langchain import LangChainInstrumentor
LangChainInstrumentor().instrument()
Endpoint should be configured via environment variables PHOENIX_HOST, PHOENIX_PORT, or PHOENIX_COLLECTOR_ENDPOINT.
Old (v2.x)
from phoenix.trace.exporter import HttpExporter # no longer necessary
from phoenix.trace.langchain import LangChainInstrumentor, OpenInferenceTracer
tracer = OpenInferenceTracer(exporter=HttpExporter(port=12345)) # no longer supported
LangChainInstrumentor(tracer).instrument()
New (v3.0.0)
from phoenix.trace.langchain import LangChainInstrumentor
os.environ["PHOENIX_PORT"] = "12345"
LangChainInstrumentor().instrument()
Calling .get_spans() on a tracer is no longer supported. Use px.Client() to get the spans as a dataframe from Phoenix.
Old (v2.x)
from phoenix.trace.trace_dataset import TraceDataset # no longer necessary
from phoenix.trace.langchain import OpenInferenceTracer # no longer supported
tracer = OpenInferenceTracer() # no longer supported
TraceDataset.from_spans(tracer.get_spans()) # .get_spans() no longer supported
New (v3.0.0)
import phoenix as px
px.Client().get_spans_dataframe()
v0.x to v1.0.0
- v1.0.0 - Phoenix now exclusively supports the
openai>=1.0.0sdk. If you are using an older version of the OpenAI SDK, you can continue to usearize-phoenix==0.1.1. However, we recommend upgrading to the latest version of the OpenAI SDK as it contains many improvements. If you are using Phoenix with LlamaIndex and and LangChain, you will have to upgrade to the versions of these packages that support the OpenAI1.0.0SDK as well (llama-index>=0.8.64,langchain>=0.0.334)