数据截至 (上游 commit 460c729002dc)
第 1 章 · RequirementAgent 主线
本章讲什么: 把一次
agent.run("...")从入参拆到最终答案,逐层看清楚谁在推进循环、循环凭什么停下、中途出岔子怎么救。约束机制本身(需求怎么变成通行证)留到第 2 章。
1.1 先看清一件事:门面和跑腿是两个对象
RequirementAgent 本身不跑循环。它是个配置容器:持有 LLM、工具表、需求表、提示模板,以及一份跨多次对话存活的持久记忆。
真正跑循环的是 RequirementAgentRunner,它在每次 run() 里新建:
# python/beeai_framework/agents/requirement/agent.py:175-189 节选
runner = self.runner_cls(
llm=self._llm,
config=AgentExecutionConfig(...),
tools=self._tools,
requirements=self._requirements,
tool_call_cycle_checker=self._create_tool_call_checker(),
run_context=RunContext.get(),
...
)
这行 self.runner_cls 是个可替换的类属性(agent.py:155),你可以继承 RequirementAgentRunner 换掉整个循环体而不动门面。
双层记忆是理解这个设计的钥匙
| 层 | 谁持有 | 生命周期 | 作用 |
|---|---|---|---|
| 持久记忆 | RequirementAgent._memory | 跨多次 run() | 多轮对话的历史 |
| 本轮记忆 | RequirementAgentRunState.memory | 单次 run() | 装本次的推理草稿:助手消息、工具调用、工具结果、临时消息 |
本轮记忆在 Runner 构造时是一个全新的 UnconstrainedMemory(_runner.py:67-69),然后把持久记忆和新消息倒进去:
# python/beeai_framework/agents/requirement/agent.py:195-196
await runner.add_messages(self.memory.messages)
await runner.add_messages(new_messages)
这样做的好处很直接:本次运行可以随便往记忆里塞临时消息、再删掉,不会污染真正的对话历史。
1.2 循环的骨架
RequirementAgentRunner.run 只有二十来行(_runner.py:250-273),骨架极其干净:
# python/beeai_framework/agents/requirement/_runner.py:257-272 节选
await self._reasoner.update(self._requirements) # 初始化需求(校验目标工具存在)
while self._state.answer is None: # 唯一的终止条件
self._increment_iteration() # 超过 max_iterations 就抛 AgentError
request = await self._create_request() # 领通行证
await self._ctx.emitter.emit("start", ...)
self._iteration_error_counter.reset()
response = await self._run(request) # 跑一轮
await self._ctx.emitter.emit("success", ...)
终止条件只有一个:state.answer 被填上。 谁填?下一节。
每一轮内部做四件事
单轮逻辑在 _run(_runner.py:275-324),顺序如下:
┌─────────────────────────────────────────────┐
│ ① 调模型 │
│ _run_llm(request) │
└───────────────┬─────────────────────────────┘
│ 没产出工具调用?
▼
┌─────────────────────────────────────────────┐
│ ② 兜底:把纯文本硬转成 final_answer 调用 │
│ _create_final_answer_tool_call │
└───────────────┬─────────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ ③ 死循环检测 │
│ ToolCallChecker.register → 命中就重签证 │
└───────────────┬─────────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ ④ 并发跑工具 → 结果写回本轮记忆 → 清临时消息 │
│ _invoke_tool_calls │
└─────────────────────────────────────────────┘