数 据截至 (上游 commit 460c729002dc)
BeeAI Framework — 架构与原理
30 秒导读: BeeAI Framework 是 IBM 捐给 Linux Foundation(LF AI & Data)的 agent 框架,Python 和 TypeScript 双实现。它最独特的东西叫 RequirementAgent:你不写
if/else控制流,而是声明约束——「第 1 步必须先思考」「搜索只能在查完天气之后」「天气工具最多用两次、不许连着用」——框架每一轮把这些约束折叠成「这一轮只准用哪几个工具、必须调哪个」,再连同强制工具调用参数一起发给模型。模型再弱,也很难跑偏。
1. 这是什么(零基础也能懂)
一句话定义: BeeAI Framework 是一个构建 AI agent 与多 agent 系统的通用框架,把「调模型 / 调工具 / 管记忆 / 编排流程 / 对外托管」这几件事做成一整套可组合的模块。
它解决谁的什么问题
假设你要写一个「旅行规划 agent」:先思考一下,再查目的地天气,再搜当地活动,最后给出计划。
你手写这个循环,会遇到三件很烦的事:
| 麻烦 | 具体表现 |
|---|---|
| 模型不听话 | 你在提示词里写「先思考再查天气 」,GPT-4 会听,某个 7B 本地模型第一步就直接瞎答 |
| 各家 API 不一样 | OpenAI 支持 tool_choice="required",Ollama 根本没有这个参数,同一份代码换个模型就崩 |
| 出错看不见 | 中途某个工具返回空、模型反复调同一个工具死循环,你只看到最后一坨输出 |
BeeAI 对这三件事各给了一个答案,也就是本套文档要讲的三条主线:
- 需求(Requirement)机制 —— 把「先思考再查天气」变成框架层面的硬约束,而不是提示词里的祈祷。
- 统一 Backend 层 —— 探测模型不支持
tool_choice时,自动降级成「用 JSON 结构化输出伪造一次工具调用」。 - Emitter + 中间件 —— 每一次模型调用、每一次工具调用都是可订阅的事件,一行
.middleware(GlobalTrajectoryMiddleware())就能把整棵执行树打印出来。
它能做什么(功能清单)
- 多种 agent:
RequirementAgent(旗舰,声明式约束)、ReActAgent(经典 Thought/Action/Observation 文本协议)、ToolCallingAgent、LiteAgent(零系统提示,用来裸测模型能力)、RAGAgent。 - 统一 LLM 接入:OpenAI / Anthropic / Ollama / watsonx / Bedrock / Gemini / Vertex AI / Groq / Mistral / xAI / DeepSeek / Qwen / MiniMax,以及跑在本机的
Transformers(Hugging Face 本地推理)。绝大多数经由 LiteLLM 转接,transformers/langchain/agentstack三个是例外(见第 3 章)。 - 内置工具:搜索(DuckDuckGo / Wikipedia)、天气(OpenMeteo)、代码执行(沙箱 / 本地 Python / Shell)、文件系统(读 / 编辑 / glob / grep)、OpenAPI、MCP 工具、以及给多 agent 用的
HandoffTool。 - 记忆:无上限 / 滑动窗口 / token 预算 / 自动摘要 / 只读。
- 编排:
Workflow状态机 +AgentWorkflow(把多个 agent 串成流水线)。 - 托管:一行把 agent 暴露成 A2A、MCP、ACP、OpenAI 兼容接口的服务。
用起来什么样
下面是仓库自带的真实示例(python/examples/agents/requirement/quickstart_requirement.py:15-37),完整地体现了这个框架的味道:
agent = RequirementAgent(
llm=ChatModel.from_name("ollama:granite4:micro"),
tools=[ThinkTool(), OpenMeteoTool(), DuckDuckGoSearchTool()],
instructions="Plan activities for a given destination based on current weather and events.",
requirements=[
ConditionalRequirement(ThinkTool, force_at_step=1), # 第 1 步必须思考
ConditionalRequirement(DuckDuckGoSearchTool,
only_after=[OpenMeteoTool], # 查完天气才能搜
min_invocations=1, max_invocations=2),
ConditionalRequirement(OpenMeteoTool,
consecutive_allowed=False, # 不许连着调两次
min_invocations=1, max_invocations=2),
],
)
response = await agent.run("What to do in Boston?").middleware(GlobalTrajectoryMiddleware())
注意三个细节:
- 模型是
ollama:granite4:micro—— 一个本地小模型。这不是随便挑的,官方例子刻意用弱模型来证明约束机制有效。 requirements是声明,不是回调 —— 你说「什么条件下允许 / 强制哪个工具」,不写循环。.middleware(...)挂在run()返回值上 ——run()返回的不是协程而是一个可以先挂钩子再 await 的Run对象(见第 4 章)。
一句话直觉
把 RequirementAgent 想成 给模型发的「本轮工具通行证」:每一轮开始前,框架根据历史记录重新签发一张通行证,上面写清楚「这轮你只能用这几个工具、其中这个必须用、这几个连看都别看」。模型的自由度被压缩到通行证之内,于是弱模型也走不丢。
2. 顶层全景(它大概怎么转)
2.1 主要部件与数据流
怎么读这张图:从上往下是一次 agent.run() 的推进方向,右边的回边表示「工具结果写回记忆,进入下一轮」。
你的代码
│ agent.run("What to do in Boston?")
▼
┌──────────────────────────────┐
│ RequirementAgent(门面/配置) │ 持久记忆·模板·工具表·需求表
└──────────────┬───────────────┘
│ 每次 run 新建一个
▼
┌──────────────────────────────┐
│ Runner(逐轮推进的循环) │◄──────────┐
└──────────────┬───────────────┘ │
① 问「这轮允许什么」 │ ④ 工具结果写回
▼ │ 本轮记忆
┌──────────────────────────────┐ │
│ Reasoner(需求→规则→通行证) │ │
└──────────────┬── ─────────────┘ │
② 允许的工具 + tool_choice │
▼ │
┌──────────────────────────────┐ │
│ ChatModel(统一 LLM 层) │ │
└──────────────┬───────────────┘ │
③ 工具调用 │
▼ │
┌──────────────────────────────┐ │
│ Tool.run(校验·缓存·重试) │───────────┘
└──────────────┬───────────────┘
│ 当模型调到 final_answer 这个特殊工具
▼
最终答案
2.2 部件一句话职责
| 部件 | 干什么 | 在哪个文件(相对克隆根) |
|---|---|---|
RequirementAgent | 门面:持有配置和跨轮持久记忆,每次 run 造一个 Runner | python/beeai_framework/agents/requirement/agent.py |
RequirementAgentRunner | 单次运行的循环体:迭代、发请求、跑工具、收尾 | python/beeai_framework/agents/requirement/_runner.py |
RequirementsReasoner | 把需求跑成规则,再折叠成「本轮允许的工具 + tool_choice」 | python/beeai_framework/agents/requirement/utils/_llm.py |
Requirement / Rule | 约束的抽象与最小单位(5 个布尔开关) | python/beeai_framework/agents/requirement/requirements/requirement.py |
FinalAnswerTool | 一个「假工具」:模型调用它 = 宣布结束,并写入结构化答案 | python/beeai_framework/agents/requirement/utils/_tool.py |
ChatModel | 统一 LLM 抽象:工具调用、结构化输出、流式、缓存、重试、能力降级 | python/beeai_framework/backend/chat.py |
Tool | 工具基类:pydantic 入参校验 → 缓存 → 重试 → 事件 | python/beeai_framework/tools/tool.py |
RunContext / Run | 每次执行的上下文树 + 可挂钩子的惰性 awaitable | python/beeai_framework/context.py |
Emitter | 分层事件总线,所有可观测性与中间件的地基 | python/beeai_framework/emitter/emitter.py |
Workflow / AgentWorkflow | 显式状态机 / 多 agent 流水线 | python/beeai_framework/workflows/ |
Server | 把 agent 注册成 A2A / MCP / ACP / OpenAI 兼容服务 | python/beeai_framework/serve/server.py |
2.3 主线走一遍(高层,不进代码)
- 入口:
agent.run("...")被@runnable_entry装饰,先开一个RunContext(拿到 run_id、abort 信号、子 emitter),返回一个Run对象。 - 建临时世界:Runner 新建一份只属于这次运行的记忆(
state.memory),把 agent 的持久记忆和这次的新消息倒进去。 - 签通行证:Reasoner 遍历所有需求,每个需求看着当前
state(已经走过哪些步、哪个工具用了几次)吐出若干Rule;这些规则按工具折叠出「允许集 / 隐藏集 / 强制哪个 / 能不能结束」。 - 问模型:系统提示按通行证现场渲染(不允许的工具也会列出来并附上「为什么不给用」),连同
tools=允许集、tool_choice=强制工具或 required发给ChatModel。 - 跑工具:模型回的工具调用被并发执行,结果作为
ToolMessage写回本轮记忆;每一步记进state.steps,供下一轮的需求判断。 - 收尾:当模型调用
final_answer这个特殊工具,FinalAnswerTool._run把答案写进state.answer,while循环条件失效,退出。 - 落账:把本轮记忆搬回 agent 的持久记忆(默认保留全部中间步骤)。
3. 阅读地图
建议顺序如下。想快速抓住这个项目的「精华」,读 01 和 02 就够;想读懂它为什么能在弱模型上工作,必须读 03。
| 章节 | 讲什么 | 什么时候读 |
|---|---|---|
| 01-requirement-agent.md | 一次 run() 的完整生命周期:双层记忆、迭代循环、文本答案兜底、死循环检测 | 先读,建立主线 |
| 02-requirements-and-rules.md | 需求怎么写、规则怎么合并成通行证、优先级到底管什么 | 本项目最值得学的一章 |
| 03-backend-chatmodel.md | 各家模型 tool_choice 能力差异怎么抹平;用 JSON schema 伪造工具调用 | 想跨模型稳定时读 |
| 04-runcontext-emitter-middleware.md | Run / RunContext / Emitter 三件套;中间件如何短路和改写任意执行 | 想做可观测、审批、流式时读 |
| 05-tools-memory-workflows-serve.md | 工具流水线、四种记忆、Workflow、Handoff、对外托管 | 当作模块速查 |
| 06-deep-dive-and-boundaries.md | 巧妙之处清单、真实边界与已知缺陷、横向对比、总代码地图 | 最后读,或用来做技术选型 |
4. 项目坐标(事实)
| 项 | 值 |
|---|---|
| 仓库 | i-am-bee/beeai-framework,Apache-2.0,LF AI & Data 项目 |
| 布局 | monorepo:python/(主力)、typescript/、docs/(新文档站)、docs-old/ |
| Python 包版本 | beeai-framework 0.1.82(python/pyproject.toml) |
| TypeScript 包版本 | 0.1.30(typescript/package.json) |
| Python 版本要求 | >=3.11,<3.14 |
| Python 代码量 | python/beeai_framework/ 约 2.7 万行 |
两个实现不同步。 Python 侧版本号远超 TS 侧,RequirementAgent 在两边都有目录(python/beeai_framework/agents/requirement/、typescript/src/agents/requirement/),但本套文档只解剖 Python 实现——所有引用都指向 python/ 下的真实文件。
5. 代码地图(总入口)
| 主题 | 文件路径 | 关键符号 |
|---|---|---|
| 旗舰 agent 门面 | python/beeai_framework/agents/requirement/agent.py | RequirementAgent |
| 单次运行循环 | python/beeai_framework/agents/requirement/_runner.py | RequirementAgentRunner.run |
| 约束折叠算法 | python/beeai_framework/agents/requirement/utils/_llm.py | RequirementsReasoner.create_request |
| 约束最小单位 | python/beeai_framework/agents/requirement/requirements/requirement.py | Rule、Requirement、requirement |
| 最常用的内置约束 | python/beeai_framework/agents/requirement/requirements/conditional.py | ConditionalRequirement |
| 统一 LLM 层 | python/beeai_framework/backend/chat.py | ChatModel、_force_tool_call_via_response_format |
| 伪造工具调用的 schema | python/beeai_framework/backend/utils.py | generate_tool_union_schema |
| provider 能力与别名表 | python/beeai_framework/backend/constants.py | ProviderDef、BackendProviders |
| 执行上下文 | python/beeai_framework/context.py | Run、RunContext.enter |
| 事件总线 | python/beeai_framework/emitter/emitter.py | Emitter、Emitter.pipe |
| 工具基类 | python/beeai_framework/tools/tool.py | Tool.run、tool |
| 状态机 | python/beeai_framework/workflows/workflow.py | Workflow |
| 对外托管 | python/beeai_framework/serve/server.py | Server.register_factory |