Skip to content

feat: Annotate active OTel spans with AI SDK metrics and config metadata#108

Draft
ctawiah wants to merge 1 commit intomainfrom
ctawiah/metrics-on-spans
Draft

feat: Annotate active OTel spans with AI SDK metrics and config metadata#108
ctawiah wants to merge 1 commit intomainfrom
ctawiah/metrics-on-spans

Conversation

@ctawiah
Copy link
Contributor

@ctawiah ctawiah commented Mar 23, 2026

Summary

Adds OpenTelemetry span annotation to the LaunchDarkly AI SDK so that every metric tracked by LDAIConfigTracker and AIGraphTracker is also written as attributes on the active OTel span. This gives users correlated LLM observability data in the tracing backend without any extra integration code.

What's included

  • LDAIObserveConfig — a developer-facing dataclass that controls how the SDK writes to spans:

    • annotate_spans (default True) — toggle all span annotation on/off
    • create_span_if_none (default True) — create an internal ld.ai.completion span when no OTel span is active
  • observe.py — the core observability module containing:

    • Span annotation helpers (annotate_span_with_tokens, annotate_span_with_duration, annotate_span_success, annotate_span_with_feedback, annotate_span_with_judge_response, etc.)
    • _span_scope() context manager — ensures a recording span exists for a block of code
    • Baggage helpers (set_ai_config_baggage, detach_ai_config_baggage) for OTel context propagation
    • LDAIBaggageSpanProcessor — a SpanProcessor that copies AI Config metadata from OTel baggage onto every new span, enabling auto-instrumented LLM libraries (e.g. OpenLLMetry) to carry LD metadata
  • LDAIClient.config_scope() — a context manager that evaluates an AI Config and scopes its metadata as OTel baggage for the duration of the block, so any spans created inside (including by auto-instrumentation) inherit AI Config identity attributes

  • Tracker span annotation — both LDAIConfigTracker and AIGraphTracker now annotate spans for every metric:

    • Config metadata: ld.ai_config.key, ld.ai_config.variation_key, ld.ai_config.version, ld.ai_config.model, ld.ai_config.provider
    • LLM metrics: duration, token counts (total/input/output), time-to-first-token, success/error status, feedback
    • Judge metrics: per-eval scores and reasoning, judge config key, success/error
    • Graph metrics: invocation success/failure, latency, total tokens, execution path, node invocations, tool calls, handoff success/failure, redirects
  • opentelemetry-api added as an optional dependency — install via pip install launchdarkly-server-sdk-ai[otel]. All OTel code is guarded behind try/except ImportError checks so there is zero impact when the package is not installed.

Span attribute naming convention

Category Attribute examples
AI Config identity ld.ai_config.key, ld.ai_config.variation_key, ld.ai_config.model, ld.ai_config.provider
LLM metrics ld.ai.metrics.tokens.total, ld.ai.metrics.duration_ms, ld.ai.metrics.time_to_first_token_ms
Generation status Span status set to OK or ERROR
Feedback ld.ai.metrics.feedback.kind
Judge evals ld.ai.judge.<metric>.score, ld.ai.judge.<metric>.reasoning
Graph metrics ld.ai.graph.latency, ld.ai.graph.tokens.total, ld.ai.graph.path

Test plan

  • All 76 existing tests pass
  • Verify span annotation works with a real OTel exporter (e.g. console exporter)
  • Verify config_scope() propagates baggage to auto-instrumented spans
  • Verify LDAIBaggageSpanProcessor copies baggage to span attributes
  • Verify graceful degradation when opentelemetry-api is not installed
  • Verify LDAIObserveConfig(annotate_spans=False) disables all span writes
  • Verify LDAIObserveConfig(create_span_if_none=False) does not create internal spans

Made with Cursor

Every tracker method (track_duration, track_tokens, track_success, etc.)
now writes the same metrics onto the active OpenTelemetry span in addition
to firing LD analytics events. This gives users correlated LLM
observability data in their tracing backend without any extra code.

Key additions:

- `LDAIObserveConfig` dataclass — controls span annotation behaviour
  (`annotate_spans`, `create_span_if_none`), passed to `LDAIClient`.
- `observe.py` — span annotation helpers, baggage helpers, and
  `LDAIBaggageSpanProcessor` for propagating AI Config metadata through
  OTel context (useful with auto-instrumented LLM libraries).
- `LDAIClient.config_scope()` — context manager that evaluates an AI
  Config and scopes its metadata as OTel baggage for the duration of the
  block, so downstream spans inherit AI Config identity.
- `opentelemetry-api` added as an optional dependency (`pip install
  launchdarkly-server-sdk-ai[otel]`). All OTel code is guarded behind
  availability checks — zero impact when the package is not installed.
- Both `LDAIConfigTracker` and `AIGraphTracker` annotate spans for every
  metric they track: duration, tokens, TTFT, success/error, feedback,
  eval scores, judge responses, graph invocation, handoffs, etc.

Made-with: Cursor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant