Movatterモバイル変換


[0]ホーム

URL:


⚠️ Snapshot (as of Python SDK v2) ⚠️view latest ↗
DocsObservabilitySDKsPythonOTEL-based SDK (v3)

Python SDK (v3)

If you are self-hosting Langfuse, the Python SDK v3 requiresLangfuse platform version >= 3.63.0 for traces to be correctly processed.

OurOpenTelemetry-based Python SDK (v3) is the latest generation of the SDK designed for a improved developer experience and enhanced ease of use. Built on the robust OpenTelemetry Python SDK, it offers a more intuitive API for comprehensive tracing of your LLM application.

The v3 SDK introduces several key benefits:

  • Improved Developer Experience: A more intuitive API means less code to write for tracing your application, simplifying the integration process.
  • Unified Context Sharing: Seamlessly hook into the tracing context of the current span to update it or create child spans. This is particularly beneficial for integrating with other instrumented libraries.
  • Broad Third-Party Integrations: Any library instrumented with OpenTelemetry will work out-of-the-box with the Langfuse SDK. Spans from these libraries are automatically captured and correctly nested within your Langfuse traces.

There are three main ways of instrumenting your application with the new Langfuse SDK. All of them are fully interoperable with each other.

The@observe decorator is the simplest way to instrument your application. It is a function decorator that can be applied to any function.

It sets the current span in the context for automatic nesting of child spans and automatically ends it when the function returns. It also automatically captures the function name, arguments, and return value.

from langfuseimport observe, get_client@observedef my_function():    return "Hello, world!" # Input/output and timings are automatically capturedmy_function()# Flush events in short-lived applicationslangfuse= get_client()langfuse.flush()

Setup

Installation

To install the v3 SDK, run:

pip install langfuse

Initialize Client

Begin by initializing theLangfuse client. You must provide your Langfuse public and secret keys. These can be passed as constructor arguments or set as environment variables (recommended).

