跳到主要内容

beeai-framework — 本课题摘录

读了哪几篇: 01-requirement-agent(主线循环)、02-requirements-and-rules(声明式约束怎么折叠)、03-backend-chatmodel(统一模型层)。 其余三篇(运行时底座、工具与记忆、深入与边界)本轮没读。

这一家贡献一个别家都没有的机制:每一轮动态算出"这一轮允许干什么、能不能收工"。

它对本课题回答了什么

决定一:每一轮把约束折叠成一张"通行证"

一条规则说的是"某个工具在这一轮的处境",只有五个开关:

开关模型能在清单里看到吗模型能调吗典型用途
默认正常
不允许(而且标注了"为什么现在不给用")不能「现在还不能搜,得先查天气」
隐藏不能不能权限不足的工具,索性不让模型知道
强制必须「第 1 步必须先思考」
禁止这轮收尾「至少调一次天气才准交卷」

(依据:Agent 库 · BeeAI Framework · 需求与规则:声明式约束怎么折叠成一张「本轮通行证」 —— Rule 有 allowed/prevent_stop/forced/hidden 四个布尔开关加一个 reason,每轮把所有 Requirement 跑成 Rule、按工具分组折叠出「允许集/隐藏集/强制工具/能否结束」,再翻译成模型的 tools 与 tool_choice 参数)

"不允许但可见"和"直接藏掉"的区别是这一家的一个小巧思:

不允许但可见,会把工具连同"为什么现在不给用"一起写进系统提示——等于教模型"你现在该干别的"。 相比之下直接藏掉,模型可能反复瞎猜。

这条对我们有直接用处。 前面 deepagents 说"不支持的工具不能只报错,还得从清单里消失", 这一家说的是另一半:有时候恰恰要让模型看见"这个工具存在但现在不能用",并告诉它为什么。 两条不矛盾:能力不具备就藏掉,时序不合适就可见但禁用。

"禁止这轮收尾"这个开关是别家没有的: 它不控制某个工具,它控制"能不能结束"。

本课题"什么时候停"这一问,前面所有实现的答案都是"满足某条件就停"; 这一家给了反面:"不满足某条件就不准停"。 两者可以同时存在。

决定二:通行证直接翻译成发给模型的参数

每一轮把所有约束跑成规则,按工具分组折叠,得出四样东西——允许集、隐藏集、强制工具、能否结束——再直接变成发给模型的工具清单和"必须调工具吗"的模式。

关键在"直接翻译":约束不是靠提示词说服模型,而是变成 API 参数。 说服是软的,参数是硬的。能用参数表达的就别写进提示词。

常见时序约束已经被参数化

90% 的场景不用自己写规则,内置的条件约束把常见时序参数化了:

参数含义
第 N 步必须调它算的是成功步数 + 1
A 和 B 都调过之后才允许前置依赖
一旦 C 调过就不再允许互斥
上一步是 A 时,这一步强制调它紧接
不到 N 次不准交卷靠"禁止收尾"
超过 N 次就禁用次数上限
不许和上一步是同一个工具防原地打转
计数时是否忽略失败的步骤默认忽略失败

(依据:Agent 库 · BeeAI Framework · 需求与规则:声明式约束怎么折叠成一张「本轮通行证」 —— ConditionalRequirement 把常见时序约束参数化——force_at_step(算成功步数+1)、only_after、only_before、force_after、min_invocations(靠 prevent_stop)、max_invocations、consecutive_allowed=False、only_success_invocations 默认 True)

"不许和上一步是同一个工具"这一条是防打转的声明式版本。 cline / cowagent / openmanus 都是"事后检测到打转再干预",这一家是事前就不给它这个选项。

"计数时默认忽略失败的步骤"这个默认值是对的: 失败的调用不该算进"你已经调过 N 次了"。

决定四:收工靠一个"假工具",而且纯文本会被兜底转成它

循环的结束条件是模型调用一个叫"最终答案"的工具。

关键在兜底:如果模型只回了纯文本、没调那个工具,框架会把纯文本硬转成一次"最终答案"调用。 (依据:Agent 库 · BeeAI Framework · RequirementAgent 主线:一次 run 的完整生命周期 —— 循环直到模型调用 final_answer 这个假工具为止;若模型只回纯文本没调,框架用 _create_final_answer_tool_call 把纯文本硬转成一次 final_answer 调用兜底;另有死循环检测,命中就重签通行证)

这条解决了"用完成工具当停止条件"这条路的固有缺陷:模型可能忘了调。 cline / openmanus / smolagents 都用"特殊工具"收工,但都没说"模型忘了调怎么办"。 这一家的答案:忘了就替它调。

死循环检测命中时的动作也很特别:不是停,是"重签通行证"——换一张更严的通行证再给它一次机会。

决定二补充:厂商不支持时,用结构化输出逼出一次合法调用

当厂商不支持所需的"必须调工具"模式时,它把"可选工具"编译成一个 JSON 格式的联合类型,用结构化输出逼模型产出一次合法工具调用,再把结果还原成工具调用消息。 (依据:Agent 库 · BeeAI Framework · 统一 LLM 层:把各家模型的能力差异抹平 —— provider 不支持所需 tool_choice 模式时,ChatModel 把可选工具编译成 JSON schema 联合类型、用结构化输出逼模型产出一次合法工具调用,再把 JSON 还原成 tool-call 消息)

这是"怎么认出模型要调工具"的第五种做法,而且是降级路径: 原生工具调用 → 不支持 → 用结构化输出模拟。 比 crewai 的"降级到文本 ReAct"更强——结构化输出仍然是格式受约束的,不用正则去抠。

它没回答什么

  • 历史怎么压——这三篇没讲。
  • 循环状态怎么存下来续跑——不在这三篇。
  • 并发——不在这三篇。

坑与代价

  • 约束系统本身是一层需要学的东西。 五个开关、十个参数、优先级语义——它自己的文档都专门指出优先级语义里有一个容易误解的地方。

    判断(无锚): 我们的最小原型不需要这一整套,但**"这一轮允许哪些工具"应该从第一天就是一个可计算的东西,而不是一个固定清单。** 如果错,会错在: 如果只有两个工具且没有任何时序约束,那"每轮算一次"是纯粹的空转,直接给全集即可。

  • 每一轮都要跑一遍所有约束。 约束多了就是每轮的固定开销。
  • "把纯文本兜底转成收工调用"会掩盖一个真问题:模型为什么没调。 兜底让流程不卡住,但也让"模型不听话"这件事变得不可见。 应该配一条日志。