Skip to content

fix(models): restore usage_metadata in MindIE tool-mode simulated streaming - #5195

Merged
WillemJiang merged 1 commit into
bytedance:mainfrom
Coder-xiaosuo:fix-issue-5192
Sep 5, 2026
Merged

WillemJiang merged 1 commit into
bytedance:mainfrom
Coder-xiaosuo:fix-issue-5192

Conversation

@Coder-xiaosuo

Copy link
Copy Markdown
Contributor

Fixes #5192.

What happens today

For tool-enabled requests MindIEChatModel._astream cannot use native
streaming: the MindIE engine drops choices when stream=True and tools are
present, so the model awaits the full _agenerate response and re-emits it as
simulated AIMessageChunks. The full response carries usage_metadata, but the
simulated chunks never copy it. Chunk aggregation (add_ai_message_chunks)
only produces usage_metadata when at least one chunk carries it, so the merged
final message ends up with usage_metadata=None — every tool-enabled streamed
turn silently loses token usage from token accounting (RunJournal), run
completion stats, thread-message persistence, and the frontend token-usage
display, even though the provider still bills for those tokens.

Fix

The native ChatOpenAI stream already solves this exact problem: it emits a
terminal usage frame as its last chunk, and aggregation carries it forward. This
PR mirrors that convention in the simulated stream — attach msg.usage_metadata
to exactly the last chunk it yields: the trailing tool-call chunk when tool
calls are present, otherwise the final text chunk, or the single tool-only
chunk. Deliberately attached to one chunk only: add_usage() sums per chunk, so
attaching it to every chunk would multiply the totals.

Tests

New regression test mirrors the issue's deterministic shape across the three
simulated-stream branches (text-only, text + tool call, tool-call only): the
underlying response carries usage, and the test asserts exactly one carrier
chunk — the last one — and that the merged message reproduces the original
usage_metadata. A chain-level test drives the public astream() wrapper (the
path LangGraph state accumulation, RunJournal.on_llm_end, and the frontend
actually consume) and asserts the persisted model_dump() shape keeps usage
despite the wrapper's synthetic trailing chunk_position="last" chunk. Fails on
main before the fix (no chunk carries usage and the aggregated message is
None), passes with it. Also run: tests/test_mindie_provider.py, 47 passed.
ruff format/check clean.

AI disclosure

AI tool used: yes (Trae IDE)
Extent: implementation and tests prepared by the agent; root cause verified by
reading langchain-core chunk-aggregation semantics (add_ai_message_chunks) and
the native ChatOpenAI terminal-usage frame before writing the fix
Human review: changes verified by running tests/test_mindie_provider.py
locally (47 passed) and ruff check / ruff format --check

…eaming

In tool-enabled requests MindIEChatModel._astream falls back to awaiting the full _agenerate response and re-emitting it as simulated AIMessageChunks. The full response carries usage_metadata, but none of the simulated chunks copied it, so chunk aggregation (add_ai_message_chunks) produced a final message with usage_metadata=None. Token usage therefore vanished from token accounting, run stats, persistence and the UI for every tool-enabled streamed turn.

Mirror OpenAI's terminal-usage-frame convention: attach msg.usage_metadata to exactly the last simulated chunk (the trailing tool-call chunk when present, else the last text chunk / the single tool-only chunk) so the aggregated message carries it exactly once. add_usage() is per-chunk additive, so attaching usage to every chunk would multiply the totals.

Scope: MindIEChatModel only; other providers keep native streaming and ainvoke/non-tool astream were already correct.

Tests: regression guard asserting exactly one carrier chunk equals the last one and that merged usage equals the original across all three simulated-stream branches, plus chain-level tests driving the public astream() wrapper and asserting the persisted model_dump() shape.

Closes bytedance#5192
@CLAassistant

CLAassistant commented Sep 5, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions Bot added area:backend Gateway / runtime / core backend under backend/ risk:medium Medium risk: regular code changes size/M PR changes 100-300 lines labels Sep 5, 2026
@WillemJiang
WillemJiang merged commit 27b2b67 into bytedance:main Sep 5, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:backend Gateway / runtime / core backend under backend/ risk:medium Medium risk: regular code changes size/M PR changes 100-300 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] MindIE tool-enabled async streaming drops token usage

3 participants