6.4 KiB
6.4 KiB
02. Agent Loop 执行机制
1. Agent Loop 的基本形态
Agent 每轮不是只调用一次模型,而是一个循环:
用户输入
-> 构造 session 和 prompt
-> 调模型
-> 模型返回 assistant text 或 tool_calls
-> 如果没有 tool_calls:输出最终回复,结束
-> 如果有 tool_calls:执行工具
-> 工具结果写回 session
-> 下一轮模型继续读取工具结果
-> 直到最终回复、预算超限、取消、max_turns 或 review 停止
这个循环允许 Agent 做“观察-执行-再观察”的任务,例如:
- 先读文件,再决定是否需要抽取。
- 先生成 plan,等待用户 review。
- 先运行脚本,再根据校验结果修复。
- 先查询线上数据,再抽样展示。
2. 新会话和恢复会话
新会话入口:
src/agent_runtime.py:413 run(...)
关键动作:
1. 清理当前 managed_agent_id 和 resume_source_session_id。
2. 创建 session_id。
3. 创建 scratchpad_directory。
4. 绑定 plan_runtime/task_runtime 到 scratchpad。
5. 调 _run_prompt(...)
6. 累计 usage。
恢复会话入口:
src/agent_runtime.py:441 resume(...)
关键动作:
1. 从 StoredAgentSession 恢复 AgentSessionState。
2. 回放 file_history 和 compaction 信息。
3. 设置 active_session_id 和 last_session_path。
4. 恢复 plugin state。
5. 复用已有 scratchpad_directory。
6. 调 _run_prompt(...)
这解释了为什么刷新页面、回到旧会话后,Agent 理论上可以继续同一个 session 的上下文,而不是新建一个任务。
3. _run_prompt 主链路
核心位置:
src/agent_runtime.py:520 _run_prompt 主体
主链路关键步骤:
1. slash command 预处理。
2. hook policy / plugin hook 修改 prompt。
3. agent_manager.start_agent(...) 记录运行。
4. 新建或复用 AgentSessionState。
5. runtime_context prepend 到模型可见的用户消息。
6. session.append_user(...)。
7. tool_context 注入 scratchpad、plan_runtime、task_runtime。
8. 生成 tool_specs。
9. 初始化 usage、cost、tool_calls、events。
10. 进入 turn loop。
对应代码点:
src/agent_runtime.py:523 agent_manager.start_agent(...)
src/agent_runtime.py:531 session = base_session or build_session(...)
src/agent_runtime.py:539 _prepend_runtime_context(...)
src/agent_runtime.py:543 session.append_user(...)
src/agent_runtime.py:546 replace(self.tool_context, scratchpad_directory=...)
src/agent_runtime.py:552 tool_specs = [tool.to_openai_tool() ...]
src/agent_runtime.py:582 stream_events = _RuntimeEventBuffer(event_sink)
src/agent_runtime.py:619 for turn_index in range(...)
4. 模型调用和 tool_calls 判断
模型调用入口:
src/agent_runtime.py:1490 _query_model(...)
非流式路径中:
turn = self.client.complete(
session.to_openai_messages(),
tool_specs,
output_schema=...
)
模型能否返回 tool_calls 取决于:
- 当前
messages。 - 系统提示词。
- Skill 列表。
- 工具描述和参数 schema。
- 模型自身 tool calling 能力。
基座不会强制某个工具被调用。基座只把工具能力暴露给模型,并在模型返回 tool_calls 后负责执行。
5. 没有 tool_calls 时
如果模型本轮没有工具调用:
src/agent_runtime.py:763 if not turn.tool_calls
后续可能有三种情况:
- 直接输出最终回复。
- 如果模型输出被截断,自动追加 continuation prompt。
- 如果达到 continuation 限制,则追加提示并结束。
最终会:
session.append_assistant(...)
_append_final_text_stream_events(...)
AgentRunResult(...)
_persist_session(...)
6. 有 tool_calls 时
如果模型返回工具调用:
src/agent_runtime.py:1008 for tool_call in turn.tool_calls
每个工具调用会:
1. tool_calls 计数 +1。
2. 检查预算。
3. session.start_tool(...) 写入工具开始消息。
4. stream_events 追加 tool_start。
5. 根据工具名执行 handler。
6. 工具结果写回 session。
7. stream_events 追加工具结果。
8. 下一轮模型读取工具结果继续判断。
关键代码点:
src/agent_runtime.py:1054 session.start_tool(...)
src/agent_runtime.py:1060 stream_events.append(type='tool_start')
src/agent_tools.py:60 execute_tool(...)
src/agent_tools.py:76 execute_tool_streaming(...)
7. 为什么 review 可以暂停流程
review 门禁不是特殊 UI 魔法,而是 Agent loop 的自然结果:
- Skill 要求模型在某一步调用 review 工具。
- 工具创建 pending state,并返回需要展示的信息。
- Agent 生成回复,告诉用户需要确认。
- 当前 run 结束。
- 用户下一轮回复“确认”。
- Agent resume 旧 session,调用 confirm 工具。
- 后续流程继续。
例如 product-data:
data_agent_prepare_generation_goal
-> pending goal
-> 停止等待用户确认
data_agent_confirm_generation_goal
-> confirmed_goal_id
-> data_agent_prepare_generation_plan
-> pending plan
-> 停止等待用户确认
data_agent_confirm_generation_plan
-> confirmed_plan_id
-> 允许生成 draft 和转换 records
8. 预算、取消和 max_turns
基座会在模型调用前后、工具请求前检查预算:
src/agent_runtime.py:592 initial_budget = self._check_budget(...)
src/agent_runtime.py:732 budget_after_model = self._check_budget(...)
src/agent_runtime.py:1014 budget_after_tool_request = self._check_budget(...)
如果达到最大轮次:
src/agent_runtime.py:1440 _build_max_turns_output(...)
输出会包含最后一次阶段说明,提醒用户可以继续补充指令。
取消由后端 run manager 和 tool process registry 处理。Web 点停止后,后端将 run 标记取消,并让工具执行上下文感知 cancel_event。
9. 事件流和前端活动区
Agent loop 内会不断追加 stream_events,后端用 event_sink 把事件推给前端。
后端关键代码:
backend/api/server.py:1751 emit_agent_event(event)
backend/api/server.py:1756 state.run_manager.record_event(...)
backend/api/server.py:1758 state.run_state_store.record_event(...)
backend/api/server.py:2034 StreamingResponse(..., media_type='application/x-ndjson')
前端活动区展示的阶段说明、工具开始、工具完成、最终文本,本质上都来自这些 runtime events 或 session replay。
