基于LangChain DeepAgents的Skills应用实践

前几期文章中已经介绍了基于#CodeBuddy#OpenClaw 等通用智能体进行 #Skills 实践的案例。当用户使用这些工具时,只需直接从第三方Skill市场下载相关技能,或通过官方的skill-creator工具快速创建个性化技能,即可在工具使用过程中实现无感调用。

但如果我们需要自研/构建个性化智能体时,应如何支撑Skills的运行呢?本文选择 #LangChain #DeepAgent 框架为基础,通过实际案例分析Agent Skills的运行机制。

以下场景围绕生活中的一个典型简单场景展开,力求全面覆盖Agent Skills的各类使用方法。

案例业务背景和运行效果

场景名称:日常着装OOTD及出行助手

说明:小张在日常生活中缺乏对穿搭及出行必备物品的规划意识,常常找不到合适的衣物,也容易遗漏必要的携带工具。为此,他设计了一款智能体,该智能体能够每日定时根据小张所在城市的实时天气情况,为其推荐适宜的穿搭方案及需携带的工具。

智能体设计

根据应用场景,我们可将该智能体拆解为以下核心模块:

主智能体:

负责接收用户的需求,规划任务执行的具体步骤并迭代执行,在任务流程中自动选择调用相关技能/工具。当获取足够的信息后,调用大模型生成答案并输出。

技能工具:

  1. 位置工具:用于获取用户当前所在的城市信息;
  2. 天气工具:获取指定城市的实时天气信息,若用户未明确指定城市,则默认获取其所在城市的天气数据,具体包含天气状况、温度、湿度、风向等关键信息;
  3. 着装及出行建议技能:基于获取到的天气信息,为用户生成针对性的穿搭方案及出行必备物品建议。

案例运行分析

我们先来看下智能体运行的效果,并分析详细的过程。

当用户提问关于位置的问题("今天穿什么衣服比较好?")时,可以看到:

  1. 智能体加载了2个skill(dress-advice、location-detector)的元数据(name和description)
  2. 智能体调用LLM规划任务,发现可以调用位置服务location-detector,需要先加载SKILL。
  3. 智能体执行read_file命令加载了location-detector技能的全文(SKILL.md)
  4. 智能体调用LLM更新规划任务,发现location-detector中有调用bash命令获取位置信息的脚本。
  5. 智能体执行execute命令调用scripts/location_detector.py脚本,返回结果:当前位置:上海。
  1. 智能体继续调用LLM更新任务,发现可以调用dress-advice技能,需要先加载SKILL。
  2. 智能体执行read_file命令加载了dress-advice技能的全文(SKILL.md)
  3. 智能体调用LLM更新规划任务,发现dress-advice中有调用天气MCP工具("get_weather")获取指定城市的历史天气信息的步骤。
  4. 智能体调用天气MCP工具("get_weather")获取天气信息。
  1. 智能体最后是调用LLM根据前面拿到的天气信息回答具体着装的问题。

案例Skill结构解读

仔细分析以上案例运行过程,可以看到它用到了获取位置的Skill,根据位置获取天气的MCP服务,以及根据天气生成着装建议的Skill等。

其中获取指定城市的实时天气信息MCP服务,具体包含天气状况、温度、湿度、风向等,具体demo实现此处不作赘述。

以下我们来详细解读下这个案例中的两个关键Skill结构:

位置工具-Skill实现

1. 作用: 获取用户当前所在的城市信息。

2. 目录结构:

3. SKILL.md详情:

md 复制代码
---  
name:"location-detector"  
description:"获取用户当前所在城市的信息。当用户需要基于位置的服务或旅行建议时调用。"  
---  
  
# 位置检测器  
此技能帮助获取用户当前所在城市的信息,用于提供基于位置的旅行建议和服务。  
## 使用方法  
调用以下脚本获取天气数据:  
```bash  
pythonscripts/location_detector.py  
```  
## 响应格式  
当前位置:{城市名称}  
### 示例:  
当前位置:青岛

