BeeAI 实战:从简单对话到 Agent 的驯服之路

本次实践基于 IBM 开源的 BeeAI 框架,完成了一套 12 章渐进实验(t2~t12 共 11 个作业文件),全程跑在本地 Qwen 模型上。路线从简单对话(ChatModel)出发,途经结构化输出、RequirementAgent、需求系统、ReAct、人工审批、自定义工具,最终抵达四智能体协作的旅行规划系统。本篇重点拆解 BeeAI 最有辨识度的设计------需求系统(Requirements):它把"请先思考再查资料、查资料最多两次"这类写在提示词里的软约束,变成了框架在执行层强制实施的硬协议。

一、BeeAI 是什么,和 LangGraph 是什么关系

前几篇博文里,LangGraph 给我们的体验是"白纸作画":StateGraph、节点、边、reducer 全部自己搭,框架只负责执行。BeeAI 走的是另一条路------预制好的智能体流水线 :一个 RequirementAgent 类打包了 LLM、工具、记忆、中间件和执行控制,你要做的不是画图,而是"提需求"。

官方给出的定位关键词:生产就绪(内置缓存、内存优化、OpenTelemetry 可观测性)、提供商无关(一个 ChatModel.from_name() 统一 OpenAI、watsonx.ai、Groq、Ollama 等十多个提供商)、以及声明式执行控制(需求系统)。

整套实验的 12 章可以归成四层,一层比一层"自治":
#mermaid-svg-XaDkjS8UBWRPxlkv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XaDkjS8UBWRPxlkv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XaDkjS8UBWRPxlkv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XaDkjS8UBWRPxlkv .error-icon{fill:#552222;}#mermaid-svg-XaDkjS8UBWRPxlkv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XaDkjS8UBWRPxlkv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XaDkjS8UBWRPxlkv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XaDkjS8UBWRPxlkv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XaDkjS8UBWRPxlkv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XaDkjS8UBWRPxlkv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XaDkjS8UBWRPxlkv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XaDkjS8UBWRPxlkv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XaDkjS8UBWRPxlkv .marker.cross{stroke:#333333;}#mermaid-svg-XaDkjS8UBWRPxlkv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XaDkjS8UBWRPxlkv p{margin:0;}#mermaid-svg-XaDkjS8UBWRPxlkv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XaDkjS8UBWRPxlkv .cluster-label text{fill:#333;}#mermaid-svg-XaDkjS8UBWRPxlkv .cluster-label span{color:#333;}#mermaid-svg-XaDkjS8UBWRPxlkv .cluster-label span p{background-color:transparent;}#mermaid-svg-XaDkjS8UBWRPxlkv .label text,#mermaid-svg-XaDkjS8UBWRPxlkv span{fill:#333;color:#333;}#mermaid-svg-XaDkjS8UBWRPxlkv .node rect,#mermaid-svg-XaDkjS8UBWRPxlkv .node circle,#mermaid-svg-XaDkjS8UBWRPxlkv .node ellipse,#mermaid-svg-XaDkjS8UBWRPxlkv .node polygon,#mermaid-svg-XaDkjS8UBWRPxlkv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XaDkjS8UBWRPxlkv .rough-node .label text,#mermaid-svg-XaDkjS8UBWRPxlkv .node .label text,#mermaid-svg-XaDkjS8UBWRPxlkv .image-shape .label,#mermaid-svg-XaDkjS8UBWRPxlkv .icon-shape .label{text-anchor:middle;}#mermaid-svg-XaDkjS8UBWRPxlkv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XaDkjS8UBWRPxlkv .rough-node .label,#mermaid-svg-XaDkjS8UBWRPxlkv .node .label,#mermaid-svg-XaDkjS8UBWRPxlkv .image-shape .label,#mermaid-svg-XaDkjS8UBWRPxlkv .icon-shape .label{text-align:center;}#mermaid-svg-XaDkjS8UBWRPxlkv .node.clickable{cursor:pointer;}#mermaid-svg-XaDkjS8UBWRPxlkv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XaDkjS8UBWRPxlkv .arrowheadPath{fill:#333333;}#mermaid-svg-XaDkjS8UBWRPxlkv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XaDkjS8UBWRPxlkv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XaDkjS8UBWRPxlkv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XaDkjS8UBWRPxlkv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XaDkjS8UBWRPxlkv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XaDkjS8UBWRPxlkv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XaDkjS8UBWRPxlkv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XaDkjS8UBWRPxlkv .cluster text{fill:#333;}#mermaid-svg-XaDkjS8UBWRPxlkv .cluster span{color:#333;}#mermaid-svg-XaDkjS8UBWRPxlkv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XaDkjS8UBWRPxlkv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XaDkjS8UBWRPxlkv rect.text{fill:none;stroke-width:0;}#mermaid-svg-XaDkjS8UBWRPxlkv .icon-shape,#mermaid-svg-XaDkjS8UBWRPxlkv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XaDkjS8UBWRPxlkv .icon-shape p,#mermaid-svg-XaDkjS8UBWRPxlkv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XaDkjS8UBWRPxlkv .icon-shape .label rect,#mermaid-svg-XaDkjS8UBWRPxlkv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XaDkjS8UBWRPxlkv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XaDkjS8UBWRPxlkv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XaDkjS8UBWRPxlkv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ④ 扩展与协作
t11 自定义工具
t12 多智能体

