AI Agent 构建实战笔记(20260524)

文章目录

    • [第一部分:基础认知 ------ 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)
      • [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 开始:两层架构)
    • 第五部分: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 的自进化 —— 实用简化方案)
      • [7.1 进化思路](#7.1 进化思路)
      • [7.2 简化版设计](#7.2 简化版设计)
      • [7.3 完整端到端实现](#7.3 完整端到端实现)
      • [7.4 执行流程与进化演示](#7.4 执行流程与进化演示)
      • [7.5 与完整方案的对比](#7.5 与完整方案的对比)

第一部分:基础认知 ------ 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 为例)

  1. 定义工具,传给 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
)
  1. LLM 返回调用指令(不执行函数)
python 复制代码
msg = response.choices[0].message
# msg.tool_calls 中包含 function.name 和 arguments(JSON 字符串)
  1. 你的代码执行真正的函数
python 复制代码
import json
args = json.loads(msg.tool_calls[0].function.arguments)
result = get_weather(args["city"])  # 你写的真实函数
  1. 将结果返回给 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_callsif-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 如何判断?

  1. 语义匹配(主要方式):LLM 根据用户意图和 Skill 元数据描述的匹配度自主决策。
  2. 关键词/意图路由(辅助方式):通过简单规则快速匹配。
  3. 向量检索(大规模场景):当 Skills 极多时,先用向量搜索筛选相关子集,再交给 LLM。

第六部分:Skill 与 MCP 的协同工作流(完整示例)

这个部分用一个完整的"发邮件"场景,展示知识(Skill)和行动(Tool)如何配合,以及代码层面如何实现。

6.1 场景设定

  • 一个 MCP Server,暴露了业务工具:validate_emailcompose_emailsend_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 关键要点

  1. 工具列表注入 LLM :启动时通过 MCPClient.load_tools() 动态获取 MCP 工具,与本地 @tool 函数合并,统一传给 create_react_agent()
  2. 渐进式披露list_skills 只返回元数据(L1),LLM 决定使用后才调用 load_skill 获取完整内容(L2)。
  3. Skill 与 MCP 的分工
    • Skill (.md 文件):知识层,告诉 LLM "怎么做"。
    • Skill 管理工具 (list_skills/load_skill):工具层,负责"搬运知识"。
    • MCP 业务工具 (validate_email等):工具层,负责"执行动作"。
  4. 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 就能跑起来,适合日常项目使用。


相关推荐
吃好睡好便好3 小时前
创建随机矩阵
开发语言·人工智能·线性代数·算法·matlab·矩阵
拙野3 小时前
【保姆级教程】Claude Code无缝集成DeepSeek V4 Pro
java·人工智能·deepseek·claudecode·ai coding
心中有国也有家3 小时前
PyTorch 适配 NPU:从 torch_npu 到 CANN 算子的全链路技术解析
人工智能·pytorch·python
Keano Reurink3 小时前
搜索API驱动的竞品监控:7×24小时跟踪对手一举一动
人工智能·搜索引擎·dreamweaver
java小吕布3 小时前
HyperFrames:写 HTML 就能渲染视频,专为 AI 智能体打造的开源渲染框架
人工智能·html·音视频
Keep Running *3 小时前
Hermes_学习笔记
笔记·学习
盼小辉丶3 小时前
PyTorch强化学习实战(10)——强化学习高级组件
人工智能·pytorch·python·强化学习
shchojj3 小时前
Advanced Technologies: Beyond Prompting - Fine-tuning
人工智能
mydeman3 小时前
智能体工程化演进:架构收敛、协议标准化与安全边界下沉
人工智能·架构·软件工程·ai编程