说明:

Name: location-detector 【注:和目录名保持一致,由小写字母和-组成。】

Description: 获取用户当前所在城市的信息。当用户需要基于位置的服务或旅行建议时调用。 【注:描述中说明触发条件,大模型会根据触发条件触发技能调用】

内容:Markdown格式,内容不固定,建议包括说明方法、响应格式等。脚本location_detector.py放到scripts/目录下,通过bash命令调用,不占用token。避免一个SKILL.md文件过大。

4. scripts/locataion_detector.py(模拟数据):

此处用模拟服务代替,生产环境中改为真实的逻辑实现。

sh 复制代码
#!/usr/bin/env python3  
"""位置检测脚本"""  
  
import random  
# 模拟城市数据  
CITIES = [  
    "北京", "上海", "广州", "深圳", "成都",  
    "杭州", "南京", "武汉", "西安", "重庆",  
    "天津", "苏州", "郑州", "长沙", "青岛"  
]  
# 城市基本信息  
CITY_INFO = {  
    "北京": "中国首都,政治文化中心",  
    "上海": "中国经济金融中心,国际化大都市",  
    "广州": "华南地区经济中心,千年商都",  
    "深圳": "科技创新之城,改革开放前沿",  
    "成都": "天府之国,休闲生活之都",  
    "杭州": "人间天堂,电子商务中心",  
    "南京": "历史文化名城,六朝古都",  
    "武汉": "九省通衢,中部地区中心城市",  
    "西安": "十三朝古都,历史文化名城",  
    "重庆": "山城,火锅之都",  
    "天津": "北方重要港口城市",  
    "苏州": "园林之城,江南水乡",  
    "郑州": "中原地区中心城市",  
    "长沙": "娱乐之都,湘菜发源地",  
    "青岛": "海滨城市,啤酒之都"  
}  
defget_current_location():  
    """  
    模拟获取当前位置  
      
    Returns:  
        dict: 包含城市信息的字典  
    """  
    # 随机选择一个城市作为当前位置  
    current_city = random.choice(CITIES)  
      
    return {  
        "city": current_city,  
        "info": CITY_INFO.get(current_city, "中国城市"),  
        "timestamp": "2026-04-15 17:00:00"  
    }  
if __name__ == "__main__":  
    location = get_current_location()  
    print(f"当前位置:{location['city']}")

着装及出行建议技能-Skill实现

1. 目录结构:

2. SKILL.md详情:

md 复制代码
---  
name: "dress-advice"  
description: "建议用户根据天气条件选择合适的服装,当用户请求服装建议或旅行建议时调用。"  
---  
  
# 服装建议  
此技能帮助用户选择根据天气条件合适的服装。  
## 执行流程  
### 步骤1:获取指定城市 的天气信息  
调用天气MCP工具("get_weather")获取指定城市的历史天气信息。  
### 步骤2:根据天气信息生成服装建议  
根据天气信息(天气、温度、湿度、风速等),生成合适的服装建议及出行建议。  
## 响应格式  
严格按照以下文件的格式输出:  
/skills/dress-advice/references/response_template.md

说明:

Name: dress-advice 【注:和目录名保持一致,由小写字母和-组成。】

Description: 获取用户当前所在城市的信息。当用户需要基于位置的服务或旅行建议时调用。 【注:描述中说明触发条件,大模型会根据触发条件触发技能调用】

内容:Markdown格式,内容不固定,建议包括说明方法、响应格式等。参考资料,response_template.md放到references/目录下,避免一个SKILL.md文件过大。

3. references/response_template.md(响应模板)

md 复制代码
## 穿衣建议如下:  
### **所在城市**:指定城市名称  
### **天气预报**:  
指定城市未来几天的天气预报,包括温度、天气状况、湿度、风速等  
### **服装建议**:  
根据天气条件的服装建议选择  
### **出行建议**:  
根据天气条件的出行建议选择

