前言
大模型 Agent 的核心能力不在于模型本身有多聪明,而在于它能调用多少工具、以及调用的安全性。工具调用(tool calling)系统是 Agent 框架的灵魂——它定义了"如何定义工具"、"如何将工具暴露给模型"、"工具执行时发生了什么"、"多模型切换时代码还能跑吗"这一系列根本性问题。
市面上的 Agent 框架对这些问题给出了截然不同的答案。OpenClaw 用 TypeScript 写了一套军工级安全的沙箱执行链路;LangChain 用 Python 拥抱灵活性,把工具绑定的控制权完全交给开发者;CrewAI 则用角色扮演和任务委派重新定义了多 Agent 协作的范式。
这三个框架代表了三种完全不同的设计哲学,本文从工具定义模型、执行机制、安全防护体系、多模型兼容性、多 Agent 编排五个维度做深度对比,源码结合设计理念一起来看。
一、整体架构:从一颗心到一座城
1.1 OpenClaw:单进程多模块的紧耦合架构
OpenClaw 是一个单进程优先的桌面/服务器应用,所有组件运行在同一个进程空间里,通过内部 RPC 通信。这套架构的核心优势是零网络开销和强类型保证。
┌──────────────────────────────────────────────────────┐
│ Gateway Process │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ HTTP/WS │ │ RPC Disp.│ │ Canvas │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ CLI │ │ Session │ │ MCP │ │
│ │ Channel │ │ Store │ │ Bridge │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Agent Runtime (Core) │ │
│ │ ProviderReg │ Session │ ExecTool │ Compaction│ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
整个系统由 8 个 npm 包组成 monorepo:
- core:Agent 运行时、session 管理、exec 工具
- gateway:HTTP/WebSocket 服务器、RPC 分发器
- cli:命令入口和 REPL 交互通道
- plugin-sdk:扩展 API 公共接口
- mcp:MCP 客户端集成
- skill:Skill 加载器和 SafeBin 信任体系
- cron:定时任务调度器
- canvas:A2UI bundle host 渲染运行时
各包之间通过 TypeScript 接口和 JSON-RPC over WebSocket 通信,类型安全由 workspace 级别的 tsconfig.json 保证。
1.2 LangChain:链式组合的松耦合架构
LangChain 的架构哲学是Everything is composable。它的核心不是某个固定的结构,而是一组可以自由组合的原子组件:
from langchain.agents import create_agent
from langchain.tools import tool
@tool
def get_weather(city: str) -> str:
"""Get weather for a given city."""
return f"It's always sunny in {city}!"
agent = create_agent(
model="claude-sonnet-4",
tools=[get_weather],
system_prompt="You are a helpful assistant"
)
LangChain 的 Agent 底层建立在 LangGraph 之上——LangGraph 是一个有向图执行引擎,每个节点是 StatefulRunnable,边代表状态转换。create_agent 实际上是对 LangGraph 的一次高层封装,帮你把 model + tools + prompt 组合成一个可执行的 agent。
这意味着 LangChain 的工具系统没有自己的"执行引擎"——它把执行控制权交给 LangGraph,让开发者决定 Agent 循环如何展开、什么时候停止、遇到错误怎么办。
1.3 CrewAI:角色驱动的层级编排架构
CrewAI 的设计核心是Role-Driven Orchestration,它把 Agent 看作有明确角色定位的工作者:
from crewai import Agent, Task, Crew
researcher = Agent(
role="Research Analyst",
goal="Find the most relevant market data",
backstory="A veteran market researcher with 20 years of experience",
tools=[search_tool, scrape_tool]
)
writer = Agent(
role="Content Writer",
goal="Write compelling narratives from research data",
backstory="A former journalist turned content strategist",
tools=[write_tool]
)
crew = Crew(agents=[researcher, writer], tasks=[research_task, write_task])
crew.kickoff()
CrewAI 的 Agent 有 role(角色)、goal(目标)、backstory(背景故事)三个字段,这些不是注释,而是会被注入到 system prompt 里影响模型的决策风格。Agent 之间可以互相委派任务(Delegate Work Tool),这在 OpenClaw 和 LangChain 里都需要自己实现。
二、工具定义模型:三种哲学
2.1 OpenClaw:TypeScript 接口 + Pydantic Schema
OpenClaw 的工具定义模型是 TypeScript-first 的,所有工具都实现 AgentTool 接口:
// plugin-sdk/index.ts
export interface AgentTool {
name: string;
label?: string;
description: string;
parameters: ToolParameters; // JSON Schema
execute(
toolCallId: string,
args: ToolParameters,
signal?: AbortSignal,
onUpdate?: (update: AgentToolUpdate) => void,
): Promise<AgentToolResult>;
}
parameters 字段是一个 JSON Schema 对象,而不是一个类的实例。这意味着工具的 schema 是纯数据,可以在序列化/反序列化后跨进程传递。
看 src/agents/bash-tools.exec.ts 里的 createExecTool 工厂函数,它返回一个完整的 AgentTool 实例,其中 parameters 由 TypeScript 类型推导后手动构造:
return {
name: "exec",
label: "Shell",
description: "Execute a shell command...",
parameters: {
type: "object",
properties: {
command: { type: "string", description: "Shell command to execute" },
workdir: { type: "string", description: "Working directory (optional)" },
timeout: { type: "number", description: "Timeout in milliseconds" },
background: { type: "boolean", description: "Run in background" },
},
required: ["command"],
},
async execute(toolCallId, args) {
// ...
},
};
这种设计的精妙之处在于:schema 和 execute 逻辑是同一个对象的两个属性,不需要额外的反射机制。TypeScript 编译器在编译时检查 execute 的参数类型,JSON Schema 在运行时告诉模型参数结构。
2.2 LangChain:装饰器 + Pydantic + bind_tools 三条路
LangChain 的工具定义支持三种方式,从简单到复杂排列:
方式一:@tool 装饰器(最简)
from langchain.tools import tool
@tool
def get_weather(city: str) -> str:
"""Get weather for a given city."""
return f"It's always sunny in {city}!"
LangChain 通过 Python 的 inspect 模块从函数签名中自动提取参数类型和 docstring,然后用这些信息构造 JSON Schema。@tool 装饰器背后调用的是 BaseTool.create_from_python_function。
方式二:StructuredTool + Pydantic(最灵活)
from langchain.tools import StructuredTool
from pydantic import BaseModel
class WeatherInput(BaseModel):
city: str
def get_weather(city: str) -> str:
return f"It's always sunny in {city}!"
weather_tool = StructuredTool.from_function(
func=get_weather,
name="get_weather",
description="Get weather for a given city",
args_schema=WeatherInput,
)
Pydantic 的 BaseModel 提供运行时验证和 JSON Schema 导出能力,StructuredTool 把这个 schema 包装成 BaseTool 接口。Pydantic 的 Field 可以加额外的元数据约束(如 min_length、max_length),这些最终也会映射到 JSON Schema 里。
方式三:bind_tools 手动绑定(最底层)
llm_with_tools = llm.bind_tools([tool1, tool2, tool3])
bind_tools 是 LangChain 对各 provider function calling API 的统一抽象层:
# 伪代码,模拟 bind_tools 内部逻辑
class BaseChatModel:
def bind_tools(self, tools: list[BaseTool], ...) -> Runnable:
# 1. 把 BaseTool[] 转换为 provider 特定的格式
if self.provider == "openai":
formatted = [{"type": "function", "function": {"name": t.name, "parameters": t.to_openai_format()}}]
elif self.provider == "anthropic":
formatted = [{"name": t.name, "description": t.description, "input_schema": t.to_anthropic_format()}]
# 2. 返回一个包装了 tools 信息的 LLM runnable
return ChatModelWithBoundTools(self, formatted)
bind_tools 的返回值仍然是一个 Runnable,但这个 Runnable 每次被调用时都会把 tools 信息注入到 provider API 请求里。
OpenClaw 和 LangChain 的根本区别:OpenClaw 的工具定义和模型绑定是同时发生的(createExecTool 直接返回带 schema 的 AgentTool),而 LangChain 是分离的(先定义工具,再通过 bind_tools 把工具绑定到模型)。分离设计更灵活,但 OpenClaw 的设计让工具 schema 的来源完全透明可控。
2.3 CrewAI:工具即服务 + 委派语义
CrewAI 的工具模型强调的是工具可以被 Agent 主动分享和委派:
from crewai.tools import Tool
# 简单的工具包装
search_tool = Tool(
name="Web Search",
description="Search the web for information",
func=my_search_function
)
# Agent 在创建时声明自己可以使用哪些工具
researcher = Agent(
role="Researcher",
goal="Find key insights",
tools=[search_tool] # 工具绑定在 Agent 层面
)
# Task 层面也可以声明需要的工具
task = Task(
description="Research market trends",
agent=researcher,
tools=[search_tool] # Task 也可以约束工具范围
)
CrewAI 还有一个独特的设计:每个 CrewAI 内置两个系统工具——Delegate Work Tool(委派工作)和 Ask Question Tool(提问),这两个工具是框架原生支持的,不需要额外定义:
# CrewAI 自动注入的内部工具
- DelegateWorkTool: agent 可以把任务委派给队友
- AskQuestionTool: agent 可以向其他 agent 提问
这种设计把"多 Agent 协作"本身变成了一个可调用的工具,而不只是框架的执行机制。这在 OpenClaw 和 LangChain 里都需要自己实现。
三、执行机制:从模型输出到工具结果的完整链路
3.1 OpenClaw:五层决策树的精确执行
OpenClaw 的 exec 工具执行链路是三个框架里最复杂的,因为它同时考虑了安全、审批、沙箱隔离三个维度。
第一层:安全门控(Security Gate)
// packages/mini-openclaw-core/src/exec-tool.ts
if (security === "deny") {
return { content: [{ type: "text", text: "Error: exec is disabled (security=deny)" }] };
}
const needsApproval = (): boolean => {
if (ask === "always") return true;
if (security === "full") return false; // full = 完全信任,不需要审批
// security === "allowlist": 在 allowlist 里就不需要审批
return !isAllowlisted(command);
};
security 有三个级别(deny | allowlist | full),ask 也有三个级别(off | on-miss | always),这两个维度组合决定了是否需要触发审批流程。
第二层:allowlist 模式的前缀匹配
function isAllowlisted(command: string): boolean {
const data = readApprovals();
return data.allowlist.some((entry) => {
if (entry.pattern.endsWith("*")) {
return command.startsWith(entry.pattern.slice(0, -1)); // glob 前缀匹配
}
return command === entry.pattern || command.startsWith(entry.pattern + " ");
});
}
注意这里用的是前缀匹配加空格边界双重判断:ls 可以匹配,但 lsExtra 不行;git commit 匹配 git 和 git commit,但不匹配 gitcommit(无空格)。这是一个实用的 UX 优化,让用户不需要记住完整的命令路径。
第三层:Unix Domain Socket 审批
OpenClaw 的审批不是轮询文件的,而是通过 Unix Domain Socket 实时推送:
// 请求发送到 socket,等待用户决策
const decision = await requestExecApprovalViaSocket({
socketPath: "/path/to/socket",
token: "auth-token",
request: approvalRequest,
timeoutMs: 15_000, // 15 秒超时
});
这种设计的好处是:用户在任何界面(macOS menubar app、CLI、网页)都可以响应审批请求,审批结果通过 socket 实时返回,不需要客户端轮询。
第四层:allow-always 的持久化
当用户选择"始终允许"时,只记录命令的第一个 token(可执行文件名),而不是完整命令:
if (decision === "allow-always") {
const firstToken = command.split(" ")[0] ?? command;
addAllowlistEntry(firstToken, sessionKey);
}
记录 git 而不是 git commit -m "fix: bug"——这样下次执行任何 git 命令都不需要重新审批,但 git commit 的具体参数每次还是需要模型自己构造。这是个安全性和便利性的折中。
第五层:子进程隔离
const proc = spawn("sh", ["-c", command], {
cwd: cmdWorkdir,
env: { ...process.env }, // 环境变量会经过 Gateway 的脱敏处理
stdio: "pipe",
});
// stdout/stderr 通过事件驱动收集,不阻塞
proc.stdout.on("data", (chunk) => {
session.stdout += chunk.toString();
});
进程通过 node:child_process 的 spawn 创建,stdout 和 stderr 通过流事件实时收集。background=true 时会立即返回 pid,后台运行。
3.2 LangChain:LangGraph 驱动的工具循环
LangChain 的工具执行是通过 LangGraph 的 agent executor 实现的:
# 伪代码,还原 create_agent 内部逻辑
def create_agent(llm, tools, system_prompt):
# 1. 构造提示词模板
prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
("placeholder", "{messages}"),
])
# 2. 把工具绑定到 LLM
llm_with_tools = llm.bind_tools(tools)
# 3. 构造 agent(用 LCEL 表达式)
agent = prompt | llm_with_tools | StrOutputParser()
# 4. 返回一个带工具执行能力的 agent
return AgentExecutor(agent=agent, tools=tools)
AgentExecutor 里的核心循环:
class AgentExecutor:
def _call(self, inputs):
while self._should_continue(iterations):
# 调用 agent 获取下一条消息
output = self.agent.invoke({"messages": self.messages})
self.messages.extend(output.messages)
# 检查是否有工具调用
last_message = self.messages[-1]
if last_message.tool_calls:
# 执行每个工具调用
for tool_call in last_message.tool_calls:
tool = self.tools_by_name[tool_call["name"]]
observation = tool.invoke(tool_call["args"])
self.messages.append(HumanMessage(content=observation))
else:
# 没有工具调用,停止
break
return {"messages": self.messages}
LangChain 的执行循环是纯数据驱动的:模型输出 tool_calls → AgentExecutor 解析 → 调用工具 → 把结果加回消息历史 → 再次调用模型。这个循环的终止条件由 _should_continue 决定,默认是检测到模型不再输出 tool_calls。
关键差异:LangChain 的工具执行是直接 Python 函数调用,没有任何沙箱或进程隔离。如果 get_weather 是 subprocess.run(['rm', '-rf', '/']),它真的会删掉你的文件系统。
3.3 CrewAI:异步任务 + Guardrails 双轨执行
CrewAI 的执行链路以 Crew.kickoff() 为入口:
class Crew:
def kickoff(self):
# 1. 按顺序或并行执行所有 task
for task in self.tasks:
# 2. 找到对应的 agent 执行
agent = task.agent
result = agent.execute_task(task)
# 3. Guardrail 检查输出
if self.guardrails:
for guardrail in self.guardrails:
guardrail.validate(result)
return self.output
agent.execute_task 的内部流程:
class Agent:
def execute_task(self, task):
# 1. 构造提示词(注入 role/goal/backstory)
prompt = self._build_prompt(task)
# 2. 调用 LLM 获取响应
response = self.llm.call(prompt)
# 3. 检查响应中是否有工具调用
if response.tool_calls:
for tool_call in response.tool_calls:
# 4. 检查工具是否在 agent 的允许列表里
if tool_call.name not in self.allowed_tools:
raise ToolNotAllowedError(tool_call.name)
tool = self.tools[tool_call.name]
observation = tool.execute(tool_call.args)
response = self.llm.call(prompt + [observation])
return response
CrewAI 的一个独特机制是 Guardrails:
from crewai.utilities import GuardrailsOutput
guardrail = GuardrailsOutput(
condition="output must be valid JSON",
validator=lambda x: json.loads(x)
)
crew = Crew(agents=[...], tasks=[...], guardrails=[guardrail])
Guardrails 在每次 agent 输出后运行,可以做 JSON 验证、内容过滤、格式检查。失败了会自动重试或标记任务失败。
四、安全防护体系:这是框架价值观的最直接体现
4.1 OpenClaw:军工级多层防线
OpenClaw 的安全体系是我见过最完整的,它把安全问题分解到了容器层、网络层、文件系统层、审批层、工具策略层、参数脱敏层六个独立防线。
容器层——Docker 沙箱
buildSandboxCreateArgs 在 src/agents/sandbox/docker.ts 里构造完整的 docker create 参数:
args.push("--read-only"); // 只读根文件系统
args.push("--security-opt", "no-new-privileges"); // 禁止提权
// 删掉所有 Linux capabilities
for (const cap of params.cfg.capDrop) {
args.push("--cap-drop", cap);
}
// 网络隔离
if (params.cfg.network) {
args.push("--network", params.cfg.network);
}
// 资源限制
args.push("--pids-limit", String(params.cfg.pidsLimit));
args.push("--memory", normalizeDockerLimit(params.cfg.memory));
--security-opt no-new-privileges 是最关键的一行——即使容器内有 setuid 二进制文件,这个 flag 让它们失去提权能力。
挂载路径的符号链接攻击防护
// 第三次路径检查——防止符号链接逃逸
const sourceCanonical = resolveSandboxHostPathViaExistingAncestor(sourceNormalized);
enforceSourcePathPolicy({ bind, sourcePath: sourceCanonical, ... });
恶意用户可能创建一个符号链接 /tmp/evil -> /etc,在挂载字符串上看起来完全正常。第三步的 resolveSandboxHostPathViaExistingAncestor 通过解析真实路径再检查,把这个攻击面封死。
环境变量脱敏
// BLOCKED_ENV_VAR_PATTERNS 里的正则会自动拦截所有常见 API key 格式
/^ANTHROPIC_API_KEY$/i, // 精确匹配
/^(GH|GITHUB)_TOKEN$/i, // GitHub token
/_?(API_KEY|TOKEN|PASSWORD)$/i, // 兜底:所有按惯例命名的变量
注意最后那条兜底正则——即使新增了 MYCOMPANY_SECRET_KEY_2025 这样的环境变量,也会自动被拦截,不需要逐个显式列举。
工具策略(Tool Policy)
// 默认 deny 所有渠道工具——沙箱里的 AI 不能发 Telegram 消息
export const DEFAULT_TOOL_DENY = [
"browser", "canvas", "nodes", "cron", "gateway",
...CHANNEL_IDS, // telegram, discord, slack, ...
];
沙箱里的 Agent 连工具调用的"开火权"都被限制了——即便模型的 tool calling 输出能力没有被沙箱阻断,它也不能调用任何外部通信工具。
审批系统的实时 Unix Socket 推送
审批结果通过 Unix Domain Socket 而非 HTTP 轮询推送,UI 端(macOS menubar、CLI、网页)实时接收并展示给用户,决策结果再通过 socket 返回:
const payload = JSON.stringify({
type: "request",
token,
id: crypto.randomUUID(),
request: approvalRequest,
});
return await requestJsonlSocket(socketPath, payload, { timeoutMs: 15_000 });
4.2 LangChain:安全靠你自己,框架给工具
LangChain 的安全哲学是**"给开发者最大的自由度,默认不做任何限制"**。这不是缺陷,而是设计选择——LangChain 是一个工具库,不是安全产品。
默认情况:零沙箱
@tool
def run_sql(query: str) -> str:
"""Execute a SQL query."""
return subprocess.run(["psql", "-c", query], capture_output=True)
这段代码在 LangChain 里完全合法,没有任何警告或拦截。如果模型的 prompt 被 injection,这个工具就是一把上膛的枪。
LangSmith Sandboxes:商业安全方案
2024 年 LangChain 推出了 LangSmith Sandboxes 作为官方安全方案:
from langsmith import sandbox
with sandbox.docker.DockerSandbox(image="python:3.11") as sbx:
# 所有代码在 Docker 容器里执行,不接触宿主机
result = sbx.run("python", ["-c", "print('hello from sandbox')"])
Sandboxes 的关键安全特性:
- 硬件虚拟化 MicroVM:不是 Linux namespace,是真正的内核级别隔离
- Auth Proxy:API key 永远不进入 sandbox,只通过认证代理访问外部服务
- 二进制授权:可以白名单允许的二进制文件列表
- 网络隧道:端口可以暴露到本地预览,不直接暴露到公网
但这是付费企业功能,不是开源框架自带的安全能力。
4.3 CrewAI:角色 + 任务边界的内建安全
CrewAI 的安全建立在最小权限原则上:Agent 只能使用自己 tools 列表里声明的工具,Task 还可以进一步收窄工具范围:
task = Task(
description="Search for data",
agent=researcher,
tools=[search_tool] # Task 层面的工具约束
)
# 如果 Agent 尝试调用不在 tools 列表里的工具
if tool_call.name not in self.allowed_tools:
raise ToolNotAllowedError(tool_call.name) # 抛出错误
CrewAI 的另一个安全机制是 Plan-then-Execute:Manager 先规划整个工作流(生成 Plan Tree),所有执行都必须在这个计划范围内进行,超出计划的工具调用会被拒绝:
# CrewAI 内置的计划机制
crew = Crew(
agents=[manager, researcher, writer],
process="hierarchical", # 层级:manager → workers
planning=True, # 开启计划模式
)
计划模式下,Manager 会先输出一个结构化的任务分解,只有在这个分解里的任务才会被分配和执行。这防止了 Agent"越界"执行未经授权的操作。
安全基准测试数据(来源:Emergent Mind 2025 研究):
| 框架 | 拒绝率 | 数据泄露防护 |
|---|---|---|
| CrewAI | 30.8% | 较强 |
| AutoGen (Mesh) | 16.4% | 较弱 |
CrewAI 的拒绝率是 AutoGen 的近两倍,主要归功于角色边界和计划约束。
但 CrewAI 的盲区:工具执行本身没有进程隔离,subprocess.run(["rm", "-rf", "/"]) 同样可以删掉文件系统。Docker 隔离需要自己集成,CrewAI 本身不提供沙箱。
五、多模型兼容性:Schema 战争的解法
5.1 OpenClaw:五层适配系统
OpenClaw 的多模型兼容性是我见过最细粒度的实现。不同 provider 对 tool calling 的要求差异极大,OpenClaw 在五个独立层上做了适配:
Layer 1:Schema 规范化
每个 provider 对 JSON Schema 的接受度不同:
- Gemini:拒绝
anyOf、oneOf,需要展平合并 - xAI:拒绝
minLength、maxLength等校验关键字 - OpenAI:要求 top-level 必须有
type: "object" - Anthropic:需要完整 JSON Schema draft 2020-12 合规
// pi-tools.schema.ts
function applyProviderCleaning(s: unknown): unknown {
if (isGeminiProvider) return cleanSchemaForGemini(s);
if (isXai) return stripXaiUnsupportedKeywords(s);
return s;
}
这段代码在工具注册时执行一次,之后无论切换到哪个 provider,调用方都使用同一套 schema。
Layer 2:Payload 格式切换
Anthropic 有两套并存的 tool calling 格式:
// native 模式:Claude 原生
{ tools: [{ name: "read", input_schema: {...} }] }
// OpenAI functions 兼容模式
{ tools: [{ type: "function", function: { name: "read", parameters: {...} } }] }
ProviderCapabilities 用一对开关控制:
type ProviderCapabilities = {
anthropicToolSchemaMode: "native" | "openai-functions";
anthropicToolChoiceMode: "native" | "openai-string-modes";
};
有个特殊 case:kimi-coding 原生支持 Anthropic tool framing,转成 OpenAI 格式反而会触发 XML fallback,所以单独配置保留原生格式。
Layer 3:Tool Call ID 规范化
不同 provider 的 tool_call_id 格式要求差异极大:
// OpenAI: _call_xxxx 格式
// Claude: 恰好 9 位数字字母
// Mistral: <function>:<index> 格式 (functions.read:0)
function normalizeToolCallIdsInMessage(message): void {
for (const block of content) {
if (isToolCallBlockType(block.type)) {
// 从 mangled id 里提取真实 tool name
// "functions.read:0" → "read"
// "functionswrite4" → "write"
const normalized = inferToolNameFromId(block.id);
}
}
}
Mistral 系列还需要 strict9 模式——transcript 里的 tool_call_id 必须恰好是 9 位,OpenClaw 在持久化前做截断/补齐。
Layer 4:流式修复包装器
模型在流式输出时会产生格式错误,OpenClaw 在 streaming pipeline 里加了三个包装器:
// 去 tool name 周围的空格
streamFn = wrapStreamFnTrimToolCallNames(streamFn, allowedToolNames);
// 修复 Claude 格式工具参数的 JSON 解析错误
streamFn = wrapStreamFnRepairMalformedToolCallArguments(streamFn);
// 解码 xAI 的双重 URL-encoded 参数
streamFn = wrapStreamFnDecodeXaiToolCallArguments(streamFn);
xAI 的双重编码尤其烦人——参数 JSON 被 URL-encoded 两层,不解码直接 JSON.parse 就会炸。
Layer 5:Provider Family 感知
"anthropic": { providerFamily: "anthropic", dropThinkingBlockModelHints: ["claude"] }
"amazon-bedrock": { providerFamily: "anthropic" } // Bedrock 上的 Claude 也算
openrouter: {
geminiThoughtSignatureSanitization: true,
geminiThoughtSignatureModelHints: ["gemini"]
}
把 provider 按 family 归组后,只需要加一条配置就能影响整组 provider 的行为。新增 provider 时在 PROVIDER_CAPABILITIES 表里加一条,不需要动核心逻辑。
5.2 LangChain:Provider 抽象 + Prompt-as-Interface
LangChain 的多模型策略是让每种 provider 的 adapter 自己处理细节,统一入口是 BaseChatModel:
class BaseChatModel(Runnable):
def bind_tools(self, tools: list[BaseTool], **kwargs):
# 不同的子类实现不同的 binding 逻辑
raise NotImplementedError
当你使用 ChatAnthropic 时,bind_tools 调用 anthropic.messages API 格式;当你使用 ChatOpenAI 时,调用 functions 格式。LangChain 在各 provider 的 integration 包里分别维护这些细节。
Prompt-as-Interface 策略:LangChain 的一个独特做法是把工具信息塞进 prompt 而不是 function calling API。对于某些不支持 native function calling 的模型:
# 工具描述转成 prompt 片段
TOOL_PROMPT_TEMPLATE = """
You have access to the following tools:
{tools_description}
To use a tool, respond with:
<tool_call>
<name>tool_name</name>
<args>
<arg1>value1</arg1>
</args>
</tool_call>
"""
把 JSON Schema 转成自然语言描述,让模型通过普通文本输出调用意图,再用正则解析。这种"软绑定"在 function calling API 不可用时提供了回退方案,但解析的可靠性远不如原生 API。
5.3 CrewAI:Prompt 驱动 + 委托作为原生能力
CrewAI 目前的多模型策略相对简单——主要通过 model 参数指定,支持 OpenAI、Anthropic、Google 等主流 provider:
agent = Agent(
model="gpt-4o", # 或 "claude-3-sonnet", "gemini-pro"
role="...",
tools=[...]
)
CrewAI 内部使用 LangChain 作为 LLM 抽象层,所以理论上继承了 LangChain 的多 provider 支持。但在 Tool Calling 兼容性 方面,CrewAI 更依赖 Prompt engineering 而不是 schema 规范化。
CrewAI 的 role、goal、backstory 字段实际上是在引导模型按照 CrewAI 期望的方式使用工具:
backstory = """
You are a senior software engineer. You are methodical and thorough.
You ALWAYS verify your findings with the official documentation before reporting.
When you don't know something, you explicitly say 'I need to search for this'.
"""
这个 prompt 策略在很多场景下有效,但不如 OpenClaw 的 schema 层适配来得精确和可靠。
六、多 Agent 编排:从单 Agent 到 Agent 舰队
6.1 OpenClaw:Subagent Registry + Session 隔离
OpenClaw 的多 Agent 能力通过 Subagent Registry 实现:
// subagent-registry.ts
export interface SubagentRunRecord {
runId: string;
parentSessionKey: string;
childSessionKey: string;
agentId: string;
model: string;
status: "running" | "completed" | "terminated";
createdAt: number;
startedAt?: number;
endedAt?: number;
}
Session 是 OpenClaw 的核心隔离单元:
- 每个对话(Session)有独立的 transcript(JSONL 文件)
- 每个 Session 可以派生一个 Subagent Session
- Subagent 通过 Gateway RPC 与父 Agent 通信
- 完成时触发双重通知:进程内事件 + Gateway RPC
关键设计:Subagent 不共享父 Agent 的工具执行上下文。如果父 Agent 有 exec 工具,Subagent 也有——但它们是两个完全独立的工具实例,审批记录互不影响。
6.2 LangChain:LangGraph 的有向图执行
LangChain 的多 Agent 能力通过 LangGraph 实现:
from langgraph.graph import StateGraph
# 定义 Agent 节点
def researcher_node(state):
return {"messages": [researcher_llm.invoke(state["messages"])]}
def synthesizer_node(state):
return {"messages": [synthesizer_llm.invoke(state["messages"])]}
# 定义边(条件路由)
def should_delegate(state) -> str:
if needs_research(state["messages"][-1]):
return "researcher"
return "END"
graph = StateGraph(State)
graph.add_node("researcher", researcher_node)
graph.add_node("synthesizer", synthesizer_node)
graph.add_conditional_edges("researcher", should_delegate)
graph.add_edge("synthesizer", END)
app = graph.compile()
LangGraph 的优势是完全可定制:你可以定义任意节点和条件边,实现并行、顺序、层级、循环等任何编排模式。缺点是需要自己实现大部分编排逻辑,没有"开箱即用"的多 Agent 协作模式。
6.3 CrewAI:开箱即用的层级 + 并行编排
CrewAI 的多 Agent 编排是三个框架里最成熟的,有两种开箱即用的模式:
Sequential Process(顺序执行):
crew = Crew(
agents=[researcher, writer, editor],
tasks=[research_task, write_task, edit_task],
process="sequential" # 顺序执行
)
crew.kickoff() # researcher → writer → editor
Hierarchical Process(层级编排):
crew = Crew(
agents=[manager, researcher, writer],
process="hierarchical", # manager 分配任务给 workers
)
层级模式下,Manager Agent 会自动接收所有任务,然后智能地分解和委派给 Worker Agent。Manager 的角色由 allow_delegation=True 控制。
Agent 间通信:CrewAI 独特的 Delegate Work Tool 让 Agent 可以动态委派:
# Agent 可以决定什么时候委派,委派给谁
"Based on my analysis, I need to delegate this to the data analyst agent."
委派的结果会作为 observation 返回给调用方 Agent,Agent 可以根据结果继续工作。这在 OpenClaw 里需要自己实现 Subagent 之间的通信,在 LangChain 里需要通过 LangGraph 显式建模。
七、设计哲学总结:三种 Agent 框架的根本分歧
| 维度 | OpenClaw | LangChain | CrewAI |
|---|---|---|---|
| 设计哲学 | 安全优先,默认不信任 | 灵活性优先,开发者主导 | 协作优先,降低多 Agent 门槛 |
| 语言 | TypeScript | Python | Python |
| 工具定义 | TypeScript Interface + JSON Schema | 装饰器 + Pydantic + bind_tools | Tool 类 + Agent 绑定 |
| 执行隔离 | Docker 容器 + 环境变量脱敏 + 审批流 | 无(商业方案 LangSmith Sandboxes) | 无(需自己集成 Docker) |
| 审批机制 | Unix Socket 实时推送 + allowlist | 无 | 无 |
| 多模型适配 | 五层 schema 规范化 + 流式修复 | Provider adapter + Prompt fallback | LangChain 抽象 + Prompt 引导 |
| 多 Agent | Subagent Registry + Session 隔离 | LangGraph 自由编排 | 内置层级/顺序编排 + Delegate Tool |
| 上线形态 | 桌面应用 / 服务器进程 | 库,集成到你的应用里 | 独立 Crew,可部署为服务 |
| 安全基准 | 军工级,默认最小权限 | 零默认,付费增强 | 角色边界 + Plan 约束,30.8% 拒绝率 |
八、实战选型建议
选 OpenClaw 如果你:
- 需要让非技术用户通过桌面应用使用 Agent
- 对工具执行有严格安全要求(shell 命令、文件系统访问)
- 需要多模型切换且不想每个模型单独调试 tool calling
- 需要 MCP 集成和 Canvas 可视化
- 接受 TypeScript 技术栈
选 LangChain 如果你:
- 快速原型验证,需要最大的灵活性和定制空间
- 已经在用 Python 技术栈,需要与 LangChain 生态(LangSmith、LangGraph)深度集成
- 需要自定义 Agent 循环、记忆、检索等复杂编排
- 愿意自己处理安全边界(Docker 集成、审批流)
- 需要对接 LangChain 生态里数百个现成的 tool integrations
选 CrewAI 如果你:
- 需要快速搭建多 Agent 协作流水线
- 需要"角色扮演 + 委派"这种开箱即用的协作模式
- 团队里有产品/运营人员而非全栈工程师
- 对 Plan-then-Execute 的可预测性有要求
- 需要内置的 Guardrails 和输出验证
三者结合的可能性:
最务实的做法是用 CrewAI 做多 Agent 编排层,用 OpenClaw 的安全思路给工具加沙箱和审批,在底层根据模型特性选择不同的 provider adapter。这不是互斥的选择,而是根据场景组合使用。
结语
三个框架的工具 calling 设计反映了一个根本分歧:Agent 框架应该有多安全,就应该有多难用。OpenClaw 选择了最难的那条路——用 TypeScript 写严格类型约束的接口、用 Docker 容器隔离执行、用 Unix Socket 做实时审批、把每个 provider 的 schema 差异精确到关键字级别。这套系统对于普通开发者来说门槛很高,但它解决的是真实存在的安全问题。
LangChain 选择了最灵活的路线——工具就是 Python 函数,Agent 就是可组合的 LCEL 表达式。这种设计让 LangChain 成为事实上的 Agent 框架标准,但安全永远是使用者自己的责任。
CrewAI 则选择了最务实的路线——不问安全问题,先让多 Agent 协作跑起来。安全通过角色边界和计划约束来保证,不需要 Docker,不需要审批流,但工具执行本身仍然是裸的 Python 调用。
没有完美的框架,只有适合的场景和取舍。选择工具 calling 框架时,首先要想清楚:你的 Agent 会执行什么操作?谁在使用它?在什么威胁模型下运行? 答案决定了你是需要 OpenClaw 的坦克级防护,LangChain 的乐高式灵活,还是 CrewAI 的开箱即用。