HandoffTool
③ 控制:需求系统
t7/t9 ThinkTool

声明式 ReAct
t8 受控执行
t10 人工审批
② 代理:RequirementAgent
t5 最小代理
t6 挂工具

WikipediaTool
① 地基:模型调用
t2 对话

ChatModel
t3 提示模板
t4 结构化输出

这个分层也暗示了 BeeAI 的核心主张:智能体的可靠性不来自更聪明的提示词,而来自执行层被约束的确定性。

二、地基三件套:对话、模板、结构化输出

2.1 ChatModel:一个接口换遍所有提供商

t2.py 是整个系列的"Hello World"。两行核心代码:

python 复制代码
from beeai_framework.backend import ChatModel, ChatModelParameters, UserMessage, SystemMessage

llm = ChatModel.from_name("openai:Qwen3.6-35B-A3B-4bit",
                          ChatModelParameters(temperature=0),
                          base_url="http://127.0.0.1:8000/v1",
                          api_key="sk-****")  # 已脱敏,建议改用环境变量注入

from_name 的前缀协议(openai:、watsonx:、ollama:)决定了后端实现,其余代码不变------这就是"提供商无关"的全部含义:换模型是改一个字符串,不是改一套代码。对话本体是标准的消息列表加异步调用:

python 复制代码
messages: list[AnyMessage] = [
    SystemMessage(content="You are a helpful AI assistant ..."),
    UserMessage(content="Help me brainstorm a unique business idea ...")
]
response = await llm.run(messages)
print(response.get_text_content())

值得注意,BeeAI 从最底层的模型调用开始就强制 async/await------后面的代理循环、多智能体协作全部建立在异步之上。

2.2 结构化输出:为"机器消费"而生的返回值

t4 用 Pydantic 定义商业计划的 schema,把它作为 response_format 传给模型:

python 复制代码
class BusinessPlan(BaseModel):
    """一个全面的商业计划结构。"""
    business_name: str = Field(description="吸引人的商业名称")
    elevator_pitch: str = Field(description="30秒的商业描述")
    target_market: str = Field(description="主要目标受众")
    unique_value_proposition: str = Field(description="使这个商业特别的地方")
    revenue_streams: List[str] = Field(description="商业赚钱的方式")
    startup_costs: str = Field(description="预计所需的初始投资")
    key_success_factors: List[str] = Field(description="成功的关键要素")

chat_response = await llm.run(messages, response_format=BusinessPlan)
response = chat_response.output_structured
assert isinstance(response, BusinessPlan)

此后 response.business_name、response.revenue_streams 都是带类型的字段而非自由文本。这一步在整条学习线里是伏笔:后面需求系统能"机械化校验",前提就是数据全程结构化。

三、RequirementAgent:从"调模型"到"组装智能体"

t5 换了个视角:不再直接调 llm.run(),而是组装一个代理。四件套:

python 复制代码
from beeai_framework.agents.requirement import RequirementAgent
from beeai_framework.memory import UnconstrainedMemory

minimal_agent = RequirementAgent(
    llm=llm,                            # 语言模型
    tools=[],                           # 目前没有工具
    memory=UnconstrainedMemory(),       # 不裁剪的对话记忆
    instructions=SYSTEM_INSTRUCTIONS    # 角色与方法论(系统提示)
)
result = await minimal_agent.run(ANALYSIS_QUERY)

