Migrate from Arize Phoenix to Langfuse
This guide walks through migrating LLM observability from Arize Phoenix to Langfuse: tracing first (usually a same-day change), then datasets, experiments, and prompts.
Dynatrace announced a definitive agreement to acquire Arize (August 2026). Arize AX and Phoenix continue to operate as they do today. This guide is for teams that have already decided to move to Langfuse. For a product comparison, see Langfuse vs. Arize AX / Phoenix.
TL;DR: Keep your OpenInference instrumentors. Replace phoenix.otel.register() with the
Langfuse client, then call .instrument() on the same OpenInference package. Datasets, prompts, and evaluators are
recreated via the Langfuse SDK/API; experiments re-run against the migrated datasets.
Want help cutting over? Talk to us about Cloud (EU, US, Japan; HIPAA on Pro+) or self-host. Core features match Cloud; enterprise governance modules need an Enterprise license when self-hosted.
Why teams migrate
Teams tend to evaluate a Phoenix-to-Langfuse move for a few recurring reasons:
- Hosting and licensing model. Phoenix is source-available under the Elastic License 2.0 (as of August 2026). It is free to self-host on SQLite or PostgreSQL. Arize AX is a separate proprietary product; self-hosting AX is an Enterprise option. Langfuse's core is MIT-licensed, and self-hosting runs the same core product as Langfuse Cloud (EU, US, Japan; HIPAA on Pro+ with a signed BAA). Enterprise governance modules need an Enterprise license when self-hosted.
- Organizations and tenancy. A Phoenix instance is one tenant: projects live inside it, with instance-wide admin / member / viewer roles and OAuth2/LDAP. Langfuse adds organizations above projects, plus project-level RBAC and Enterprise SSO. Phoenix documents org/space multi-tenancy and SAML as Arize AX features.
- One product for the production loop. Phoenix covers tracing, evals, datasets, experiments, and prompts as a local-first app. Continuous evals on production traffic with alerting are an Arize AX feature. Langfuse keeps that loop on one MIT codebase: production traces feed datasets and experiments, managed evaluators can run on live traffic, and custom dashboards sit on the same data model.
Phoenix remains a capable tool for local, notebook, and self-hosted workflows, and if it serves your team well, there is no urgency to move. This guide is for teams that have decided to consolidate on Langfuse.
Concept mapping
Phoenix and Langfuse share most concepts, which keeps the mental migration small:
| Phoenix | Langfuse | Notes |
|---|---|---|
| Project | Project | Langfuse project is selected by API keys, not Phoenix project_name |
| Traces / spans (OpenInference) | Traces / observations | Same OTel foundation; spans map to observations |
| Datasets | Datasets | Versioned example collections in both |
| Experiments | Experiments / dataset runs | Runs linked to dataset items and scores |
| Evals (LLM and code) | Evaluators / LLM-as-a-judge + code evaluators | Both can score experiments and traces. AX productizes online evals with alerting |
| Playground | Playground | Replay and iterate on traced calls |
| Prompt Management | Prompt Management | Phoenix tags map to Langfuse labels |
Step 1: Keep OpenInference, replace register()
Phoenix apps typically send traces with phoenix.otel.register(). Keep your OpenInference
instrumentor (pip install langfuse). Replace register() with the Langfuse client, then
call .instrument() on the same package. Application LLM calls stay unchanged.
# Before
from phoenix.otel import register
register(project_name="my-llm-app", auto_instrument=True)
# After: your existing OpenInference instrumentor (OpenAI shown)
from langfuse import get_client
from openinference.instrumentation.openai import OpenAIInstrumentor # or AnthropicInstrumentor, LangChainInstrumentor, ...
get_client()
OpenAIInstrumentor().instrument()Set LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, and LANGFUSE_BASE_URL
(https://cloud.langfuse.com for EU; see Get started for
US, Japan, HIPAA, and self-hosted). Confirm LLM spans show as generations with input, output,
and token/cost.
Worked example: Tracing using the OpenInference SDK.
Phoenix tracing notes
Do not rely on a naive OTEL env-var swap while still calling register(). That helper prefers
PHOENIX_COLLECTOR_ENDPOINT, Bearer auth, and on Python defaults toward gRPC. Langfuse
accepts OTLP over HTTP with Basic auth.
In the application process, unset PHOENIX_COLLECTOR_ENDPOINT and PHOENIX_API_KEY, and
check for a .env.phoenix file (Phoenix SDKs auto-load it). Restart after the change. Langfuse
routes traces by API key, not Phoenix project_name. Call get_client() before
.instrument(). In scripts and notebooks, call langfuse.flush() before exit.
If you already export with a generic OTLPSpanExporter or a collector (not register()),
point that exporter at Langfuse:
OTEL_EXPORTER_OTLP_ENDPOINT="https://cloud.langfuse.com/api/public/otel" # EU
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic ${AUTH_STRING},x-langfuse-ingestion-version=4"
OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"AUTH_STRING is echo -n "pk-lf-...:sk-lf-..." | base64. The traces path is
/api/public/otel/v1/traces when the exporter needs a signal-specific URL.
Langfuse maps OpenTelemetry user.id / session.id (and langfuse.user.id /
langfuse.session.id) when those attributes are set. HIPAA Cloud
(https://hipaa.cloud.langfuse.com) is on Pro+ and needs a signed BAA before sending PHI.
Step 2: Migrate datasets
Export dataset examples with the Phoenix client and recreate them with the Langfuse SDK. The shapes are close: each example's input, expected output, and metadata map directly.
from phoenix.client import Client as PhoenixClient
from langfuse import get_client
phoenix = PhoenixClient(base_url="http://localhost:6006") # your Phoenix URL
source = phoenix.datasets.get_dataset(dataset="my-dataset")
langfuse = get_client()
langfuse.create_dataset(name="my-dataset") # skip if the dataset already exists
for example in source:
langfuse.create_dataset_item(
id=example["id"],
dataset_name="my-dataset",
input=example["input"],
expected_output=example["output"],
metadata=example.get("metadata") or {},
)Dataset import notes
This copies the latest snapshot, not the full version history. Pass base_url so the Phoenix
client does not need PHOENIX_COLLECTOR_ENDPOINT. Authenticated Phoenix instances still need
PHOENIX_API_KEY (or api_key=) in this export process only.
A stable id (the Phoenix example id) makes a retry safe. Extra fields (tags, split labels,
provenance) belong in metadata. Confirm item counts match after import.
Step 3: Recreate prompts and evaluators
Copy Phoenix chat messages into a Langfuse type="chat" prompt. Phoenix tags map to Langfuse
labels (for example production).
langfuse.create_prompt(
name="my-prompt",
type="chat",
prompt=[{"role": "user", "content": "Answer in one sentence: {{question}}"}],
labels=["production"],
)
prompt = langfuse.get_prompt("my-prompt", label="production", type="chat")
prompt.compile(question="Why is the ocean salty?")- Evaluators: recreate LLM evals as managed or custom LLM-as-a-judge evaluators, or as code evaluators where they were Python functions. Evaluators in Langfuse can target production traces continuously (online evaluation) in addition to experiment runs.
- Experiments: re-run against the migrated datasets via the experiments SDK or from the UI.
dataset = langfuse.get_dataset("my-dataset")
def task(*, item, **kwargs):
messages = prompt.compile(**item.input)
return run_your_app(messages) # same path as production
dataset.run_experiment(name="after-migration", task=task)Prompt template notes
Use type="text" only for a single string. Convert F-string placeholders ({question}) to
Langfuse variables ({{question}}). Simple Mustache variables already match; sections and
conditionals are not handled by Langfuse compile(). Put model, tools, and response-format
settings in prompt config if your app will read them; saving config does not apply them
automatically. Unresolved {{variables}} are left as-is. Do not import historical Phoenix
experiment scores.
Step 4: Decide what to do with historical traces
Most teams cut over fresh: old traces stay queryable in Phoenix, and Langfuse becomes the system of record from cutover day. Bulk-importing historical traces is rarely worth it beyond a few showcase traces.
If you need a few historical traces
There is no turn-key history import. Re-run those requests, or send OpenTelemetry via the
OTLP endpoint with x-langfuse-ingestion-version=4. Do
not use the deprecated ingestion API.
Validation checklist
-
phoenix.otel.register()is gone; traces arrive as generations with token/cost data - User and session attribution works if you set
user.id/session.id - Datasets migrated with item counts matching the source
- Prompts resolve by name+label from application code
- An experiment run exists on the migrated dataset
- Team access set up (org/project roles, SSO if applicable)
- Old exporter removed (or parallel window scheduled to end)
FAQ
Do I have to re-instrument my application?
No. Keep OpenInference. Replace phoenix.otel.register() with
Langfuse get_client() and the same instrumentor's
.instrument() call. Re-instrumenting with the Langfuse SDKs later is optional.
Does Langfuse support the frameworks Phoenix instrumented?
Langfuse has native integrations for the major frameworks (LangChain, LlamaIndex, OpenAI, Vercel AI SDK, and more) and accepts OpenInference instrumentation via OTLP, so framework coverage carries over rather than resetting. See the OpenInference cookbook.
Is Langfuse open source where Phoenix is?
Langfuse's core platform is MIT-licensed and self-hostable. Core features match Cloud; enterprise governance modules need an Enterprise license when self-hosted. Phoenix is licensed under the Elastic License 2.0 (source-available) as of August 2026. Check both licenses against your compliance requirements: ELv2 restricts offering the software as a managed service, which matters to some platform teams.
Can I evaluate old Phoenix traces in Langfuse?
Evaluators run on data in Langfuse, so historical evaluation requires those traces to exist in Langfuse first (see Step 4). The pragmatic path: start evaluators on new traffic at cutover and backfill only if a specific analysis demands it.
No traces, generic spans, or duplicate dataset items?
- No traces: confirm keys and
LANGFUSE_BASE_URLwere set beforeget_client(), the instrumentor ran before application calls,.env.phoenixis unset in the app process, and short-lived processes callflush(). - Generic spans: check that OpenInference is actually instrumenting the library you call.
LLM spans should carry
input.value/output.valueand model attributes. - Duplicate dataset items: pass a stable
id(the Phoenix example id works) oncreate_dataset_item().
Get help with the migration
Start on Langfuse Cloud or self-host. If you want a migration plan, talk to us.
Last edited