数据截至 (上游 commit 3a4e2ae3eec0)
第 1 章 · reply 状态机与事件流
这一章讲:一次
agent.reply()内部到底发生了什么,以及它凭什么能停在半路又接着跑。
1.1 先看形状:循环写成了什么样
大多数 agent 框架的 ReAct 循环长这样(伪代码):
# 示意,非源码:常见写法
while step < max_steps:
response = call_model(memory) # 推理
if not response.tool_calls:
return response.text # 出口埋在循环体里
for call in response.tool_calls:
memory.append(run_tool(call)) # 行动
问题在于出口和状 态判断散落在循环体各处。一旦要加「这个工具调用得等用户点同意」,你就得在 run_tool 里往外抛信号、在 while 里接、还要记住恢复时跳回哪一行。
AgentScope 把它翻了个面:
# 示意,非源码:AgentScope 的形状
while True:
action = self._next_action(final_msg) # 只读状态,返回三选一
match action:
case Reasoning(...): ... # 调模型
case Acting(...): ... # 跑工具
case Exit(...): return # 收尾
重点看:循环体里没有任何判断「该不该结束」的逻辑,那些全在 _next_action 里。真实实现在 _reply_impl 方法(src/agentscope/agent/_agent.py:915)的 match next_action: 段(src/agentscope/agent/_agent.py:1028-1161),三个动作类型定义在 src/agentscope/agent/_utils.py:26-43:
class Acting(BaseModel):
tool_calls: list[ToolCallBlock]
class Reasoning(BaseModel):
hint: HintBlock | None = None
tool_choice: ToolChoice | None = None
class Exit(BaseModel):
exit_msg: Msg
exit_events: list[AgentEvent] | None = None
注意 Exit.exit_events 是可空的——空表示「这不是真的结束,只是停下来」。这一个可空字段撑起了整个人工确认机制,下面 1.4 详述。
1.2 _next_action:一张决策表
_next_action(src/agentscope/agent/_agent.py:3248)按固定顺序问三组问题。理解它就理解了整个循环。
决策顺序
Step 1 有没有能跑的工具调用?
├─ 有 ────────────────────────► Acting
└─ 没有,但有在等用户/等外部的 ─► Exit(exit_events=None,即挂起)
│
▼
Step 2 这次 reply 要求结构化输出吗?
├─ 要求了,且已拿到 ──────────► Exit(COMPLETED)
├─ 要求了,还没拿到 ─── ───────► Reasoning(塞一条提醒 hint)
└─ 没要求 ─┐
▼
Step 3 上一轮推理是不是产出了纯文本?
├─ 是 ────────────────────────► Exit(COMPLETED)
├─ 否,且 cur_iter ≥ max_iters ► Exit(EXCEED_MAX_ITERS)
└─ 否 ────────────────────────► Reasoning
Step 1 的细节:为什么要分「可执行」和「在等」
源码里这段过滤是核心(src/agentscope/agent/_agent.py:3268-3279):
executable_tool_calls = [
_
for _ in last_msg.get_content_blocks("tool_call")
if _.id not in finished_ids
and (
_.state == ToolCallState.ALLOWED
or (
_.state == ToolCallState.PENDING
and not awaiting_tool_calls
)
)
]
翻译:只要还有任何一个调用在等用户回话,处于 PENDING 的调用就都不许动。这是为了避免「用户还在看第一个确认框,第二个工具已经把文件改了」。已经被用户放行(ALLOWED)的可以继续跑。