与 LangGraph 对照:LangGraph 的节点是你写的函数,框架只知道图的拓扑;BeeAI 的代理是框架提供的成品执行循环 (LLM ↔ 工具 ↺),你通过参数注入个性。instructions 里的系统提示在所有后续实验中保持不变------一段网络安全分析师的方法论------为的就是唯一变量只有工具与需求,逐层对比出每个能力点的净贡献。这是整套实验设计上最值得学的一手:控制变量法做功能验证。

这里埋着本篇最重要的主线:tools=[] 时代理只是"会聊天的 LLM";接下来把工具一件件装上去,再用需求系统一件件"管起来"。

四、需求系统:把提示词里的"请"变成框架里的"必须"

这是 BeeAI 与众不同的地方。多数框架里,"先思考再查资料、最多查两次"是写给模型看的嘱咐 ------模型可以不理。BeeAI 把这类约束抽成了独立的 requirements 列表,在每次工具调用前由框架校验,不通过就拦截。

4.1 执行循环与需求闸门

RequirementAgent 的每一步执行都经过同一道闸门:
#mermaid-svg-aUbRvejSmRLT3Zox{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aUbRvejSmRLT3Zox .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aUbRvejSmRLT3Zox .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aUbRvejSmRLT3Zox .error-icon{fill:#552222;}#mermaid-svg-aUbRvejSmRLT3Zox .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aUbRvejSmRLT3Zox .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aUbRvejSmRLT3Zox .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aUbRvejSmRLT3Zox .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aUbRvejSmRLT3Zox .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aUbRvejSmRLT3Zox .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aUbRvejSmRLT3Zox .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aUbRvejSmRLT3Zox .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aUbRvejSmRLT3Zox .marker.cross{stroke:#333333;}#mermaid-svg-aUbRvejSmRLT3Zox svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aUbRvejSmRLT3Zox p{margin:0;}#mermaid-svg-aUbRvejSmRLT3Zox .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aUbRvejSmRLT3Zox .cluster-label text{fill:#333;}#mermaid-svg-aUbRvejSmRLT3Zox .cluster-label span{color:#333;}#mermaid-svg-aUbRvejSmRLT3Zox .cluster-label span p{background-color:transparent;}#mermaid-svg-aUbRvejSmRLT3Zox .label text,#mermaid-svg-aUbRvejSmRLT3Zox span{fill:#333;color:#333;}#mermaid-svg-aUbRvejSmRLT3Zox .node rect,#mermaid-svg-aUbRvejSmRLT3Zox .node circle,#mermaid-svg-aUbRvejSmRLT3Zox .node ellipse,#mermaid-svg-aUbRvejSmRLT3Zox .node polygon,#mermaid-svg-aUbRvejSmRLT3Zox .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aUbRvejSmRLT3Zox .rough-node .label text,#mermaid-svg-aUbRvejSmRLT3Zox .node .label text,#mermaid-svg-aUbRvejSmRLT3Zox .image-shape .label,#mermaid-svg-aUbRvejSmRLT3Zox .icon-shape .label{text-anchor:middle;}#mermaid-svg-aUbRvejSmRLT3Zox .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aUbRvejSmRLT3Zox .rough-node .label,#mermaid-svg-aUbRvejSmRLT3Zox .node .label,#mermaid-svg-aUbRvejSmRLT3Zox .image-shape .label,#mermaid-svg-aUbRvejSmRLT3Zox .icon-shape .label{text-align:center;}#mermaid-svg-aUbRvejSmRLT3Zox .node.clickable{cursor:pointer;}#mermaid-svg-aUbRvejSmRLT3Zox .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aUbRvejSmRLT3Zox .arrowheadPath{fill:#333333;}#mermaid-svg-aUbRvejSmRLT3Zox .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aUbRvejSmRLT3Zox .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aUbRvejSmRLT3Zox .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aUbRvejSmRLT3Zox .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aUbRvejSmRLT3Zox .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aUbRvejSmRLT3Zox .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aUbRvejSmRLT3Zox .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aUbRvejSmRLT3Zox .cluster text{fill:#333;}#mermaid-svg-aUbRvejSmRLT3Zox .cluster span{color:#333;}#mermaid-svg-aUbRvejSmRLT3Zox div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aUbRvejSmRLT3Zox .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aUbRvejSmRLT3Zox rect.text{fill:none;stroke-width:0;}#mermaid-svg-aUbRvejSmRLT3Zox .icon-shape,#mermaid-svg-aUbRvejSmRLT3Zox .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aUbRvejSmRLT3Zox .icon-shape p,#mermaid-svg-aUbRvejSmRLT3Zox .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aUbRvejSmRLT3Zox .icon-shape .label rect,#mermaid-svg-aUbRvejSmRLT3Zox .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aUbRvejSmRLT3Zox .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aUbRvejSmRLT3Zox .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aUbRvejSmRLT3Zox :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不满足 → 拦截,LLM 重新决策
提议调用工具 → 放行
纯文本答复 → 直通
用户消息
LLM 决策

