litellm/docs/my-website/docs/observability/langfuse_otel_integration.md
stuxf a6c30b30bf
build: migrate packaging, CI, and Docker from Poetry to uv (#25007)
* build: migrate packaging metadata to uv

* ci: move automation and local tooling to uv

* docker: migrate image builds and runtime setup to uv

* docs: update install and deployment guidance for uv

* chore: align auxiliary scripts and tests with uv

* test: harden test_litellm isolation

* fix: keep release and health check images self-contained

* build: pin uv tooling and health check deps

* test: isolate bedrock image request formatting from suite state

* test: cover sandbox executor requirements flow

* ci: fix circleci no-op command steps

* ci: fix circleci publish workflow parsing

* fix: stabilize remaining uv migration CI checks

* ci: increase matrix test timeout headroom

* fix: restore published docker and license coverage

* fix: restore proxy runtime build parity

* fix: restore proxy extras parity and venv migrations

* ci: persist uv path across circleci steps

* fix: keep psycopg binary in default test env

* docker: preserve prisma cache across stages

* test: run local proxy checks through uv python

* build: restore runtime deps moved into ci

* build: refresh uv lock after upstream merge

* fix: restore module import in test_check_migration after merge

The conflict resolution imported only the function but the test body
references check_migration as a module throughout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert dependency promotions, remove nodejs-wheel-binaries, fix Docker layer caching

- Move google-generativeai, Pillow, tenacity back to ci group (they are
  lazily imported and bloat the base SDK install needlessly)
- Remove nodejs-wheel-binaries from extra_proxy and proxy-dev (redundant
  in Docker where system Node.js is already installed via apk)
- Remove all nodejs-wheel node replacement and venv npm patching blocks
  from Dockerfiles since the wheel is no longer installed
- Add --no-default-groups to CodSpeed benchmark workflow so the benchmark
  environment matches the old minimal pip install footprint
- Apply standard uv two-phase Docker pattern: copy metadata first, install
  deps (cached layer), then copy source and install project
- Replace CircleCI enterprise no-op with proper uv sync command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate uv.lock after removing nodejs-wheel-binaries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): use cache/restore instead of cache to prevent cache poisoning

The old workflow used actions/cache/restore (read-only). The uv migration
changed it to actions/cache (read-write), which zizmor flags as a cache
poisoning risk. Restore the safer read-only variant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): disable setup-uv built-in cache to silence cache-poisoning alert

The setup-uv action enables caching by default, which zizmor flags as a
cache poisoning risk. Disable it since we already use a read-only
cache/restore step.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): disable setup-uv cache in publish workflow

Silences zizmor cache-poisoning alert. Publishing workflow runs
infrequently on protected branches so caching adds no real benefit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(test): remove duplicate verbose_logger mock in test_check_migration

The logger was patched twice — first via mocker.patch() then via
mocker.patch.object(autospec=True). The second call fails because
autospec cannot inspect an already-mocked attribute. Remove the
redundant first patch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): free disk space before Docker build in test-server-root-path

The Dockerfile.non_root build ran out of disk on the CI runner. Remove
Android SDK, .NET, Boost, and GHC toolchains (~12GB) to free space.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:46:23 -07:00

7.8 KiB
Raw Blame History

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

import Image from '@theme/IdealImage';

🪢 Langfuse OpenTelemetry Integration

The Langfuse OpenTelemetry integration allows you to send LiteLLM traces and observability data to Langfuse using the OpenTelemetry protocol. This provides a standardized way to collect and analyze your LLM usage data.

<Image img={require('../../img/langfuse_otel.png')} />

Features

  • Automatic trace collection for all LiteLLM requests
  • Support for Langfuse Cloud (EU and US regions)
  • Support for self-hosted Langfuse instances
  • Custom endpoint configuration
  • Secure authentication using Basic Auth
  • Consistent attribute mapping with other OTEL integrations

Prerequisites

  1. Langfuse Account: Sign up at Langfuse Cloud or set up a self-hosted instance
  2. API Keys: Get your public and secret keys from your Langfuse project settings
  3. Dependencies: Install required packages:
    uv add litellm opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp
    

Configuration

Environment Variables

Variable Required Description Example
LANGFUSE_PUBLIC_KEY Yes Your Langfuse public key pk-lf-...
LANGFUSE_SECRET_KEY Yes Your Langfuse secret key sk-lf-...
LANGFUSE_OTEL_HOST No OTEL endpoint host https://otel.my-langfuse.com

Endpoint Resolution

The integration automatically constructs the OTEL endpoint from LANGFUSE_OTEL_HOST

  • Default (US): https://us.cloud.langfuse.com/api/public/otel
  • EU Region: https://cloud.langfuse.com/api/public/otel
  • Self-hosted: {LANGFUSE_OTEL_HOST}/api/public/otel

Usage

Basic Setup

import os
import litellm

# Set your Langfuse credentials
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..."
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..."

