Skip to content

[issue-9069] [SDK] fix: keep function calls and all text in streamed Gemini traces - #9320

Open
surlymochan wants to merge 1 commit into
comet-ml:mainfrom
surlymochan:fix/9069-genai-stream-aggregator
Open

surlymochan wants to merge 1 commit into
comet-ml:mainfrom
surlymochan:fix/9069-genai-stream-aggregator

Conversation

@surlymochan

Copy link
Copy Markdown
Contributor

Details

Streamed Gemini traces lost function calls: aggregate_response_content_items joined every chunk's .text (which is None for a function-call-only chunk) and wrote the result into the last chunk's first part, dropping function calls from earlier chunks and overwriting a function-call part in the last chunk with plain text. The aggregator now walks every chunk's first candidate parts in arrival order, concatenating consecutive plain-text parts into one part and keeping every other part (function calls, inline data, ...) as its own part. Response-level metadata still comes from the last chunk, and a trailing usage-only chunk without candidates no longer discards the parts aggregated from earlier chunks.

Note: #9094 addressed the same issue but was closed by the external-PR-limit bot (author at the 3-open-PR limit), not on its merits; this is a fresh implementation against current main.

Change checklist

  • User facing
  • Documentation update

Issues

AI-WATERMARK

AI-WATERMARK: yes

  • Tools: Muse (Meta AI agent)
  • Model(s): Muse Spark
  • Scope: aggregator fix and unit tests in this PR
  • Human verification: none yet; local pytest/ruff run by the authoring agent, awaiting maintainer review

Testing

  • Repro first (RED): new unit test test_function_call_between_text_chunks_is_kept_in_order failed on the old code with a single Part(text='Checking done') — the function call was gone.
  • After the fix (GREEN): PYTHONPATH=src pytest tests/unit/integrations/genai/test_generations_aggregators.py — 6 passed, covering text split across chunks, function calls between/after text, a function call in the last chunk, text after a function call in the same chunk, a usage-only trailing chunk, and last-chunk metadata.
  • ruff check and ruff format --check clean on both changed files.
  • Not run: library-integration genai tests (they need a running Opik backend); the aggregator is only consumed as the span-output builder in opik_tracker.py, whose interface is unchanged.

Documentation

  • No user-facing docs change; behavior now matches the documented trace fidelity for streamed Gemini calls.

@surlymochan
surlymochan requested a review from a team as a code owner October 31, 2026 01:37
@github-actions github-actions Bot added python Pull requests that update Python code tests Including test files, or tests related like configuration. Python SDK 🟡 size/M labels Oct 31, 2026
Comment on lines +7 to +12
"""A part is mergeable text only when ``text`` is its sole set field.

Any part carrying another payload (a function call, inline data, a
thought signature, ...) must stay a separate part, otherwise merging
would silently drop that payload.
"""

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.

Private helper docstring exposes implementation details

The private helper’s docstring duplicates the rationale in aggregate_response_content_items and documents implementation details like field inspection and payload types as if they were part of the contract; remove it or keep only a brief local rationale if needed.

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`sdks/python/src/opik/integrations/genai/generations_aggregators.py` around lines 7-12,
simplify the `_is_plain_text_part` helper’s docstring. Remove the
implementation-specific explanation of `model_dump`, set-field inspection, and payload
examples, or replace it with one concise local rationale explaining that only text-only
parts may be merged; keep the helper’s behavior unchanged.

Comment on lines +47 to +58
merged_parts: List[genai_types.Part] = []
for item in items:
for part in _parts_of(item):
part_copy = part.model_copy(deep=True)
if (
_is_plain_text_part(part_copy)
and merged_parts
and _is_plain_text_part(merged_parts[-1])
):
merged_parts[-1].text = (merged_parts[-1].text or "") + (
part_copy.text or ""
)

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.

Aggregation failures break the primary operation

A malformed provider part or serialization failure can escape aggregate_response_content_items and fail trace/span construction instead of letting the primary operation continue. Catch expected aggregation errors at the boundary, log them with exc_info=True and response context, and return the unaggregated/degraded response or omit aggregated output.

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`sdks/python/src/opik/integrations/genai/generations_aggregators.py` around lines 47-60,
update `aggregate_response_content_items` so failures from `model_copy`,
`_is_plain_text_part`/`model_dump`, or part mutation cannot escape the stream
aggregator. Wrap the aggregation logic in a broad, appropriate exception boundary, log
the exception with `exc_info=True` and relevant response context, then return an
unaggregated or otherwise degraded response that preserves the underlying operation
instead of failing trace/span construction.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Python SDK python Pull requests that update Python code 🟡 size/M tests Including test files, or tests related like configuration.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Streamed Gemini traces lose function calls (and text next to them)

1 participant