产出文本 或 提议调用工具
需求系统校验

本步放行条件是否满足?
执行工具

ThinkTool / WikipediaTool / 自定义
最终答案
GlobalTrajectoryMiddleware

记录轨迹

框架在每一轮做的事:LLM 若提议调用工具,先问需求系统"这一步允许吗"------force_at_step 到了吗?依赖的上游工具执行了吗?次数上限爆了吗?需要人工批准吗?不满足就拦截这一提议,让 LLM 带着约束重新决策。

4.2 ConditionalRequirement:七个参数,一张执行契约

需求系统的主力是 ConditionalRequirement,t6 只用一个参数就给代理加上了"节流阀":

python 复制代码
from beeai_framework.agents.requirement.requirements.conditional import ConditionalRequirement
from beeai_framework.tools.search.wikipedia import WikipediaTool

wikipedia_agent = RequirementAgent(
    llm=llm,
    tools=[WikipediaTool()],
    memory=UnconstrainedMemory(),
    instructions=SYSTEM_INSTRUCTIONS,
    middlewares=[GlobalTrajectoryMiddleware(included=[Tool])],  # 记录所有工具轨迹
    requirements=[ConditionalRequirement(WikipediaTool, max_invocations=2)]
)
参数 含义 实验中的用法
force_at_step 第 N 步强制调用该工具 t8/t9:force_at_step=1,开口先思考
force_after 每次调用某类工具后强制调用该工具 t9:force_after=Tool,每行动必反思
only_after 必须在指定工具之后才能调用 t8:查维基之前必须先过 ThinkTool
min_invocations 至少调用几次 保证"不能不研究就下结论"
max_invocations 至多调用几次 限流,防工具空转
consecutive_allowed 是否允许连续重复调用 设 False 防止同一个工具连打
(类本身) AskPermissionRequirement t10:执行前请求人工批准

这些参数的共同点是:它们描述的是流程契约,不是提示词。模型换、任务换,契约不变。

4.3 一条需求写出 ReAct

ReAct 那篇博文里,我们用 LangGraph 手写了"推理-行动"循环:条件边、状态、循环回指一样不少。t9 展示了 BeeAI 的表达方式------ReAct 不是一段代码,而是一条需求描述:

python 复制代码
reasoning_agent = RequirementAgent(
    llm=llm,
    tools=[ThinkTool(), WikipediaTool()],   # 思考 + 研究
    memory=UnconstrainedMemory(),
    instructions=SYSTEM_INSTRUCTIONS,
    middlewares=[GlobalTrajectoryMiddleware(included=[Tool])],
    requirements=[
        ConditionalRequirement(
            ThinkTool,
            force_at_step=1,          # 必须从思考开始
            force_after=Tool,         # 每次工具调用后强制反思
            min_invocations=1,
            max_invocations=5,
            consecutive_allowed=False # 不允许连续空想
        )
    ]
)

