IntegrationsFrameworksOpenAI Agents
This is a Jupyter notebook

Trace the OpenAI Agents SDK with Langfuse

This notebook demonstrates how to integrate Langfuse into your OpenAI Agents workflow to monitor, debug and evaluate your AI agents.

What is the OpenAI Agents SDK?: The OpenAI Agents SDK is a lightweight, open-source framework that lets developers build AI agents and orchestrate multi-agent workflows. It provides building blocks—such as tools, handoffs, and guardrails to configure large language models with custom instructions and integrated tools. Its Python-first design supports dynamic instructions and function tools for rapid prototyping and integration with external systems.

What is Langfuse?: Langfuse is an open-source observability platform for AI agents. It helps you visualize and monitor LLM calls, tool usage, cost, latency, and more.

1. Install Dependencies

Below we install the openai-agents library (the OpenAI Agents SDK), and the pydantic-ai[logfire] OpenTelemetry instrumentation.

%pip install openai-agents langfuse nest_asyncio "pydantic-ai[logfire]"

2. Configure Environment & Langfuse Credentials

Next, set up your Langfuse API keys. You can get these keys by signing up for a free Langfuse Cloud account or by self-hosting Langfuse. These environment variables are essential for the Langfuse client to authenticate and send data to your Langfuse project.

import os
 
# Get keys for your project from the project settings page: https://cloud.langfuse.com
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..." 
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..." 
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com" # 🇪🇺 EU region
# os.environ["LANGFUSE_HOST"] = "https://us.cloud.langfuse.com" # 🇺🇸 US region
 
# Your openai key
os.environ["OPENAI_API_KEY"] = "sk-proj-..."

3. Instrumenting the Agent

Pydantic Logfire offers an instrumentation for the OpenAi Agent SDK. We use this to send traces to Langfuse.

import nest_asyncio
nest_asyncio.apply()
import logfire
 
# Configure logfire instrumentation.
logfire.configure(
    service_name='my_agent_service',
    send_to_logfire=False,
)
# This method automatically patches the OpenAI Agents SDK to send logs via OTLP to Langfuse.
logfire.instrument_openai_agents()

Now initialize the Langfuse client. get_client() initializes the Langfuse client using the credentials provided in the environment variables.

from langfuse import get_client
 
langfuse = get_client()
 
# Verify connection
if langfuse.auth_check():
    print("Langfuse client is authenticated and ready!")
else:
    print("Authentication failed. Please check your credentials and host.")

4. Hello World Example

Below we create an OpenAI Agent that always replies in haiku form. We run it with Runner.run and print the final output.

import asyncio
from agents import Agent, Runner
 
async def main():
    agent = Agent(
        name="Assistant",
        instructions="You only respond in haikus.",
    )
 
    result = await Runner.run(agent, "Tell me about recursion in programming.")
    print(result.final_output)
 
loop = asyncio.get_running_loop()
await loop.create_task(main())

Example trace in Langfuse

Example: Langfuse Trace

Clicking the link above (or your own project link) lets you view all sub-spans, token usage, latencies, etc., for debugging or optimization.

5. Multi-agent Handoff Example

Here we create:

  • A Spanish agent that responds only in Spanish.
  • An English agent that responds only in English.
  • A Triage agent that routes the request to the correct agent based on the input language.

Any calls or handoffs are captured as part of the trace. That way, you can see which sub-agent or tool was used, as well as the final result.

from agents import Agent, Runner
import asyncio
 
spanish_agent = Agent(
    name="Spanish agent",
    instructions="You only speak Spanish.",
)
 
english_agent = Agent(
    name="English agent",
    instructions="You only speak English",
)
 
triage_agent = Agent(
    name="Triage agent",
    instructions="Handoff to the appropriate agent based on the language of the request.",
    handoffs=[spanish_agent, english_agent],
)
 
result = await Runner.run(triage_agent, input="Hola, ¿cómo estás?")
print(result.final_output)

Example trace in Langfuse

Example: Langfuse Trace

6. Functions Example

The OpenAI Agents SDK allows the agent to call Python functions. With Langfuse instrumentation, you can see which functions are called, their arguments, and the return values. Here we define a simple function get_weather(city: str) and add it as a tool.

