Skip to content

fix(adapters): 流式响应丢失尾块 usage - #244

Closed
AmriaLin wants to merge 2 commits into
mainfrom
fix/stream-usage-dropped
Closed

AmriaLin wants to merge 2 commits into
mainfrom
fix/stream-usage-dropped

Conversation

@AmriaLin

@AmriaLin AmriaLin commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

问题

流式请求下 UniResponse.usage 恒为 None,ledger 里只有非流式轮次的记录,最终统计的 token 用量远小于实际消耗。

原因

OpenAI 兼容的流式响应,最后一个 chunk 携带 usage,而它的 choices 是空数组:

{"choices": [], "usage": {"prompt_tokens": 100, "completion_tokens": 20, "total_tokens": 120}}

call_api 的流式分支里,usage 提取写在 try 内部,且排在 chunk.choices[0] 之后:

async for chunk in completion:
    try:
        if chunk.choices[0].finish_reason is not None:
            ...
        if chunk.usage:              # 永远走不到
            uni_usage = ...
    except IndexError:
        break                        # 循环在这里结束

空列表访问 choices[0] 抛 IndexError,except 直接 break,那个带 usage 的 chunk 被整个跳过。

call_tools 的分支写法是对的——先取 usage,再 if not chunk.choices: continue。两处不一致。

改动

  • usage 提取移到 try 之前
  • IndexError 分支由 break 改为 continue

验证

新增回归测试 test_call_api_streaming_usage_in_tail_chunk,构造一个正文 chunk 加一个 choices=[] 的尾块,断言 usage 的 prompt_tokens/completion_tokens/total_tokens 都能拿到。

回退源码后该测试失败(usage 为 null),应用修复后通过。

全量测试:687 passed。

Summary by Sourcery

Fix streaming response accounting so token usage from the final usage-only chunk is retained.

Bug Fixes:

  • Preserve usage metadata from usage-only tail chunks in streaming OpenAI-compatible responses.

Tests:

  • Add regression coverage for streaming responses whose final empty-choices chunk contains token usage.

OpenAI 兼容的流式响应,最后一个 chunk 携带 usage 且 choices 为空数组。
原实现先访问 choices[0],空列表触发 IndexError 后直接 break,
该 chunk 的 usage 从未被读取,导致 UniResponse.usage 恒为 None,
只有非流式的工具轮次会计入 ledger,最终用量远小于实际。

- usage 提取移到 try 之前,与 call_tools 的写法对齐
- IndexError 分支由 break 改为 continue,避免跳过后续 chunk
- 新增回归测试:构造 choices 为空的尾块并断言 usage 被捕获
@sourcery-ai

sourcery-ai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implementation

Extract usage before accessing choices and continue past empty-choice chunks so streaming tail usage is retained.

  • src/amrita_core/adapters/openai.py L161-168, L182 (removed lines), L204-206

Tests

Add a regression test for usage on an empty-choices tail chunk and import its usage type.

  • tests/test_adapter.py L18, L319-364

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@AmriaLin AmriaLin closed this Nov 11, 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.

1 participant