三条参数恰好拼出经典 ReAct 轨迹:
#mermaid-svg-38fhrvXEz2pDPPRr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-38fhrvXEz2pDPPRr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-38fhrvXEz2pDPPRr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-38fhrvXEz2pDPPRr .error-icon{fill:#552222;}#mermaid-svg-38fhrvXEz2pDPPRr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-38fhrvXEz2pDPPRr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-38fhrvXEz2pDPPRr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-38fhrvXEz2pDPPRr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-38fhrvXEz2pDPPRr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-38fhrvXEz2pDPPRr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-38fhrvXEz2pDPPRr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-38fhrvXEz2pDPPRr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-38fhrvXEz2pDPPRr .marker.cross{stroke:#333333;}#mermaid-svg-38fhrvXEz2pDPPRr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-38fhrvXEz2pDPPRr p{margin:0;}#mermaid-svg-38fhrvXEz2pDPPRr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-38fhrvXEz2pDPPRr .cluster-label text{fill:#333;}#mermaid-svg-38fhrvXEz2pDPPRr .cluster-label span{color:#333;}#mermaid-svg-38fhrvXEz2pDPPRr .cluster-label span p{background-color:transparent;}#mermaid-svg-38fhrvXEz2pDPPRr .label text,#mermaid-svg-38fhrvXEz2pDPPRr span{fill:#333;color:#333;}#mermaid-svg-38fhrvXEz2pDPPRr .node rect,#mermaid-svg-38fhrvXEz2pDPPRr .node circle,#mermaid-svg-38fhrvXEz2pDPPRr .node ellipse,#mermaid-svg-38fhrvXEz2pDPPRr .node polygon,#mermaid-svg-38fhrvXEz2pDPPRr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-38fhrvXEz2pDPPRr .rough-node .label text,#mermaid-svg-38fhrvXEz2pDPPRr .node .label text,#mermaid-svg-38fhrvXEz2pDPPRr .image-shape .label,#mermaid-svg-38fhrvXEz2pDPPRr .icon-shape .label{text-anchor:middle;}#mermaid-svg-38fhrvXEz2pDPPRr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-38fhrvXEz2pDPPRr .rough-node .label,#mermaid-svg-38fhrvXEz2pDPPRr .node .label,#mermaid-svg-38fhrvXEz2pDPPRr .image-shape .label,#mermaid-svg-38fhrvXEz2pDPPRr .icon-shape .label{text-align:center;}#mermaid-svg-38fhrvXEz2pDPPRr .node.clickable{cursor:pointer;}#mermaid-svg-38fhrvXEz2pDPPRr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-38fhrvXEz2pDPPRr .arrowheadPath{fill:#333333;}#mermaid-svg-38fhrvXEz2pDPPRr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-38fhrvXEz2pDPPRr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-38fhrvXEz2pDPPRr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-38fhrvXEz2pDPPRr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-38fhrvXEz2pDPPRr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-38fhrvXEz2pDPPRr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-38fhrvXEz2pDPPRr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-38fhrvXEz2pDPPRr .cluster text{fill:#333;}#mermaid-svg-38fhrvXEz2pDPPRr .cluster span{color:#333;}#mermaid-svg-38fhrvXEz2pDPPRr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-38fhrvXEz2pDPPRr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-38fhrvXEz2pDPPRr rect.text{fill:none;stroke-width:0;}#mermaid-svg-38fhrvXEz2pDPPRr .icon-shape,#mermaid-svg-38fhrvXEz2pDPPRr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-38fhrvXEz2pDPPRr .icon-shape p,#mermaid-svg-38fhrvXEz2pDPPRr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-38fhrvXEz2pDPPRr .icon-shape .label rect,#mermaid-svg-38fhrvXEz2pDPPRr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-38fhrvXEz2pDPPRr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-38fhrvXEz2pDPPRr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-38fhrvXEz2pDPPRr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} force_after=Tool
答案就绪
force_at_step=1

先思考
行动

调用工具
再思考

基于工具结果反思
最终回答
consecutive_allowed=False

禁止连续思考

  • force_at_step=1:第一步必须思考,杜绝"上来就乱查";
  • force_after=Tool:每次行动之后强制反思------这正是 ReAct 里 Reasoning 与 Acting 的交替;
  • consecutive_allowed=False:不许连着两次思考,逼模型思考后必须行动。

t8 则把参数用成了"组合拳":ThinkTool 要求 force_at_step=1, min_invocations=1, max_invocations=3, consecutive_allowed=False,WikipediaTool 加上 only_after=[ThinkTool], max_invocations=2------先强制思考,思考之后才准查证,查证最多两次。执行轨迹从此可预测:思考 → 研究 → 思考 → 答复。

4.4 真实世界的两个补丁

跑本地模型时,t8 起多了两行指导书里没有的配置,恰恰是最有实战价值的部分:

python 复制代码
llm = ChatModel.from_name("openai:Qwen3.6-35B-A3B-4bit", ...,
                          tool_choice_support={"none", "auto"})
llm.allow_parallel_tool_calls = False
  • tool_choice_support={"none", "auto"}:本地推理端点对强制工具选择(tool_choice)的支持有限,如实声明能力集,框架会据此调整调用策略;
  • allow_parallel_tool_calls = False:并行工具调用一次返回多个调用,会打乱需求系统按"步"计数的节奏------顺序化执行,需求契约才有意义。

另有一个版本漂移的细节:指导书的导入路径是 beeai_framework.agents.experimental、结果访问用 result.answer.text;而本地实际安装的版本里,代理已迁到 beeai_framework.agents.requirement,结果字段变成了 result.output_structured。框架迭代很快,以本地版本的实际导入路径为准,这也解释了作业文件与教程文本的路径差异。

五、AskPermissionRequirement:把人拉进循环

t10 在需求列表里加入了一个特殊成员------AskPermissionRequirement:

python 复制代码
from beeai_framework.agents.requirement.requirements.ask_permission import AskPermissionRequirement

requirements=[
    ConditionalRequirement(ThinkTool, force_at_step=1, min_invocations=1,
                           max_invocations=2, consecutive_allowed=False),
    # 安全:外部访问需要许可
    AskPermissionRequirement(WikipediaTool),
    # 获得许可之后,仍然受限
    ConditionalRequirement(WikipediaTool, only_after=[ThinkTool],
                           min_invocations=0,   # 获批后可选
                           max_invocations=1)   # 即使获批也只准一次
]

执行轨迹从"思考 → 研究 → 答复"变成"思考 → 审批 → 研究 → 答复 "。两个细节体现设计分寸:审批只是"放行门槛",获批后的 ConditionalRequirement 依然生效(最多调一次);min_invocations=0 则意味着批准了也可以不查。人工介入被建模成需求系统里的一种约束,而不是一个特殊事件------监督与限流用的是同一套语言。这对生产环境(合规审计、高危操作管控)正是刚需。

六、自定义工具:四要素造一个计算器

t11 展示了如何给代理造新器官。BeeAI 工具的四要素:Pydantic 输入 schema、name/description(给 LLM 看的说明书)、_create_emitter(观测钩子)、_run(真正干活):

python 复制代码
class CalculatorInput(BaseModel):
    """基本数学计算的输入模型。"""
    expression: str = Field(description="使用 +, -, *, / 的数学表达式")

class SimpleCalculatorTool(Tool[CalculatorInput, ToolRunOptions, StringToolOutput]):
    name = "SimpleCalculator"
    description = "执行基本的算术计算:加法 (+)、减法 (-)、乘法 (*) 和除法 (/)。"
    input_schema = CalculatorInput

    def _create_emitter(self) -> Emitter:
        return Emitter.root().child(namespace=["tool", "calculator", "basic"], creator=self)

    async def _run(self, input, options, context) -> StringToolOutput:
        result = self._safe_calculate(input.expression.strip())
        return StringToolOutput(f"表达式:{input.expression}\n结果:{result}")

_safe_calculate 的防御值得一看:先用字符白名单过滤,再在 eval(expr, {"builtins": {}}, {}) 的空内建环境里求值------LLM 传来的字符串永远不可信,工具层负责把危险挡在自己内部。错误也不抛异常打断循环,而是格式化成错误信息返回给 LLM,让代理有机会自我修正。

七、HandoffTool:把专家代理变成工具

收官实验 t12 组了一支四人旅行规划团队。关键洞察是 BeeAI 的多智能体协调没有发明新协议------代理即工具:

python 复制代码
from beeai_framework.tools.handoff import HandoffTool

handoff_to_destination = HandoffTool(
    destination_expert,
    name="DestinationResearch",
    description="咨询我们的目的地研究专家,获取有关旅行目的地、景点和实用旅行指导的信息。"
)

travel_coordinator = RequirementAgent(
    llm=llm,
    tools=[handoff_to_destination, handoff_to_weather, handoff_to_language, ThinkTool()],
    instructions="您是旅行协调员......使用交接工具将特定查询委派给适当的专家代理......"
)

