[Bug] dify-agent: usage of the in-flight request is lost when a run is stopped or the stream aborts

该报错出现在 Dify Agent 运行被用户停止、超时或网关断流时, [Bug] dify-agent: usage of the in-flight request is lost when a run is stopped or the stream aborts 。优先排查 DifyLLMA

快速结论:该报错出现在 Dify Agent 运行被用户停止、超时或网关断流时,[Bug] dify-agent: usage of the in-flight request is lost when a run is stopped or the stream aborts。优先排查 DifyLLMAdapterModel._record_usage 是否只在正常结束时被调用,导致最后一次请求的 token 用量未写入 accumulated_usage。

适用环境:Dify 1.18.0,dify-agent 适配层;Issue 作者在 Windows 上以 uv run 运行本地 pytest 完成验证,没有给出其他 Python、CUDA 或显卡信息。

最快修复方案:暂无确认的一步修复方案。Issue 中已明确的是问题定位:DifyStreamedResponse 已经保存最后一次 usage 载荷,但 request() 与 request_stream() 仅在成功路径调用 _record_usage;未验证代码修改前,不要把它当作已修复版本。

注意事项:该 Issue 已关闭,但讨论中没有给出可直接照做的补丁或命令。关联 Issue #43453 涉及的流资源关闭属于另一个需求,只修复流关闭不会补回已收到的 usage。

问题场景

在 Dify Agent 应用中使用 DifyLLMAdapterModel 发起 LLM 请求时,如果运行被停止(runner 抛出 CancelledError)、被 asyncio.timeout 截断,或 request() 在收到 usage 分片后网关连接中断(如 ModelHTTPError: 503),最后一次请求的 token 用量会丢失。随后终态事件和消息记录只会上报 model.accumulated_usage 中已经正常完成的请求用量,被中断的那次请求按零 token 计。

报错原文

[Bug] dify-agent: usage of the in-flight request is lost when a run is stopped or the stream aborts

adapter.accumulated_usage   # None

ModelHTTPError: 503

原因分析

最可能的原因是适配层的记账时机问题。DifyStreamedResponse 在收到 usage 分片时就会保存最近的用量数据,但 request() 和 request_stream() 都只在正常走完 async for 循环或流消费后才调用 _record_usage(response.dify_usage)。一旦中途抛出异常或调用方提前关闭流,这一行不会执行,已拿到的 usage 就无法进入 accumulated_usage。runner 侧虽然在 finally 中读取 accumulated_usage,但读到的仍是缺少最后一次请求的旧值。

环境排查

  • 确认 Dify 版本:Issue 标注为 1.18.0,并说明可在当时的 main(64a99c5)复现。
  • 确认 dify-agent 适配层代码位置:dify-agent/src/dify_agent/adapters/llm/model.py 中的 _record_usage、request()、request_stream()。
  • 确认 runner 版本行为:dify-agent/src/dify_agent/runtime/runner.py 中停止运行与 asyncio.timeout 相关逻辑,以及终态事件读取 model.accumulated_usage 的位置。
  • 确认 API 持久化路径:api/core/app/apps/agent_app/app_runner.py 中 _persist_message_usage 对停止/取消运行的处理。
  • Issue 仅确认 Windows 下使用 uv run python -m pytest tests/local/dify_agent/adapters/llm -q 的本地测试环境;Python、CUDA、显卡、PyTorch 版本未在 Issue 中给出,无需作为排查前置条件。

解决步骤

  1. 先复现并确认版本:在目标 Dify 版本上运行停止或断流场景,检查 adapter.accumulated_usage 是否在已经收到 usage 分片后仍为 None,或是否未包含被中断请求的用量。
  2. 检查 model.py 中 request() 与 request_stream() 的 _record_usage(response.dify_usage) 调用位置,确认它是否只在成功路径执行。
  3. 检查 DifyStreamedResponse 是否已保存最近的 usage 载荷;Issue 指出该数据已存在(model.py:270-272),但未进入 accumulated_usage。
  4. 可优先尝试的方向是:在异常或取消路径中,只要已经收到 usage 载荷,也把该用量记入 accumulated_usage,而不是仅依赖成功结束后的调用。此方向来自 Issue 的期望行为描述,Issue 讨论中未给出已合并的具体补丁,落到代码前需自行验证。
  5. 将资源关闭(关联 Issue #43453 的流提前退出问题)与 usage 保留分开处理:仅关闭流不会补回已收到的 usage,两者应分别验证。

验证方法

参照 Issue 中的复现方式:用 request_stream() 消费完包含 usage 分片的事件后主动抛出异常(模拟运行停止),检查 accumulated_usage 是否包含该次请求的 prompt_tokens 与 completion_tokens;再验证 request() 在收到 usage 后遇到 ModelHTTPError: 503 时是否同样记账。最后检查停止/取消运行的终态事件和消息记录中的用量是否不再是零或缺失。Issue 提到相关测试用例已随关联 PR 添加,可按需补充本项目的回归测试。

参考来源

langgenius/dify #45299

langgenius/dify #43453

langgenius/dify #40937

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 34939

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注