跳转至

Agent Loop 设计文档

当前 Agent Loop 融合了 ReAct 论文、OpenClaw 的分层结构和 nanobot 的极简风格,loop 本身保持精简,复杂性由 Provider / Tool / Memory / Skills 各子系统承担。


两种模式

方法 说明
Agent.chat(messages) 非流式,等待完整响应后返回 Message
Agent.stream_chat(messages) 流式,逐 token yield 文本,tool call 前后推送 ToolEvent

Loop 流程

每次对话开始前,先由 _get_route() 决定走哪条轨道(见 routing.md):

Agent Loop 流程

卡死检测 + 优雅收尾core/loop_control.py):主循环不再是裸 ReAct。 LoopMonitor 记录每轮工具调用的签名(工具名 + 参数 JSON);连续 3 轮同签名(或连续 2 轮同签名且都报错)判定「原地打转」,注入一条强制反思 system 提示(要求模型用 <diagnosis> 标签诊断根因并换路,禁止原样重试)。反思后仍重复则追加二次强提醒;最多反思 2 次仍卡住,则禁用工具、让模型生成「已完成 / 卡点 / 需用户提供」的收尾报告。跑满 max_iters 时同样留最后一轮禁工具收尾,而不是返回 [max tool iterations reached] 死字符串。chat / stream_chat 两条路径共用这套逻辑。


系统提示词构建(_build_system

Full / Medium Path 按以下顺序拼接,Current time: 是稳定层与动态层的分界点(用于 Prompt Caching):

<soul>                  ← system/soul.md(CRITICAL,最高优先级,稳定,可缓存)
<identity>              ← system/identity.md(稳定)
<agent_protocols>       ← system/agent.md(稳定)
<tools_reference>       ← system/tools.md(稳定)
<available_skills>      ← 所有 Skill 名称列表(稳定)
─── Current time: ───   ← 缓存分割线
workspace 路径
定时任务摘要
<memory_context>        ← FactStore top-15 facts
<user_profile>          ← ~/.ethan/memory/user_profile.md(用户画像)
<behavioral_guidelines> ← ProcedureStore(行为规则)
<relevant_skills>       ← 关键词匹配的 Skill 正文

Fast Path 保留:soul + identity + 当前时间 + workspace 路径 + top-5 facts + user_profile + behavioral_guidelines + 匹配到的 Skill


工具执行(ToolExecutor)

轮次内去重缓存

同一轮 chat 内,相同工具 + 相同参数的调用结果会被缓存,避免重复执行。

# 缓存键:tool_name + args_hash(MD5)
cache_key = f"{tc.name}:{hashlib.md5(json.dumps(tc.arguments, sort_keys=True)).hexdigest()}"

每次新的 chat() / stream_chat() 开始时调用 executor.reset_cache() 清空缓存,确保不同轮次之间互不干扰。

并发执行

LLM 有时在一次回复中请求多个 tool,asyncio.gather() 并发执行可显著减少延迟。

超长结果压缩(ResultCompressor)

工具结果超过 4000 字符时,ToolExecutor 自动调用 maybe_compress(),用廉价模型将原始输出提炼为不超过 1200 字的摘要,再喂给主模型。

ResultCompressor

压缩失败时退化为截断(保留前 4000 字符)。


消息格式

整个 loop 维护 working_messages 列表,格式同时兼容 Anthropic 和 OpenAI 协议(各 Provider 负责自己的转换):

[user]       "帮我查一下当前时间"
[assistant]  tool_calls: [shell(command="date")]
[tool]       "Wed Jun 11 13:49:00 CST 2026"
[assistant]  "当前时间是 2026年6月11日 下午1点49分"  ← 最终返回

设计决策

三档路由:fast / medium / full 三档兼顾延迟与推理深度。medium 档用完整上下文但限制迭代次数,适合大多数短问答,避免为简单请求跑完整 10 轮 ReAct。

并发 tool calls:LLM 有时在一次回复中请求多个 tool,asyncio.gather() 并发执行可以显著减少延迟。

轮次内工具缓存:相同工具+参数在同一轮不重复执行,防止 LLM 循环调用相同工具浪费资源。

超长输出压缩:shell 或 web 工具有时返回数万字符,不经处理直接塞回主模型既昂贵又低效,廉价模型先摘要再注入。

max_iterations 上限:防止 LLM 陷入 tool call 死循环(工具持续报错时)。Fast 固定为 2,Medium 默认 4,Full 默认 10,可通过 config 调整。跑满上限不再吐死字符串,而是留最后一轮禁用工具、让模型收尾总结(见上文「卡死检测 + 优雅收尾」)。

卡死检测优于纯靠迭代上限:迭代上限只是兜底——它要等跑满所有轮次才止损,期间用户干等、token 照烧。LoopMonitor 在连续 3 轮(或 2 轮同错误)原地打转时就主动介入注入反思,让模型显式诊断换路,比被动等上限更省也更可控。签名按「工具名 + 全量参数」比对——递增分页(offset 变化)会产生不同签名,天然不被误判为卡住。

UsageStatscache_tokens 同时统计 cache_readcache_creation,确保状态栏显示的缓存命中数准确。