HandoffTool 把一个完整的 RequirementAgent 包装成一个普通工具:协调员"调用工具",实际上是把子任务连同对话上下文移交给专家代理;专家跑完自己的小循环(受各自的需求约束),结果作为工具输出回到协调员,由它综合成最终方案。
#mermaid-svg-tzzVGEpL8v25ji53{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tzzVGEpL8v25ji53 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tzzVGEpL8v25ji53 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tzzVGEpL8v25ji53 .error-icon{fill:#552222;}#mermaid-svg-tzzVGEpL8v25ji53 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tzzVGEpL8v25ji53 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tzzVGEpL8v25ji53 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tzzVGEpL8v25ji53 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tzzVGEpL8v25ji53 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tzzVGEpL8v25ji53 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tzzVGEpL8v25ji53 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tzzVGEpL8v25ji53 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tzzVGEpL8v25ji53 .marker.cross{stroke:#333333;}#mermaid-svg-tzzVGEpL8v25ji53 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tzzVGEpL8v25ji53 p{margin:0;}#mermaid-svg-tzzVGEpL8v25ji53 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tzzVGEpL8v25ji53 .cluster-label text{fill:#333;}#mermaid-svg-tzzVGEpL8v25ji53 .cluster-label span{color:#333;}#mermaid-svg-tzzVGEpL8v25ji53 .cluster-label span p{background-color:transparent;}#mermaid-svg-tzzVGEpL8v25ji53 .label text,#mermaid-svg-tzzVGEpL8v25ji53 span{fill:#333;color:#333;}#mermaid-svg-tzzVGEpL8v25ji53 .node rect,#mermaid-svg-tzzVGEpL8v25ji53 .node circle,#mermaid-svg-tzzVGEpL8v25ji53 .node ellipse,#mermaid-svg-tzzVGEpL8v25ji53 .node polygon,#mermaid-svg-tzzVGEpL8v25ji53 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tzzVGEpL8v25ji53 .rough-node .label text,#mermaid-svg-tzzVGEpL8v25ji53 .node .label text,#mermaid-svg-tzzVGEpL8v25ji53 .image-shape .label,#mermaid-svg-tzzVGEpL8v25ji53 .icon-shape .label{text-anchor:middle;}#mermaid-svg-tzzVGEpL8v25ji53 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tzzVGEpL8v25ji53 .rough-node .label,#mermaid-svg-tzzVGEpL8v25ji53 .node .label,#mermaid-svg-tzzVGEpL8v25ji53 .image-shape .label,#mermaid-svg-tzzVGEpL8v25ji53 .icon-shape .label{text-align:center;}#mermaid-svg-tzzVGEpL8v25ji53 .node.clickable{cursor:pointer;}#mermaid-svg-tzzVGEpL8v25ji53 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tzzVGEpL8v25ji53 .arrowheadPath{fill:#333333;}#mermaid-svg-tzzVGEpL8v25ji53 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tzzVGEpL8v25ji53 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tzzVGEpL8v25ji53 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tzzVGEpL8v25ji53 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tzzVGEpL8v25ji53 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tzzVGEpL8v25ji53 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tzzVGEpL8v25ji53 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tzzVGEpL8v25ji53 .cluster text{fill:#333;}#mermaid-svg-tzzVGEpL8v25ji53 .cluster span{color:#333;}#mermaid-svg-tzzVGEpL8v25ji53 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tzzVGEpL8v25ji53 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tzzVGEpL8v25ji53 rect.text{fill:none;stroke-width:0;}#mermaid-svg-tzzVGEpL8v25ji53 .icon-shape,#mermaid-svg-tzzVGEpL8v25ji53 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tzzVGEpL8v25ji53 .icon-shape p,#mermaid-svg-tzzVGEpL8v25ji53 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tzzVGEpL8v25ji53 .icon-shape .label rect,#mermaid-svg-tzzVGEpL8v25ji53 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tzzVGEpL8v25ji53 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tzzVGEpL8v25ji53 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tzzVGEpL8v25ji53 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} DestinationResearch
WeatherPlanning
LanguageCulturalGuidance
用户查询:两周日本文化之旅
旅行协调员

3 个 HandoffTool + ThinkTool
目的地专家

Wikipedia + Think

先思考后查证 ≤4 次
旅行气象学家

OpenMeteo + Think

思考后查天气 ×1
语言文化专家

Wikipedia + Think

仅强制先思考
结果回传 → 协调员综合
完整旅行方案

