Skip to content

fix(tracing): preserve message parts and embedded tool calls - #44078

Closed
Nikhi00718 wants to merge 4 commits into
BerriAI:mainfrom
Nikhi00718:Nikhi00718/fix-tracing-message-parts
Closed

Nikhi00718 wants to merge 4 commits into
BerriAI:mainfrom
Nikhi00718:Nikhi00718/fix-tracing-message-parts

Conversation

@Nikhi00718

@Nikhi00718 Nikhi00718 commented Oct 1, 2026 •

Copy link
Copy Markdown

TLDR

Problem this solves:

  • GenAI message parts render as raw fields
  • Embedded tool calls disappear from agent traces
  • Tool results fail to render as conversation messages
  • Oversized parts-based messages lose their text during truncation

How it solves it:

  • Recognize GenAI parts alongside existing message content
  • Extract embedded calls in the Rust LangSmith normalizer and Python viewer
  • Preserve tool arguments and JSON result values
  • Normalize parts before truncation and retain calls that fit

User Flow

Before: a developer inspecting an agent run cannot follow its conversation or embedded tool calls

  1. Export an agent trace containing GenAI parts or LangSmith content blocks to POST http://localhost:4000/v1/traces
  2. Open the run at http://localhost:4000/ui/?page=logs and select its model span
  3. GenAI messages appear as raw JSON, and calls carried only in LangSmith content blocks are missing

After: the same run exposes its conversation, tool names, arguments, and results

  1. Export the same agent trace to POST http://localhost:4000/v1/traces
  2. Open the run at http://localhost:4000/ui/?page=logs and select its model span
  3. The detail pane receives normalized messages and calls, including GenAI tool results

Validation

Base: 5ccb1a143b759a83752ccb350a7031c596b9dec4. Source tested at ad7a142364; current head f06deca364 adds only the two CI timeout adjustments described below

  • Clean Python base: 37 regression failures and 20 preservation passes in the captured reproduction run
  • Clean Rust base: eight new regression cases fail, sixteen cases pass
  • Fixed source: 92 targeted viewer, truncation, and store cases pass on Windows
  • Linux: 90 cases passed before the final type-narrowing correction and two added preservation tests
  • Rust: 24 unit cases and 25 public OTLP decoder cases pass
  • Python changed-line coverage: 100% of 83 executable lines
  • Rust Clippy passes for the traces library with warnings denied
  • Source/test Ruff, formatting, import safety, and test-quality scanner pass
  • Linux strict Ruff, type-discipline, and test-quality budgets pass without changing ceilings
  • Focused basedpyright confirms the two new CI tuple-narrowing diagnostics are removed

Hosted CI on current head f06deca364: 101 checks pass, one is skipped, none fail or remain pending. All ten GitHub-required checks pass, including the full canonical type-check gate. The production Rust wheel, compiled-extension tests, full workspace nextest step, Rust documentation tests, token-counter feature cases, and secret-manager feature cases pass

The additional Python 3.14 dependency-resolution job hit its configured 15-minute timeout while installing lowest runtime dependencies: its locked runtime installation consumed about 13 minutes. The Rust test job hit its 20-minute timeout during secret-manager feature combinations, after the workspace, documentation, and token-counter tests passed. Rerun attempts did not schedule either job; the resolver rerun explicitly requires repository admin rights. These cancelled jobs are not counted as passes

f06deca364 increases the resolver job allowance from 15 to 25 minutes and the Rust test allowance from 20 to 35 minutes to accommodate cold native builds and the complete feature matrix. All commands, assertions, feature cases, and quality budgets are retained. Both workflow files parse successfully. Python 3.14 resolution passes in 14m53s; the full Rust test job passes in 23m44s, exceeding the former 20-minute cap. Every Python version in the resolver matrix passes

Tests cover GenAI conversations, embedded calls, Rust LangSmith normalization, JSON scalar results, malformed arguments, incomplete blocks, optional names, content precedence, and duplicate prevention. Oversized-message cases cover Unicode, control characters, long roles, and calls that fit the byte budget. Exported argument strings remain unchanged rather than being repaired for inference

LITELLM_LOCAL_MODEL_COST_MAP=True python -m pytest \
  tests/test_litellm/tracing/test_ui_format.py \
  tests/test_litellm/tracing/test_decode.py \
  tests/test_litellm/tracing/test_store.py \
  --confcutdir=tests/test_litellm/tracing \
  -k 'not decode or oversized or shortened' \
  --cov=litellm/tracing --cov-branch -q

