Switch from Langfuse V3 to Confident AI
Move your traces from Langfuse SDK v3 to Confident AI with confident-trace. Your spans, provider clients and frameworks stay untouched.
Overview
Langfuse Python SDK v3 already runs on OpenTelemetry. confident-trace speaks the same protocol, so the switch is an exporter swap: replace the Langfuse client wrapper with init(), rename the decorator, move get_client() updates onto update_trace(), and keep every span you emit today. Langfuse TypeScript SDK v3 still sends traces through the Langfuse ingestion API, so in TypeScript the switch replaces the Langfuse client with init(), span and updateTrace().
In this guide, you will:
- Pick a migration method: one copyable prompt for your coding agent, manual, or, in Python, adding a Confident AI exporter to the OpenTelemetry setup Langfuse already runs.
- Install confident-trace and replace the Langfuse keys with one
CONFIDENT_API_KEY. - Replace the client wrapper with
init()so auto instrumentation emits the spans. - Rename @observe to @span so every trace carries a name, an input and an output.
- Move trace and span updates onto
update_trace()andupdate_span()so users, sessions, tags and metadata stay on your traces. - See what changes once the traces land in Confident AI: evals on every trace, datasets curated from production, alerts when quality drops.
Prerequisites
- A Project API Key,
CONFIDENT_API_KEY(for exampleconfident_us_proj_...). Retrieve yours here. - Python 3.10+ for the Python SDK, or Node.js 22+ for the TypeScript SDK.
- Your existing Langfuse setup on SDK v3: Python
langfuse>=3,<4(runpip show langfuse) or TypeScriptlangfuse@3(runnpm ls langfuse). The before snippets below follow the Langfuse v3 SDKs, last released as Python 3.15.0 and TypeScript 3.39.2.
Choose a Migration Method
| Method | What you do | Langfuse SDK v3 |
|---|---|---|
| Coding-agent migration | Give your coding assistant one prompt and review its changes | Supported in Python and TypeScript. See Switch in One Prompt below |
| Manual migration | Make the SDK changes yourself, step by step | Supported in Python and TypeScript. See Switch Manually below |
| OpenTelemetry migration | Add a Confident AI exporter and keep your Langfuse instrumentation | Supported in Python. See Switch Using OpenTelemetry below. Not available in TypeScript: Langfuse TypeScript SDK v3 sends traces through the Langfuse ingestion API, not OpenTelemetry |
Switch in One Prompt
Give the prompt below to your coding assistant, such as Claude Code, Cursor or Codex, from the root of your repository. It finds your Langfuse integration, confirms the SDK version, makes the changes from this guide, and tells you what still needs your hands. Review the diff before you merge it.
Migrate this codebase from Langfuse to Confident AI using confident-trace.
1. Find every Langfuse integration: imports of langfuse, @observe, get_client(), start_as_current_span(), langfuse.openai, observeOpenAI, new Langfuse() clients, langfuse.trace(), trace.span(), trace.generation(), the Langfuse CallbackHandler, and LANGFUSE_* environment variables.
2. Confirm the installed Langfuse SDK version with pip show langfuse or npm ls langfuse. This migration is for Langfuse SDK v3. If the major version is different, stop and tell me which version you found.
3. Make the changes from https://www.confident-ai.com/docs/guides/migrate-to-langfuse-alternative-v3:
- install confident-trace and call init() once at startup
- replace langfuse.openai and observeOpenAI with the plain OpenAI client
- rename @observe() to @span, start_as_current_span() to span(...), and trace.span() to span in TypeScript
- move update_current_trace() and langfuse.trace() fields onto update_trace() (updateTrace() in TypeScript), with session_id as thread_id (threadId)
- move update_current_span(), update_current_generation() and span.update() onto update_span() (updateSpan())
- keep generations for model calls auto instrumentation does not cover as LLM spans with update_span()
- remove the Langfuse CallbackHandler from LangChain callbacks
- replace the LANGFUSE_* variables with CONFIDENT_API_KEY, and the Langfuse flush calls with flush() / shutdown()
4. Preserve existing tracing behavior where possible: the same span names, nesting, inputs, outputs, users, sessions, tags and metadata.
5. Do not delete Langfuse prompt, dataset or score calls. List them for me instead.
6. When you are done, summarize every change and list anything that needs manual configuration, such as setting CONFIDENT_API_KEY, the EU endpoint, or model calls you could not map.Switch Manually
Install confident-trace
One package. Provider SDKs stay optional peers.
CONFIDENT_API_KEYreplaces the Langfuse public key, secret key and host. For the EU region, also setCONFIDENT_OTEL_ENDPOINT=https://eu.otel.confident-ai.com/v1/traces.pip uninstall langfuse pip install confident-trace export CONFIDENT_API_KEY=... # replaces LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_HOSTnpm uninstall langfuse npm install confident-trace export CONFIDENT_API_KEY=... # replaces LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_BASEURL node --import confident-trace/register dist/index.jsReplace the client wrapper with init()
Drop the Langfuse wrapper around OpenAI. Auto instrumentation emits the spans from the plain client.
from langfuse.openai import openai response = openai.chat.completions.create( model="gpt-4.1-mini", messages=[{"role": "user", "content": "Hello"}], )from confident_trace import init from openai import OpenAI init() client = OpenAI() response = client.chat.completions.create( model="gpt-4.1-mini", messages=[{"role": "user", "content": "Hello"}], )import { observeOpenAI } from "langfuse"; import OpenAI from "openai"; const openai = observeOpenAI(new OpenAI()); const response = await openai.chat.completions.create({ model: "gpt-4.1-mini", messages: [{ role: "user", content: "Hello" }], });import { init } from "confident-trace"; import OpenAI from "openai"; const tracing = init(); const client = new OpenAI(); const response = await client.chat.completions.create({ model: "gpt-4.1-mini", messages: [{ role: "user", content: "Hello" }], });Rename @observe to @span
Same shape. Return values and exceptions pass through. In TypeScript, Langfuse v3 has no decorator, so a
trace.span()you opened and ended by hand becomes aspanfunction.from langfuse import observe @observe() def answer(question: str) -> str: return openai.chat.completions.create( model="gpt-4.1-mini", messages=[{"role": "user", "content": question}], ).choices[0].message.contentfrom confident_trace import span @span def answer(question: str) -> str: return client.chat.completions.create( model="gpt-4.1-mini", messages=[{"role": "user", "content": question}], ).choices[0].message.contentwith langfuse.start_as_current_span(name="...")becomeswith span("..."):.import type { LangfuseTraceClient } from "langfuse"; async function answer(trace: LangfuseTraceClient, question: string) { const span = trace.span({ name: "answer", input: question }); const output = await generateAnswer(question); span.end({ output }); return output; }import { span } from "confident-trace"; const answer = span({ name: "answer" }, async (question: string) => { return generateAnswer(question); });Move trace and span updates onto update_trace() and update_span()
get_client().update_current_trace()becomesupdate_trace(), andsession_idbecomesthread_id.update_current_span()andupdate_current_generation()becomeupdate_span(). In TypeScript, the fields you passed tolangfuse.trace()move ontoupdateTrace(), andspan.update()becomesupdateSpan().from langfuse import get_client, observe langfuse = get_client() @observe() def support_chat(question: str) -> str: langfuse.update_current_trace( name="support-chat", user_id="user-7", session_id="session-42", tags=["support"], metadata={"plan": "enterprise"}, ) langfuse.update_current_span(metadata={"region": "EU"}) return answer(question)from confident_trace import span, update_span, update_trace @span def support_chat(question: str) -> str: update_trace( name="support-chat", user_id="user-7", thread_id="session-42", tags=["support"], metadata={"plan": "enterprise"}, ) update_span(metadata={"region": "EU"}) return answer(question)import { Langfuse } from "langfuse"; const langfuse = new Langfuse(); async function supportChat(question: string) { const trace = langfuse.trace({ name: "support-chat", userId: "user-7", sessionId: "session-42", tags: ["support"], metadata: { plan: "enterprise" }, }); const span = trace.span({ name: "handle", metadata: { region: "EU" } }); const output = await answer(trace, question); span.end({ output }); trace.update({ output }); return output; }import { span, updateSpan, updateTrace } from "confident-trace"; const supportChat = span({ name: "support-chat" }, async (question: string) => { updateTrace({ userId: "user-7", threadId: "session-42", tags: ["support"], metadata: { plan: "enterprise" }, }); updateSpan({ metadata: { region: "EU" } }); return answer(question); });A Python function decorated with
@observe(as_type="generation"), or a TypeScript generation, around an OpenAI call is now redundant: auto instrumentation records the same model span. For model calls it does not cover, use an LLM span (@span(type="llm"), orwithSpan({ type: "llm" }, fn)in TypeScript) and pass the model and token counts toupdate_span()(updateSpan()) where you passedmodelandusage_details(usageDetails) before. For LangChain, remove the LangfuseCallbackHandler(fromlangfuse.langchainin Python,langfuse-langchainin TypeScript) from your callbacks.init()instruments LangChain directly.
Run your app once. The trace appears in your project's Observatory within seconds. If it does not, check that CONFIDENT_API_KEY is set in the same process and that the app flushes before it exits. flush() from confident-trace replaces get_client().flush() in Python, and await tracing.shutdown() replaces await langfuse.shutdownAsync() in TypeScript.
Field Mapping
| Langfuse v3 | confident-trace |
|---|---|
get_client() | init() |
@observe() | @span |
@observe(as_type="generation") | Auto instrumentation, or @span(type="llm") |
start_as_current_span(name=...) | with span("..."): |
update_current_trace(name=..., user_id=...) | update_trace(name=..., user_id=...) |
update_current_trace(session_id=...) | update_trace(thread_id=...) |
update_current_trace(tags=..., metadata=...) | update_trace(tags=..., metadata=...) |
update_current_span(input=..., output=..., metadata=...) | update_span(input=..., output=..., metadata=...) |
update_current_generation(model=...) | update_span(model=...) |
update_current_generation(usage_details={"input": ..., "output": ...}) | update_span(input_token_count=..., output_token_count=...) |
get_client().flush() | flush() |
| Langfuse v3 | confident-trace |
|---|---|
new Langfuse() | init() |
observeOpenAI(new OpenAI()) | new OpenAI() with auto instrumentation |
langfuse.trace({ name, userId }) | span({ name }, fn) and updateTrace({ userId }) |
langfuse.trace({ sessionId }) | updateTrace({ threadId }) |
langfuse.trace({ tags, metadata }) | updateTrace({ tags, metadata }) |
trace.update({ input, output }) | updateTrace({ input, output }) |
trace.span({ name }) | span({ name }, fn) or withSpan({ name }, fn) |
span.update({ input, output, metadata }) | updateSpan({ input, output, metadata }) |
trace.generation({ model, input }) | Auto instrumentation, or withSpan({ type: "llm" }, fn) with updateSpan({ model, input }) |
usageDetails: { input, output } | updateSpan({ inputTokenCount, outputTokenCount }) |
await langfuse.shutdownAsync() | await tracing.shutdown() |
Switch Using OpenTelemetry
Langfuse Python SDK v3 creates OpenTelemetry spans on the global tracer provider. Add a Confident AI exporter to that provider and every span Langfuse emits reaches Confident AI as well. Your @observe decorators, langfuse.openai and get_client() calls stay as they are. In TypeScript, Langfuse SDK v3 is not OpenTelemetry, so use Switch Manually instead.
Add the Confident AI exporter
The OTLP exporter already ships with Langfuse v3, so there is nothing to install. Keep
LANGFUSE_PUBLIC_KEYandLANGFUSE_SECRET_KEYset: without them, the Langfuse SDK creates no spans at all.import os from langfuse import get_client from opentelemetry import trace from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace.export import BatchSpanProcessor langfuse = get_client() trace.get_tracer_provider().add_span_processor( BatchSpanProcessor( OTLPSpanExporter( endpoint="https://otel.confident-ai.com/v1/traces", headers={"x-confident-api-key": os.environ["CONFIDENT_API_KEY"]}, ) ) )For the EU region, use
https://eu.otel.confident-ai.com/v1/traces. If you pass your owntracer_providertoLangfuse(), add the processor to that provider instead.Stop sending to Langfuse when you cut over
While you compare, both platforms receive every trace. When you are ready, block the Langfuse SDK's own scope from the Langfuse exporter. Spans are still created and still reach Confident AI.
from langfuse import Langfuse langfuse = Langfuse(blocked_instrumentation_scopes=["langfuse-sdk"])
Run your app once. The trace appears in your project's Observatory within seconds.
What Carries Over
| Langfuse | Confident AI |
|---|---|
| Span tree, names, timing and OpenTelemetry status | Kept as they are |
| Observation types | Span types, with generations as LLM spans |
| Observation input and output | Span input and output |
| Model, usage details and cost details | Model, token counts and cost per token |
level="ERROR" and status message | Errored span with that error |
| Trace name, tags, metadata, input and output | The same trace fields |
session_id and user_id | Thread and user |
| Prompt name and version | Prompt alias and version |
| Scores, prompts and datasets | Not sent over OpenTelemetry. We import them on the migration call |
Every span on the provider is exported, including spans from other instrumentations such as HTTP clients. The Langfuse SDK stays in your app. To replace it with confident-trace and its auto instrumentation later, switch manually or with a coding agent.
What Changes
Tracing is where you start. Evals are where it pays off. Every trace gets a score. Low scores become dataset rows. Dataset rows become regression tests. Your next release ships against real production failures.
| Capability | Langfuse | Confident AI |
|---|---|---|
| OpenTelemetry ingestion | Yes | OTLP over HTTP and gRPC |
| Auto-instrumented providers | OpenAI wrapper and LangChain callback | OpenAI, Anthropic, Google GenAI, Bedrock, 5 gateways, 16 frameworks |
| Built-in eval metrics | Managed evaluators, custom scoring | 50+ research-backed metrics through DeepEval |
| Online evals on traces, spans and threads | Traces only | Yes |
| Quality-aware alerting on eval scores | Slack, webhook, GitHub Actions | Email, Slack, Discord, Teams |
| Prompt and use case drift detection | No | Yes |
| Automatic dataset curation from production | Manual add to dataset | Ingestion tasks, filtered and tagged |
| Multi-turn simulation | No | Yes |
| Git-based prompt management | No | Branches, PRs, approvals, eval actions |
| Cross-functional workflows, no code | No | PMs and QA run evals over HTTP |
| Regression testing and CI gates | No | Yes |
| Safety monitoring | No | Toxicity, bias, PII on production traffic |
Migration Questions
Do I have to rewrite my instrumentation?
In Python, no. confident-trace exports through standard OTLP. Existing OpenTelemetry spans from your frameworks are exported as they are. In TypeScript, Langfuse v3 is not OpenTelemetry, so the Langfuse client calls move onto confident-trace as shown above.
Do I have to upgrade to Langfuse v4 first?
No. You move straight from Langfuse SDK v3 to confident-trace.
Can I run both during the switch?
Yes. In Python, point an OpenTelemetry Collector at both endpoints and compare for a week before you cut over. CONFIDENT_OTEL_ENDPOINT accepts a Collector URL. In TypeScript, Langfuse v3 and confident-trace do not share a client, so both can trace the same app while you compare.
What about my Langfuse datasets and prompts?
We import them on the migration call. Langfuse exports through its public API and scheduled blob storage export. We map the result to Confident AI datasets and prompt versions.
How do I get the $200?
Book the call. After it, we apply a $200 coupon to your organization's first paid invoice. One reward per organization.
Is the TypeScript SDK ready?
The Python SDK is stable on PyPI. The TypeScript SDK is an alpha release on npm. The TypeScript snippets in this guide follow the alpha API.
Related
OpenTelemetry integration
Send OTLP traces from any SDK or Collector to Confident AI, with the attribute keys the platform reads.
Build test runs from traces
Turn your production traces into an evaluated test run before you ship.
LLM Observability
Evals on every trace, drift detection, alerts, and datasets curated from production.
confident-trace on GitHub
The OpenTelemetry-native tracing SDK with 25 integrations for Python and TypeScript.
Last updated on