Description / 描述
当前 agent_stats SSE 事件只在 agent runner 完全结束(done() 为 True)时推送一次:
astrbot/core/astr_agent_run_util.py 第 293 行:
if agent_runner.done():
# 只在 agent 完全结束时发送 agent_stats
if astr_event.get_platform_name() == "webchat":
await astr_event.send(
MessageChain(
type="agent_stats",
chain=[Json(data=agent_runner.stats.to_dict())],
)
)
break
agent_runner.done() 只在 LLM 返回了不带 tool_call 的最终回复、或用户点了 Stop 时才变为 True。
这意味着:在一次 agent run 中,所有中间 LLM 调用(首次思考选择工具 → 工具返回后分析结果 → 可能继续调用工具 → ...)期间,前端 token 圆环完全冻结。直到 agent 结束,圆环才一次性跳变。以 deepseek-v4-pro(1M 上下文)为例,一次复杂 agent 任务可能持续 30-90 秒,经历 5-10 轮 LLM 调用,圆环全程纹丝不动。
具体场景走读
用户: "搜索近三年发表的关于 Agent Memory 的论文,
逐篇总结核心方案,再横向对比它们的优劣"
[0s] LLM 调用1(思考,决定调用 search_papers) → 圆环:初始值,不变
[3s] 工具返回 15 篇论文元数据 ~80K tokens → 圆环:不变
[6s] LLM 调用2(分析,决定调用 read_paper_segments) → 圆环:不变
[12s] 工具分段读取 5 篇论文全文 ~200K tokens → 圆环:不变
[15s] LLM 调用3(继续分析,需要更多论文) → 圆环:不变
[20s] 工具再读取 3 篇论文 ~120K tokens → 圆环:不变
[25s] LLM 调用4(综合分析,写出对比表格) → 圆环:不变
[35s] LLM 调用5(最终回复) → 圆环:不变
[40s] 回复交付,agent 结束 → 圆环突然从初始值跳到 48%
用户全程不知道上下文消耗节奏。
期望行为
在每次 LLM 调用完成后(无论后续是否还有工具调用),立刻推送增量 agent_stats,让圆环实时反映当前上下文占用。用户能看到:
- 哪一轮工具调用返回了特别大的结果(圆环快速上涨)
- 上下文正在逼近警戒线时可以主动 Stop,而不是等 agent 报 context exceeded
- agent 多轮思考的 token 消耗节奏
实现要点
- 后端:
astr_agent_run_util.py 把 agent_stats SSE 推送从 done() 分支移到内部 LLM 响应循环中,每次 LLM 调用完成后推送
- 区分中间/最终:可加
partial: true 标记中间更新,前端可用不同动画区分(中间用过渡动画,最终定格为实色)
- 前端已兼容:
useMessages.ts 中已支持重复覆盖 content.agentStats,每次推送会自动更新,无需额外逻辑
- 动画:圆环加 CSS
transition 平滑过渡,避免 0.5 秒内跳多次造成的视觉抖动
依赖
此功能依赖 #9248 先修复——如果 stats 里的 token_usage 仍然是累加值而非当前上下文占用,那么中间更新的数据也是错的,做了没有意义。
需要等到 #9255 合并才能工作
Use Case / 使用场景
- 长文档分析:用户上传一篇 200 页 PDF,agent 需要分段读取 → 分析 → 再分段 → 综合。整个过程 60 秒,用户想实时看到每段读取后上下文的增长情况
- 多步研究任务:agent 需要搜索论文 → 阅读 → 比较 → 撰写报告,5-10 轮 LLM 调用。用户想知道哪一步吃掉了最多上下文
- 便于发现问题:某个工具意外返回了超大结果(如一次搜索返回了 300K 的原始 HTML),圆环瞬间飙升到 90%,用户可以立刻发现问题
而且这个过程作为简单的前端更新,带来的成本或者资源消耗非常低,效果却很明显,比较值得。
Willing to Submit PR? / 是否愿意提交PR?
Code of Conduct
Description / 描述
当前
agent_statsSSE 事件只在 agent runner 完全结束(done()为True)时推送一次:astrbot/core/astr_agent_run_util.py第 293 行:agent_runner.done()只在 LLM 返回了不带 tool_call 的最终回复、或用户点了 Stop 时才变为 True。这意味着:在一次 agent run 中,所有中间 LLM 调用(首次思考选择工具 → 工具返回后分析结果 → 可能继续调用工具 → ...)期间,前端 token 圆环完全冻结。直到 agent 结束,圆环才一次性跳变。以 deepseek-v4-pro(1M 上下文)为例,一次复杂 agent 任务可能持续 30-90 秒,经历 5-10 轮 LLM 调用,圆环全程纹丝不动。
具体场景走读
期望行为
在每次 LLM 调用完成后(无论后续是否还有工具调用),立刻推送增量
agent_stats,让圆环实时反映当前上下文占用。用户能看到:实现要点
astr_agent_run_util.py把agent_statsSSE 推送从done()分支移到内部 LLM 响应循环中,每次 LLM 调用完成后推送partial: true标记中间更新,前端可用不同动画区分(中间用过渡动画,最终定格为实色)useMessages.ts中已支持重复覆盖content.agentStats,每次推送会自动更新,无需额外逻辑transition平滑过渡,避免 0.5 秒内跳多次造成的视觉抖动依赖
此功能依赖 #9248 先修复——如果
stats里的token_usage仍然是累加值而非当前上下文占用,那么中间更新的数据也是错的,做了没有意义。需要等到 #9255 合并才能工作
Use Case / 使用场景
而且这个过程作为简单的前端更新,带来的成本或者资源消耗非常低,效果却很明显,比较值得。
Willing to Submit PR? / 是否愿意提交PR?
Code of Conduct