Skip to content

test(e2e): otel trace completeness on streaming /v1/messages (LIT-3787) - #33247

Merged
yucheng-berri merged 3 commits into
litellm_e2e_otel_stream_chat_tracefrom
litellm_e2e_otel_stream_messages_trace
Jul 14, 2026
Merged

test(e2e): otel trace completeness on streaming /v1/messages (LIT-3787)#33247
yucheng-berri merged 3 commits into
litellm_e2e_otel_stream_chat_tracefrom
litellm_e2e_otel_stream_messages_trace

Conversation

@yucheng-berri

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

https://linear.app/litellm-ai/issue/LIT-3787 (streaming follow-up; second of the streaming stack)

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review

Screenshots / Proof of Fix

Stacked on the streaming /chat/completions PR (base of this branch); merge that first. Same infra, no new services. Verification run on 2026-07-14 at this branch's tip, proxies from source at exact refs. Outputs verbatim.

  1. Evidence-first: a streamed /v1/messages call was driven by hand and its trace dumped from the destination before assertions were written. Exactly one gen-AI span, streaming fingerprint present:

    /v1/messages: content_type=text/event-stream; charset=utf-8 chunks=16 call_id=630a1754-...
    /v1/messages: traces=1 spans=16 genai_count=1 roots=['POST /v1/messages']
      chat claude-haiku-4-5: streaming=True call_id_present=True kind=client
    
  2. Pass on current code (compose stack):

    $ LITELLM_PROXY_URL=http://localhost:4000 uv run pytest \
        "logging/test_otel_trace_e2e.py::TestOtelTraceCompleteness::test_messages_stream_exports_complete_trace" -v
    ============================== 1 passed in 6.05s ===============================
    
  3. Fail-before-fix at 1bd603d (parent of fix(otel): one v2 logger owns the global provider; scope tenant OTLP creds per exporter #30590): the streamed call's gen-AI span arrives alone, dangling:

    $ LITELLM_PROXY_URL=http://localhost:4611 E2E_OTEL_QUERY_URL=http://localhost:16690 \
        uv run pytest "logging/test_otel_trace_e2e.py::...::test_messages_stream_exports_complete_trace"
    E  AssertionError: span(s) ['chat claude-haiku-4-5'] reference a parent that never
       reached the destination (orphaned trace); spans present: ['chat claude-haiku-4-5']
    
  4. Static gates: make lint-e2e-basedpyright -> 0 errors; coverage_registry.collector --strict passes (the stream row is already counted covered by the chat test on the base branch; this PR adds the messages surface attribution via exercised_on).

Type

✅ Test

Changes

Second surface of the logging.otel.stream.exports_metric row: streamed /v1/messages. Same streaming lifecycle risk as chat (the gen-AI span closes from the stream-consumption path), verified to orphan identically at the foil commit.

  • logging/test_otel_trace_e2e.py: adds test_messages_stream_exports_complete_trace with the shared completeness contract plus the stream-specific assertions (event-stream content type, at least one chunk consumed, exactly ONE gen-AI span, and litellm.request.streaming=true on it, which a live trace confirms this surface stamps).
  • models.py: AnthropicMessagesBody gains an optional stream field (mirrors ChatBody).
  • logging/logging_client.py: messages_raw gains a stream mode that consumes the SSE body and counts events, mirroring chat_raw.

Behavior changes

None; test-only changes under tests/e2e.

QA runbook

  • tests/e2e/logging/test_otel_trace_e2e.py::TestOtelTraceCompleteness::test_messages_stream_exports_complete_trace - one successful streamed Anthropic-native call shows up at the OTEL destination as one connected trace tree with a single streaming-marked model-call span
    • GET /health/readiness/details with the master key and confirm OpenTelemetryV2 appears in success_callbacks
    • POST /key/generate with {"models":["claude-haiku-4-5"],"key_alias":"otel-stream-messages-<uniq>"} and save the returned key
    • curl -N POST /v1/messages with "stream": true, max_tokens: 16, and a unique phrase; retry the first call on 401 for a few seconds; confirm content-type: text/event-stream, that data: events actually arrive, and note the x-litellm-call-id response header
    • In Jaeger (http://localhost:16686, service litellm), search by tag litellm.call_id=<that id> and wait for the trace (spans flush in batches; the cost write lands last)
    • Confirm exactly ONE trace holds the call-id
    • Confirm a single root span POST /v1/messages (kind server) and no span referencing a parent missing from the trace
    • Confirm the children: auth /v1/messages, a postgres ... span, and batch_write_to_db _PROXY_track_cost_callback
    • Confirm exactly ONE chat claude-haiku-4-5 span (kind client), that its parent chain reaches the root, and that its tags include litellm.request.streaming: true
    • POST /key/delete to clean up

Final attestation: "I agree this test is testing what the writer wanted to test."

@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a streaming /v1/messages OTEL trace completeness test (test_messages_stream_exports_complete_trace) as the second surface of the logging.otel.stream.exports_metric coverage row, following the same pattern established by the earlier /chat/completions streaming test. The supporting changes add a stream field to AnthropicMessagesBody and a streaming mode to messages_raw in the test client.

  • test_otel_trace_e2e.py: new test asserts event-stream content type, at least one chunk consumed, exactly one gen-AI span, litellm.request.streaming=true tag, and a fully-connected trace tree — identical contract to the chat completions streaming test.
  • logging_client.py: messages_raw gains a stream parameter that routes through transport.stream() and sets the body field to True/None (omitting it when non-streaming, consistent with Anthropic API conventions).
  • models.py: AnthropicMessagesBody adds stream: bool | None = None, following the repo's PEP 604 union syntax preference.

Confidence Score: 5/5

Test-only changes confined to tests/e2e/; no production code paths are modified.

All three changed files live entirely within tests/e2e/. The new test mirrors an already-reviewed streaming test (test_chat_completions_stream_exports_complete_trace) with minimal structural divergence. The model field addition uses the correct optional syntax, the client routing matches the existing pattern, and no assertions are weakened relative to prior tests.

No files require special attention.

Important Files Changed

Filename Overview
tests/e2e/models.py Adds optional `stream: bool
tests/e2e/logging/logging_client.py Extends messages_raw with a stream parameter; when true, routes through transport.stream() and sets stream=True on the body (using None for non-streaming to omit the field), mirroring the existing chat_raw pattern.
tests/e2e/logging/test_otel_trace_e2e.py Adds test_messages_stream_exports_complete_trace that mirrors the existing chat completions streaming test: asserts event-stream content type, at least one chunk, exactly one gen-AI span, and litellm.request.streaming=true tag in the complete OTEL trace.

Reviews (3): Last reviewed commit: "test(e2e): make the stream field coercio..." | Re-trigger Greptile

@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@yucheng-berri yucheng-berri changed the title [test] e2e: OTEL trace completeness on streaming /v1/messages (LIT-3787) test(e2e): otel trace completeness on streaming /v1/messages Jul 14, 2026
@yucheng-berri yucheng-berri changed the title test(e2e): otel trace completeness on streaming /v1/messages test(e2e): OTEL trace completeness on streaming /v1/messages (LIT-3787) Jul 14, 2026
Covers the messages surface of logging.otel.stream.exports_metric: same tree
contract as the non-streaming tests plus the stream-specific assertions (the
response actually streamed, exactly one gen-AI span, and the span records
litellm.request.streaming=true). Adds a stream field to the shared
AnthropicMessagesBody and a stream mode to messages_raw
@yucheng-berri
yucheng-berri force-pushed the litellm_e2e_otel_stream_messages_trace branch from ac9c99c to 183bfb1 Compare July 14, 2026 20:50
@yucheng-berri
yucheng-berri force-pushed the litellm_e2e_otel_stream_chat_trace branch from d862a91 to 22ceb38 Compare July 14, 2026 20:50
@yucheng-berri yucheng-berri changed the title test(e2e): OTEL trace completeness on streaming /v1/messages (LIT-3787) test(e2e): otel trace completeness on streaming /v1/messages (LIT-3787) Jul 14, 2026
@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

…87) (#33262)

* test(e2e): OTEL trace completeness on streaming /v1/responses

Covers the responses surface of logging.otel.stream.exports_metric: same tree
contract plus the stream-side assertions (event-stream content type, chunks
consumed, exactly one gen-AI span). Two assertions are knowingly relaxed on
this surface, both verified against live traces and tracked in LIT-4428: the
responses route does not stamp litellm.request.streaming on the gen-AI span,
and the spend write for a streamed responses call records spend correctly but
emits no batch_write_to_db cost span. Adds a stream mode to responses_raw

* test(e2e): parenthesize the settle-names ternary and make stream coercion explicit per review
@yucheng-berri
yucheng-berri merged commit c77bd78 into litellm_e2e_otel_stream_chat_trace Jul 14, 2026
1 check was pending
@yucheng-berri
yucheng-berri deleted the litellm_e2e_otel_stream_messages_trace branch July 14, 2026 22:42
yucheng-berri added a commit that referenced this pull request Jul 14, 2026
…7) (#33247)

* test(e2e): OTEL trace completeness on streaming /v1/messages

Covers the messages surface of logging.otel.stream.exports_metric: same tree
contract as the non-streaming tests plus the stream-specific assertions (the
response actually streamed, exactly one gen-AI span, and the span records
litellm.request.streaming=true). Adds a stream field to the shared
AnthropicMessagesBody and a stream mode to messages_raw

* test(e2e): make the stream field coercion explicit per review

* test(e2e): otel trace completeness on streaming /v1/responses (LIT-3787) (#33262)

* test(e2e): OTEL trace completeness on streaming /v1/responses

Covers the responses surface of logging.otel.stream.exports_metric: same tree
contract plus the stream-side assertions (event-stream content type, chunks
consumed, exactly one gen-AI span). Two assertions are knowingly relaxed on
this surface, both verified against live traces and tracked in LIT-4428: the
responses route does not stamp litellm.request.streaming on the gen-AI span,
and the spend write for a streamed responses call records spend correctly but
emits no batch_write_to_db cost span. Adds a stream mode to responses_raw

* test(e2e): parenthesize the settle-names ternary and make stream coercion explicit per review
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