If you are self-hosting Langfuse or using a data region other than the default (EU,https://cloud.langfuse.com), ensure you configure thehost argument or theLANGFUSE_HOST environment variable (recommended).

.env
LANGFUSE_PUBLIC_KEY="pk-lf-..."LANGFUSE_SECRET_KEY="sk-lf-..."LANGFUSE_HOST="https://cloud.langfuse.com" # US region: https://us.cloud.langfuse.com

Verify connection withlangfuse.auth_check()

You can also verify your connection to the Langfuse server usinglangfuse.auth_check(). We do not recommend using this in production as this adds latency to your application.

from langfuseimport get_clientlangfuse= get_client()# Verify connection, do not use in production as this is a synchronous callif langfuse.auth_check():    print("Langfuse client is authenticated and ready!")else:    print("Authentication failed. Please check your credentials and host.")

Key configuration options:

Constructor ArgumentEnvironment VariableDescriptionDefault value
public_keyLANGFUSE_PUBLIC_KEYYour Langfuse project’s public API key.Required.
secret_keyLANGFUSE_SECRET_KEYYour Langfuse project’s secret API key.Required.
hostLANGFUSE_HOSTThe API host for your Langfuse instance."https://cloud.langfuse.com"
timeoutLANGFUSE_TIMEOUTTimeout in seconds for API requests.5
httpx_client-Customhttpx.Client for making non-tracing HTTP requests.
debugLANGFUSE_DEBUGEnables debug mode for more verbose logging. Set toTrue or"True".False
tracing_enabledLANGFUSE_TRACING_ENABLEDEnables or disables the Langfuse client. IfFalse, all observability calls become no-ops.True
flush_atLANGFUSE_FLUSH_ATNumber of spans to batch before sending to the API.512
flush_intervalLANGFUSE_FLUSH_INTERVALTime in seconds between batch flushes.5
environmentLANGFUSE_TRACING_ENVIRONMENTEnvironment name for tracing (e.g., “development”, “staging”, “production”). Must be lowercase alphanumeric with hyphens/underscores."default"
releaseLANGFUSE_RELEASERelease version/hash of your application. Used for grouping analytics.
media_upload_thread_countLANGFUSE_MEDIA_UPLOAD_THREAD_COUNTNumber of background threads for handling media uploads.1
sample_rateLANGFUSE_SAMPLE_RATESampling rate for traces (float between 0.0 and 1.0).1.0 means 100% of traces are sampled.1.0
mask-A function(data: Any) -> Any tomask sensitive data in traces before sending to the API.
LANGFUSE_MEDIA_UPLOAD_ENABLEDWhether to upload media files to Langfuse S3. In self-hosted environments this might be useful to disable.True

Accessing the Client Globally

The Langfuse client is a singleton. It can be accessed anywhere in your application using theget_client function.

Optionally, you can initialize the client viaLangfuse() to pass in configuration options (see above). Otherwise, it is created automatically when you callget_client() based on environment variables.

from langfuseimport get_client# Optionally, initialize the client with configuration options# langfuse = Langfuse(public_key="pk-lf-...", secret_key="sk-lf-...")# Get the default clientclient= get_client()

Basic Tracing

Langfuse provides flexible ways to create and manage traces and their constituent observations (spans and generations).

@observe Decorator

The@observe() decorator provides a convenient way to automatically trace function executions, including capturing their inputs, outputs, execution time, and any errors. It supports both synchronous and asynchronous functions.

from langfuseimport observe@observe()def my_data_processing_function(data, parameter):    # ... processing logic ...    return {"processed_data": data,"status":"ok"}@observe(name="llm-call",as_type="generation")async def my_async_llm_call(prompt_text):    # ... async LLM call ...    return "LLM response"

Parameters:

  • name: Optional[str]: Custom name for the created span/generation. Defaults to the function name.
  • as_type: Optional[Literal["generation"]]: If set to"generation", a Langfuse generation object is created, suitable for LLM calls. Otherwise, a regular span is created.
  • capture_input: bool: Whether to capture function arguments as input. Defaults to env varLANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED orTrue if not set.
  • capture_output: bool: Whether to capture function return value as output. Defaults to env varLANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED orTrue if not set.
  • transform_to_string: Optional[Callable[[Iterable], str]]: For functions that return generators (sync or async), this callable can be provided to transform the collected chunks into a single string for theoutput field. If not provided, and all chunks are strings, they will be concatenated. Otherwise, the list of chunks is stored.

Trace Context and Special Keyword Arguments:

The@observe decorator automatically propagates the OTEL trace context. If a decorated function is called from within an active Langfuse span (or another OTEL span), the new observation will be nested correctly.

You can also pass special keyword arguments to a decorated function to control its tracing behavior:

  • langfuse_trace_id: str: Explicitly set the trace ID for this function call. Must be a valid W3C Trace Context trace ID (32-char hex). If you have a trace ID from an external system, you can useLangfuse.create_trace_id(seed=external_trace_id) to generate a valid deterministic ID.
  • langfuse_parent_observation_id: str: Explicitly set the parent observation ID. Must be a valid W3C Trace Context span ID (16-char hex).
@observe()def my_function(a, b):    return a+ b# Call with a specific trace contextmy_function(1,2,langfuse_trace_id="1234567890abcdef1234567890abcdef")

The observe decorator is capturing the args, kwargs and return value of decorated functions by default. This may lead to performance issues in your application if you have large or deeply nested objects there. To avoid this, explicitly disable function IO capture on the decorated function by passingcapture_input / capture_output with valueFalse or globally by setting the environment variableLANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED=False.

Context Managers

You can create spans or generations anywhere in your application. If you need more control than the@observe decorator, the primary way to do this is using context managers (withwith statements), which ensure that observations are properly started and ended.

  • langfuse.start_as_current_span(): Creates a new span and sets it as the currently active observation in the OTEL context for its duration. Any new observations created within this block will be its children.
  • langfuse.start_as_current_generation(): Similar to the above, but creates a specialized “generation” observation for LLM calls.
from langfuseimport get_clientlangfuse= get_client()with langfuse.start_as_current_span(    name="user-request-pipeline",    input={"user_query":"Tell me a joke about OpenTelemetry"},)as root_span:    # This span is now active in the context.    # Add trace attributes    root_span.update_trace(        user_id="user_123",        session_id="session_abc",        tags=["experimental","comedy"]    )    # Create a nested generation    with langfuse.start_as_current_generation(        name="joke-generation",        model="gpt-4o",        input=[{"role":"user","content":"Tell me a joke about OpenTelemetry"}],        model_parameters={"temperature":0.7}    )as generation:        # Simulate an LLM call        joke_response= "Why did the OpenTelemetry collector break up with the span? Because it needed more space... for its attributes!"        token_usage= {"input_tokens":10,"output_tokens":25}        generation.update(            output=joke_response,            usage_details=token_usage        )        # Generation ends automatically here    root_span.update(output={"final_joke": joke_response})    # Root span ends automatically here

Manual Observations

For scenarios where you need to create an observation (a span or generation) without altering the currently active OpenTelemetry context, you can uselangfuse.start_span() orlangfuse.start_generation().

from langfuseimport get_clientlangfuse= get_client()span= langfuse.start_span(name="my-span")span.end()# Important: Manually end the span
⚠️

If you uselangfuse.start_span() orlangfuse.start_generation(), you areresponsible for calling.end() on the returned observation object. Failureto do so will result in incomplete or missing observations in Langfuse. Theirstart_as_current_... counterparts used with awith statement handle thisautomatically.

Key Characteristics:

  • No Context Shift: Unlike theirstart_as_current_... counterparts, these methodsdo not set the new observation as the active one in the OpenTelemetry context. The previously active span (if any) remains the current context for subsequent operations in the main execution flow.
  • Parenting: The observation created bystart_span() orstart_generation() will still be a child of the span that was active in the context at the moment of its creation.
  • Manual Lifecycle: These observations are not managed by awith block and thereforemust be explicitly ended by calling their.end() method.
  • Nesting Children:
    • Subsequent observations created using the globallangfuse.start_as_current_span() (or similar global methods) willnot be children of these “manual” observations. Instead, they will be parented by the original active span.
    • To create children directly under a “manual” observation, you would use methodson that specific observation object (e.g.,manual_span.start_as_current_span(...)).

When to Use:

This approach is useful when you need to:

  • Record work that is self-contained or happens in parallel to the main execution flow but should still be part of the same overall trace (e.g., a background task initiated by a request).
  • Manage the observation’s lifecycle explicitly, perhaps because its start and end are determined by non-contiguous events.
  • Obtain an observation object reference before it’s tied to a specific context block.

Example with more complex nesting:

from langfuseimport get_clientlangfuse= get_client()# This outer span establishes an active context.with langfuse.start_as_current_span(name="main-operation")as main_operation_span:    # 'main_operation_span' is the current active context.    # 1. Create a "manual" span using langfuse.start_span().    #    - It becomes a child of 'main_operation_span'.    #    - Crucially, 'main_operation_span' REMAINS the active context.    #    - 'manual_side_task' does NOT become the active context.    manual_side_task= langfuse.start_span(name="manual-side-task")    manual_side_task.update(input="Data for side task")    # 2. Start another operation that DOES become the active context.    #    This will be a child of 'main_operation_span', NOT 'manual_side_task',    #    because 'manual_side_task' did not alter the active context.    with langfuse.start_as_current_span(name="core-step-within-main")as core_step_span:        # 'core_step_span' is now the active context.        # 'manual_side_task' is still open but not active in the global context.        core_step_span.update(input="Data for core step")        # ... perform core step logic ...        core_step_span.update(output="Core step finished")    # 'core_step_span' ends. 'main_operation_span' is the active context again.    # 3. Complete and end the manual side task.    # This could happen at any point after its creation, even after 'core_step_span'.    manual_side_task.update(output="Side task completed")    manual_side_task.end()# Manual end is crucial for 'manual_side_task'    main_operation_span.update(output="Main operation finished")# 'main_operation_span' ends automatically here.# Expected trace structure in Langfuse:# - main-operation#   |- manual-side-task#   |- core-step-within-main#     (Note: 'core-step-within-main' is a sibling to 'manual-side-task', both children of 'main-operation')

Nesting Observations

The function call hierarchy is automatically captured by the@observe decorator reflected in the trace.

from langfuseimport observe@observedef my_data_processing_function(data, parameter):    # ... processing logic ...    return {"processed_data": data,"status":"ok"}@observedef main_function(data, parameter):    return my_data_processing_function(data, parameter)

Updating Observations

You can update observations with new information as your code executes.

  • For spans/generations created via context managers or assigned to variables: use the.update() method on the object.
  • To update thecurrently active observation in the context (without needing a direct reference to it): uselangfuse.update_current_span() orlangfuse.update_current_generation().

LangfuseSpan.update() /LangfuseGeneration.update() parameters:

ParameterTypeDescriptionApplies To
inputOptional[Any]Input data for the operation.Both
outputOptional[Any]Output data from the operation.Both
metadataOptional[Any]Additional metadata (JSON-serializable).Both
versionOptional[str]Version identifier for the code/component.Both
levelOptional[SpanLevel]Severity:"DEBUG","DEFAULT","WARNING","ERROR".Both
status_messageOptional[str]A message describing the status, especially for errors.Both
completion_start_timeOptional[datetime]Timestamp when the LLM started generating the completion (streaming).Generation
modelOptional[str]Name/identifier of the AI model used.Generation
model_parametersOptional[Dict[str, MapValue]]Parameters used for the model call (e.g., temperature).Generation
usage_detailsOptional[Dict[str, int]]Token usage (e.g.,{"input_tokens": 10, "output_tokens": 20}).Generation
cost_detailsOptional[Dict[str, float]]Cost information (e.g.,{"total_cost": 0.0023}).Generation
promptOptional[PromptClient]AssociatedPromptClient object from Langfuse prompt management.Generation
from langfuseimport get_clientlangfuse= get_client()with langfuse.start_as_current_generation(name="llm-call",model="gpt-3.5-turbo")as gen:    gen.update(input={"prompt":"Why is the sky blue?"})    # ... make LLM call ...    response_text= "Rayleigh scattering..."    gen.update(        output=response_text,        usage_details={"input_tokens":5,"output_tokens":50},        metadata={"confidence":0.9}    )# Alternatively, update the current observation in context:with langfuse.start_as_current_span(name="data-processing"):    # ... some processing ...    langfuse.update_current_span(metadata={"step1_complete":True})    # ... more processing ...    langfuse.update_current_span(output={"result":"final_data"})

Setting Trace Attributes

Trace-level attributes apply to the entire trace, not just a single observation. You can set or update these using:

  • The.update_trace() method on anyLangfuseSpan orLangfuseGeneration object within that trace.
  • langfuse.update_current_trace() to update the trace associated with the currently active observation.

Trace attribute parameters:

ParameterTypeDescription
nameOptional[str]Name for the trace.
user_idOptional[str]ID of the user associated with this trace.
session_idOptional[str]Session identifier for grouping related traces.
versionOptional[str]Version of your application/service for this trace.
inputOptional[Any]Overall input for the entire trace.
outputOptional[Any]Overall output for the entire trace.
metadataOptional[Any]Additional metadata for the trace.
tagsOptional[List[str]]List of tags to categorize the trace.
publicOptional[bool]Whether the trace should be publicly accessible (if configured).

Example: Setting Multiple Trace Attributes

from langfuseimport get_clientlangfuse= get_client()with langfuse.start_as_current_span(name="initial-operation")as span:    # Set trace attributes early    span.update_trace(        user_id="user_xyz",        session_id="session_789",        tags=["beta-feature","llm-chain"]    )    # ...    # Later, from another span in the same trace:    with span.start_as_current_generation(name="final-generation")as gen:        # ...        langfuse.update_current_trace(output={"final_status":"success"},public=True)

Trace Input/Output Behavior

In v3, trace input and output are automatically set from theroot observation (first span/generation) by default. This differs from v2 where integrations could set trace-level inputs/outputs directly.

Default Behavior

from langfuseimport get_clientlangfuse= get_client()with langfuse.start_as_current_span(    name="user-request",    input={"query":"What is the capital of France?"}# This becomes the trace input)as root_span:    with langfuse.start_as_current_generation(        name="llm-call",        model="gpt-4o",        input={"messages": [{"role":"user","content":"What is the capital of France?"}]}    )as gen:        response= "Paris is the capital of France."        gen.update(output=response)        # LLM generation input/output are separate from trace input/output    root_span.update(output={"answer":"Paris"})# This becomes the trace output

Override Default Behavior

If you need different trace inputs/outputs than the root observation, explicitly set them:

from langfuseimport get_clientlangfuse= get_client()with langfuse.start_as_current_span(name="complex-pipeline")as root_span:    # Root span has its own input/output    root_span.update(input="Step 1 data",output="Step 1 result")    # But trace should have different input/output (e.g., for LLM-as-a-judge)    root_span.update_trace(        input={"original_query":"User's actual question"},        output={"final_answer":"Complete response","confidence":0.95}    )    # Now trace input/output are independent of root span input/output

Critical for LLM-as-a-Judge Features

LLM-as-a-judge and evaluation features typically rely on trace-level inputs and outputs. Make sure to set these appropriately:

from langfuseimport observe, get_clientlangfuse= get_client()@observe()def process_user_query(user_question:str):    # LLM processing...    answer= call_llm(user_question)    # Explicitly set trace input/output for evaluation features    langfuse.update_current_trace(        input={"question": user_question},        output={"answer": answer}    )    return answer

Trace and Observation IDs

Langfuse uses W3C Trace Context compliant IDs:

  • Trace IDs: 32-character lowercase hexadecimal string (16 bytes).
  • Observation IDs (Span IDs): 16-character lowercase hexadecimal string (8 bytes).

You can retrieve these IDs:

  • langfuse.get_current_trace_id(): Gets the trace ID of the currently active observation.
  • langfuse.get_current_observation_id(): Gets the ID of the currently active observation.
  • span_obj.trace_id andspan_obj.id: Access IDs directly from aLangfuseSpan orLangfuseGeneration object.

For scenarios where you need to generate IDs outside of an active trace (e.g., to link scores to traces/observations that will be created later, or to correlate with external systems), use:

  • Langfuse.create_trace_id(seed: Optional[str] = None)(static method): Generates a new trace ID. If aseed is provided, the ID is deterministic. Use the same seed to get the same ID. This is useful for correlating external IDs with Langfuse traces.
from langfuseimport get_client, Langfuselangfuse= get_client()# Get current IDswith langfuse.start_as_current_span(name="my-op")as current_op:    trace_id= langfuse.get_current_trace_id()    observation_id= langfuse.get_current_observation_id()    print(f"Current Trace ID:{trace_id}, Current Observation ID:{observation_id}")    print(f"From object: Trace ID:{current_op.trace_id}, Observation ID:{current_op.id}")# Generate IDs deterministicallyexternal_request_id= "req_12345"deterministic_trace_id= Langfuse.create_trace_id(seed=external_request_id)print(f"Deterministic Trace ID for{external_request_id}:{deterministic_trace_id}")

Linking to Existing Traces (Trace Context)

If you have atrace_id (and optionally aparent_span_id) from an external source (e.g., another service, a batch job), you can link new observations to it using thetrace_context parameter. Note that OpenTelemetry offers native cross-service context propagation, so this is not necessarily required for calls between services that are instrumented with OTEL.

from langfuseimport get_clientlangfuse= get_client()existing_trace_id= "abcdef1234567890abcdef1234567890" # From an upstream serviceexisting_parent_span_id= "fedcba0987654321" # Optional parent span in that tracewith langfuse.start_as_current_span(    name="process-downstream-task",    trace_context={        "trace_id": existing_trace_id,        "parent_span_id": existing_parent_span_id# If None, this becomes a root span in the existing trace    })as span:    # This span is now part of the trace `existing_trace_id`    # and a child of `existing_parent_span_id` if provided.    print(f"This span's trace_id:{span.trace_id}")# Will be existing_trace_id    pass

Client Management

flush()

Manually triggers the sending of all buffered observations (spans, generations, scores, media metadata) to the Langfuse API. This is useful in short-lived scripts or before exiting an application to ensure all data is persisted.

from langfuseimport get_clientlangfuse= get_client()# ... create traces and observations ...langfuse.flush()# Ensures all pending data is sent

Theflush() method blocks until the queued data is processed by the respective background threads.

shutdown()

Gracefully shuts down the Langfuse client. This includes:

  1. Flushing all buffered data (similar toflush()).
  2. Waiting for background threads (for data ingestion and media uploads) to finish their current tasks and terminate.

It’s crucial to callshutdown() before your application exits to prevent data loss and ensure clean resource release. The SDK automatically registers anatexit hook to callshutdown() on normal program termination, but manual invocation is recommended in scenarios like:

  • Long-running daemons or services when they receive a shutdown signal.
  • Applications whereatexit might not reliably trigger (e.g., certain serverless environments or forceful terminations).
from langfuseimport get_clientlangfuse= get_client()# ... application logic ...# Before exiting:langfuse.shutdown()

Integrations

OpenAI Integration

Langfuse offers a drop-in replacement for the OpenAI Python SDK to automatically trace all your OpenAI API calls. Simply change your import statement:

- import openai+ from langfuse.openai import openai# Your existing OpenAI code continues to work as is# For example:# client = openai.OpenAI()# completion = client.chat.completions.create(...)

What’s automatically captured:

  • Requests & Responses: All prompts/completions, including support for streaming, async operations, and function/tool calls.
  • Timings: Latencies for API calls.
  • Errors: API errors are captured with their details.
  • Model Usage: Token counts (input, output, total).
  • Cost: Estimated cost in USD (based on model and token usage).
  • Media: Input audio and output audio from speech-to-text and text-to-speech endpoints.

The integration is fully interoperable with@observe and manual tracing methods (start_as_current_span, etc.). If an OpenAI call is made within an active Langfuse span, the OpenAI generation will be correctly nested under it.

Passing Langfuse arguments to OpenAI calls:

You can pass Langfuse-specific arguments directly to OpenAI client methods. These will be used to enrich the trace data.

from langfuseimport get_clientfrom langfuse.openaiimport openailangfuse= get_client()client= openai.OpenAI()with langfuse.start_as_current_span(name="qna-bot-openai")as span:    langfuse.update_current_trace(tags=["qna-bot-openai"])    # This will be traced as a Langfuse generation    response= client.chat.completions.create(        name="qna-bot-openai",# Custom name for this generation in Langfuse        metadata={"user_tier":"premium","request_source":"web_api"},# will be added to the Langfuse generation        model="gpt-4o",        messages=[{"role":"user","content":"What is OpenTelemetry?"}],    )

Setting trace attributes via metadata:

You can set trace attributes (session_id,user_id,tags) directly on OpenAI calls using special fields in themetadata parameter:

from langfuse.openaiimport openaiclient= openai.OpenAI()response= client.chat.completions.create(    model="gpt-4o",    messages=[{"role":"user","content":"Hello"}],    metadata={        "langfuse_session_id":"session_123",        "langfuse_user_id":"user_456",        "langfuse_tags": ["production","chat-bot"],        "custom_field":"additional metadata"  # Regular metadata fields work too    })

The special metadata fields are:

  • langfuse_session_id: Sets the session ID for the trace
  • langfuse_user_id: Sets the user ID for the trace
  • langfuse_tags: Sets tags for the trace (should be a list of strings)

Supported Langfuse arguments:name,metadata,langfuse_prompt

Langchain Integration

Langfuse provides a callback handler for Langchain to trace its operations.

Setup:

Initialize theCallbackHandler and add it to your Langchain calls, either globally or per-call.

from langfuseimport get_clientfrom langfuse.langchainimport CallbackHandlerfrom langchain_openaiimport ChatOpenAI# Example LLMfrom langchain_core.promptsimport ChatPromptTemplatelangfuse= get_client()# Initialize the Langfuse handlerlangfuse_handler= CallbackHandler()# Example: Using it with an LLM callllm= ChatOpenAI(model_name="gpt-4o")prompt= ChatPromptTemplate.from_template("Tell me a joke about{topic}")chain= prompt| llmwith langfuse.start_as_current_span(name="joke-chain")as span:    langfuse.update_current_trace(tags=["joke-chain"])    response= chain.invoke({"topic":"cats"},config={"callbacks": [langfuse_handler]})    print(response)

Setting trace attributes via metadata:

You can set trace attributes (session_id,user_id,tags) directly during chain invocation using special fields in themetadata configuration:

from langfuse.langchainimport CallbackHandlerfrom langchain_openaiimport ChatOpenAIfrom langchain_core.promptsimport ChatPromptTemplate# Initialize the Langfuse handlerlangfuse_handler= CallbackHandler()# Create your LangChain componentsllm= ChatOpenAI(model_name="gpt-4o")prompt= ChatPromptTemplate.from_template("Tell me a joke about{topic}")chain= prompt| llm# Set trace attributes via metadata in chain invocationresponse= chain.invoke(    {"topic":"cats"},    config={        "callbacks": [langfuse_handler],        "metadata": {            "langfuse_session_id":"session_123",            "langfuse_user_id":"user_456",            "langfuse_tags": ["production","humor-bot"],            "custom_field":"additional metadata"  # Regular metadata fields work too        }    })

The special metadata fields are:

  • langfuse_session_id: Sets the session ID for the trace
  • langfuse_user_id: Sets the user ID for the trace
  • langfuse_tags: Sets tags for the trace (should be a list of strings)

You can also passupdate_trace=True to the CallbackHandler init to force a trace update with the chains input, output and metadata.

What’s captured:

The callback handler maps various Langchain events to Langfuse observations:

  • Chains (on_chain_start,on_chain_end,on_chain_error): Traced as spans.
  • LLMs (on_llm_start,on_llm_end,on_llm_error,on_chat_model_start): Traced as generations, capturing model name, prompts, responses, and usage if available from the LLM provider.
  • Tools (on_tool_start,on_tool_end,on_tool_error): Traced as spans, capturing tool input and output.
  • Retrievers (on_retriever_start,on_retriever_end,on_retriever_error): Traced as spans, capturing the query and retrieved documents.
  • Agents (on_agent_action,on_agent_finish): Agent actions and final finishes are captured within their parent chain/agent span.

Langfuse attempts to parse model names, usage, and other relevant details from the information provided by Langchain. Themetadata argument in Langchain calls can be used to pass additional information to Langfuse, includinglangfuse_prompt to link with managed prompts.

Third-party integrations

The Langfuse SDK seamlessly integrates with any third-party library that uses OpenTelemetry instrumentation. When these libraries emit spans, they are automatically captured and properly nested within your trace hierarchy. This enables unified tracing across your entire application stack without requiring any additional configuration.

For example, if you’re using OpenTelemetry-instrumented databases, HTTP clients, or other services alongside your LLM operations, all these spans will be correctly organized within your traces in Langfuse.

You can use any third-party, OTEL-based instrumentation library for Anthropic to automatically trace all your Anthropic API calls in Langfuse.

In this example, we are using theopentelemetry-instrumentation-anthropic library.

from anthropicimport Anthropicfrom opentelemetry.instrumentation.anthropicimport AnthropicInstrumentorfrom langfuseimport get_client# This will automatically emit OTEL-spans for all Anthropic API callsAnthropicInstrumentor().instrument()langfuse= get_client()anthropic_client= Anthropic()with langfuse.start_as_current_span(name="myspan"):    # This will be traced as a Langfuse generation nested under the current span    message= anthropic_client.messages.create(        model="claude-3-7-sonnet-20250219",        max_tokens=1024,        messages=[{"role":"user","content":"Hello, Claude"}],    )    print(message.content)# Flush events to Langfuse in short-lived applicationslangfuse.flush()

Scoring traces and observations

  • span_or_generation_obj.score(): Scores the specific observation object.
  • span_or_generation_obj.score_trace(): Scores the entire trace to which the object belongs.
from langfuseimport get_clientlangfuse= get_client()with langfuse.start_as_current_generation(name="summary_generation")as gen:    # ... LLM call ...    gen.update(output="summary text...")    # Score this specific generation    gen.score(name="conciseness",value=0.8,data_type="NUMERIC")    # Score the overall trace    gen.score_trace(name="user_feedback_rating",value="positive",data_type="CATEGORICAL")

Score Parameters:

ParameterTypeDescription
namestrName of the score (e.g., “relevance”, “accuracy”).Required.
valueUnion[float, str]Score value. Float forNUMERIC/BOOLEAN, string forCATEGORICAL.Required.
trace_idstrID of the trace to associate with (forcreate_score).Required.
observation_idOptional[str]ID of the specific observation to score (forcreate_score).
session_idOptional[str]ID of the specific session to score (forcreate_score).
score_idOptional[str]Custom ID for the score (auto-generated if None).
data_typeOptional[ScoreDataType]"NUMERIC","BOOLEAN", or"CATEGORICAL". Inferred if not provided based on value type and score config on server.
commentOptional[str]Optional comment or explanation for the score.
config_idOptional[str]Optional ID of a pre-defined score configuration in Langfuse.

SeeScoring for more details.

Datasets

Langfuse Datasets are essential for evaluating and testing your LLM applications by allowing you to manage collections of inputs and their expected outputs.

Interacting with Datasets

  • Fetching: Retrieve a dataset and its items usinglangfuse.get_dataset(name: str). This returns aDatasetClient instance, which contains a list ofDatasetItemClient objects (accessible viadataset.items). EachDatasetItemClient holds theinput,expected_output, andmetadata for an individual data point.
  • Creating: You can programmatically create new datasets withlangfuse.create_dataset(...) and add items to them usinglangfuse.create_dataset_item(...).
from langfuseimport get_clientlangfuse= get_client()# Fetch an existing datasetdataset= langfuse.get_dataset(name="my-eval-dataset")for itemin dataset.items:    print(f"Input:{item.input}, Expected:{item.expected_output}")# Briefly: Creating a dataset and an itemnew_dataset= langfuse.create_dataset(name="new-summarization-tasks")langfuse.create_dataset_item(    dataset_name="new-summarization-tasks",    input={"text":"Long article..."},    expected_output={"summary":"Short summary."})

Linking Traces to Dataset Items for Runs

The most powerful way to use datasets is by linking your application’s executions (traces) to specific dataset items when performing an evaluation run. See ourdatasets documentation for more details. TheDatasetItemClient.run() method provides a context manager to streamline this process.

Howitem.run() works:

When you usewith item.run(run_name="your_eval_run_name") as root_span::

  1. Trace Creation: A new Langfuse trace is initiated specifically for processing this dataset item within the context of the named run.
  2. Trace Naming & Metadata:
    • The trace is automatically named (e.g., “Dataset run: your_eval_run_name”).
    • Essential metadata is added to this trace, includingdataset_item_id (the ID ofitem),run_name, anddataset_id.
  3. DatasetRunItem Linking: The SDK makes an API call to Langfuse to create aDatasetRunItem. This backend object formally links:
    • Thedataset_item_id
    • Thetrace_id of the newly created trace
    • The providedrun_name
    • Anyrun_metadata orrun_description you pass toitem.run().This linkage is what populates the “Runs” tab for your dataset in the Langfuse UI, allowing you to see all traces associated with a particular evaluation run.
  4. Contextual Span: The context manager yieldsroot_span, which is aLangfuseSpan object representing the root span of this new trace.
  5. Automatic Nesting: Any Langfuse observations (spans or generations) createdinside thewith block will automatically become children ofroot_span and thus part of the trace linked to this dataset item and run.

Example:

from langfuseimport get_clientlangfuse= get_client()dataset_name= "qna-eval"current_run_name= "qna_model_v3_run_05_20" # Identifies this specific evaluation run# Assume 'my_qna_app' is your instrumented application functiondef my_qna_app(question:str, context:str, item_id:str, run_name:str):    with langfuse.start_as_current_generation(        name="qna-llm-call",        input={"question": question,"context": context},        metadata={"item_id": item_id,"run": run_name},# Example metadata for the generation        model="gpt-4o"    )as generation:        # Simulate LLM call        answer= f"Answer to '{question}' using context." # Replace with actual LLM call        generation.update(output={"answer": answer})        # Update the trace with the input and output        generation.update_trace(            input={"question": question,"context": context},            output={"answer": answer},        )        return answerdataset= langfuse.get_dataset(name=dataset_name)# Fetch your pre-populated datasetfor itemin dataset.items:    print(f"Running evaluation for item:{item.id} (Input:{item.input})")    # Use the item.run() context manager    with item.run(        run_name=current_run_name,        run_metadata={"model_provider":"OpenAI","temperature_setting":0.7},        run_description="Evaluation run for Q&A model v3 on May 20th"    )as root_span:# root_span is the root span of the new trace for this item and run.        # All subsequent langfuse operations within this block are part of this trace.        # Call your application logic        generated_answer= my_qna_app(            question=item.input["question"],            context=item.input["context"],            item_id=item.id,            run_name=current_run_name        )        print(f"  Item{item.id} processed. Trace ID:{root_span.trace_id}")        # Optionally, score the result against the expected output        if item.expected_outputand generated_answer== item.expected_output.get("answer"):            root_span.score_trace(name="exact_match",value=1.0)        else:            root_span.score_trace(name="exact_match",value=0.0)print(f"\nFinished processing dataset '{dataset_name}' for run '{current_run_name}'.")

By usingitem.run(), you ensure each dataset item’s processing is neatly encapsulated in its own trace, and these traces are aggregated under the specifiedrun_name in the Langfuse UI. This allows for systematic review of results, comparison across runs, and deep dives into individual processing traces.

Advanced Configuration

Masking Sensitive Data

If your trace data (inputs, outputs, metadata) might contain sensitive information (PII, secrets), you can provide amask function during client initialization. This function will be applied to all relevant data before it’s sent to Langfuse.

Themask function should acceptdata as a keyword argument and return the masked data. The returned data must be JSON-serializable.

from langfuseimport Langfuseimport redef pii_masker(data:any,**kwargs) ->any:    # Example: Simple email masking. Implement your more robust logic here.    if isinstance(data,str):        return re.sub(r"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+","[EMAIL_REDACTED]", data)    elif isinstance(data,dict):        return {k: pii_masker(data=v)for k, vin data.items()}    elif isinstance(data,list):        return [pii_masker(data=item)for itemin data]    return datalangfuse= Langfuse(mask=pii_masker)# Now, any input/output/metadata will be passed through pii_maskerwith langfuse.start_as_current_span(name="user-query",input={"email":"[email protected]","query":"..."})as span:    # The 'email' field in the input will be masked.    pass

Logging

The Langfuse SDK uses Python’s standardlogging module. The main logger is named"langfuse".To enable detailed debug logging, you can either:

  1. Set thedebug=True parameter when initializing theLangfuse client.
  2. Set theLANGFUSE_DEBUG="True" environment variable.
  3. Configure the"langfuse" logger manually:
import logginglangfuse_logger= logging.getLogger("langfuse")langfuse_logger.setLevel(logging.DEBUG)

The default log level for thelangfuse logger islogging.WARNING.

Sampling

You can configure the SDK to sample traces by setting thesample_rate parameter during client initialization (or via theLANGFUSE_SAMPLE_RATE environment variable). This value should be a float between0.0 (sample 0% of traces) and1.0 (sample 100% of traces).

If a trace is not sampled, none of its observations (spans, generations) or associated scores will be sent to Langfuse.

from langfuseimport Langfuse# Sample approximately 20% of traceslangfuse_sampled= Langfuse(sample_rate=0.2)

Filtering by Instrumentation Scope

You can configure the SDK to filter out spans from specific instrumentation libraries by using theblocked_instrumentation_scopes parameter. This is useful when you want to exclude infrastructure spans while keeping your LLM and application spans.

from langfuseimport Langfuse# Filter out database spanslangfuse= Langfuse(    blocked_instrumentation_scopes=["sqlalchemy","psycopg"])

How it works:

When third-party libraries create OpenTelemetry spans (through their instrumentation packages), each span has an associated “instrumentation scope” that identifies which library created it. The Langfuse SDK filters spans at the export level based on these scope names.

You can see the instrumentation scope name for any span in the Langfuse UI under the span’s metadata (metadata.scope.name). Use this to identify which scopes you want to filter.

⚠️

Cross-Library Span Relationships

When filtering instrumentation scopes, be aware that blocking certain libraries may break trace tree relationships if spans from blocked and non-blocked libraries are nested together.

For example, if you block parent spans but keep child spans from a separate library, you may see “orphaned” LLM spans whose parent spans were filtered out. This can make traces harder to interpret.

Consider the impact on trace structure when choosing which scopes to filter.

Isolated TracerProvider

You can configure a separate OpenTelemetry TracerProvider for use with Langfuse. This creates isolation between Langfuse tracing and your other observability systems.

Benefits of isolation:

  • Langfuse spans won’t be sent to your other observability backends (e.g., Datadog, Jaeger, Zipkin)
  • Third-party library spans won’t be sent to Langfuse
  • Independent configuration and sampling rates
⚠️

While TracerProviders are isolated, they share the same OpenTelemetry context for tracking active spans. This can cause span relationship issues where:

  • A parent span from one TracerProvider might have children from another TracerProvider
  • Some spans may appear “orphaned” if their parent spans belong to a different TracerProvider
  • Trace hierarchies may be incomplete or confusing

Plan your instrumentation carefully to avoid confusing trace structures.

from opentelemetry.sdk.traceimport TracerProviderfrom langfuseimport Langfuselangfuse_tracer_provider= TracerProvider()# do not set to global tracer provider to keep isolationlangfuse= Langfuse(tracer_provider=langfuse_tracer_provider)langfuse.start_span(name="myspan").end()# Span will be isolated from remaining OTEL instrumentation

Using ThreadPoolExecutors or ProcessPoolExecutors

The observe decorator uses Python’scontextvars to store the current trace context and to ensure that the observations are correctly associated with the current execution context. However, when using Python’s ThreadPoolExecutors and ProcessPoolExecutorsand when spawning threads from inside a trace (i.e. the executor is run inside a decorated function) the decorator will not work correctly as thecontextvars are not correctly copied to the new threads or processes. There is anexisting issue in Python’s standard library and agreat explanation in the fastapi repo that discusses this limitation.

The recommended workaround is to pass the parent observation id and the trace ID as a keyword argument to each multithreaded execution, thus re-establishing the link to the parent span or trace:

from concurrent.futuresimport ThreadPoolExecutor, as_completedfrom langfuseimport get_client, observe@observedef execute_task(*args):    return args@observedef execute_groups(task_args):    trace_id= get_client().get_current_trace_id()    observation_id= get_client().get_current_observation_id()    with ThreadPoolExecutor(3)as executor:        futures= [            executor.submit(                execute_task,                *task_arg,langfuse_parent_trace_id=trace_id,langfuse_parent_observation_id=observation_id,            )            for task_argin task_args        ]        for futurein as_completed(futures):            future.result()    return [f.result()for fin futures]@observe()def main():    task_args= [["a","b"], ["c","d"]]    execute_groups(task_args)main()get_client().flush()

Distributed tracing

To maintain the trace context across service / process boundaries, please rely on the OpenTelemetry native context propagation across service / process boundaries as much as possible.

Using thetrace_context argument to ‘force’ the parent child relationship may lead to unexpected trace updates as the resulting span will be treated as a root span server side.

Multi-Project Setup (Experimental)

⚠️

Multi-project setups areexperimental and have important limitations regarding third-party OpenTelemetry integrations.

The Langfuse Python SDK supports routing traces to different projects within the same application by using multiple public keys. This works because the Langfuse SDK adds a specific span attribute containing the public key to all spans it generates.

How it works:

  1. Span Attributes: The Langfuse SDK adds a specific span attribute containing the public key to spans it creates
  2. Multiple Processors: Multiple span processors are registered onto the global tracer provider, each with their respective exporters bound to a specific public key
  3. Filtering: Within each span processor, spans are filtered based on the presence and value of the public key attribute

Important Limitation with Third-Party Libraries:

Third-party libraries that emit OpenTelemetry spans automatically (e.g., HTTP clients, databases, other instrumentation libraries) donot have the Langfuse public key span attribute. As a result:

  • These spans cannot be routed to a specific project
  • They are processed by all span processors and sent to all projects
  • All projects will receive these third-party spans

Why is this experimental?This approach requires that thepublic_key parameter be passed to all Langfuse SDK executions across all integrations to ensure proper routing, and third-party spans will appear in all projects.

Initialization

To set up multiple projects, initialize separate Langfuse clients for each project:

from langfuseimport Langfuse# Initialize clients for different projectsproject_a_client= Langfuse(    public_key="pk-lf-project-a-...",    secret_key="sk-lf-project-a-...",    host="https://cloud.langfuse.com")project_b_client= Langfuse(    public_key="pk-lf-project-b-...",    secret_key="sk-lf-project-b-...",    host="https://cloud.langfuse.com")

Integration Usage

For all integrations in multi-project setups, you must specify thepublic_key parameter to ensure traces are routed to the correct project.

Observe Decorator:

Passlangfuse_public_key as a keyword argument to thetop-most observed function (not the decorator). From Python SDK >= 3.2.2, nested decorated functions will automatically pick up the public key from the execution context they are currently into. Also, calls toget_client will be also aware of the currentlangfuse_public_key in the decorated function execution context, so passing thelangfuse_public_key here again is not necessary.

from langfuseimport observe@observedef nested():    # get_client call is context aware    # if it runs inside another decorated function that has    # langfuse_public_key passed, it does not need passing here again    get_client().update_current_trace(user_id='myuser')@observedef process_data_for_project_a(data):    # passing `langfuse_public_key` here again is not necessarily    # as it is stored in execution context    nested()    return {"processed": data}@observedef process_data_for_project_b(data):    # passing `langfuse_public_key` here again is not necessarily    # as it is stored in execution context    nested()    return {"enhanced": data}# Route to Project A# Top-most decorated function needs `langfuse_public_key` kwargresult_a= process_data_for_project_a(    data="input data",    langfuse_public_key="pk-lf-project-a-...")# Route to Project B# Top-most decorated function needs `langfuse_public_key` kwargresult_b= process_data_for_project_b(    data="input data",    langfuse_public_key="pk-lf-project-b-...")

OpenAI Integration:

Addlangfuse_public_key as a keyword argument to the OpenAI execution:

from langfuse.openaiimport openaiclient= openai.OpenAI()# Route to Project Aresponse_a= client.chat.completions.create(    model="gpt-4o",    messages=[{"role":"user","content":"Hello from Project A"}],    langfuse_public_key="pk-lf-project-a-...")# Route to Project Bresponse_b= client.chat.completions.create(    model="gpt-4o",    messages=[{"role":"user","content":"Hello from Project B"}],    langfuse_public_key="pk-lf-project-b-...")

Langchain Integration:

Addpublic_key to the CallbackHandler constructor:

from langfuse.langchainimport CallbackHandlerfrom langchain_openaiimport ChatOpenAIfrom langchain_core.promptsimport ChatPromptTemplate# Create handlers for different projectshandler_a= CallbackHandler(public_key="pk-lf-project-a-...")handler_b= CallbackHandler(public_key="pk-lf-project-b-...")llm= ChatOpenAI(model_name="gpt-4o")prompt= ChatPromptTemplate.from_template("Tell me about{topic}")chain= prompt| llm# Route to Project Aresponse_a= chain.invoke(    {"topic":"machine learning"},    config={"callbacks": [handler_a]})# Route to Project Bresponse_b= chain.invoke(    {"topic":"data science"},    config={"callbacks": [handler_b]})

Important Considerations:

  • Every Langfuse SDK execution across all integrations must include the appropriate public key parameter
  • Missing public key parameters may result in traces being routed to the default project or lost
  • Third-party OpenTelemetry spans (from HTTP clients, databases, etc.) will appear in all projects since they lack the Langfuse public key attribute

Self-signed SSL certificates (self-hosted Langfuse)

If you areself-hosting Langfuse and you’d like to use self-signed SSL certificates, you will need to configure the SDK to trust the self-signed certificate:

⚠️

Changing SSL settings has major security implications depending on your environment. Be sure you understand these implications before you proceed.

1. Set OpenTelemetry span exporter to trust self-signed certificate

.env
OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE="/path/to/my-selfsigned-cert.crt"

2. Set HTTPX to trust certificate for all other API requests to Langfuse instance

main.py
import osimport httpxfrom langfuseimport Langfusehttpx_client= httpx.Client(verify=os.environ["OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE"])langfuse= Langfuse(httpx_client=httpx_client)

OTEL and Langfuse

The Langfuse v3 SDK is built uponOpenTelemetry (OTEL), a standard for observability. Understanding the relation between OTEL and Langfuse is not required to use the SDK, but it is helpful to have a basic understanding of the concepts. OTEL related concepts are abstracted away and you can use the SDK without being deeply familiar with them.

  • OTEL Trace: An OTEL-trace represents the entire lifecycle of a request or transaction as it moves through your application and its services. A trace is typically a sequence of operations, like an LLM generating a response followed by a parsing step. The root (first) span created in a sequence defines the OTEL-trace. OTEL-traces do not have a start and end time, they are defined by the root span.
  • OTEL Span: A span represents a single unit of work or operation within a trace. Spans have a start and end time, a name, and can have attributes (key-value pairs of metadata). Spans can be nested to create a hierarchy, showing parent-child relationships between operations.
  • Langfuse Trace: A Langfuse trace collects observations and holds trace attributes such assession_id,user_id as well as overall input and outputs. It shares the same ID as the OTEL trace and its attributes are set via specific OTEL span attributes that are automatically propagated to the Langfuse trace.
  • Langfuse Observation: In Langfuse terminology, an “observation” is a Langfuse-specific representation of an OTEL span. It can be a generic span (Langfuse-span) or a specialized “generation” (Langfuse-generation) or a point in time event (Langfuse-event)
    • Langfuse Span: A Langfuse-span is a generic OTEL-span in Langfuse, designed for non-LLM operations.
    • Langfuse Generation: A Langfuse-generation is a specialized type of OTEL-span in Langfuse, designed specifically for Large Language Model (LLM) calls. It includes additional fields likemodel,model_parameters,usage_details (tokens), andcost_details.
    • Langfuse Event: A Langfuse-event tracks a point in time action.
  • Context Propagation: OpenTelemetry automatically handles the propagation of the current trace and span context. This means when you call another function (whether it’s also traced by Langfuse, an OTEL-instrumented library, or a manually created span), the new span will automatically become a child of the currently active span, forming a correct trace hierarchy.

The Langfuse SDK provides wrappers around OTEL spans (LangfuseSpan,LangfuseGeneration) that offer convenient methods for interacting with Langfuse-specific features like scoring and media handling, while still being native OTEL spans under the hood. You can also use these wrapper objects to add Langfuse trace attributes.

Upgrade from v2

The v3 SDK introduces significant improvements and changes compared to v2. It isnot fully backward compatible. This comprehensive guide will help you migrate based on your current integration.

Core Changes to SDK v2:

  • OpenTelemetry Foundation: v3 is built on OpenTelemetry standards
  • Trace Input/Output: Now derived from root observation by default
  • Trace Attributes (user_id,session_id, etc.) Can be set via enclosing spans OR directly on integrations using metadata fields (OpenAI call, Langchain invocation)
  • Context Management: Automatic OTEL context propagation

Migration Path by Integration Type

@observe Decorator Users

v2 Pattern:

from langfuse.decoratorsimport langfuse_context, observe@observe()def my_function():    # This was the trace    langfuse_context.update_current_trace(user_id="user_123")    return "result"

v3 Migration:

from langfuseimport observe, get_client# new import@observe()def my_function():    # This is now the root span, not the trace    langfuse= get_client()    # Update trace explicitly    langfuse.update_current_trace(user_id="user_123")    return "result"

OpenAI Integration

v2 Pattern:

from langfuse.openaiimport openairesponse= openai.chat.completions.create(    model="gpt-4o",    messages=[{"role":"user","content":"Hello"}],    # Trace attributes directly on the call    user_id="user_123",    session_id="session_456",    tags=["chat"],    metadata={"source":"app"})

v3 Migration:

If you do not set additional trace attributes, no changes are needed.

If you set additional trace attributes, you have two options:

Option 1: Use metadata fields (simplest migration):

from langfuse.openaiimport openairesponse= openai.chat.completions.create(    model="gpt-4o",    messages=[{"role":"user","content":"Hello"}],    metadata={        "langfuse_user_id":"user_123",        "langfuse_session_id":"session_456",        "langfuse_tags": ["chat"],        "source":"app"  # Regular metadata still works    })

Option 2: Use enclosing span (for more control):

from langfuseimport get_clientfrom langfuse.openaiimport openailangfuse= get_client()with langfuse.start_as_current_span(name="chat-request")as span:    # Set trace attributes on the enclosing span    span.update_trace(        user_id="user_123",        session_id="session_456",        tags=["chat"],        # Explicit trace input/output for LLM-as-a-judge features        input={"query":"Hello"},    )    response= openai.chat.completions.create(        model="gpt-4o",        messages=[{"role":"user","content":"Hello"}],        metadata={"source":"app"}    )    # Set trace output explicitly    span.update_trace(output={"response": response.choices[0].message.content})

LangChain Integration

v2 Pattern:

from langfuse.callbackimport CallbackHandlerhandler= CallbackHandler(    user_id="user_123",    session_id="session_456",    tags=["langchain"])response= chain.invoke({"input":"Hello"},config={"callbacks": [handler]})

v3 Migration:

You have two options for setting trace attributes:

Option 1: Use metadata fields in chain invocation (simplest migration):

from langfuse.langchainimport CallbackHandlerhandler= CallbackHandler()response= chain.invoke(    {"input":"Hello"},    config={        "callbacks": [handler],        "metadata": {            "langfuse_user_id":"user_123",            "langfuse_session_id":"session_456",            "langfuse_tags": ["langchain"]        }    })

Option 2: Use enclosing span (for more control):

from langfuseimport get_clientfrom langfuse.langchainimport CallbackHandlerlangfuse= get_client()with langfuse.start_as_current_span(name="langchain-request")as span:    span.update_trace(        user_id="user_123",        session_id="session_456",        tags=["langchain"],        input={"query":"Hello"}# Explicit trace input    )    handler= CallbackHandler()    response= chain.invoke({"input":"Hello"},config={"callbacks": [handler]})    # Set trace output explicitly    span.update_trace(output={"response": response})

LlamaIndex Integration Users

v2 Pattern:

from langfuse.llama_indeximport LlamaIndexCallbackHandlerhandler= LlamaIndexCallbackHandler()Settings.callback_manager= CallbackManager([handler])response= index.as_query_engine().query("Hello")

v3 Migration:

from langfuseimport get_clientfrom openinference.instrumentation.llama_indeximport LlamaIndexInstrumentor# Use third-party OTEL instrumentationLlamaIndexInstrumentor().instrument()langfuse= get_client()with langfuse.start_as_current_span(name="llamaindex-query")as span:    span.update_trace(        user_id="user_123",        input={"query":"Hello"}    )    response= index.as_query_engine().query("Hello")    span.update_trace(output={"response":str(response)})

Low-Level SDK Users

v2 Pattern:

from langfuseimport Langfuselangfuse= Langfuse()trace= langfuse.trace(    name="my-trace",    user_id="user_123",    input={"query":"Hello"})generation= trace.generation(    name="llm-call",    model="gpt-4o")generation.end(output="Response")

v3 Migration:

In v3, all spans / generations must be ended by calling.end() on thereturned object.

from langfuseimport get_clientlangfuse= get_client()# Use context managers instead of manual objectswith langfuse.start_as_current_span(    name="my-trace",    input={"query":"Hello"}# Becomes trace input automatically)as root_span:    # Set trace attributes    root_span.update_trace(user_id="user_123")    with langfuse.start_as_current_generation(        name="llm-call",        model="gpt-4o"    )as generation:        generation.update(output="Response")    # If needed, override trace output    root_span.update_trace(output={"response":"Response"})

Key Migration Checklist

  1. Update Imports:

    • Usefrom langfuse import get_client to access global client instance configured via environment variables
    • Usefrom langfuse import Langfuse to create a new client instance configured via constructor parameters
    • Usefrom langfuse import observe to import the observe decorator
    • Update integration imports:from langfuse.langchain import CallbackHandler
  2. Trace Attributes Pattern:

    • Option 1: Use metadata fields (langfuse_user_id,langfuse_session_id,langfuse_tags) directly in integration calls
    • Option 2: Moveuser_id,session_id,tags to enclosing spans and usespan.update_trace() orlangfuse.update_current_trace()
  3. Trace Input/Output:

    • Critical for LLM-as-a-judge: Explicitly set trace input/output
    • Don’t rely on automatic derivation from root observation if you need specific values
  4. Context Managers:

    • Replace manuallangfuse.trace(),trace.span() with context managers if you want to use them
    • Usewith langfuse.start_as_current_span() instead
  5. LlamaIndex Migration:

    • Replace Langfuse callback with third-party OTEL instrumentation
    • Install:pip install openinference-instrumentation-llama-index
  6. ID Management:

    • No Custom Observation IDs: v3 uses W3C Trace Context standard - you cannot set custom observation IDs
    • Trace ID Format: Must be 32-character lowercase hexadecimal (16 bytes)
    • External ID Correlation: UseLangfuse.create_trace_id(seed=external_id) to generate deterministic trace IDs from external systems
    from langfuseimport Langfuse, observe# v3: Generate deterministic trace ID from external systemexternal_request_id= "req_12345"trace_id= Langfuse.create_trace_id(seed=external_request_id)@observe(langfuse_trace_id=trace_id)def my_function():    # This trace will have the deterministic ID    pass
  7. Initialization:

    • Replace constructor parameters:
      • enabledtracing_enabled
      • threadsmedia_upload_thread_count

Detailed Change Summary

  1. Core Change: OpenTelemetry Foundation

    • Built on OpenTelemetry standards for better ecosystem compatibility
  2. Trace Input/Output Behavior

    • v2: Integrations could set trace input/output directly
    • v3: Trace input/output derived from root observation by default
    • Migration: Explicitly set viaspan.update_trace(input=..., output=...)
  3. Trace Attributes Location

    • v2: Could be set directly on integration calls
    • v3: Must be set on enclosing spans
    • Migration: Wrap integration calls withlangfuse.start_as_current_span()
  4. Creating Observations:

    • v2:langfuse.trace(),langfuse.span(),langfuse.generation()
    • v3:langfuse.start_as_current_span(),langfuse.start_as_current_generation()
    • Migration: Use context managers, ensure.end() is called or usewith statements
  5. IDs and Context:

    • v3: W3C Trace Context format, automatic context propagation
    • Migration: Uselangfuse.get_current_trace_id() instead ofget_trace_id()
  6. Event Size Limitations:

    • v2: Events were limited to 1MB in size
    • v3: No size limits enforced on the SDK-side for events

Future support for v2

We will continue to support the v2 SDK for the foreseeable future with critical bug fixes and security patches. We will not be adding any new features to the v2 SDK.

Troubleshooting

  • Authentication Issues:
    • EnsureLANGFUSE_PUBLIC_KEY,LANGFUSE_SECRET_KEY, andLANGFUSE_HOST (if not using default cloud) are correctly set either as environment variables or in theLangfuse() constructor.
    • Uselangfuse.auth_check() after initialization to verify credentials. Do not use this in production as this method waits for a response from the server.
  • No Traces Appearing:
    • Check iftracing_enabled isTrue (default).
    • Verifysample_rate is not0.0.
    • Ensurelangfuse.shutdown() is called or the program exits cleanly to allowatexit hooks to flush data. Manually calllangfuse.flush() to force data sending.
    • Enable debug logging (debug=True orLANGFUSE_DEBUG="True") to see SDK activity and potential errors during exporting.
  • Incorrect Nesting or Missing Spans:
    • Ensure you are using context managers (with langfuse.start_as_current_span(...)) for proper context propagation.
    • If manually creating spans (langfuse.start_span()), ensure they are correctly ended with.end().
    • In async code, ensure context is not lost acrossawait boundaries if not using Langfuse’s async-compatible methods.
  • Langchain/OpenAI Integration Not Working:
    • Confirm the respective integration (e.g.,from langfuse.openai import openai orLangfuseCallbackHandler) is correctly set upbefore the calls to the LLM libraries are made.
    • Check for version compatibility issues between Langfuse, Langchain, and OpenAI SDKs.
  • Media Not Appearing:
    • EnsureLangfuseMedia objects are correctly initialized and passed ininput,output, ormetadata.
    • Check debug logs for any media upload errors. Media uploads happen in background threads.

If you encounter persistent issues, please:

  1. Enable debug logging to gather more information.
  2. Check the Langfuse status page (if applicable for cloud users).
  3. Raise an issue on ourGitHub repository with details about your setup, SDK version, code snippets, and debug logs.
Was this page helpful?

[8]ページ先頭

©2009-2025 Movatter.jp