# Enable Langfuse OTEL integration
litellm.callbacks = ["langfuse_otel"]

# Make LLM requests as usual
response = litellm.completion(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": "Hello!"}]
)

Advanced Configuration

import os
import litellm

# Set your Langfuse credentials
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..."
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..."

# Use EU region
os.environ["LANGFUSE_OTEL_HOST"] = "https://cloud.langfuse.com"  # EU region
# os.environ["LANGFUSE_OTEL_HOST"] = "https://otel.my-langfuse.company.com"  # custom OTEL endpoint

# Or use self-hosted instance
# os.environ["LANGFUSE_OTEL_HOST"] = "https://my-langfuse.company.com"

# Optional: Ignore otel context propagation to prevent parent-child relationships with spans from other providers
# os.environ["OTEL_IGNORE_CONTEXT_PROPAGATION"] = "true"

litellm.callbacks = ["langfuse_otel"]

Manual OTEL Configuration

If you need direct control over the OpenTelemetry configuration:

import os
import base64
import litellm

# 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_OTEL_HOST"] = "https://cloud.langfuse.com" # EU region
# os.environ["LANGFUSE_OTEL_HOST"] = "https://us.cloud.langfuse.com" # US region
# os.environ["LANGFUSE_OTEL_HOST"] = "https://otel.my-langfuse.company.com" # custom OTEL endpoint

LANGFUSE_AUTH = base64.b64encode(
    f"{os.environ.get('LANGFUSE_PUBLIC_KEY')}:{os.environ.get('LANGFUSE_SECRET_KEY')}".encode()
).decode()

host = os.environ.get("LANGFUSE_OTEL_HOST")
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = host + "/api/public/otel"
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"Authorization=Basic {LANGFUSE_AUTH}"

litellm.callbacks = ["langfuse_otel"]

With LiteLLM Proxy

Add the integration to your proxy configuration:

  1. Add the credentials to your environment variables
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_OTEL_HOST="https://us.cloud.langfuse.com"  # Default US region
# export LANGFUSE_OTEL_HOST="https://otel.my-langfuse.company.com"  # custom OTEL endpoint

# Optional: Ignore otel context propagation to prevent parent-child relationships with spans from other providers
# export OTEL_IGNORE_CONTEXT_PROPAGATION="true"
  1. Setup config.yaml
# config.yaml
litellm_settings:
  callbacks: ["langfuse_otel"]
  1. Run the proxy
litellm --config /path/to/config.yaml

Data Collected

The integration automatically collects the following data:

  • Request Details: Model, messages, parameters (temperature, max_tokens, etc.)
  • Response Details: Generated content, token usage, finish reason
  • Timing Information: Request duration, time to first token
  • Metadata: User ID, session ID, custom tags (if provided)
  • Error Information: Exception details and stack traces (if errors occur)

Metadata Support

All metadata fields available in the vanilla Langfuse integration are now fully supported when you use the OTEL integration.

  • Any key you pass in the metadata dictionary (generation_name, trace_id, session_id, tags, and the rest) is exported as an OpenTelemetry span attribute.
  • Attribute names are prefixed with langfuse. so you can filter or search for them easily in your observability backend. Examples: langfuse.generation.name, langfuse.trace.id, langfuse.trace.session_id.

Passing Metadata Example

response = litellm.completion(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": "Hello!"}],
    metadata={
        "generation_name": "welcome-message",
        "trace_id": "trace-123",
        "session_id": "sess-42",
        "tags": ["prod", "beta-user"]
    }
)

The resulting span will contain attributes similar to:

langfuse.generation.name   = "welcome-message"
langfuse.trace.id          = "trace-123"
langfuse.trace.session_id  = "sess-42"
langfuse.trace.tags        = ["prod", "beta-user"]

Use the Langfuse UI (Traces tab) to search, filter and analyse spans that contain the langfuse.* attributes. The OTEL exporter in this integration sends data directly to Langfuses OTLP HTTP endpoint; it is not intended for Grafana, Honeycomb, Datadog, or other generic OTEL back-ends.

Authentication

The integration uses HTTP Basic Authentication with your Langfuse public and secret keys:

Authorization: Basic <base64(public_key:secret_key)>

This is automatically handled by the integration - you just need to provide the keys via environment variables.

Troubleshooting

Common Issues

  1. Missing Credentials Error

    ValueError: LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY must be set
    

    Solution: Ensure both environment variables are set with valid keys.

  2. Connection Issues

    • Check your internet connection
    • Verify the endpoint URL is correct
    • For self-hosted instances, ensure the /api/public/otel endpoint is accessible
  3. Authentication Errors

    • Verify your public and secret keys are correct
    • Check that the keys belong to the same Langfuse project
    • Ensure the keys have the necessary permissions

Debug Mode

Enable verbose logging to see detailed information:

import litellm
litellm._turn_on_debug()
export LITELLM_LOG="DEBUG"

This will show:

  • Endpoint resolution logic
  • Authentication header creation
  • OTEL trace submission details