OpenTelemetry LLM analytics installation

  1. Install dependencies

    Required
    Full working examples

    The Node.js and Python OpenAI examples show a complete end-to-end OpenTelemetry setup. Swap the instrumentation for any other gen_ai.*-emitting library to trace a different provider or framework.

    Install the OpenTelemetry SDK, PostHog's OpenTelemetry helper, and an OpenTelemetry instrumentation for the provider you want to trace. The examples below use the OpenAI instrumentation, but any library that emits gen_ai.* spans will work.

    pip install openai opentelemetry-sdk posthog[otel] opentelemetry-instrumentation-openai-v2
  2. Set up OpenTelemetry tracing

    Required

    Configure OpenTelemetry to export spans to PostHog via the PostHogSpanProcessor. The processor only forwards AI-related spans — spans whose name or attribute keys start with gen_ai., llm., ai., or traceloop. — and drops everything else. PostHog converts gen_ai.* spans into $ai_generation events automatically.

    from opentelemetry import trace
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.resources import Resource, SERVICE_NAME
    from posthog.ai.otel import PostHogSpanProcessor
    from opentelemetry.instrumentation.openai_v2 import OpenAIInstrumentor
    resource = Resource(attributes={
    SERVICE_NAME: "my-app",
    "posthog.distinct_id": "user_123", # optional: identifies the user in PostHog
    "foo": "bar", # custom properties are passed through
    })
    provider = TracerProvider(resource=resource)
    provider.add_span_processor(
    PostHogSpanProcessor(
    api_key="<ph_project_token>",
    host="https://us.i.posthog.com",
    )
    )
    trace.set_tracer_provider(provider)
    OpenAIInstrumentor().instrument()

    PostHog identifies each event using the posthog.distinct_id attribute on the OpenTelemetry Resource (with user.id as a fallback, then a random UUID if neither is set). Because the Resource applies to every span in a batched export, you only need to set the distinct ID once — there's no need for a BaggageSpanProcessor or per-span propagation. Any other Resource or span attributes pass through as event properties.

  3. Make an LLM call

    Required

    With the processor and instrumentation wired up, any LLM call made through the instrumented SDK is captured. PostHog receives the emitted gen_ai.* span and converts it into an $ai_generation event.

    import openai
    client = openai.OpenAI(api_key="<openai_api_key>")
    response = client.chat.completions.create(
    model="gpt-5-mini",
    messages=[
    {"role": "user", "content": "Tell me a fun fact about hedgehogs"}
    ],
    )
    print(response.choices[0].message.content)

    Note: If you want to capture LLM events anonymously, omit the posthog.distinct_id resource attribute. See our docs on anonymous vs identified events to learn more.

    You can expect captured $ai_generation events to have the following properties:

    PropertyDescription
    $ai_modelThe specific model, like gpt-5-mini or claude-4-sonnet
    $ai_latencyThe latency of the LLM call in seconds
    $ai_time_to_first_tokenTime to first token in seconds (streaming only)
    $ai_toolsTools and functions available to the LLM
    $ai_inputList of messages sent to the LLM
    $ai_input_tokensThe number of tokens in the input (often found in response.usage)
    $ai_output_choicesList of response choices from the LLM
    $ai_output_tokensThe number of tokens in the output (often found in response.usage)
    $ai_total_cost_usdThe total cost in USD (input + output)
    [...]See full list of properties
  4. How attributes map to event properties

    Recommended

    PostHog translates standard OpenTelemetry GenAI semantic convention attributes into the same $ai_* event properties our native SDK wrappers emit, so traces look the same in PostHog whether they arrive through OpenTelemetry or a native wrapper. The most common mappings:

    OpenTelemetry attributePostHog event property
    gen_ai.response.model (or gen_ai.request.model)$ai_model
    gen_ai.provider.name (or gen_ai.system)$ai_provider
    gen_ai.input.messages$ai_input
    gen_ai.output.messages$ai_output_choices
    gen_ai.usage.input_tokens (or gen_ai.usage.prompt_tokens)$ai_input_tokens
    gen_ai.usage.output_tokens (or gen_ai.usage.completion_tokens)$ai_output_tokens
    server.address$ai_base_url
    telemetry.sdk.name / telemetry.sdk.version$ai_lib / $ai_lib_version
    Span start/end timestamps$ai_latency (computed in seconds)
    Span name$ai_span_name

    Additional behavior worth knowing:

    • Custom attributes pass through. Any Resource or span attribute that isn't part of the known mapping is forwarded onto the event as-is, so you can add dimensions like conversation_id or tenant_id and filter on them in PostHog.
    • Trace and span IDs are preserved as $ai_trace_id, $ai_span_id, and $ai_parent_id, so multi-step traces reconstruct correctly.
    • Events are classified by operation. gen_ai.operation.name=chat becomes an $ai_generation event; embeddings becomes $ai_embedding. Spans without a recognized operation become $ai_span (or $ai_trace if they're the root of a trace).
    • Vercel AI SDK, Pydantic AI, and Traceloop/OpenLLMetry emit their own namespaces (ai.*, pydantic_ai.*, traceloop.*) and PostHog normalizes those to the same $ai_* properties.
    • Noisy resource attributes are dropped. OpenTelemetry auto-detected attributes under host.*, process.*, os.*, and telemetry.* (except telemetry.sdk.name / telemetry.sdk.version) don't pollute event properties.
  5. Other instrumentations, direct OTLP, and troubleshooting

    Optional

    Alternative instrumentation libraries. Any library that emits standard gen_ai.* spans (or ai.* / traceloop.* / pydantic_ai.*) works with the setup above. Swap @opentelemetry/instrumentation-openai / opentelemetry-instrumentation-openai-v2 for one of these to broaden provider coverage:

    • OpenLIT — single instrumentation that covers many providers, vector DBs, and frameworks.
    • OpenLLMetry (Traceloop) — broad provider and framework support in Python and JavaScript.
    • OpenInference (Arize) — provider- and framework-specific instrumentations for Python and JavaScript.
    • MLflow tracing — if you already run MLflow.

    Direct OTLP export. If you run an OpenTelemetry Collector, or want to export from a language that isn't Python or Node.js, point any OTLP/HTTP exporter directly at PostHog's AI ingestion endpoint. PostHog accepts OTLP over HTTP in both application/x-protobuf and application/json, authenticated with a Bearer token. The endpoint is signal-specific (traces only), so use the OTEL_EXPORTER_OTLP_TRACES_* variants rather than the general OTEL_EXPORTER_OTLP_* ones (the SDK appends /v1/traces to the latter and would 404).

    OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://us.i.posthog.com/i/v0/ai/otel"
    OTEL_EXPORTER_OTLP_TRACES_HEADERS="Authorization=Bearer <ph_project_token>"

    Limits and troubleshooting.

    • Only AI spans are ingested. Spans whose name and attribute keys don't start with gen_ai., llm., ai., or traceloop. are dropped server-side, so it's safe to send a mixed trace stream.
    • HTTP only, no gRPC. The endpoint speaks OTLP over HTTP in either application/x-protobuf or application/json. If your collector or SDK is configured for gRPC, switch to HTTP.
    • Request body is capped at 4 MB. Large or unbounded traces (for example, long chat histories with base64-encoded images) can exceed this. Use a collector with the batch processor to keep individual exports small.
    • Missing traces? Make sure you're pointing at the traces-specific OTLP variable (OTEL_EXPORTER_OTLP_TRACES_ENDPOINT / traces_endpoint) rather than the general one, and that your project token is set correctly in the Authorization: Bearer header.
  6. Verify traces and generations

    Recommended
    Confirm LLM events are being sent to PostHog

    Let's make sure LLM events are being captured and sent to PostHog. Under LLM analytics, you should see rows of data appear in the Traces and Generations tabs.


    LLM generations in PostHog
    Check for LLM events in PostHog
  7. Next steps

    Recommended

    Now that you're capturing AI conversations, continue with the resources below to learn what else LLM Analytics enables within the PostHog platform.

    ResourceDescription
    BasicsLearn the basics of how LLM calls become events in PostHog.
    GenerationsRead about the $ai_generation event and its properties.
    TracesExplore the trace hierarchy and how to use it to debug LLM calls.
    SpansReview spans and their role in representing individual operations.
    Anaylze LLM performanceLearn how to create dashboards to analyze LLM performance.

Community questions

Was this page useful?

Questions about this page? or post a community question.