> ## Documentation Index
> Fetch the complete documentation index at: https://docs.restate.dev/llms.txt
> Use this file to discover all available pages before exploring further.

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://docs.restate.dev/feedback

```json
{
  "path": "/ai/ecosystem-integrations/langfuse",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>

# Langfuse

> Trace and monitor your Restate AI agents with Langfuse. Get full visibility into LLM calls, tool executions, and workflow steps.

export const GitHubLink = ({url}) => <div style={{
  marginTop: '-8px',
  marginBottom: '8px',
  textAlign: 'right'
}}>
    <a href={url} target="_blank" rel="noopener noreferrer" style={{
  fontSize: '0.75rem',
  color: '#6B7280',
  textDecoration: 'none',
  display: 'inline-flex',
  alignItems: 'center',
  gap: '3px',
  padding: '2px 6px',
  borderRadius: '3px',
  border: '1px solid #E5E7EB',
  backgroundColor: 'transparent',
  transition: 'all 0.2s ease'
}} onMouseOver={e => {
  e.target.style.color = '#6B7280';
  e.target.style.backgroundColor = '#F9FAFB';
}} onMouseOut={e => {
  e.target.style.color = '#6B7280';
  e.target.style.backgroundColor = 'transparent';
}}>
      <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
        <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.230 3.297-1.230.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
      </svg>
      View on GitHub
    </a>
  </div>;

export const GlobalTab = ({title, icon, children}) => {
  return <div>{children}</div>;
};

export const GlobalTabs = ({children, className = ''}) => {
  const [activeTab, setActiveTab] = useState(0);
  const tabs = React.Children.toArray(children).filter(child => child.type && child.type.name === 'GlobalTab');
  useEffect(() => {
    const savedLanguage = localStorage.getItem('language');
    if (savedLanguage) {
      const matchingIndex = tabs.findIndex(tab => tab.props.title === savedLanguage);
      if (matchingIndex !== -1) {
        setActiveTab(matchingIndex);
      }
    }
  }, [tabs]);
  useEffect(() => {
    const handleGlobalTabChange = event => {
      const targetTitle = event.detail.title;
      const matchingIndex = tabs.findIndex(tab => tab.props.title === targetTitle);
      if (matchingIndex !== -1 && matchingIndex !== activeTab) {
        setActiveTab(matchingIndex);
      }
    };
    window.addEventListener('globalTabChange', handleGlobalTabChange);
    return () => window.removeEventListener('globalTabChange', handleGlobalTabChange);
  }, [tabs, activeTab]);
  const handleTabClick = index => {
    setActiveTab(index);
    const title = tabs[index].props.title;
    localStorage.setItem('language', title);
    window.dispatchEvent(new CustomEvent('globalTabChange', {
      detail: {
        title
      }
    }));
  };
  return <div className={`tabs tabs tab-container ${className}`}>
            <ul className="not-prose mb-6 pb-[1px] flex-none min-w-full overflow-auto border-b border-gray-200 gap-x-6 flex dark:border-gray-200/10" data-component-part="tabs-list">
                {tabs.map((tab, index) => <li key={index} className="cursor-pointer">
                        <button className={index === activeTab ? "flex text-sm items-center gap-1.5 leading-6 font-semibold whitespace-nowrap pt-3 pb-2.5 -mb-px max-w-max border-b text-primary dark:text-primary-light border-current" : "flex text-sm items-center gap-1.5 leading-6 font-semibold whitespace-nowrap pt-3 pb-2.5 -mb-px max-w-max border-b text-gray-900 border-transparent hover:border-gray-300 dark:text-gray-200 dark:hover:border-gray-700"} data-component-part="tab-button" data-active={index === activeTab} onClick={() => handleTabClick(index)}>
                            {tab.props.icon && <img src={tab.props.icon} alt="" className="h-4 w-4 not-prose" noZoom />}
                            {tab.props.title}
                        </button>
                    </li>)}
            </ul>
            <div className="prose dark:prose-dark overflow-x-auto" data-component-part="tab-content">
                {tabs[activeTab]?.props.children}
            </div>
        </div>;
};

Combine Restate and [Langfuse](https://langfuse.com/) to get full observability into your agent executions.
Langfuse traces every LLM call, tool invocation, token usage, and cost.
Restate traces every durable step, so you see agentic steps alongside regular workflow steps in a single trace.

You don't need to change your agent code. You only add the Langfuse instrumentation to your entry point.

<Card title="Langfuse Documentation" icon="link" href="https://langfuse.com/docs">
  Learn more about Langfuse's observability, prompt management, and evaluation features.
</Card>

## Instrumentation setup

Select your Agent SDK:

<GlobalTabs>
  <GlobalTab title="OpenAI Agents" icon={"/img/languages/python.svg"} />

  <GlobalTab title="Google ADK" icon={"/img/languages/python.svg"} />

  <GlobalTab title="Pydantic AI" icon={"/img/languages/python.svg"} />

  <GlobalTab title="Restate Py" icon={"/img/languages/python.svg"} />
</GlobalTabs>

<GlobalTabs className={"hidden-tabs"}>
  <GlobalTab title="OpenAI Agents">
    Initialize the Langfuse client and wrap the OpenTelemetry tracer with `RestateTracer` to correlate AI spans with Restate's execution journal:

    ```python __main__.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/openai-agents/examples/langfuse/__main__.py#here"}  theme={null}
    from langfuse import get_client
    from opentelemetry import trace as trace_api
    from openinference.instrumentation import OITracer, TraceConfig
    from openinference.instrumentation.openai_agents._processor import (
        OpenInferenceTracingProcessor,
    )
    from agents import set_trace_processors
    from restate.ext.tracing import RestateTracer

    # Initialize Langfuse (sets up the global OTEL tracer provider + exporter)
    langfuse = get_client()
    tracer = OITracer(
        RestateTracer(trace_api.get_tracer("openinference.openai_agents")),
        config=TraceConfig(),
    )
    set_trace_processors([OpenInferenceTracingProcessor(tracer)])
    ```

    <GitHubLink url={"https://github.com/restatedev/ai-examples/blob/main/openai-agents/examples/langfuse"} />

    <Accordion title="Run the example" icon="laptop">
      **Prerequisites**: [Langfuse account and API key](https://langfuse.com/), [OpenAI API key](https://platform.openai.com/api-keys), [Restate installed](/installation).

      Get the example:

      ```bash theme={null}
      restate example python-openai-agents-examples && cd python-openai-agents-examples/langfuse
      ```

      Add your API keys to an `.env` file:

      ```bash theme={null}
      echo 'LANGFUSE_PUBLIC_KEY=pk-lf-...' > .env
      echo 'LANGFUSE_SECRET_KEY=sk-lf-...' >> .env
      echo 'LANGFUSE_BASE_URL=https://cloud.langfuse.com' >> .env
      echo 'OPENAI_API_KEY=sk-proj-...' >> .env
      ```

      Start the agent service:

      ```bash theme={null}
      uv run --env-file .env .
      ```

      Start Restate with Langfuse tracing:

      ```bash theme={null}
      source .env
      export RESTATE_TRACING_HEADERS__AUTHORIZATION="Basic $(echo -n "${LANGFUSE_PUBLIC_KEY}:${LANGFUSE_SECRET_KEY}" | base64)"
      restate-server --tracing-endpoint otlp+https://cloud.langfuse.com/api/public/otel/v1/traces
      ```

      Go to the Restate UI at `http://localhost:9070`, register the service at `http://localhost:9080`, click on the handler to go to the playground, and send the default request.
    </Accordion>
  </GlobalTab>

  <GlobalTab title="Google ADK">
    Initialize the Langfuse client and instrument Google ADK with `RestateTracerProvider` to correlate AI spans with Restate's execution journal:

    ```python __main__.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/google-adk/examples/langfuse/__main__.py#here"}  theme={null}
    from langfuse import get_client
    from opentelemetry import trace as trace_api
    from openinference.instrumentation.google_adk import GoogleADKInstrumentor
    from restate.ext.tracing import RestateTracerProvider

    # Initialize Langfuse (sets up the global OTEL tracer provider + exporter)
    langfuse = get_client()

    # Instrument Google ADK with Restate-aware tracing
    GoogleADKInstrumentor().instrument(
        tracer_provider=RestateTracerProvider(trace_api.get_tracer_provider())
    )
    ```

    <GitHubLink url={"https://github.com/restatedev/ai-examples/blob/main/google-adk/examples/langfuse"} />

    <Accordion title="Run the example" icon="laptop">
      **Prerequisites**: [Langfuse account and API key](https://langfuse.com/), [Google API key](https://aistudio.google.com/app/apikey), [Restate installed](/installation).

      Get the example:

      ```bash theme={null}
      restate example python-google-adk-examples && cd python-google-adk-examples/langfuse
      ```

      Add your API keys to an `.env` file:

      ```bash theme={null}
      echo 'LANGFUSE_PUBLIC_KEY=pk-lf-...' > .env
      echo 'LANGFUSE_SECRET_KEY=sk-lf-...' >> .env
      echo 'LANGFUSE_BASE_URL=https://cloud.langfuse.com' >> .env
      echo 'GOOGLE_API_KEY=your-api-key' >> .env
      ```

      Start the agent service:

      ```bash theme={null}
      uv run --env-file .env .
      ```

      Start Restate with Langfuse tracing:

      ```bash theme={null}
      source .env
      export RESTATE_TRACING_HEADERS__AUTHORIZATION="Basic $(echo -n "${LANGFUSE_PUBLIC_KEY}:${LANGFUSE_SECRET_KEY}" | base64)"
      restate-server --tracing-endpoint otlp+https://cloud.langfuse.com/api/public/otel/v1/traces
      ```

      Go to the Restate UI at `http://localhost:9070`, register the service at `http://localhost:9080`, click on the handler to go to the playground, and send the default request.
    </Accordion>
  </GlobalTab>

  <GlobalTab title="Pydantic AI">
    Initialize the Langfuse client and instrument Pydantic AI with `RestateTracerProvider` to correlate AI spans with Restate's execution journal:

    ```python __main__.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/pydantic-ai/examples/langfuse/__main__.py#here"}  theme={null}
    from langfuse import get_client
    from opentelemetry import trace as trace_api
    from pydantic_ai import Agent
    from pydantic_ai.models.instrumented import InstrumentationSettings
    from restate.ext.tracing import RestateTracerProvider

    # Initialize Langfuse (sets up the global OTEL tracer provider + exporter)
    langfuse = get_client()

    # Instrument Pydantic AI with Restate-aware tracing
    Agent.instrument_all(
        InstrumentationSettings(
            tracer_provider=RestateTracerProvider(trace_api.get_tracer_provider())
        )
    )
    ```

    <GitHubLink url={"https://github.com/restatedev/ai-examples/blob/main/pydantic-ai/examples/langfuse"} />

    <Accordion title="Run the example" icon="laptop">
      **Prerequisites**: [Langfuse account and API key](https://langfuse.com/), [OpenAI API key](https://platform.openai.com/api-keys), [Restate installed](/installation).

      Get the example:

      ```bash theme={null}
      restate example python-pydantic-ai-examples && cd python-pydantic-ai-examples/langfuse
      ```

      Add your API keys to an `.env` file:

      ```bash theme={null}
      echo 'LANGFUSE_PUBLIC_KEY=pk-lf-...' > .env
      echo 'LANGFUSE_SECRET_KEY=sk-lf-...' >> .env
      echo 'LANGFUSE_BASE_URL=https://cloud.langfuse.com' >> .env
      echo 'OPENAI_API_KEY=sk-proj-...' >> .env
      ```

      Start the agent service:

      ```bash theme={null}
      uv run --env-file .env .
      ```

      Start Restate with Langfuse tracing:

      ```bash theme={null}
      source .env
      export RESTATE_TRACING_HEADERS__AUTHORIZATION="Basic $(echo -n "${LANGFUSE_PUBLIC_KEY}:${LANGFUSE_SECRET_KEY}" | base64)"
      restate-server --tracing-endpoint otlp+https://cloud.langfuse.com/api/public/otel/v1/traces
      ```

      Go to the Restate UI at `http://localhost:9070`, register the service at `http://localhost:9080`, click on the handler to go to the playground, and send the default request.
    </Accordion>
  </GlobalTab>

  <GlobalTab title="Restate Py">
    Initialize the Langfuse client and configure LiteLLM with `RestateTracerProvider` to correlate AI spans with Restate's execution journal:

    ```python __main__.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/python-restate-only/examples/langfuse/__main__.py#here"}  theme={null}
    import litellm
    from langfuse import get_client
    from opentelemetry import trace as trace_api
    from restate.ext.tracing import RestateTracerProvider
    from litellm.integrations.langfuse.langfuse_otel import LangfuseOtelLogger

    # Initialize Langfuse (sets up the global OTEL tracer provider + exporter)
    langfuse = get_client()

    # Create Restate-aware Langfuse OTEL logger for LiteLLM
    litellm.callbacks = [
        LangfuseOtelLogger(
            tracer_provider=RestateTracerProvider(trace_api.get_tracer_provider())
        )
    ]
    ```

    <GitHubLink url={"https://github.com/restatedev/ai-examples/blob/main/python-restate-only/examples/langfuse"} />

    <Accordion title="Run the example" icon="laptop">
      **Prerequisites**: [Langfuse account and API key](https://langfuse.com/), [OpenAI API key](https://platform.openai.com/api-keys), [Restate installed](/installation).

      Get the example:

      ```bash theme={null}
      restate example python-restate-agent-examples && cd python-restate-agent-examples/langfuse
      ```

      Add your API keys to an `.env` file:

      ```bash theme={null}
      echo 'LANGFUSE_PUBLIC_KEY=pk-lf-...' > .env
      echo 'LANGFUSE_SECRET_KEY=sk-lf-...' >> .env
      echo 'LANGFUSE_OTEL_HOST=https://cloud.langfuse.com' >> .env
      echo 'OPENAI_API_KEY=sk-proj-...' >> .env
      ```

      Start the agent service:

      ```bash theme={null}
      uv run --env-file .env .
      ```

      Start Restate with Langfuse tracing:

      ```bash theme={null}
      source .env
      export RESTATE_TRACING_HEADERS__AUTHORIZATION="Basic $(echo -n "${LANGFUSE_PUBLIC_KEY}:${LANGFUSE_SECRET_KEY}" | base64)"
      restate-server --tracing-endpoint otlp+https://cloud.langfuse.com/api/public/otel/v1/traces
      ```

      Go to the Restate UI at `http://localhost:9070`, register the service at `http://localhost:9080`, click on the handler to go to the playground, and send the default request.
    </Accordion>
  </GlobalTab>
</GlobalTabs>

## What you see in Langfuse

Once you send a request, you can inspect the trace in Langfuse. You see the agentic steps (LLM calls, tool invocations) alongside regular workflow steps (e.g. currency conversion, reimbursement), with inputs, outputs, model configuration, and token usage for each LLM call.

Restate manages the execution, starts the parent span, and exports the full journal as OpenTelemetry traces. The Langfuse SDK attaches AI-specific spans and metadata under Restate's parent span.

<Frame>
  <img src="https://mintcdn.com/restate-6d46e1dc/uSzXk4YvCepc4Qsl/img/ai/ecosystem-integrations/langfuse.png?fit=max&auto=format&n=uSzXk4YvCepc4Qsl&q=85&s=2b330fb91bee56885c8ade065f34a715" alt="Langfuse trace" width="1658" height="1464" data-path="img/ai/ecosystem-integrations/langfuse.png" />
</Frame>

<Frame>
  <img src="https://mintcdn.com/restate-6d46e1dc/uSzXk4YvCepc4Qsl/img/ai/ecosystem-integrations/langfuse-timeline.png?fit=max&auto=format&n=uSzXk4YvCepc4Qsl&q=85&s=b3b96b4f1d7a3c4df4d53e6c2993424f" alt="Langfuse timeline" width="1658" height="1124" data-path="img/ai/ecosystem-integrations/langfuse-timeline.png" />
</Frame>

<Info>
  Restate's Tracer Provider flattens the Langfuse spans to make them appear consistently structured with the Restate spans in the UI.
  We are working on a next iteration of the integration which will respect the Langfuse span nesting and puts the Restate spans at the right depths inside them.
</Info>

## Going further

Langfuse and Restate complement each other beyond basic tracing:

### Versioning

Restate's [versioning model](/services/versioning) ensures each trace is linked to a single immutable code version. Compare quality across versions in Langfuse and spot regressions.

<Frame>
  <img src="https://mintcdn.com/restate-6d46e1dc/uSzXk4YvCepc4Qsl/img/ai/ecosystem-integrations/langfuse-trace-versioning.png?fit=max&auto=format&n=uSzXk4YvCepc4Qsl&q=85&s=a5085e5f3ea04b055dab0f3d959b4b6c" alt="Langfuse timeline" width="2017" height="737" data-path="img/ai/ecosystem-integrations/langfuse-trace-versioning.png" />
</Frame>

### Prompt management

Fetch [version-controlled prompts from Langfuse](https://langfuse.com/docs/prompts) as a durable step with `ctx.run`. Retries reuse the same prompt; new executions pick up the latest version.

### Async evals example

Run [LLM-as-a-Judge evaluations](https://langfuse.com/docs/evaluation/evaluation-methods/llm-as-a-judge) as async Restate workflows that don't block agent execution. Restate acts as both the queue and the orchestrator. See the example below.

You can submit an evaluation from the agent via a [one-way call](/develop/python/service-communication#sending-messages), so it runs asynchronously without blocking the main agent.
The eval workflow runs an LLM judge and writes the score back to the original trace in Langfuse:

```python evaluation.py {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/openai-agents/examples/langfuse/evaluation.py#here"}  theme={null}

langfuse = get_client()

judge_agent = Agent(
    name="ClaimEvaluationJudge",
    instructions=(
        "You are an expert evaluator of insurance claim processing. "
        "Rate the overall quality of the claim agent's response as a score "
        "between 0.0 and 1.0, and provide a brief reason for your rating."
    ),
    output_type=EvaluationScore,
)

evaluation_service = restate.Service("LLMJudgeEvaluation")


@evaluation_service.handler()
async def evaluate(ctx: restate.Context, req: EvaluationRequest) -> None:
    # Step 1: Run the LLM judge (durable — retried on failure)
    result = await DurableRunner.run(
        judge_agent,
        f"Evaluate this insurance claim processing:\n\n"
        f"**Claim Input:**\n{req.input}\n\n"
        f"**Agent Output:**\n{req.output}",
    )
    evaluation: EvaluationScore = result.final_output

    # Step 2: Write the score to Langfuse on the original claim trace
    async def score_trace() -> None:
        langfuse.create_score(
            trace_id=req.trace_id(),
            name="quality",
            value=evaluation.score,
            data_type="NUMERIC",
            comment=evaluation.reason,
        )
        langfuse.flush()

    await ctx.run_typed("Score trace in Langfuse", score_trace)
```

<GitHubLink url={"https://github.com/restatedev/ai-examples/blob/main/openai-agents/examples/langfuse/evaluation.py"} />

If you run the OpenAI Agents example above, then you will see the evaluation score in Langfuse:

<Frame>
  <img src="https://mintcdn.com/restate-6d46e1dc/uSzXk4YvCepc4Qsl/img/ai/ecosystem-integrations/langfuse-evals.png?fit=max&auto=format&n=uSzXk4YvCepc4Qsl&q=85&s=475eff807579343858bffbe2f43a8713" alt="Langfuse timeline" width={"300px"} data-path="img/ai/ecosystem-integrations/langfuse-evals.png" />
</Frame>

For a full walkthrough of these features, see the [Restate + Langfuse blog post](https://restate.dev/blog/reliable-ai-agents-with-restate-and-langfuse/).
