Guide to implementing LLMObs plugins in dd-trace-js.
Leaf plugins extend LLMObsPlugin at packages/dd-trace/src/llmobs/plugins/base.js. A composite root can extend
CompositePlugin and select among leaf implementations, as ai/index.js does.
The LLMObs base class handles span registration, context management, and lifecycle hooks. Leaf plugins implement two methods.
Defines span metadata for registration with LLMObs. Called at span start.
Returns an object with:
kind(string) — span type.SPAN_KINDSlists'llm','agent','workflow','task','tool','embedding', and'retrieval'; plugin extensions such as'step'also existname(string, optional) — operation name (e.g.'openai.chat.completions'); the event falls back to the APM span name when omittedmodelProvider(string, optional) — provider name (e.g.'openai','anthropic','google')modelName(string, optional) — model identifier (e.g.'gpt-4','claude-3-sonnet')sessionId(string, optional) — session identifier when the integration supplies one
Return nothing to skip recording an LLMObs span for a given ctx entirely — base.js only tests the
result for truthiness, and the plugins use a bare return (openai/index.js does this for the methods it
does not trace).
Extracts and tags LLM-specific data after the operation completes. Called in asyncEnd.
Responsibilities:
- Extract the kind-specific input from the channel's
ctxfields - Extract the kind-specific output from the channel's
ctxfields - Extract token usage metrics when available
- Extract model parameters when available
- Tag all data via
this._taggermethods (see below)
Tag the input when the channel provides it. Error output is integration-specific: OpenAI and GenAI use an empty message, while integrations without a result omit output. Pin that contract in the integration's spec.
start(ctx)— registers the LLMObs span and captures parent context- The wrapped operation is invoked
end(ctx)— restores parent context after the wrapped call returnsasyncEnd(ctx)— callssetLLMObsTags()after a promise-backed operation settles
Tag data using this._tagger, which provides:
tagLLMIO(span, inputMessages, outputMessages)— forllmspanstagEmbeddingIO(span, inputDocuments, outputValue)— forembeddingspanstagRetrievalIO(span, inputValue, outputDocuments)— forretrievalspanstagTextIO(span, inputValue, outputValue)— forworkflow,agent,task,step, andtoolspanstagMetadata(span, metadata)— model parameters (temperature, max_tokens, etc.)tagMetrics(span, metrics)— token usage (input_tokens,output_tokens,total_tokens)tagSpanTags(span, tags)— arbitrary key/value span tagstagPrompt(span, prompt, strictValidation = false)— prompt tracking metadatatagToolDefinitions(span, toolDefinitions)— the tools a request declared, for tool-calling integrationstagModelName(span, modelName)— a model name discovered after registration
Each leaf plugin class needs:
static integration— integration name for LLMObs telemetry ('openai','google_genai')static id— unique plugin ID. Often the same string as the integration ('openai'), but not necessarily: genai pairsid = 'google-genai'withintegration = 'google_genai'. A package that hooks several operations qualifies it per operation ('llmobs_langgraph_pregel_stream')static prefix— diagnostic channel prefix (e.g.'tracing:apm:openai:request')
OpenAI and GenAI tag an empty output message on error:
if (ctx.error) {
this._tagger.tagLLMIO(span, inputMessages, [{ content: '' }])
return
}That is not a base-class invariant. Anthropic omits output when no result exists, and non-llm integrations follow
their own kind-specific contract.
See existing plugins for complete working examples:
packages/dd-trace/src/llmobs/plugins/openai/index.js— simple messages array, standard token usagepackages/dd-trace/src/llmobs/plugins/anthropic/— nested content inutil.js, usage extraction inindex.jspackages/dd-trace/src/llmobs/plugins/genai/util.js— contents/parts format, role normalizationpackages/dd-trace/src/llmobs/plugins/langgraph/index.js— orchestration,workflowspan kind, no messages