文章目录
-
- [第一部分:基础认知 ------ Agent 到底是什么?](#第一部分:基础认知 —— Agent 到底是什么?)
-
- [1.1 从 LLM 到 Agent](#1.1 从 LLM 到 Agent)
- [1.2 核心概念澄清:Agent、Skill、Tool、MCP](#1.2 核心概念澄清:Agent、Skill、Tool、MCP)
- [第二部分:深入 Function Calling 的底层机制](#第二部分:深入 Function Calling 的底层机制)
-
- [2.1 Function Calling 本质](#2.1 Function Calling 本质)
- [2.2 LangChain 如何统一不同模型的差异](#2.2 LangChain 如何统一不同模型的差异)
-
- [工具定义的统一:用 `@tool` 装饰器](#工具定义的统一:用
@tool装饰器) - [响应解析的统一:全部转为 `AIMessage`](#响应解析的统一:全部转为
AIMessage)
- [工具定义的统一:用 `@tool` 装饰器](#工具定义的统一:用
- [2.3 LangGraph 的角色:流程编排,不是解析器](#2.3 LangGraph 的角色:流程编排,不是解析器)
-
- [💡 白话理解](#💡 白话理解)
- [🧱 代码上怎么体现](#🧱 代码上怎么体现)
- [🚀 复杂任务才显出真价值](#🚀 复杂任务才显出真价值)
- [第三部分:传统 Function Calling vs. MCP 架构](#第三部分:传统 Function Calling vs. MCP 架构)
-
- [3.1 传统方式:工具写死在 Agent 里](#3.1 传统方式:工具写死在 Agent 里)
- [3.2 现代方式:用 MCP 实现工具的解耦与标准化](#3.2 现代方式:用 MCP 实现工具的解耦与标准化)
- [3.3 对比总结](#3.3 对比总结)
- [第四部分:Agent 的架构设计:从 MVP 到完整四层](#第四部分:Agent 的架构设计:从 MVP 到完整四层)
-
- [4.1 完整的四层架构](#4.1 完整的四层架构)
- [4.1.1 什么是 ReAct 循环?](#4.1.1 什么是 ReAct 循环?)
- [4.1.2 四层架构的技术栈](#4.1.2 四层架构的技术栈)
- [4.1.3 Skill 到底属于哪一层?](#4.1.3 Skill 到底属于哪一层?)
- [4.1.4 人格提示词的两种加载方式](#4.1.4 人格提示词的两种加载方式)
- [4.2 从 MVP 开始:两层架构](#4.2 从 MVP 开始:两层架构)
-
- [MVP 是什么?](#MVP 是什么?)
- 最简代码示例(两层架构)
- 第五部分:Skill(技能)的加载与决策
-
- [5.1 Skill 的加载:渐进式披露](#5.1 Skill 的加载:渐进式披露)
- [5.2 Skill 的决策:LLM 如何判断?](#5.2 Skill 的决策:LLM 如何判断?)
- [第六部分:Skill 与 MCP 的协同工作流(完整示例)](#第六部分:Skill 与 MCP 的协同工作流(完整示例))
-
- [6.1 场景设定](#6.1 场景设定)
- [6.2 准备工作:定义工具和 Skill](#6.2 准备工作:定义工具和 Skill)
- [6.3 核心:用 LangGraph 组装 Agent](#6.3 核心:用 LangGraph 组装 Agent)
- [6.4 完整的执行流程](#6.4 完整的执行流程)
- [6.5 关键要点](#6.5 关键要点)
- [第七部分:Agent 的自进化 ------ 实用简化方案](#第七部分:Agent 的自进化 —— 实用简化方案)
第一部分:基础认知 ------ Agent 到底是什么?
1.1 从 LLM 到 Agent
单纯的 LLM 就像一个博学的书呆子,只能一问一答,没有状态,不能执行动作。
Agent = LLM + 工具 + 记忆 + 规划能力,它能自主拆解任务、调用工具、记住上下文,并完成真实世界的操作。
1.2 核心概念澄清:Agent、Skill、Tool、MCP
为了避免混淆,我们只使用以下四个明确定义的术语:
- Agent (智能体):负责规划、决策、调度的"大脑"。
- Skill (技能) :特指知识型的
.md文件。它是教 Agent "怎么做"的操作手册(流程、规范、最佳实践),LLM需要阅读并理解它。 - Tool (工具) :特指可执行的函数/方法。它是 Agent 完成具体动作的"手脚"(如查天气、发邮件、操作数据库),可以通过本地函数或 MCP Server 暴露。
- MCP (模型上下文协议) :连接 Agent 和外部工具的标准化协议。它为 Agent 提供了"即插即用"调用各种工具的能力。同时,它也可以作为"传输管道",被用来读取 Skill 文件。
协同公式 :用户任务 → Agent 决策 → (需要流程指导时)通过 skill_load 工具加载 Skill 手册 → (需要执行动作时)通过 MCP 调用 Tool → 执行并反馈。
第二部分:深入 Function Calling 的底层机制
2.1 Function Calling 本质
它不是 LLM 暴露给外部的"接口",而是 LLM 被训练出的结构化响应能力 。
当你把 tools 参数(包含工具名称、描述和参数的 JSON Schema)传给 API 后,LLM 会返回一个特殊的 JSON 对象,例如:
json
{
"tool_name": "get_weather",
"arguments": {"city": "北京"}
}
你的代码拿到这个 JSON,去执行真正的函数,再把结果返回给 LLM 生成最终回复。
代码流程(以 OpenAI GPT-4o 为例):
- 定义工具,传给 API:
python
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
response = openai.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "东京天气怎么样?"}],
tools=tools
)
- LLM 返回调用指令(不执行函数):
python
msg = response.choices[0].message
# msg.tool_calls 中包含 function.name 和 arguments(JSON 字符串)
- 你的代码执行真正的函数:
python
import json
args = json.loads(msg.tool_calls[0].function.arguments)
result = get_weather(args["city"]) # 你写的真实函数
- 将结果返回给 LLM 生成最终回复:
python
final = openai.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "东京天气怎么样?"},
msg,
{"role": "tool", "tool_call_id": msg.tool_calls[0].id, "content": result}
]
)
本质:LLM 只输出 JSON 指令,真正执行全靠你的代码。LangChain 等框架将这个循环自动化了。
2.2 LangChain 如何统一不同模型的差异
LangChain 通过 模型适配器(Model Integration) 将不同厂商的差异封装起来,开发者只需用一套标准格式定义工具,剩下的转换全自动完成。
工具定义的统一:用 @tool 装饰器
你只需写一个 Python 函数并加上装饰器,LangChain 会自动为不同模型生成对应的 Schema:
python
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""获取指定城市的天气信息"""
return f"{city}:晴,25°C"
当你选择不同模型时,LangChain 内部会将其转换为:
| 模型 | 转换后的格式 |
|---|---|
| OpenAI | {"type": "function", "function": {"name": "get_weather", "description": "...", "parameters": {...}}} |
| Anthropic | {"name": "get_weather", "description": "...", "input_schema": {...}} |
| Google Gemini | {"name": "get_weather", "description": "...", "parameters": {...}} (放入 tools.functionDeclarations) |
响应解析的统一:全部转为 AIMessage
无论哪个模型返回的原始响应,LangChain 都会将其解析为统一的 AIMessage 对象:
python
from langchain_core.messages import AIMessage
# 所有模型的响应最终都变成这种标准格式
ai_msg = AIMessage(
content="", # 如果调用了工具,content 可为空
tool_calls=[{
"name": "get_weather",
"args": {"city": "北京"},
"id": "call_xxx"
}]
)
开发者只要检查 ai_msg.tool_calls 即可,无需关心底层是 OpenAI 的 message.tool_calls 还是 Anthropic 的 content[0].tool_use。
一句话总结 :你用一套
@tool定义,LangChain 负责"翻译"成各模型的"方言",并把各模型的"方言回复"统一解析成标准的AIMessage。
2.3 LangGraph 的角色:流程编排,不是解析器
💡 白话理解
把 Agent 想象成一场舞台剧:
- 演员 = LLM(负责思考和说话)
- 道具组 = LangChain(负责递工具、翻译响应)
- 导演 = LangGraph(指挥流程:谁上场、什么时候切换)
LangGraph 是导演 ,它不管台词怎么念(那是 LangChain 的事),只管"现在轮到谁上场"和"下一步去哪"。
invoke 就是导演喊的那声"开机!",整场戏自动按排练跑完。
🧱 代码上怎么体现
简单任务(一问一调一回) :你原本需要手动写 while 循环来调度 LLM 和工具:
python
response = llm.invoke(user_input)
while response.has_tool_calls:
result = execute_tool(...)
response = llm.invoke(result)
用 LangGraph 后,你把"角色"和"切换规则"定义成图,然后一声 invoke,它自动循环:
python
from langgraph.graph import StateGraph, END
workflow = StateGraph(dict)
workflow.add_node("agent", call_llm)
workflow.add_node("tools", call_tool)
workflow.add_conditional_edges("agent", should_continue, {"tools": "tools", "end": END})
workflow.add_edge("tools", "agent")
app = workflow.compile()
result = app.invoke({"input": "查天气"})
你不再需要自己写 while,LangGraph 在后台自动管理循环和跳转。
🚀 复杂任务才显出真价值
当任务涉及多步判断、人工确认时,手动 while + if-else 会变得混乱且难以维护。
场景:"查北京天气,如果下雨就给老板发邮件提醒(发前需要用户确认)"
手动实现需要状态变量和一堆分支:
python
step = "call_weather"
while True:
if step == "call_weather": ...
elif step == "check_rain": ...
elif step == "send_email": ...
每增加一个步骤,就要多一个 elif,逻辑纠缠。
LangGraph 实现:把每个步骤定义成独立节点,边定义路由,流程如蓝图般清晰:
python
workflow.add_node("weather", call_weather)
workflow.add_node("after_weather", after_weather)
workflow.add_node("wait_human", wait_for_human)
workflow.add_node("send_email", send_email)
workflow.add_conditional_edges("after_weather", lambda s: "wait_human" if "雨" in s else END)
workflow.add_conditional_edges("wait_human", lambda s: "send_email" if user_says_yes else END)
workflow.add_edge("send_email", END)
修改流程只需增删节点或改连线,不影响现有节点逻辑。
核心差别 :手动
while让流程和业务逻辑耦合,LangGraph 把流程变成可独立维护的"蓝图",复杂任务下优势巨大。
第三部分:传统 Function Calling vs. MCP 架构
3.1 传统方式:工具写死在 Agent 里
- 工具定义:在代码中硬编码 JSON Schema。
- 调用逻辑 :手动解析 LLM 的
tool_calls并if-else判断执行哪个函数。 - 缺点:工具和 Agent 强耦合,新增工具要改代码、重启服务,无法复用。
代码示例(OpenAI 原生调用,工具与 Agent 紧耦合):
python
import openai, json
# 1. 硬编码工具定义
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
# 2. Agent 调用
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=[{"role": "user", "content": "北京天气?"}],
tools=tools
)
# 3. 手动解析与执行
msg = response.choices[0].message
if msg.tool_calls:
tool_call = msg.tool_calls[0]
args = json.loads(tool_call.function.arguments)
if tool_call.function.name == "get_weather":
result = get_weather(args["city"]) # 本地函数
# ... 更多工具需要更多 if-else
⚠️ 想加一个"发邮件"工具?你得修改
tools列表,增加elif分支,然后重启服务。
3.2 现代方式:用 MCP 实现工具的解耦与标准化
- 工具定义 :在独立的 MCP Server 中实现,通过
list_tools()动态获取。 - Agent:作为 MCP Client 连接 Server,自动加载工具清单,统一用 function calling 格式发给 LLM。
- 优点:工具热插拔、多 Agent 复用、统一安全治理。
代码示例(MCP Server + Agent 分离):
① MCP Server(tool_server.py)------ 工具定义与 Agent 解耦:
python
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP
app = FastAPI()
@app.get("/weather/{city}", operation_id="get_weather")
async def get_weather(city: str):
return {"city": city, "weather": "晴", "temperature": "25°C"}
mcp = FastApiMCP(app, name="工具箱", base_url="http://localhost:8000")
mcp.mount() # 所有端点自动暴露为 MCP 工具
② Agent 侧(agent.py)------ 动态发现与调用:
python
from langchain_mcp_adapters.client import MCPClient
from langchain.agents import create_openai_tools_agent
async def main():
client = MCPClient(server_url="http://localhost:8000/mcp")
tools = await client.load_tools() # 动态获取,无需硬编码
agent = create_openai_tools_agent("gpt-4o", tools, system_prompt="你是助手")
# ... 运行 agent
✅ 新增工具?只需在 MCP Server 上加一个端点,Agent 零改动即可自动发现。
3.3 对比总结
| 对比维度 | 传统 Function Calling | MCP 方式 |
|---|---|---|
| 工具定义位置 | 嵌在 Agent 代码中 | 独立 MCP Server |
| 新增工具 | 改代码 + 重启 | 动态发现,无需重启 Agent |
| 复用性 | 低,紧耦合 | 高,多 Agent 共享 |
| 安全治理 | 分散实现 | MCP Server 层统一控制 |
第四部分:Agent 的架构设计:从 MVP 到完整四层
4.1 完整的四层架构
1. 大脑层 (LLM + 人格)
2. 记忆与知识层 (短期/长期记忆、RAG, Skill)
3. 规划与调度层 (ReAct 循环、任务编排)
4. 工具/身体层 (MCP 工具、本地函数)
这是从真实产品中抽象出的通用模型,得到了社区和工业界的普遍认可。几乎所有 Agent 都可以映射到这四层,实际落地时会根据场景"剪裁、合并或强化"。
4.1.1 什么是 ReAct 循环?
ReAct = Reasoning + Acting(推理 + 行动),是目前 Agent 最主流的推理模式。
其核心是让 LLM 在解决问题时,严格按照 "思考 → 行动 → 观察 → 再思考..." 的循环进行,直到任务完成。
- Thought (思考):当前目标是什么?需要什么信息?应该用哪个工具?
- Action (行动):调用具体工具(如查天气、计算器)。
- Observation (观察):工具返回了什么结果?
- Final Answer (最终答案):根据观察结果,生成给用户的回复。
当你用 LangChain 或 LangGraph 创建 Agent 并开启 verbose=True,后台输出的正是这个循环过程。
4.1.2 四层架构的技术栈
| 架构层 | 常见技术栈 | 说明 |
|---|---|---|
| 1. 大脑层 | LLM API : OpenAI, Anthropic, Gemini, 本地模型 (vLLM, Ollama) 人格加载 : 硬编码 system_prompt(MVP)或 persona.md 动态加载(正式) |
负责"思考"。人格也可当作特殊 Skill。 |
| 2. 记忆与知识层 | 短期记忆 : 对话历史列表 长期记忆 : 向量数据库 (Chroma, Pinecone, Milvus) + 嵌入模型 知识库(RAG) : LangChain/LlamaIndex 技能(Skill): Markdown 文件 + 渐进式披露 | 为大脑提供上下文。Skill (.md) 属于这一层。 |
| 3. 规划与调度层 | Agent 框架 : LangGraph (图编排), CrewAI (多Agent协作), AutoGen (对话驱动) 推理模式: ReAct, Plan-and-Execute | 负责流程骨架。 |
| 4. 工具/身体层 | 工具定义 : MCP Server (推荐), 本地 @tool 函数 工具调用 : Function Calling 集成 : langchain-mcp-adapters |
负责执行具体动作。 |
4.1.3 Skill 到底属于哪一层?
Skill (.md 文件) 属于第二层:记忆与知识层 。它为 LLM 提供"流程性知识",与 RAG 文档的"陈述性知识"互补。而用来加载 Skill 的 skill_load 工具本身属于第四层。
4.1.4 人格提示词的两种加载方式
| 方式 | 做法 | 适用场景 |
|---|---|---|
| 硬加载 | 直接在代码里写 system_prompt = "你是一个..." |
MVP 阶段、快速验证 |
| 动态加载 | 把人格写成一个 persona.md 文件,像 Skill 一样在启动时通过 load_skill("persona") 加载 |
正式项目、需要灵活切换人格 |
推荐做法:把人格当作一个特殊的 Skill,统一走渐进式披露通道。
- 架构统一:人格和普通 Skill 用同一套机制管理。
- 灵活修改 :改人格只需编辑
.md文件,不用动代码。 - 可进化 :甚至可以让 Agent 自己更新人格(如
skill_update("persona", ...))。
在 MVP 示例(4.2 节)中我们用了硬加载,那是为了最快跑通。正式搭建时建议改用动态加载。
4.2 从 MVP 开始:两层架构
MVP 是什么?
MVP = Minimum Viable Product(最小可行产品)。用最少的代码、最简单的架构,先把核心功能跑通,验证想法可行后再逐步加东西。
最简代码示例(两层架构)
一个能查天气的 Agent,只用 Python + OpenAI API,不依赖任何框架:
python
import openai, json
# ========== 第一层:工具(本地函数) ==========
def get_weather(city: str) -> str:
"""查询天气的工具函数"""
weather_data = {"北京": "晴,25°C", "东京": "多云,18°C"}
return weather_data.get(city, "未找到该城市天气")
# 手动定义工具 schema(LLM 看的"菜单")
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string", "description": "城市名称"}},
"required": ["city"]
}
}
}]
# ========== 第二层:大脑 + 规划 ==========
def agent(user_input: str) -> str:
# 1. 加载人格 + 发起第一次 LLM 调用
messages = [
{"role": "system", "content": "你是一个乐于助人的助手。"},
{"role": "user", "content": user_input}
]
response = openai.chat.completions.create(
model="gpt-4o", messages=messages, tools=tools
)
msg = response.choices[0].message
# 2. ReAct 循环:检查 LLM 是否想调用工具
while msg.tool_calls:
for tool_call in msg.tool_calls:
args = json.loads(tool_call.function.arguments)
result = get_weather(**args)
messages.append(msg)
messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": result})
response = openai.chat.completions.create(model="gpt-4o", messages=messages, tools=tools)
msg = response.choices[0].message
return msg.content
print(agent("北京今天天气怎么样?"))
# 输出:北京今天天气晴朗,气温25°C。
这个不到 40 行的代码包含了 Agent 的所有核心要素:
| 要素 | 对应代码 |
|---|---|
| 大脑 (LLM) | openai.chat.completions.create() |
| 人格 | system: "你是一个乐于助人的助手。" |
| 工具 | get_weather() 函数 + tools schema |
| 规划 (ReAct 循环) | while msg.tool_calls: 循环 |
| 记忆(短期) | messages 列表累积对话历史 |
何时升级?
- 工具数量 > 5 且需复用 → 引入 MCP 工具层。
- 多步推理、条件分支 → 引入 LangGraph 规划层。
- 跨会话记忆 → 引入 记忆层。
第五部分:Skill(技能)的加载与决策
5.1 Skill 的加载:渐进式披露
Skill 是知识型文件(.md),内容可能很长。为解决上下文窗口问题,必须采用 渐进式披露,按需分层加载:
| 层级 | 内容 | 何时加载 | 大小 |
|---|---|---|---|
| L1 元数据 | 技能名称 + 一句话描述 | 启动时加载 | ~100 tokens/个 |
| L2 核心指令 | 完整的 SKILL.md 正文 |
LLM 决定使用后按需加载 | <5000 tokens/个 |
| L3 资源 | 引用的脚本、文档等 | 按需进一步加载 | 不定 |
渐进式披露的核心设计思想
不是提前把所有技能内容塞给 LLM,而是把"查询技能目录"和"加载指定技能"这两个能力本身,作为工具(Tool)暴露给 LLM。 LLM 就像拿着一张菜单(list_skills 返回的技能名称+简介),想吃哪道菜再叫服务员去后厨拿完整的菜谱(load_skill 返回 .md 全文)。
启动时:LLM 只知道有 list_skills 工具,不知道任何技能内容(0 token)
用户提问后:LLM 主动调用 list_skills → 拿到目录(L1,~100 tokens/个)
决定使用后:LLM 调用 load_skill → 拿到完整手册(L2,只加载一个)
整个过程完全由 LLM 自主决策,加载技能本身也是 function calling 的一种。这就是渐进式披露能与 MCP 工具无缝融合的根本原因。
5.2 Skill 的决策:LLM 如何判断?
- 语义匹配(主要方式):LLM 根据用户意图和 Skill 元数据描述的匹配度自主决策。
- 关键词/意图路由(辅助方式):通过简单规则快速匹配。
- 向量检索(大规模场景):当 Skills 极多时,先用向量搜索筛选相关子集,再交给 LLM。
第六部分:Skill 与 MCP 的协同工作流(完整示例)
这个部分用一个完整的"发邮件"场景,展示知识(Skill)和行动(Tool)如何配合,以及代码层面如何实现。
6.1 场景设定
- 一个 MCP Server,暴露了业务工具:
validate_email、compose_email、send_email - 几个 Skill 文件,存放在
./skills/目录下 - 一个 LangGraph Agent,负责调度一切
6.2 准备工作:定义工具和 Skill
① MCP Server 上的业务工具(tool_server.py):
python
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP
app = FastAPI()
@app.post("/validate_email", operation_id="validate_email")
async def validate_email(address: str) -> dict:
"""校验收件人邮箱格式是否正确"""
if "@" in address and "." in address:
return {"valid": True, "address": address}
return {"valid": False, "reason": "邮箱格式不正确"}
@app.post("/compose_email", operation_id="compose_email")
async def compose_email(to: str, subject: str, body: str) -> dict:
"""生成邮件草稿并返回预览"""
draft = f"收件人: {to}\n主题: {subject}\n正文: {body}"
return {"draft": draft, "status": "ready_for_review"}
@app.post("/send_email", operation_id="send_email")
async def send_email(to: str, subject: str, body: str) -> dict:
"""实际发送邮件"""
return {"status": "sent", "message": f"邮件已成功发送至 {to}"}
mcp = FastApiMCP(app, name="邮件工具箱", base_url="http://localhost:8000")
mcp.mount()
② Skill 文件(./skills/send_email/SKILL.md):
markdown
# 发送邮件标准流程
1. 使用 `validate_email` 校验收件人地址格式。
2. 如果格式错误,提示用户修正后重新开始。
3. 使用 `compose_email` 生成邮件草稿。
4. 将草稿展示给用户确认(等待用户回复"确认"或"取消")。
5. 用户确认后,使用 `send_email` 发送邮件。
6. 发送成功后,向用户报告结果。
③ Skill 管理工具(本地 @tool 函数):
python
import os
from langchain_core.tools import tool
SKILLS_DIR = "./skills"
@tool
def list_skills() -> str:
"""列出所有可用技能的元数据(名称 + 简短描述)"""
skills = []
for skill_name in os.listdir(SKILLS_DIR):
path = os.path.join(SKILLS_DIR, skill_name, "SKILL.md")
if os.path.isfile(path):
with open(path, "r", encoding="utf-8") as f:
first_line = f.readline().strip("# ").strip()
skills.append(f"- {skill_name}: {first_line}")
return "\n".join(skills) if skills else "暂无可用技能。"
@tool
def load_skill(skill_name: str) -> str:
"""加载指定技能的完整 SKILL.md 内容"""
path = os.path.join(SKILLS_DIR, skill_name, "SKILL.md")
if not os.path.isfile(path):
return f"技能 '{skill_name}' 不存在。"
with open(path, "r", encoding="utf-8") as f:
return f.read()
6.3 核心:用 LangGraph 组装 Agent
将上述所有工具(MCP 业务工具 + Skill 管理工具)统一注入 Agent:
python
import asyncio
from langgraph.prebuilt import create_react_agent
from langchain_mcp_adapters.client import MCPClient
from langchain_openai import ChatOpenAI
async def main():
# ====== 第一步:加载所有工具 ======
# 1. 从 MCP Server 动态加载业务工具
mcp_client = MCPClient(server_url="http://localhost:8000/mcp")
mcp_tools = await mcp_client.load_tools()
# 2. 合并本地 Skill 管理工具
all_tools = mcp_tools + [list_skills, load_skill]
# ====== 第二步:创建 Agent(注入人格提示词) ======
system_prompt = """
你是一个专业的邮件助手。你有以下能力:
1. 调用 `list_skills` 查看可用的操作手册。
2. 当用户的任务需要标准流程指导时,先调用 `load_skill` 获取详细步骤,再按步骤执行。
3. 严格按照加载的 Skill 文件中的流程操作,不要跳过任何步骤。
4. 在发送邮件前,必须先展示草稿并等待用户确认。
"""
llm = ChatOpenAI(model="gpt-4o", temperature=0)
agent = create_react_agent(llm, all_tools, system_prompt=system_prompt)
# ====== 第三步:运行 ======
result = await agent.ainvoke({
"messages": [
{"role": "user", "content": "帮我给 john@example.com 发一封邮件,主题'会议纪要',正文'明天下午三点开会'。"}
]
})
print(result["messages"][-1].content)
if __name__ == "__main__":
asyncio.run(main())
6.4 完整的执行流程
| 步骤 | Agent 动作 | 工具来源 | 说明 |
|---|---|---|---|
| 1 | 收到用户任务 | --- | LLM 判断需要发邮件,但不确定完整流程 |
| 2 | 调用 list_skills() |
本地 @tool |
看到 send_email: 发送邮件标准流程(L1 元数据) |
| 3 | 调用 load_skill("send_email") |
本地 @tool |
获取完整 SKILL.md 内容(L2 渐进式加载) |
| 4 | 阅读手册,按步骤1执行 | --- | 需要先校验邮箱 |
| 5 | 调用 validate_email(...) |
MCP 工具 | 返回 {"valid": true} |
| 6 | 按步骤3执行 | --- | 生成草稿 |
| 7 | 调用 compose_email(...) |
MCP 工具 | 返回草稿预览 |
| 8 | 展示草稿,等待用户确认 | --- | 用户回复"确认" |
| 9 | 按步骤5执行 | --- | 发送邮件 |
| 10 | 调用 send_email(...) |
MCP 工具 | 返回发送成功 |
| 11 | 生成最终回复 | --- | "邮件已成功发送至 john@example.com!" |
6.5 关键要点
- 工具列表注入 LLM :启动时通过
MCPClient.load_tools()动态获取 MCP 工具,与本地@tool函数合并,统一传给create_react_agent()。 - 渐进式披露 :
list_skills只返回元数据(L1),LLM 决定使用后才调用load_skill获取完整内容(L2)。 - Skill 与 MCP 的分工 :
- Skill (
.md文件):知识层,告诉 LLM "怎么做"。 - Skill 管理工具 (
list_skills/load_skill):工具层,负责"搬运知识"。 - MCP 业务工具 (
validate_email等):工具层,负责"执行动作"。
- Skill (
- LLM 的角色:阅读 Skill 手册后,自主规划并按顺序调用 MCP 工具完成任务。
第七部分:Agent 的自进化 ------ 实用简化方案
7.1 进化思路
完整自进化系统(如 Hermes)非常复杂,包含多条件触发、多层记忆、自动修复等机制。但对于一般项目,我们只需要一个最小可行自进化:通过简单的规则让 Agent 自主创建和更新 Skill,实现经验沉淀。
7.2 简化版设计
核心规则
| 触发条件 | 动作 | 产出 |
|---|---|---|
| Agent 调用了 3个以上不同工具 完成任务 | 调用 skill_create 生成新技能 |
skills/xxx.md |
| 用户纠正 Agent 的做法(说"不对/错了/应该这样") | 调用 skill_update 修改相关技能 |
更新对应 SKILL.md |
| 每次任务开始前 | Agent 可调用 list_skills 检查有无新技能 |
无额外产出 |
所需工具
skill_create:创建新技能文件。skill_update:更新已有技能文件。list_skills/load_skill:渐进式加载技能知识。
7.3 完整端到端实现
这里给出一个融合了业务工具、技能管理和自进化的完整可运行示例。Agent 执行退款任务时会自动沉淀技能。
① MCP Server (tool_server.py)
python
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP
app = FastAPI()
@app.post("/validate_email", operation_id="validate_email")
async def validate_email(address: str) -> dict:
if "@" in address and "." in address:
return {"valid": True, "address": address}
return {"valid": False, "reason": "邮箱格式不正确"}
@app.post("/compose_email", operation_id="compose_email")
async def compose_email(to: str, subject: str, body: str) -> dict:
draft = f"收件人: {to}\n主题: {subject}\n正文: {body}"
return {"draft": draft, "status": "ready_for_review"}
@app.post("/send_email", operation_id="send_email")
async def send_email(to: str, subject: str, body: str) -> dict:
return {"status": "sent", "message": f"邮件已发送至 {to}"}
@app.get("/query_order", operation_id="query_order")
async def query_order(order_id: str) -> dict:
return {"order_id": order_id, "status": "已完成", "refundable": True}
@app.post("/refund_create", operation_id="refund_create")
async def refund_create(order_id: str) -> dict:
return {"refund_id": "R123", "status": "退款已发起"}
mcp = FastApiMCP(app, name="业务工具箱", base_url="http://localhost:8000")
mcp.mount()
② 本地 Skill 管理工具 (skill_tools.py)
python
import os
from langchain_core.tools import tool
SKILLS_DIR = "./skills"
os.makedirs(SKILLS_DIR, exist_ok=True)
@tool
def list_skills() -> str:
skills = []
for skill_name in os.listdir(SKILLS_DIR):
path = os.path.join(SKILLS_DIR, skill_name, "SKILL.md")
if os.path.isfile(path):
with open(path, "r", encoding="utf-8") as f:
first_line = f.readline().strip("# ").strip()
skills.append(f"- {skill_name}: {first_line}")
return "\n".join(skills) if skills else "暂无可用技能。"
@tool
def load_skill(skill_name: str) -> str:
path = os.path.join(SKILLS_DIR, skill_name, "SKILL.md")
if not os.path.isfile(path):
return f"技能 '{skill_name}' 不存在。"
with open(path, "r", encoding="utf-8") as f:
return f.read()
@tool
def skill_create(name: str, description: str, content: str) -> str:
path = os.path.join(SKILLS_DIR, name, "SKILL.md")
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write(f"# {description}\n\n{content}")
return f"技能 '{name}' 已创建。"
@tool
def skill_update(skill_name: str, new_content: str) -> str:
path = os.path.join(SKILLS_DIR, skill_name, "SKILL.md")
if not os.path.isfile(path):
return f"技能 '{skill_name}' 不存在,请先用 skill_create 创建。"
with open(path, "w", encoding="utf-8") as f:
f.write(new_content)
return f"技能 '{skill_name}' 已更新。"
③ 带自进化的 Agent 主程序 (agent.py)
python
import asyncio
from langgraph.prebuilt import create_react_agent
from langchain_mcp_adapters.client import MCPClient
from langchain_openai import ChatOpenAI
from skill_tools import list_skills, load_skill, skill_create, skill_update
async def main():
# 加载工具
mcp_client = MCPClient(server_url="http://localhost:8000/mcp")
mcp_tools = await mcp_client.load_tools()
all_tools = mcp_tools + [list_skills, load_skill, skill_create, skill_update]
# 系统提示词(含自进化指令)
system_prompt = """
你是一个智能助手,拥有自我进化能力。
## 核心行为准则
1. 当需要执行标准化流程时,先调用 `list_skills` 查看是否有现成技能。
2. 若有相关技能,调用 `load_skill` 获取详细步骤并严格遵循。
3. 若无现成技能,使用通用推理完成任务。
## 自我进化规则(重要!)
1. **创建新技能**:完成一个任务后,若满足以下任一条件,请调用 `skill_create` 沉淀经验:
- 任务中调用了 **3个以上不同的工具**
- 这是一个你之前没做过的新流程
- 用户要求记住这个做法
2. **更新现有技能**:若用户明确指出你的做法有误或不够好,请调用 `skill_update` 修正对应技能。
3. 技能名称用英文小写+下划线,内容简洁可操作,Markdown 格式。
"""
llm = ChatOpenAI(model="gpt-4o", temperature=0)
agent = create_react_agent(llm, all_tools, system_prompt=system_prompt)
# 运行:处理退款(新任务,将触发自进化)
result = await agent.ainvoke({
"messages": [
{"role": "user", "content": "请帮我把订单 #1234 退款,并邮件通知客户 john@example.com。"}
]
})
print("Agent 回复:", result["messages"][-1].content)
if __name__ == "__main__":
asyncio.run(main())
7.4 执行流程与进化演示
| 步骤 | Agent 动作 | 工具来源 | 说明 |
|---|---|---|---|
| 1 | 收到用户退款请求 | --- | 任务复杂,涉及查单、退款、发邮件 |
| 2 | 调用 list_skills() |
本地 | 检查是否有退款相关技能(返回空) |
| 3 | 发现无现成技能,开始通用推理 | --- | 自行决定流程 |
| 4 | 调用 query_order("1234") |
MCP | 确认订单可退款 |
| 5 | 调用 refund_create("1234") |
MCP | 发起退款 |
| 6 | 调用 validate_email("john@example.com") |
MCP | 校验邮箱 |
| 7 | 调用 send_email(...) |
MCP | 发送通知邮件 |
| 8 | 触发进化(调用了4个工具≥3) | --- | Agent 决定沉淀技能 |
| 9 | 调用 skill_create("handle_refund", "退款处理标准流程", "1. 用 query_order 查订单\n2. 若可退款,用 refund_create 发起退款\n3. 用 validate_email 校验邮箱\n4. 用 send_email 通知客户") |
本地 | 技能文件写入 ./skills/handle_refund/SKILL.md |
| 10 | 生成最终回复 | --- | "退款已处理完毕,邮件已通知客户。" |
第二次使用(进化已生效):
用户: 帮我把订单 #5678 也退款,通知 alice@example.com。
Agent: 调用 list_skills → 发现 handle_refund 技能 → 调用 load_skill → 按步骤自动执行所有操作。
7.5 与完整方案的对比
| 完整 Hermes 方案 | 我们的简化版 |
|---|---|
| 五层记忆体系 | 只用 .md 文件 + list_skills 发现 |
| 多条件触发评估 | 两个简单规则:工具数≥3 或用户纠正 |
| 自动精准修复 | skill_update 全量替换,LLM 自主决定修改内容 |
| 后台持续自循环 | 每次任务后即时判断,不跑后台服务 |
这个方案零额外框架、零额外服务 ,只靠 Prompt 规则和已有的 skill_create/skill_update 就能跑起来,适合日常项目使用。