概述
在 AI Agent 开发中,SKILL、Function Calling、MCP、CLI是四个核心概念。它们各自解决不同层次的问题,共同构成了现代 AI Agent 的技术体系。
AI Agent笔记系列文章:
MCP+SKILL实践
Function Calling实践
CLI+SKILL实践
本文
ReAct Agent工程实践...(敬请期待)
1 Skill(技能)
是什么
Skill 是 AI Agent 的可复用能力单元,采用渐进式披露机制,让 Agent 在需要时才加载完整技能详情。
加载流程
Skill 的优势
- 节省上下文:不必把所有技能细节都塞进 System Prompt
- 按需加载:只加载当前需要的技能
- 易于管理:技能可独立开发、测试、分享
示例:天气技能
skill.md
# skill.md
name: weather查询
description: 查询城市天气
trigger: 当用户询问天气时使用
steps:
1. 解析城市名
2. 调用 weather_api 获取数据
3. 格式化回复
2、四者关系总结
Function Calling 解决了核心问题:让 LLM 能够稳定地输出结构化的工具调用请求,实现了"非结构化→结构化"的转换。这是 AI Agent 工具能力的基础。
但在实际应用中,开发者很快发现了一个新的问题:工具集成成本太高。
在 Function Calling 的框架下,每个既有系统都需要单独集成到应用中。每个组织或公司都有自己的 API、认证方式、数据格式,开发者需要为每个组织或公司编写对应的函数实现。这就是 MCP 产生的原因:提供一个服务,可以让既有系统快速集成到 LLM 中。
CLI命令行是一种文本型工具,开发者与AI编码助手可通过终端调用,与软件、系统、服务交互。MCP理念很好,但实际用起来不如CLI简洁高效。
MCP 的核心其实还是基于 Function Calling 的。它做的事情很简单:把 Function Calling 的调用,在客户端转换成一套 JSON+HTTP 的请求。然后提供一套 Server 来响应这个 JSON+HTTP 请求,这样就能实现各类应用都可以被 LLM 使用的效果。
但在 Function Calling 和 MCP 的框架下,用户面临一个两难的问题:当前的大模型很难仅仅依托自己的模型能力就做出最优的工具调用步骤。很多任务需要特定的执行顺序、规则和约束,但把这些步骤全部写成代码又不太现实。这就是 Skills 产生的原因:提供一个方式,让用户可以用文字定义指令、脚本和资源,形成可复用的任务流程。
Skills 的核心其实也是基于 Function Calling 的。它做的事情很巧妙:通过一个固化的函数和参数,让模型去查找和加载固定的 skills 文档。
通过前面的分析,我们可以看到 Function Calling、MCP 和 Skills 三者之间的本质关系:MCP 和 Skills 都是基于 Function Calling 的,它们只是在 Function Calling 这个基础能力之上的不同应用方式。
MCP 与 SKILL 的关系
(1)竞争关系:当用户选择使用 Skills 并且在 Skill 中编写脚本实现功能时,他们就不需要在 MCP Server 的函数中编写复杂的串接代码了;反之,如果选择在 MCP Server 中实现完整逻辑,Skills 的价值就会降低。它们解决的是同一个问题(如何整合多个既有系统),但采用了不同的方法:
- MCP:协议转换 Server + 代码型流程定义
- Skills:直接命令行调用 + 文字化流程定义
(2)互补关系:SKILL 作为 MCP 的使用说明书
MCP可以脱离Skill通过Function Calling + MCP Client被大模型调用。
3、OpenClaw 源码深度分析:从用户提问到工具执行
整体架构
基于 OpenClaw 源码分析,整个系统分为以下层次:
源码关键文件
| 文件 | 作用 |
|---|
| agent-runner.runtime-*.js | 主循环:runAgentTurnWithFallback() |
| pi-bundle-mcp-runtime-*.js | MCP 运行时:listTools() / callTool() |
| pi-bundle-mcp-tools-*.js | MCP 工具材料化 |
| bash-tools-*.js | CLI/Exec 工具:execSchema / processSchema |
| facade-loader-*.js | Skill 懒加载器 |
| runtime-*.js | 插件运行时:createRuntimeAgent() |
Step by Step 流程解析
Step 1: 消息入口
plaintext
飞书消息 → Gateway → agent-runner.runAgentTurnWithFallback()
源码位置:agent-runner.runtime-*.js:2944
Step 2: System Prompt 组装
OpenClaw 动态组装 System Prompt,包含:
| 文件 | 内容 |
|---|
| SOUL.md | Agent 性格、语气、决策风格 |
| IDENTITY.md | Agent 身份定义 |
| USER.md | 用户信息(职业、偏好) |
| AGENTS.md | 工作区配置、工具约定 |
| HEARTBEAT.md | 心跳检查清单 |
Step 3: Skill 渐进式披露
System Prompt 只包含已安装 skill 的名称和简短描述:
javascript
const agentTool = {{
name: safeToolName, // 工具名
label: tool.title, // 显示名
description: tool.description, // 简短描述(~100 token)
parameters: tool.inputSchema,
execute: async (_toolCallId, input) => {{
const result = await params.runtime.callTool(...);
return toAgentToolResult({{...}});
}}
}};
Step 4: LLM 决策(Function Calling)
javascript
response = await llm.chat.completions.create({{
messages: [...systemPrompt, ...conversation],
tools: toolDefinitions, // MCP tools + built-in tools
tool_choice: "auto"
}});
if (response.choices[0].message.tool_calls) {{
const toolCall = response.choices[0].message.tool_calls[0];
// {{ id: "call_xxx", name: "mcp_github__list_issues", arguments: {{...}} }}
Step 5: 工具分发(Tool Dispatch)
根据 tool_calls[].name 前缀判断类型:
mcp_xxx__yyy → MCP 工具exec → CLI/Exec 工具process → 进程管理工具read/write → 文件工具
5.1 MCP 工具执行
javascript
async callTool(serverName, toolName, input) {{
const session = getMcpSession(serverName);
return await session.client.callTool({{
name: toolName,
arguments: input
}});
}}
5.2 CLI/Exec 工具执行
javascript
execute: async (_toolCallId, args, signal, onUpdate) => {{
const child = spawn(preparedSpawn.command, preparedSpawn.args, {{
detached: process.platform !== "win32",
shell: false,
}});
// 处理输出流
}}
Step 6: 结果返回 LLM
javascript
second_response = await llm.chat.completions.create({{
messages: [
{{"role": "user", "content": "用户问题"}},
assistant_msg_with_tool_call,
{{"role": "tool", "tool_call_id": tool_call.id, "content": result}}
]
}});
MCP Server 启动方式
1. STDIO 模式(本地进程)
javascript
const child = spawn(command, args, {{
detached: process.platform !== "win32",
shell: false,
windowsHide: process.platform === "win32"
}});
2. HTTP/SSE 模式(远程服务)
javascript
const transport = new StreamableHTTPClientTransport({{ url }});
const client = new Client({{ name, version }}, {{ timeout: 30000 }});
await client.connect(transport);
Skill 的懒加载机制
javascript
function createLazyFacadeProxyValue(params) {{
const resolve = createLazyFacadeValueLoader(params.load);
return new Proxy(params.target, {{
get(_target, property, receiver) {{
return Reflect.get(resolve(), property, receiver);
}}
}});
}}
当 Agent 判断需要某个 skill 时:
- 调用"加载 skill"工具
- 读取对应
skill.md 文件 - 将完整内容加入上下文
- Agent 根据说明执行对应脚本
完整调用链示例
用户说:"帮我查一下 GitHub 上最新的 open issues"
plaintext
1. 飞书 → Gateway → agent-runner
↓
2. 组装 System Prompt(含 github skill 简短描述)
↓
3. LLM 判断需要 github 工具 → 返回 tool_calls
↓
4. bundle-mcp 调用 MCP Server
→ JSON-RPC: {{"method": "tools/call", ...}}
↓
5. MCP Server(gh CLI)执行
↓
6. 结果返回 Agent Runner
↓
7. LLM 生成最终回复
↓
8. Gateway → 飞书用户
关键设计思想
| 设计 | 目的 | 源码位置 |
|---|
| 渐进式披露 | 节省上下文,只加载需要的 skill | pi-bundle-mcp-tools |
| 单 Agent 循环 | 简化架构,避免多 Agent 协调复杂度 | agent-runner |
| 上下文压缩 | 防止上下文膨胀,自动压缩历史 | runtime-schema |
| 双调度机制 | 支持定时任务(Cron)+ 心跳任务 | heartbeat-wake |
| Tool Result 链式传递 | 并行/串行执行多个工具调用 | agent-runner:919 |
end