You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
we're seeing a bunch of error logs with span creation and field extration on llmobs for instrumented openai streaming calls. happy to give more info, for now will dump from claude.
Summary
On the OpenAI Responses API with stream=True, an AttributeError in _compute_completion_tokens causes the openai.request LLMObs span to be dropped entirely — its children are orphaned and its token metrics are lost.
Error extracting LLMObs fields for span <Span(... name=openai.request)>, likely due to malformed data
AttributeError: 'tuple' object has no attribute 'get'
Error preparing LLMObs span event for span <Span(... name=openai.request)>, missing span kind in span context.
Root cause
Two independent defects compound.
1. _process_finished_stream doesn't unwrap chunk storage for the response operation.
Streamed chunks accumulate into a defaultdict(list) (contrib/internal/openai/utils.py), but the unwrap is gated on the operation type:
For operation_type == "response" it stays a dict. In _extract_llmobs_metrics_tags, the isinstance(resp, list) guard above the usage fast path is then False, so it falls through to _compute_completion_tokens(resp), which iterates the dict and gets keys:
This is only reached when the terminal usage chunk never arrived — i.e. a truncated stream. In our traffic that is overwhelmingly a client disconnect (GeneratorExit / TLS read abort mid-stream), which is why the usage early-return normally masks it.
2. _llmobs_set_tags computes metrics before it records the span kind, so the raise loses the span.
# llmobs/_integrations/openai.py::_llmobs_set_tags106metrics=self._extract_llmobs_metrics_tags(span, response, span_kind, kwargs) # raises
...
109# Set kind before helpers so that input/output messages are routed correctly110_annotate_llmobs_span_data(span, name=span_name, kind=span_kind, ...) # never runs
base.py::llmobs_set_tags swallows the exception, then _llmobs.py::_prepare_llmobs_span_data finds no kind, logs missing span kind in span context, and _on_span_finish drops the span and scrubs its meta_struct.
The comment on line 109 states the intended invariant, but one call was left ahead of the assignment — so the ordering it describes isn't actually enforced. This is the same class of failure as #18840 (which fixed it for google_adk by moving the kind assignment ahead of extraction); the openai integration has that shape already, but this one call defeats it.
Impact
LLMObs llm spans silently disappear for aborted/truncated streamed Responses calls.
Child spans are orphaned; token metrics are lost, so cost and latency dashboards undercount, biased toward disconnects.
High-volume ERROR logs with full tracebacks.
Suggested fix
Either alone stops the drops; both together seem preferable:
Unwrap chunk storage for operation_type == "response" as well, or make _compute_completion_tokens skip non-mapping entries.
Tracer Version(s)
4.9.7
Python Version(s)
3.13
Pip Version(s)
uv==0.8.24 (not pip)
Bug Report
we're seeing a bunch of error logs with span creation and field extration on llmobs for instrumented openai streaming calls. happy to give more info, for now will dump from claude.
Summary
On the OpenAI Responses API with
stream=True, anAttributeErrorin_compute_completion_tokenscauses theopenai.requestLLMObs span to be dropped entirely — its children are orphaned and its token metrics are lost.Root cause
Two independent defects compound.
1.
_process_finished_streamdoesn't unwrap chunk storage for theresponseoperation.Streamed chunks accumulate into a
defaultdict(list)(contrib/internal/openai/utils.py), but the unwrap is gated on the operation type:For
operation_type == "response"it stays adict. In_extract_llmobs_metrics_tags, theisinstance(resp, list)guard above theusagefast path is then False, so it falls through to_compute_completion_tokens(resp), which iterates the dict and gets keys:This is only reached when the terminal
usagechunk never arrived — i.e. a truncated stream. In our traffic that is overwhelmingly a client disconnect (GeneratorExit/ TLS read abort mid-stream), which is why theusageearly-return normally masks it.2.
_llmobs_set_tagscomputes metrics before it records the span kind, so the raise loses the span.base.py::llmobs_set_tagsswallows the exception, then_llmobs.py::_prepare_llmobs_span_datafinds no kind, logsmissing span kind in span context, and_on_span_finishdrops the span and scrubs itsmeta_struct.The comment on line 109 states the intended invariant, but one call was left ahead of the assignment — so the ordering it describes isn't actually enforced. This is the same class of failure as #18840 (which fixed it for
google_adkby moving the kind assignment ahead of extraction); the openai integration has that shape already, but this one call defeats it.Impact
llmspans silently disappear for aborted/truncated streamed Responses calls.Suggested fix
Either alone stops the drops; both together seem preferable:
operation_type == "response"as well, or make_compute_completion_tokensskip non-mapping entries._extract_llmobs_metrics_tagsafter the_annotate_llmobs_span_datakind assignment (setting metrics separately), so any future extraction failure degrades to missing metrics rather than a lost span — enforcing the invariant fix(llmobs): keep google_adk agent span when a Gemini part is unhandled [backport 4.10] #18840 established.Related: #14030 fixed the same function for a
'str'variant by making it dict/object tolerant, but did not address the dict-not-unwrapped case.Environment
ddtrace4.9.7 (pinnedddtrace>=4.9.0,<4.10)openaiResponses API,stream=True, async clientuv0.8.24 (no pip in the environment)integrations_enabled=True, agent-proxy modeReproduction Code
No response
Error Logs
No response
Libraries in Use
No response
Operating System
No response