Skip to content

流式响应超过 256 KiB 时 usage(含 cached_tokens)丢失,缓存命中统计退化为未知 - #1

Merged
buhuo666 merged 1 commit into
buhuo666:mainfrom
roy-tong:fix/usage-beyond-capture-window
Sep 9, 2026
Merged

buhuo666 merged 1 commit into
buhuo666:mainfrom
roy-tong:fix/usage-beyond-capture-window

Conversation

@roy-tong

@roy-tong roy-tong commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

版本

commit 25b48e69cf472acc8fb1044bafd0a668d0c9e51e(main, 2026-09-08)

现有行为

app.py 的 stream_body()(流式代理路径)只捕获 SSE 流的前 262144 字节:

async for chunk in upstream.aiter_raw():
    if len(captured) < 262144:
        captured.extend(chunk[:262144 - len(captured)])
    yield chunk
...
stats = response_text_cache_stats(message_record["response"])

而 llama.cpp(以及 OpenAI 兼容流式协议)把 usage 放在最后一个 chunk。因此当一次流式响应的 SSE 总字节超过 256 KiB 时,usage 行根本不在捕获文本里,response_text_cache_stats 提取不到任何东西。

实测(真实 app + 模拟 llama.cpp 上游,末 chunk 携带 usage.prompt_tokens_details.cached_tokens=80):

  • 短流(988 B):/admin/api/messages 记录 cached_tokens=80, prompt_tokens=100, cache_hit=True(正确)
  • 长流(307,388 B):cached_tokens=None, prompt_tokens=None,cache_hit 字段缺失——prefix_cache_result 事件同样报 stats_available=False

连带影响:

  1. README 强调"应以 llama.cpp 返回的统计为准、restore 不等于命中",但长输出场景下这个权威统计被静默丢弃;
  2. clear_binding_after_cache_miss() 依赖 cached_tokens 非空,长流下永远不会清绑定;
  3. 内存复用路径(prefix_cache_reused_in_memory)预先置 cache_hit=True,长流下没有响应数据能纠偏它。

对长输出的 coding agent 场景(本项目的主要目标用法),这恰恰是最容易超过 256 KiB 的场景。

期望行为

无论流多长,最终 usage chunk(含 cached_tokens / prompt_tokens)都应被解析并合入消息记录与 prefix_cache_result 事件;头部截断只应影响 response 预览文本,不应影响统计。

最小修改

在 stream_body() 中额外维护一个滚动的尾部缓冲(64 KiB),流结束后若总长超过捕获窗口,则解析尾部并合并非空统计。补丁见附件(31 行,含注释):

  • 应用后长流(307,388 B)实测:cached_tokens=80, prompt_tokens=100, cache_hit=True
  • 现有测试套件 pytest -q:10 passed

复现 fixture

模拟上游返回三条 SSE:一条大 content delta(~300 KiB)、一条携带 usage 的末 chunk、data: [DONE]。fixture 由 AgentMeasure 测试构造,无需安装额外工具即可用任意 mock 上游复现。

@buhuo666

buhuo666 commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Thanks for your submission, this issue has been approved for merging.

@buhuo666
buhuo666 merged commit 4648dab into buhuo666:main Sep 9, 2026
2 checks passed
@roy-tong

Copy link
Copy Markdown
Contributor Author

Merged — thanks! The >256KiB stream shape that hides usage in the tail chunk is exactly the class the corpus covers; more tail-vs-header shapes are there if useful:

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.

2 participants