Files
zk-data-agent/frontend/app/public/doc-assets/technical-docs/02-agent-loop.md
T
2026-05-18 19:28:02 +08:00

6.4 KiB
Raw Blame History

02. Agent Loop 执行机制

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

后续可能有三种情况:

  1. 直接输出最终回复。
  2. 如果模型输出被截断,自动追加 continuation prompt。
  3. 如果达到 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 的自然结果:

  1. Skill 要求模型在某一步调用 review 工具。
  2. 工具创建 pending state,并返回需要展示的信息。
  3. Agent 生成回复,告诉用户需要确认。
  4. 当前 run 结束。
  5. 用户下一轮回复“确认”。
  6. Agent resume 旧 session,调用 confirm 工具。
  7. 后续流程继续。

例如 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。