Langfuse @ AI Engineer ยท Booth LG-39 โ†’
IntegrationsAG2

Integrate Langfuse with AG2

This guide shows how to integrate Langfuse with AG2 using AG2's built-in OpenTelemetry tracing for full observability into your multi-agent workflows.

What is AG2? AG2 (GitHub) is an open-source Python framework for building multi-agent AI systems. AG2 provides tools for orchestrating collaborative agents, tool use, group chats, and distributed agent-to-agent (A2A) deployments. AG2 v0.11+ includes native OpenTelemetry tracing that captures every conversation, agent turn, LLM call, tool execution, and speaker selection as structured spans.

What is Langfuse? Langfuse is an open-source LLM engineering platform. It offers tracing and monitoring capabilities for AI applications. Langfuse helps developers debug, analyze, and optimize their AI systems by providing detailed insights and integrating with a wide array of tools and frameworks through native integrations, OpenTelemetry, and dedicated SDKs.

AG2 is the community-driven continuation of AutoGen. If you are using Microsoft AutoGen instead, see the AutoGen integration, which traces via OpenLIT rather than AG2's native OpenTelemetry support.

Get started

AG2's native OpenTelemetry tracing exports rich, hierarchical spans that Langfuse ingests directly โ€” no intermediary libraries needed.

Step 1: Install dependencies

pip install "ag2[openai,tracing]" opentelemetry-exporter-otlp langfuse -U

Step 2: Configure Langfuse SDK

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.

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_BASE_URL"] = "https://cloud.langfuse.com" # ๐Ÿ‡ช๐Ÿ‡บ EU region
# os.environ["LANGFUSE_BASE_URL"] = "https://us.cloud.langfuse.com" # ๐Ÿ‡บ๐Ÿ‡ธ US region

# Your OpenAI key
os.environ["OPENAI_API_KEY"] = "sk-proj-..."

Initialize the Langfuse client and verify the connection:

from langfuse import get_client

langfuse = get_client()

if langfuse.auth_check():
    print("Langfuse client is authenticated and ready!")

Step 3: Configure OpenTelemetry to export to Langfuse

Set up an OpenTelemetry TracerProvider that exports spans directly to Langfuse's OTel endpoint:

import os
import base64
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

LANGFUSE_PUBLIC_KEY = os.environ["LANGFUSE_PUBLIC_KEY"]
LANGFUSE_SECRET_KEY = os.environ["LANGFUSE_SECRET_KEY"]
LANGFUSE_BASE_URL = os.environ["LANGFUSE_BASE_URL"]

auth = base64.b64encode(
    f"{LANGFUSE_PUBLIC_KEY}:{LANGFUSE_SECRET_KEY}".encode()
).decode()

resource = Resource.create({"service.name": "ag2-langfuse-demo"})
tracer_provider = TracerProvider(resource=resource)

exporter = OTLPSpanExporter(
    endpoint=f"{LANGFUSE_BASE_URL}/api/public/otel/v1/traces",
    headers={"Authorization": f"Basic {auth}"},
)
tracer_provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(tracer_provider)

Step 4: Instrument agents and run

Create AG2 agents and instrument them with AG2's built-in tracing:

from autogen import ConversableAgent, LLMConfig
from autogen.opentelemetry import instrument_agent, instrument_llm_wrapper

llm_config = LLMConfig({"model": "gpt-4o-mini"})

assistant = ConversableAgent(
    name="assistant",
    system_message="You are a helpful assistant.",
    llm_config=llm_config,
    human_input_mode="NEVER",
)

user_proxy = ConversableAgent(
    name="user_proxy",
    human_input_mode="NEVER",
    max_consecutive_auto_reply=1,
)

# Instrument agents and LLM calls
instrument_llm_wrapper(tracer_provider=tracer_provider)
instrument_agent(assistant, tracer_provider=tracer_provider)
instrument_agent(user_proxy, tracer_provider=tracer_provider)

# Run a chat โ€” traces are sent to Langfuse automatically
result = user_proxy.run(
    assistant,
    message="What is the capital of France?",
    max_turns=2,
)
result.process()

# Flush spans before exit
tracer_provider.shutdown()

Step 5: View traces in Langfuse

After executing the application, navigate to your Langfuse Trace Table. You will see hierarchical traces showing the full conversation flow โ€” each agent turn, LLM call (with model, tokens, and cost), and tool execution nested in a tree that mirrors your agent workflow.

AG2's tracing emits 7 span types that map to Langfuse observations:

AG2 span typeWhat it captures
conversationOverall chat with total token usage and cost
agentIndividual agent turn with input/output messages
llmLLM API call with model, tokens, cost, parameters
toolTool execution with arguments and return value
code_executionCode execution with output
human_inputHuman input prompt and response
speaker_selectionGroup chat speaker selection with candidates

Tool use example

AG2 traces tool executions with full argument and return value capture. This example reuses the tracer_provider configured in Step 3 above:

from typing import Annotated
from autogen import ConversableAgent, LLMConfig
from autogen.opentelemetry import instrument_agent, instrument_llm_wrapper
from autogen.tools import tool

@tool(description="Get weather information for a city")
def get_weather(city: Annotated[str, "The city name"]) -> str:
    """Get weather information for a city."""
    weather_data = {
        "new york": "Sunny, 72F",
        "london": "Cloudy, 15C",
        "tokyo": "Rainy, 18C",
    }
    return weather_data.get(city.lower(), f"Weather data not available for {city}")

llm_config = LLMConfig({"model": "gpt-4o-mini"})