Linux Python validation disables sockets and permits Unix sockets for asyncio wakeups. These scoped runs exclude parent proxy fixtures. The native decoder is exercised separately by the Rust OTLP tests. The full canonical type-check gate has not completed locally: the Linux runtime stopped during setup. CI found two tuple-narrowing diagnostics in the preceding commit; both are removed in this commit, and focused basedpyright confirms neither remains

cd litellm-rust
cargo test --locked -p litellm-traces --lib --test otlp
cargo clippy --locked -p litellm-traces --lib -- -D warnings

Pre-Submission checklist

  • I have added meaningful tests
  • The focused tests covering this change pass locally
  • My PR passes all CI/CD checks on the current head; 101 pass and one is skipped
  • My PR solves one problem: trace message normalization
  • I have received a Greptile Confidence Score of at least 4/5

Screenshots / Proof of Fix

Live proxy and dashboard proof has not been captured. End-to-end verification remains pending.

Type

Bug Fix

Caveats

Medium

  • Live proxy and dashboard verification is still required

Low

  • The merged Rust refactor is preserved; LangSmith extraction now lives in Rust
  • Greptile's existing summary reviewed the old commit; fresh review is pending
  • Historical normalized traces cannot recover already discarded tool blocks

Final Attestation

  • Real-world customer flows have been verified end to end

@CLAassistant

CLAassistant commented Oct 1, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@Nikhi00718
Nikhi00718 marked this pull request as ready for review October 1, 2026 21:00
@Nikhi00718
Nikhi00718 requested a review from a team October 1, 2026 21:00
@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Medium risk] Extends message parsing to handle additional content formats.

The PR does not appear safe to merge until oversized parts-based traces retain their conversation content and tool calls.

Findings

  1. P1 Oversized parts lose their content ▶
  2. P2 Optional name rejects valid blocks ▶

Summary

The PR recognizes GenAI message parts in the trace viewer, extracts embedded tool calls for direct and LangSmith-normalized messages, and formats JSON tool results. It adds parser regression tests. Oversized parts-based messages still lose their content during trace truncation.

Reviews (1) · Last reviewed commit: "fix(tracing): preserve message parts and..."

role: str | None = None
type: str | None = None
content: JsonValue = None
parts: JsonValue = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Oversized parts lose their content

When a GenAI message with parts exceeds the trace attribute limit, the truncation path shortens content rather than parts. It can store a role and a "null…" content marker instead of the message. By the time this viewer reads the trace, the conversation and embedded tool calls have been lost.

Comment thread litellm/tracing/normalizers/messages.py Outdated
text: str | None = None
content: JsonValue = None
result: JsonValue = None
name: str = ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Optional name rejects valid blocks

A text block with name: null now fails validation because name must be a string. Validation rejects the entire block list, so a valid tool call beside that text block is no longer extracted and the viewer shows raw JSON instead. This limits the new extraction behavior for mixed block lists.

Comment thread litellm/tracing/messages.py Fixed
@codspeed

codspeed Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing Nikhi00718:Nikhi00718/fix-tracing-message-parts (f06deca) with main (fe9b6fd)

Open in CodSpeed

@codecov

codecov Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Nikhi00718
Nikhi00718 force-pushed the Nikhi00718/fix-tracing-message-parts branch from 8478197 to 93af86c Compare October 2, 2026 06:17
@Nikhi00718

Copy link
Copy Markdown
Author

Hi maintainers, could you review this trace-normalization fix when you have a chance? It preserves GenAI message parts, embedded tool calls, JSON tool results, and oversized conversation content, with regression coverage.

Current head f06deca364 has 101 passing checks, one skipped, and no failures or pending checks. The reported parsing findings and both CI timeouts have been addressed. Fresh Greptile review and live dashboard proof remain pending, as noted in the PR description. Happy to adjust the implementation or scope based on your feedback. Thanks!

@Nikhi00718

Copy link
Copy Markdown
Author

Closing: after #44363, the Python tracing formatter this PR changes no longer exists. Message normalization now lives in the Rust tracing stack, where parts and embedded tool calls are handled in
ormalize/messages.rs.

@Nikhi00718 Nikhi00718 closed this Oct 8, 2026
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.

3 participants