每个专家都是独立的 RequirementAgent,工具与需求各不相同------上一节练的"组合拳"在这里直接复用成了每个代理的岗位说明书:

代理 角色 工具 需求要点
目的地专家 研究目的地 Wikipedia, Think 先思考后查证,最多 4 次
旅行气象学家 天气分析 OpenMeteo, Think 思考后查天气,恰好 1 次
语言文化专家 语言与文化指导 Wikipedia, Think 仅强制先思考
旅行协调员 主要接口 3 个 HandoffTool, Think 禁止连续思考

对比 LangGraph 那篇的编排者-工作器模式:那里用 Send() 把任务广播 给工作器,靠状态 reducer 合并;这里协调员用工具调用点对点委派 ,专家之间互不可见,综合权始终在协调员手里。前者是数据流视角的编排,后者是调用栈视角的委派------多智能体协作的两种基本姿势,BeeAI 用一个 HandoffTool 就实现了后者的全部语义。

八、两种世界观:BeeAI vs LangGraph

维度 BeeAI LangGraph
心智模型 组装成品代理,声明行为需求 白纸画图,自定义节点与边
可靠性来源 框架在执行层强制需求契约 图拓扑与 reducer 的状态语义
ReAct 一条需求:force_at_step=1 + force_after=Tool 手写循环:条件边 + 状态回指
动态并行 HandoffTool 点对点委派 Send() 广播 + reducer 汇聚
人机协作 AskPermissionRequirement(也是一种需求) 中断 + Command(resume=...)
可观测性 GlobalTrajectoryMiddleware / OpenTelemetry Mermaid 可视化 + 状态快照
适合场景 快速构建可审计的生产智能体 需要完全掌控流程结构的复杂工作流

两者并不互斥:需求系统的思想(执行契约)完全可以搬进 LangGraph 的节点里;BeeAI 拿到复杂图结构需求时,内部跑的也是同一个"LLM↔工具"循环。选择的核心是:你要"约束一个现成的循环",还是"绘制一张自己的图"。

结语

11 个作业文件做完,BeeAI 给我最深的印象不是某个 API,而是一条设计哲学:把对智能体行为的期望,从提示词的"修辞"下沉为执行层的"法律" 。提示词说"请先思考",模型可以不听;force_at_step=1 写进需求列表,框架会让这条路径成为唯一的可能。从 max_invocations 的节流,到 only_after 的依赖,再到 AskPermissionRequirement 的人工闸门,需求系统用同一套声明式语言覆盖了限流、编排、审批三种原本毫不相干的问题。

再回顾之前的实践:LangGraph 系列教会我们"智能体的骨架是图",ReAct 实验揭示了"循环是智能体的心脏";BeeAI 则补上了最后一块------"秩序可以是被声明的"。当多智能体系统从 demo 走向生产,决定成败的往往不是模型的聪明程度,而是这条路径上有多少行为是被契约保证的。


相关推荐
欢喜躲在眉梢里1 小时前
时序数据库选型指南:从大数据架构视角拆解 Apache IoTDB 的适用边界
大数据·人工智能·ai·架构·时序数据库·模型
奇牙coding1 小时前
Codex C接 配置教程:的 字段从迁移时必须写完整后缀,填旧值或省略 会静默回退默认模型
java·c语言·数据库·ai
打不了嗝 ᥬ᭄1 小时前
AI-Agent入门
人工智能·agent
程序员无隅1 小时前
Agent 评测与调优:从 Trace 检查到反馈回流
ai·可用性测试
张忠琳1 小时前
【hermes-agent】Hermes Agent 自我进化原理之二
ai·agent·hermes
笨蛋©8 小时前
[实战] 2026年工程图纸扫描转DXF的精度控制与数字化质量管理流程
ai·数字化·cad·质量管理·制造业
吃饱了得干活9 小时前
Agent 的决策与规划:ReAct、Plan-and-Execute、Reflexion 与 Tree of Thoughts
人工智能·llm·agent
ZhangJun9511 小时前
在 32GB 内存电脑上本地搭建 Qwen3.6-35B-A3B 大模型踩坑实录
运维·人工智能·阿里云·ai·软件构建
CoderJia程序员甲11 小时前
GitHub 热榜项目 - 周榜(2026-09-26)
ai·大模型·llm·github