CCM-33801: capture LiteLLM streaming responses in spans#12
Merged
t-santoshsahu merged 1 commit intoJul 10, 2026
Merged
Conversation
The LiteLLM instrumentation ended the litellm_request span as soon as completion()/acompletion() returned, which for stream=True is the not-yet-consumed CustomStreamWrapper. Response-side telemetry (usage, response id, finish reasons, content) is only available after the stream is iterated, so streaming spans were missing all of it. Defer span completion for streaming: wrap the returned stream in a transparent proxy (_StreamSpanWrapper) that forwards every chunk while accumulating them, then on exhaustion (or error) rebuilds the aggregated response via litellm.stream_chunk_builder, copies response metadata onto the span, and ends it. Mirrors the deferred-span pattern already used by the OpenAI/Anthropic instrumentors. Adds sync + async streaming tests asserting the span is not finished until the stream is consumed and that response metadata is captured. Co-authored-by: Cursor <cursoragent@cursor.com>
|
|
t-santoshsahu
approved these changes
Jul 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Harness SDK LiteLLM instrumentation did not capture response telemetry for streaming (
stream=True) calls. Thelitellm_requestspan was ended as soon ascompletion()/acompletion()returned — but for streaming, the return value is a not-yet-consumedCustomStreamWrapper. Usage, response id, finish reasons, and content only exist after the stream is iterated, so streaming spans lost all response-side attributes.This mirrors the deferred-span pattern already used by the OpenAI/Anthropic instrumentors in this SDK.
What changed
_StreamSpanWrapper(a transparentwrapt.ObjectProxy) now wraps the returned stream. It forwards every chunk to the caller unchanged while accumulating them, and on stream exhaustion (or error) rebuilds the aggregated response vialitellm.stream_chunk_builder, copies response metadata onto the span, and ends it._LiteLLMSpanRun.wrap_stream()hands span ownership to the proxy: it detaches the active context and clears the re-dispatch guard without ending the span, so span lifecycle is owned by the stream._is_stream_response) and route through the proxy; non-streaming behavior is unchanged.__del__safety net ends the span if a consumer abandons the stream early.Why the fix is at the SDK level
The consuming service already injects
stream_options={"include_usage": True}and fully consumes the stream. The gap was purely that the SDK closed the span before those chunks were read.Test plan
test_litellm_streaming_span_defers_until_consumed— sync: span is not finished before consumption; after consumption the span carriesgen_ai.request.streaming=Trueand aggregatedgen_ai.response.finish_reasons.test_litellm_async_streaming_span_defers_until_consumed— async equivalent.Made with Cursor