案例Agent代码解读

本案例的主智能体是基于LangChain的DeepAgents框架实现,详细说明如下:

初始化模型

说明:模型配置放到settings里,替换为自己的模型参数。

py 复制代码
llm = init_chat_model(  
        model=settings.OPENAI_MODEL,  
        api_key=settings.OPENAI_API_KEY,  
        base_url=settings.OPENAI_API_BASE,  
        streaming=True,  
)

创建MCP服务客户端,可配置多个服务

py 复制代码
    mcp_client = MultiServerMCPClient(  
        {  
            "weather":{  
                "transport":"http",  
                "url":"http://127.0.0.1:9001/mcp"  
            }  
        }  
    )  
  
    tools = await mcp_client.get_tools()

创建Backend后端运行环境

DeepAgent运行时需要操作后台文件(ls, read,write, glob,grep等),运行脚本(execute),此处为了简单先使用LocalShellbackend,生产环境建议采用沙箱环境,以确保安全。

py 复制代码
    # 初始化backend  
    root_dir = Path.cwd()  
    shell_env = os.environ.copy()  
    backend = LocalShellBackend(root_dir=root_dir, inherit_env=True, env=shell_env, virtual_mode=True)

创建DeepAgent

py 复制代码
    agent = create_deep_agent(  
        model=llm,  
        tools=tools,  
        backend=backend,  
        skills=[skills_dir],  
        checkpointer=checkpointer,  
        system_prompt=SYSTEM_PROMPT  
    )

说明:

  1. Model: 大模型配置
  2. Tools: MCP或者Langchain工具
  3. Backend: 本地shell运行环境
  4. Skills: 指向本地存放skills的目录
  5. Checkpointer: 本地内存记忆
  6. System_prompt: 智能体系统提示词。

智能体流式响应

py 复制代码
async defastream_main(q):  
    agent = await create_agent()  
  
    inputs = {"messages": [{"role": "user", "content": q}]}  
    step = 0  
    asyncfor chunk in agent.astream(inputs, config={"configurable": {"thread_id": "job_01"}}):  
        # 监控:是否加载成功  
        if'SkillsMiddleware.before_agent'in chunk:  
            count = len(chunk['SkillsMiddleware.before_agent'].get('skills_metadata', []))  
            print(f"> 📊 状态: 已加载 {count} 个技能")  
            for skill in chunk['SkillsMiddleware.before_agent'].get('skills_metadata', []):  
                print(f">  - {skill.get('name', '')} : {skill.get('description', '')}")  
        # # 监控:正在做什么  
        if'model'in chunk:  
            step += 1  
            msg = chunk['model']['messages'][0]  
            if msg.tool_calls:  
                print(f"> # {step} LLM调用工具")  
                # 只打印动作名称,不打印那一长串内容  
                for idx, tool_call inenumerate(msg.tool_calls):  
                    print(f"> ## 工具{idx}: [{tool_call['name']}]")  
                    print(f"> ## 参数: [{tool_call['args']}]")  
            elif msg.content:  
                print(f"## {step} LLM直接输出:\n {msg.content}")  
        if'tools'in chunk:  
            step += 1  
            print(f"> # {step} 工具:{chunk['tools']['messages'][0].name}")

完整代码dress_agent.py

py 复制代码
"""智能体核心模块"""  
from langchain_core.language_models import BaseChatModel  
from langgraph.checkpoint.memory import MemorySaver  
from langchain.chat_models import init_chat_model  
from langchain_core.messages import  HumanMessage, AIMessage, ToolMessage  
from deepagents import create_deep_agent  
from deepagents.backends import LocalShellBackend  
from langchain_mcp_adapters.client import MultiServerMCPClient  
import os  
from pathlib import Path  
import asyncio  
try:  
    # 作为模块导入时使用相对导入  
    from .config import settings  
