Prerequisites
To set up LLM observability, you need:- Access to LaunchDarkly observability
- A LaunchDarkly SDK key for your environment
Set up LLM observability by SDK
When your application calls an LLM provider, instrumentation captures telemetry about the model request, the LaunchDarkly observability SDK exports that telemetry, and LaunchDarkly records the request as an LLM span on the Traces page.Python
Expand Python code sample
Expand Python code sample
To enable LLM observability in Python:Then construct the plugin at application startup, before your application calls a model provider. Constructing After the plugin initializes, calls to a supported provider generate LLM spans without further changes to your model code:To disable instrumentation for a specific library, set
- Install and initialize the Python SDK with the observability plugin.
- Initialize your LLM provider.
ObservabilityPlugin starts OpenTelemetry auto-instrumentation:disabled_instrumentations in ObservabilityConfig, or set the OTEL_PYTHON_DISABLED_INSTRUMENTATIONS environment variable. For the full set of plugin options, read Configuration for server-side observability.Node.js
Expand Node.js code sample
Expand Node.js code sample
To enable LLM observability in Node.js:Then register the instrumentation in your application:
- Install and initialize the Node.js (server-side) SDK.
- Register provider instrumentation for your LLM library.
- Initialize your LLM provider.
Unsupported providers
If your provider does not support automatic instrumentation, you can manually instrument LLM spans. Manual instrumentation lets you create spans around model calls, attach metadata such as token counts and request duration, and ensure LaunchDarkly associates model activity with LLM spans. To learn how, read Manually instrument LLM spans.Instrument your application for conversations
LaunchDarkly normalizes several common non-standard formats when it receives them, including the OpenLLMetryllm.* attributes and Claude Code telemetry. If you use one of those, you can rely on the normalized behavior described here.
Set the conversation ID
Setgen_ai.conversation.id on every span that belongs to the conversation, including tool spans, agent spans, and any spans your framework creates in between.
This is the only requirement. Without it, spans never join a conversation and the conversation does not appear in the LaunchDarkly UI.
Describe each span
Set these attributes on your model call and tool spans:| Attribute | Purpose |
|---|---|
gen_ai.operation.name | What the span represents. Use a standard value such as chat, execute_tool, invoke_agent, embeddings, or retrieval. This determines how LaunchDarkly categorizes the span and assigns a matching icon. |
gen_ai.provider.name | The provider handling the request. Required for cost estimation. |
gen_ai.request.model | The model handling the request. Required for cost estimation and for the Model column. |
gen_ai.usage.input_tokens | Input token count. |
gen_ai.usage.output_tokens | Output token count. |
gen_ai.usage.cache_read.input_tokens | Tokens read from a prompt cache. Set this if you use prompt caching. |
gen_ai.usage.cache_creation.input_tokens | Tokens written to a prompt cache. Set this if you use prompt caching. |
Emit message content
Provide conversation message content asgen_ai.input.messages and gen_ai.output.messages, either as span attributes or as attributes on a gen_ai.client.inference.operation.details span event. Use the standard message format:
gen_ai.system_instructions for the system prompt. LaunchDarkly displays it once per conversation, even when your agent re-sends it on every trace.
Emit message deltas where you can. If your agent re-sends the entire running transcript on each request, LaunchDarkly reconstructs the true sequence, but timing for the recovered history is less precise and those turns display without a time offset.
Content is matched by structure, not by textLaunchDarkly reconciles repeated observations of the same message across many spans. Identical messages that genuinely occur twice, such as a person saying “yes” at two different points, remain separate turns.
Structure tool calls
Tool calls need to connect to the message that requested them. Getting this wrong is the most common cause of a transcript that reads out of order. Set these attributes on the tool span:gen_ai.tool.name: The name of the tool that ran.gen_ai.tool.call.id: The tool call identifier. LaunchDarkly pairs a tool with the model call that requested it by matching this value against thetool_callpart in that model call’s output messages.gen_ai.tool.call.arguments: The arguments passed to the tool.gen_ai.tool.call.result: The result the tool returned.
tool_call part in the gen_ai.output.messages of the model call that requested the tool, using the same ID.
Capture the opening question
A conversation only displays the person’s opening question if your instrumentation records it. Always writegen_ai.input.messages on the span that receives the request, usually the run or envelope span.
Without this attribute, the conversation transcript opens with the agent answering a question that is not displayed.
Key the text per message rather than accumulating it in a single buffer. Because some frameworks emit the user’s prompt as an ordinary text part, a combined output buffer records the person’s question as agent output.
Report evaluations
To attach a quality score to a turn, emit agen_ai.evaluation.result span event on the span being evaluated. Use these attributes in the event:
gen_ai.evaluation.name: The name of the measure, such asrelevanceortoxicity.gen_ai.evaluation.score.value: A numeric score.gen_ai.evaluation.score.label: A text label. LaunchDarkly uses this label in place of the numeric score when it is present.gen_ai.evaluation.explanation: Optional reasoning, displayed on hover.