import asyncio
from agents import Agent, Runner, function_tool
 
# Example function tool.
@function_tool
def get_weather(city: str) -> str:
    return f"The weather in {city} is sunny."
 
agent = Agent(
    name="Hello world",
    instructions="You are a helpful agent.",
    tools=[get_weather],
)
 
async def main():
    result = await Runner.run(agent, input="What's the weather in Tokyo?")
    print(result.final_output)
 
loop = asyncio.get_running_loop()
await loop.create_task(main())

Example trace in Langfuse

Example: Langfuse Trace

When viewing the trace, you’ll see a span capturing the function call get_weather and the arguments passed.

7. Grouping Agent Runs

In some workflows, you want to group multiple calls into a single trace—for instance, when building a small chain of prompts that all relate to the same user request. You can use a trace(...) context manager to nest multiple calls under one top-level trace.

from agents import Agent, Runner, trace
import asyncio
 
async def main():
    agent = Agent(name="Joke generator", instructions="Tell funny jokes.")
 
    with trace("Joke workflow"):
        first_result = await Runner.run(agent, "Tell me a joke")
        second_result = await Runner.run(agent, f"Rate this joke: {first_result.final_output}")
        print(f"Joke: {first_result.final_output}")
        print(f"Rating: {second_result.final_output}")
 
loop = asyncio.get_running_loop()
await loop.create_task(main())

Example trace in Langfuse

Example: Langfuse Trace

Each child call is represented as a sub-span under the top-level Joke workflow span, making it easy to see the entire conversation or sequence of calls.

If you manage your prompt with Langfuse Prompt Management, you can link the used prompt to the trace by setting up an OTel Span processor.

⚠️

Limitation: This method links the Langfuse Prompt to all generation spans in the trace that start with the defined string (see next cell).

from contextvars import ContextVar
from typing import Optional
from opentelemetry import context as context_api
from opentelemetry.sdk.trace.export import Span, SpanProcessor
 
prompt_info_var = ContextVar("prompt_info", default=None)
 
class LangfuseProcessor(SpanProcessor):
    def on_start(
        self,
        span: 'Span',
        parent_context: Optional[context_api.Context] = None,
    ) -> None:
        if span.name.startswith('Responses API'): # The name of the generation spans in your OpenAI Agent trace 
            prompt_info = prompt_info_var.get()
            if prompt_info:
                span.set_attribute('langfuse.prompt.name', prompt_info.get("name"))
                span.set_attribute('langfuse.prompt.version', prompt_info.get("version"))
import logfire
from langfuse import get_client
 
logfire.configure(
    service_name='my_agent_service',
    additional_span_processors=[LangfuseProcessor()], # Passing the LangfuseProcessor to the logfire configuration will automatically link the prompt to the trace
    send_to_logfire=False,
)
 
logfire.instrument_openai_agents()
langfuse = get_client()
# Fetch the prompt from Langfuse Prompt Management
langfuse_prompt = langfuse.get_prompt("movie-critic")
system_prompt = langfuse_prompt.compile(criticlevel = "expert", movie = "Dune 2")
 
# Pass the prompt to the SpanProcessor
prompt_info_var.set({
    "name": langfuse_prompt.name,
    "version": langfuse_prompt.version,
})
 
# Run the agent ...

Resources

Interoperability with the Python SDK

You can use this integration together with the Langfuse Python SDK to add additional attributes to the trace.

The @observe() decorator provides a convenient way to automatically wrap your instrumented code and add additional attributes to the trace.

from langfuse import observe, get_client
 
langfuse = get_client()
 
@observe()
def my_instrumented_function(input):
    output = my_llm_call(input)
 
    langfuse.update_current_trace(
        input=input,
        output=output,
        user_id="user_123",
        session_id="session_abc",
        tags=["agent", "my-trace"],
        metadata={"email": "[email protected]"},
        version="1.0.0"
    )
 
    return output

Learn more about using the Decorator in the Python SDK docs.

Next Steps

Once you have instrumented your code, you can manage, evaluate and debug your application:

Was this page helpful?