except ImportError:  
    # 直接运行时使用绝对导入  
    from config import settings  
  
SYSTEM_PROMPT = """  
## 角色  
你是一个专业的服装建议助手,帮助用户选择合适的服装。  
## 重要规则:  
1. 始终用中文回复  
"""  
asyncdefcreate_agent() :  
    """创建服装建议智能体  
      
    Returns:  
        配置好的 DeepAgent 实例  
    """  
     
    llm = init_chat_model(  
        model=settings.OPENAI_MODEL,  
        api_key=settings.OPENAI_API_KEY,  
        base_url=settings.OPENAI_API_BASE,  
        streaming=True,  
    )  
    mcp_client = MultiServerMCPClient(  
        {  
            "weather":{  
                "transport":"http",  
                "url":"http://127.0.0.1:9001/mcp"  
            }  
        }  
    )  
    tools = await mcp_client.get_tools()  
    # 初始化checkpointer  
    checkpointer = MemorySaver()  
    # 初始化backend  
    root_dir = f"/ws_data/travel_plan"# Path.cwd()  
    skills_dir = f"/skills"  
    shell_env = os.environ.copy()  
    backend = LocalShellBackend(root_dir=root_dir, inherit_env=True, env=shell_env, virtual_mode=True)  
    # 创建 agent  
    agent = create_deep_agent(  
        model=llm,  
        tools=tools,  
        backend=backend,  
        skills=[skills_dir],  
        checkpointer=checkpointer,  
        system_prompt=SYSTEM_PROMPT  
    )   
    return agent  
asyncdefastream_main(q):  
    agent = await create_agent()  
    inputs = {"messages": [{"role": "user", "content": q}]}  
    step = 0  
    asyncfor chunk in agent.astream(inputs, config={"configurable": {"thread_id": "job_01"}}):  
        # 监控:是否加载成功  
        if'SkillsMiddleware.before_agent'in chunk:  
            count = len(chunk['SkillsMiddleware.before_agent'].get('skills_metadata', []))  
            print(f"> 📊 状态: 已加载 {count} 个技能")  
            for skill in chunk['SkillsMiddleware.before_agent'].get('skills_metadata', []):  
                print(f">  - {skill.get('name', '')} : {skill.get('description', '')}")  
        # # 监控:正在做什么  
        if'model'in chunk:  
            step += 1  
            msg = chunk['model']['messages'][0]  
            if msg.tool_calls:  
                print(f"> # {step} LLM调用工具")  
                # 只打印动作名称,不打印那一长串内容  
                for idx, tool_call inenumerate(msg.tool_calls):  
                    print(f"> ## 工具{idx}: [{tool_call['name']}]")  
                    print(f"> ## 参数: [{tool_call['args']}]")  
            elif msg.content:  
                print(f"## {step} LLM直接输出:\n {msg.content}")  
        if'tools'in chunk:  
            step += 1  
            print(f"> # {step} 工具:{chunk['tools']['messages'][0].name}")  
            print(f"> ## 工具运行结果:\n> {chunk['tools']['messages'][0].content}")

程序入口main.py

提供用户和智能体一问一答的功能。

py 复制代码
"""TravelPlan 旅行计划智能体 - CLI 入口"""  
  
from agents.agent import astream_main  
import asyncio  
  