weather_agent = ConversableAgent(
    name="weather",
    system_message="Use the get_weather tool to answer weather questions.",
    functions=[get_weather],
    llm_config=llm_config,
    human_input_mode="NEVER",
)

instrument_llm_wrapper(tracer_provider=tracer_provider)
instrument_agent(weather_agent, tracer_provider=tracer_provider)

result = weather_agent.run(message="What is the weather in Tokyo?", max_turns=2)
result.process()

In Langfuse, execute_tool get_weather spans appear nested under the agent span, with tool arguments and return values visible in the observation input/output.

Group chat example

For group chats, use instrument_pattern to instrument all agents in a single call (again reusing the tracer_provider from Step 3):

from autogen import ConversableAgent, LLMConfig
from autogen.agentchat import run_group_chat
from autogen.agentchat.group.patterns import AutoPattern
from autogen.opentelemetry import instrument_llm_wrapper, instrument_pattern

llm_config = LLMConfig({"model": "gpt-4o-mini"})

researcher = ConversableAgent(
    name="researcher",
    system_message="You research topics and provide factual information.",
    llm_config=llm_config,
    human_input_mode="NEVER",
)

writer = ConversableAgent(
    name="writer",
    system_message="You write clear summaries. Say TERMINATE when done.",
    llm_config=llm_config,
    human_input_mode="NEVER",
)

user = ConversableAgent(name="user", human_input_mode="NEVER", llm_config=False)

pattern = AutoPattern(
    initial_agent=researcher,
    agents=[researcher, writer],
    user_agent=user,
    group_manager_args={"llm_config": llm_config},
)

instrument_llm_wrapper(tracer_provider=tracer_provider)
instrument_pattern(pattern, tracer_provider=tracer_provider)

result = run_group_chat(
    pattern=pattern,
    messages="Explain quantum computing in simple terms.",
    max_rounds=5,
)
result.process()

This produces hierarchical traces in Langfuse including speaker_selection spans that show which agent was chosen and why.

Distributed tracing with A2A

AG2 supports the A2A (Agent-to-Agent) protocol for distributed multi-service deployments. When agents run as separate services, AG2 propagates W3C Trace Context headers across HTTP calls, and Langfuse stitches the spans into a unified trace.

from autogen import ConversableAgent, LLMConfig
from autogen.a2a import A2aAgentServer
from autogen.opentelemetry import instrument_a2a_server

agent = ConversableAgent(
    name="assistant",
    llm_config=LLMConfig({"model": "gpt-4o-mini"}),
)

server = A2aAgentServer(agent, url="http://localhost:18123/")
instrument_a2a_server(server, tracer_provider=tracer_provider)

Environment variables (alternative setup)

Instead of configuring the exporter in code, you can use environment variables:

export OTEL_EXPORTER_OTLP_ENDPOINT="https://cloud.langfuse.com/api/public/otel"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic $(echo -n 'pk-lf-...:sk-lf-...' | base64)"
export OTEL_SERVICE_NAME="ag2-app"

Learn more

Interoperability with the Python SDK

You can use this integration together with the Langfuse SDKs to add additional attributes to the observation.

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

from langfuse import observe, propagate_attributes, get_client

langfuse = get_client()

@observe()
def my_llm_pipeline(input):
    # Add additional attributes (user_id, session_id, metadata, version, tags) to all spans created within this execution scope
    with propagate_attributes(
        user_id="user_123",
        session_id="session_abc",
        tags=["agent", "my-observation"],
        metadata={"email": "user@langfuse.com"},
        version="1.0.0"
    ):

        # YOUR APPLICATION CODE HERE
        result = call_llm(input)

        return result

# Run the function
my_llm_pipeline("Hi")

Learn more about using the Decorator in the Langfuse SDK instrumentation docs.

The Context Manager allows you to wrap your instrumented code using context managers (with with statements), which allows you to add additional attributes to the observation.

from langfuse import get_client, propagate_attributes

langfuse = get_client()

with langfuse.start_as_current_observation(
    as_type="span",
    name="my-observation",
    trace_context={"trace_id": "abcdef1234567890abcdef1234567890"},  # Must be 32 hex chars
) as observation:

    # Add additional attributes (user_id, session_id, metadata, version, tags)
    # to all observations created within this execution scope
    with propagate_attributes(
        user_id="user_123",
        session_id="session_abc",
        metadata={"experiment": "variant_a", "env": "prod"},
        version="1.0",
    ):
        # YOUR APPLICATION CODE HERE
        result = call_llm("some input")

# Flush events in short-lived applications
langfuse.flush()

Learn more about using the Context Manager in the Langfuse SDK instrumentation docs.

Troubleshooting

No observations appearing

First, enable debug mode in the Python SDK:

export LANGFUSE_DEBUG="True"

Then run your application and check the debug logs:

  • OTel observations appear in the logs: Your application is instrumented correctly but observations are not reaching Langfuse. To resolve this:
    1. Call langfuse.flush() at the end of your application to ensure all observations are exported.
    2. Verify that you are using the correct API keys and base URL.
  • No OTel spans in the logs: Your application is not instrumented correctly. Make sure the instrumentation runs before your application code.
Unwanted observations in Langfuse

The Langfuse SDK is based on OpenTelemetry. Other libraries in your application may emit OTel spans that are not relevant to you. These still count toward your billable units, so you should filter them out. See Unwanted spans in Langfuse for details.

Missing attributes

Some attributes may be stored in the metadata object of the observation rather than being mapped to the Langfuse data model. If a mapping or integration does not work as expected, please raise an issue on GitHub.

Next Steps

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


Was this page helpful?

Last edited