defmain():  
    """主入口函数"""  
    print("=" * 50)  
    print("🗺️  您好,我是的专属生活助手!")  
    print("=" * 50)  
    print()  
    # 检查配置  
    print("输入您的问题开始对话,输入 'quit' 或 'exit' 退出")  
    print("示例:'今天穿什么衣服比较好?'")  
    print("-" * 50)  
    # 交互循环  
    whileTrue:  
        try:  
            user_input = input("\n🧑 我:").strip()  
        except (EOFError, KeyboardInterrupt):  
            print("\n\n👋 再见,祝生活愉快!")  
            break  
        ifnot user_input:  
            continue  
        if user_input.lower() in ("quit", "exit", "q"):  
            print("👋 再见,祝您的生活愉快!")  
            break  
        if user_input.lower() in ("help", "帮助"):  
            print("\n可用命令:")  
            print("  - 直接输入问题,如'今天穿什么衣服比较好?'")  
            print("  - quit/exit:退出程序")  
            print("  - help:显示帮助")  
            continue  
        # 调用智能体  
        print("\n🤖 服装建议:", end="", flush=True)  
        try:  
            asyncio.run(astream_main(user_input))  
              
        except Exception as e:  
            print(f"\n❌ 处理问题时出错:{e}")  
            print("请重试或换一种方式描述您的问题")  
if __name__ == "__main__":  
    main()

启动智能体对话程序

在终端里执行 python run main.py,就可以启动该智能体,和你进行日常穿搭的交互了:

DeepAgent源码分析

上文描述了如何基于DeepAgent框架实现一个带有两个Skill的智能体,分析了该智能体的源码构建过程。

接下来我们进一步看看这个智能体底层的DeepAgent框架本身的源码,分析下它是如何规划任务并调用Skills、Tools等工具的。

create_deep_agent源码分析

我们从DeepAgent的源码入手,创建DeepAgent的源码如下:

py 复制代码
def create_deep_agent(# noqa: C901, PLR0912  # Complex graph assembly logic with many conditional branches  
    model: str | BaseChatModel | None = None,  
    tools: Sequence[BaseTool | Callable | dict[str, Any]] | None = None,  
    *,  
    system_prompt: str | SystemMessage | None = None,  
    middleware: Sequence[AgentMiddleware] = (),  
    subagents: Sequence[SubAgent | CompiledSubAgent | AsyncSubAgent] | None = None,  
    skills: list[str] | None = None,  
    memory: list[str] | None = None,  
    response_format: ResponseFormat | None = None,  
    context_schema: type[Any] | None = None,  
    checkpointer: Checkpointer | None = None,  
    store: BaseStore | None = None,  
    backend: BackendProtocol | BackendFactory | None = None,  
    interrupt_on: dict[str, bool | InterruptOnConfig] | None = None,  
    debug: bool = False,  
    name: str | None = None,  
    cache: BaseCache | None = None,  
) -> CompiledStateGraph:  
    model = get_default_model() if model isNoneelse resolve_model(model)  
    backend = backend if backend isnotNoneelse (StateBackend)  
  
    # Build general-purpose subagent with default middleware stack  
    gp_middleware: list[AgentMiddleware[Any, Any, Any]] = [  
        TodoListMiddleware(),  
        FilesystemMiddleware(backend=backend),  
        create_summarization_middleware(model, backend),  
        PatchToolCallsMiddleware(),  
    ]  
    if skills isnotNone:  
        gp_middleware.append(SkillsMiddleware(backend=backend, sources=skills))  
    gp_middleware.append(AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"))  
    if interrupt_on isnotNone:  
        gp_middleware.append(HumanInTheLoopMiddleware(interrupt_on=interrupt_on))  
  
    general_purpose_spec: SubAgent = {  # ty: ignore[missing-typed-dict-key]  
        **GENERAL_PURPOSE_SUBAGENT,  
        "model": model,  
        "tools": tools or [],  
        "middleware": gp_middleware,  
    }  
  
    # Set up subagent middleware  
    inline_subagents: list[SubAgent | CompiledSubAgent] = []  
    async_subagents: list[AsyncSubAgent] = []  
    for spec in subagents or []:  
        if"graph_id"in spec:  
            # Then spec is an AsyncSubAgent  
            async_subagents.append(cast("AsyncSubAgent", spec))  
            continue  
        if"runnable"in spec:  
            # CompiledSubAgent - use as-is  
            inline_subagents.append(spec)  
        else:  
            # SubAgent - fill in defaults and prepend base middleware  
            subagent_model = spec.get("model", model)  
            subagent_model = resolve_model(subagent_model)  
  
            # Build middleware: base stack + skills (if specified) + user's middleware  
            subagent_middleware: list[AgentMiddleware[Any, Any, Any]] = [  
                TodoListMiddleware(),  
                FilesystemMiddleware(backend=backend),  
                create_summarization_middleware(subagent_model, backend),  
                PatchToolCallsMiddleware(),  
            ]  
            subagent_skills = spec.get("skills")  
            if subagent_skills:  
                subagent_middleware.append(SkillsMiddleware(backend=backend, sources=subagent_skills))  
            subagent_middleware.extend(spec.get("middleware", []))  
            subagent_middleware.append(AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"))  
  
            processed_spec: SubAgent = {  # ty: ignore[missing-typed-dict-key]  
                **spec,  
                "model": subagent_model,  
                "tools": spec.get("tools", tools or []),  
                "middleware": subagent_middleware,  
            }  
            inline_subagents.append(processed_spec)  
  
    # If an agent with general purpose name already exists in subagents, then don't add it  
    # This is how you overwrite/configure general purpose subagent  
    ifnotany(spec["name"] == GENERAL_PURPOSE_SUBAGENT["name"] for spec in inline_subagents):  
        # Add a general purpose subagent if it doesn't exist yet  
        inline_subagents.insert(0, general_purpose_spec)  
  
    # Build main agent middleware stack  
    deepagent_middleware: list[AgentMiddleware[Any, Any, Any]] = [  
        TodoListMiddleware(),  
    ]  
    if skills isnotNone:  
        deepagent_middleware.append(SkillsMiddleware(backend=backend, sources=skills))  
    deepagent_middleware.extend(  
        [  
            FilesystemMiddleware(backend=backend),  
            SubAgentMiddleware(  
                backend=backend,  
                subagents=inline_subagents,  
            ),  
            create_summarization_middleware(model, backend),  
            PatchToolCallsMiddleware(),  
        ]  
    )  
  
    if async_subagents:  
        # Async here means that we run these subagents in a non-blocking manner.  
        # Currently this supports agents deployed via LangSmith deployments.  
        deepagent_middleware.append(AsyncSubAgentMiddleware(async_subagents=async_subagents))  
  
    if middleware:  
        deepagent_middleware.extend(middleware)  
    # Caching + memory after all other middleware so memory updates don't  
    # invalidate the Anthropic prompt cache prefix.  
    deepagent_middleware.append(AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"))  
    if memory isnotNone:  
        deepagent_middleware.append(MemoryMiddleware(backend=backend, sources=memory))  
    if interrupt_on isnotNone:  
        deepagent_middleware.append(HumanInTheLoopMiddleware(interrupt_on=interrupt_on))  
  
    # Combine system_prompt with BASE_AGENT_PROMPT  
    if system_prompt isNone:  
        final_system_prompt: str | SystemMessage = BASE_AGENT_PROMPT  
    elifisinstance(system_prompt, SystemMessage):  
        final_system_prompt = SystemMessage(content_blocks=[*system_prompt.content_blocks, {"type": "text", "text": f"\n\n{BASE_AGENT_PROMPT}"}])  
    else:  
        # String: simple concatenation  
        final_system_prompt = system_prompt + "\n\n" + BASE_AGENT_PROMPT  
  
    return create_agent(  
        model,  
        system_prompt=final_system_prompt,  
        tools=tools,  
        middleware=deepagent_middleware,  
        response_format=response_format,  
        context_schema=context_schema,  
        checkpointer=checkpointer,  
        store=store,  
        debug=debug,  
        name=name,  
        cache=cache,  
    ).with_config(  
        {  
            "recursion_limit": 10_001,  
            "metadata": {  
                "ls_integration": "deepagents",  
                "versions": {"deepagents": __version__},  
                "lc_agent_name": name,  
            },  
        }  
    )

从源码中可以看出,DeepAgent是在Agent的基础上添加了一系列的插件,包括tools、skills、subagent等,其中skills和subagent等通过middleware的形式注册到智能体Agent,MCP服务作为tools注册到智能体Agent。如图所示:

create_agent源码分析

进一步分析以上代码中引用的create_agent的源码:

py 复制代码
def create_agent(  
        model: str | BaseChatModel,  
        tools: Sequence[BaseTool | Callable[..., Any] | dict[str, Any]] | None = None,  
        *,  
        system_prompt: str | SystemMessage | None = None,  
        middleware: Sequence[AgentMiddleware[StateT_co, ContextT]] = (),  
        response_format: ResponseFormat[ResponseT] | type[ResponseT] | dict[str, Any] | None = None,  
        state_schema: type[AgentState[ResponseT]] | None = None,  
        context_schema: type[ContextT] | None = None,  
        checkpointer: Checkpointer | None = None,  
        store: BaseStore | None = None,  
        interrupt_before: list[str] | None = None,  
        interrupt_after: list[str] | None = None,  
        debug: bool = False,  
        name: str | None = None,  
        cache: BaseCache[Any] | None = None,  
) -> CompiledStateGraph[  
    AgentState[ResponseT], ContextT, _InputAgentState, _OutputAgentState[ResponseT]  
]:  
    ...  
  
    # create graph, add nodes  
    graph: StateGraph[  
        AgentState[ResponseT], ContextT, _InputAgentState, _OutputAgentState[ResponseT]  
    ] = StateGraph(  
        state_schema=resolved_state_schema,  
        input_schema=input_schema,  
        output_schema=output_schema,  
        context_schema=context_schema,  
    )  
      
    ...  
    # Use sync or async based on model capabilities  
    graph.add_node("model", RunnableCallable(model_node, amodel_node, trace=False))  
  
    # Only add tools node if we have tools  
    if tool_node isnotNone:  
        graph.add_node("tools", tool_node)  
  
    # Add middleware nodes  
    for m in middleware:  
        if (  
                m.__class__.before_agent isnot AgentMiddleware.before_agent  
                or m.__class__.abefore_agent isnot AgentMiddleware.abefore_agent  
        ):  
            ...  
            graph.add_node(  
                f"{m.name}.before_agent", before_agent_node, input_schema=resolved_state_schema  
            )  
  
        if (  
                m.__class__.before_model isnot AgentMiddleware.before_model  
                or m.__class__.abefore_model isnot AgentMiddleware.abefore_model  
        ):  
            ...  
            graph.add_node(  
                f"{m.name}.before_model", before_node, input_schema=resolved_state_schema  
            )  
  
        if (  
                m.__class__.after_model isnot AgentMiddleware.after_model  
                or m.__class__.aafter_model isnot AgentMiddleware.aafter_model  
        ):  
            ...  
            graph.add_node(f"{m.name}.after_model", after_node, input_schema=resolved_state_schema)  
  
        if (  
                m.__class__.after_agent isnot AgentMiddleware.after_agent  
                or m.__class__.aafter_agent isnot AgentMiddleware.aafter_agent  
        ):  
            ...  
            graph.add_node(  
                f"{m.name}.after_agent", after_agent_node, input_schema=resolved_state_schema  
            )  
  
    ...  
      
    graph.add_edge(START, entry_node)  
    # add conditional edges only if tools exist  
    if tool_node isnotNone:  
        ...  
        graph.add_conditional_edges(  
            "tools",  
            RunnableCallable(  
                _make_tools_to_model_edge(  
                    tool_node=tool_node,  
                    model_destination=loop_entry_node,  
                    structured_output_tools=structured_output_tools,  
                    end_destination=exit_node,  
                ),  
                trace=False,  
            ),  
            tools_to_model_destinations,  
        )  
  
        ...  
          
        graph.add_conditional_edges(  
            loop_exit_node,  
            RunnableCallable(  
                _make_model_to_tools_edge(  
                    model_destination=loop_entry_node,  
                    structured_output_tools=structured_output_tools,  
                    end_destination=exit_node,  
                ),  
                trace=False,  
            ),  
            model_to_tools_destinations,  
        )  
    eliflen(structured_output_tools) > 0:  
        graph.add_conditional_edges(  
            loop_exit_node,  
            RunnableCallable(  
                _make_model_to_model_edge(  
                    model_destination=loop_entry_node,  
                    end_destination=exit_node,  
                ),  
                trace=False,  
            ),  
            [loop_entry_node, exit_node],  
        )  
    elif loop_exit_node == "model":  
        # If no tools and no after_model, go directly to exit_node  
        graph.add_edge(loop_exit_node, exit_node)  
    ...  
    return graph.compile(  
        checkpointer=checkpointer,  
        store=store,  
        interrupt_before=interrupt_before,  
        interrupt_after=interrupt_after,  
        debug=debug,  
        name=name,  
        cache=cache,  
    ).with_config(config)

可以看出Agent 的本质是基于LangGraph的一个流程图:LLM + 工具 + 动态决策循环,其基本原理如下:

其中的工具包含:

1、系统默认工具:包括任务规划、文件操作等工具。

2、用户注入的工具:包括MCP工具、Middleware(skills、subagents)等。

案例Agent 不是固定链式调用,而是一个典型的ReAct模式智能体,每一步都由 LLM 自己决定下一步做什么:

  1. Reason(思考)LLM 接收用户问题 + 历史上下文 + 工具描述,自主判断:
  • 是否需要调用工具?
  • 调用哪个工具?
  • 传入什么参数?
  1. Act(行动)执行工具(搜索、API、数据库、代码解释器等)。

  2. Observe(观察)把工具返回结果丢回给 LLM,进入下一轮思考。

  3. 终止条件LLM 认为信息足够,直接输出最终答案,循环结束。


本系列说明:在本系列中我们将通过不同 AI Agent(如 Code Buddy、OpenClaw、OpenCode、Hermes Agent 等)作为载体,结合具体业务案例进行实战,以深入剖析 Skill 的实现机制和底层原理。

参考对比:

用一个业务案例,摸透Code Buddy的Skill原理

用OpenClaw帮孩子成为真学霸---带你拆解AI Agent执行Skill+MCP的原理

Skill 深度解析:从案例到 OpenClaw 源码实现

---End---

本文作者:陈治中

本文原载:公众号"木昆子记录AI"

相关推荐
三秋树1 小时前
豆包 Agent Harness 工程师入门 | 第 8 章 后台任务
ai编程
Hello-Mr.Wang1 小时前
【保姆级教程】MasterGo MCP + Cursor 一键实现 UI 设计稿还原
前端·javascript·vue.js·ai编程
Peter·Pan爱编程2 小时前
第四篇:Cursor 深度评测 —— Composer 模式下的全栈 vibe 体验
人工智能·ai编程·composer
好运的阿财2 小时前
OpenClaw工具拆解之memory_search+memory_get
人工智能·python·ai编程·openclaw·openclaw工具
奔跑吧树袋熊3 小时前
Opus 4.7 + GPT-5.5“双核驱动”——2026最强AI编程工作流实测
gpt·ai编程
_Evan_Yao3 小时前
一文搞懂:AI编程辅助工具——从GitHub Copilot到通义灵码,不同人群如何驾驭AI编程助手?
人工智能·后端·copilot·ai编程
jimy15 小时前
进入codex后,如何检验codex是否在bubblewrap沙箱运行
ai编程
icestone20005 小时前
智能客服如何按客户类型切换话术?一套支持“渠道标签 + 用户自选 + 对话推断“的分类架构设计
大数据·人工智能·ai编程
豆包MarsCode5 小时前
TRAE SOLO 移动端上线!我们请你喝星巴克
ai编程·trae