• LangChain DeepAgents 学习笔记


    LangChain DeepAgents 学习笔记

    摘要

    LangChain的DeepAgents已经发布有段时间了,最近的几个版本基本覆盖了大多数智能体都应该有的功能。本笔记基于 LangChain 的 DeepAgents 框架,演示如何构建了一个具有角色扮演能力的 AI 助手,并集成了自定义技能系统。以此方便后续的knowledge review以及vibe coding的reference。

    环境配置

    环境变量设置

    from dotenv import load_dotenv
    import os
    
    # 加载 .env 文件中的环境变量
    load_dotenv()
    
    # 设置环境变量供 deepagents 使用(OpenAI 兼容格式)
    os.environ["OPENAI_API_KEY"] = os.getenv("DEEPSEEK_API_KEY", "")
    os.environ["OPENAI_API_BASE"] = os.getenv("DEEPSEEK_API_BASE", "https://api.deepseek.com/v1")
    

    这里我们使用在线版本的deepseek。

    尝试过使用本地的小模型,需要留意的是,deepseek的小参数量模型,不支持工具的调用。即使换到qwen的小模型,对工具的调用也总会出莫名其妙的问题。所以为方便学习和测试,建议走在线的模型。

    工具函数定义

    为了演示方便,这里创建两个方法作为DeepAgent的工具。

    时间查询工具

    from datetime import datetime
    
    def get_time():
        """Get current time"""
        return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    

    位置查询工具

    def get_location():
        """Get location"""
        return "北京"
    

    为方便演示,位置查询的output直接写死。

    注:方法要有注释,标注这个方法是干什么的,方便智能体的了解,不然会报错。

    核心组件

    记忆检查点(Checkpointer)

    from langgraph.checkpoint.memory import InMemorySaver
    
    checkpointer1 = InMemorySaver()
    

    由于大模型的对话都是无状态的,比如你第一次对话问:来一首唐诗,第二次对话追问:再来一首,那么大模型会不知道到底再来一首啥。LangChain封装了这么一套方法来维护对话的状态。

    代码讲解

    • InMemorySaver 是 LangGraph 提供的内存检查点存储
    • 用于保存对话状态,实现多轮对话的记忆功能
    • 每次对话后自动保存状态,下次可以继续同一话题
    • 支持通过 thread_id 区分不同的对话线程

    中间件(Middleware)

    from langchain.agents.middleware import SummarizationMiddleware
    
    middleware=[
        SummarizationMiddleware(
            model="deepseek:deepseek-chat",
            trigger=("tokens",1000),
            keep=("messages",3)
        )
    ]
    

    LangChain通过中间件的方式,来处理模型对话历史过长的问题,处理的方法有很多,比如消息裁剪等。这里演示的是更常用的,将历史对话进行大模型语义汇总的方式,基本的方法就是将指定久远之外的消息汇总成一个简短的摘要。

    代码讲解

    • SummarizationMiddleware:摘要中间件,用于优化长对话的上下文管理
    • 参数说明
      • model:用于生成摘要的模型
      • trigger=("tokens",1000):当上下文达到 1000 tokens 时触发摘要
      • keep=("messages",3):保留3 条消息
    • 作用:当对话过长时,自动将早期对话摘要为简短总结,节省 token 并保持关键信息

    如果keep的messages设置为3,那么最近的两条message会被完整保留,第三条是根据以前的会话记录summaize下来的信息。

    本地 Shell 后端

    from deepagents.backends import LocalShellBackend
    
    backend=LocalShellBackend(
        root_dir=".",
        virtual_mode=True
    )
    

    这个是用来确保,智能体能够读取到skills,并且能够运行里面的脚本。

    代码讲解

    • LocalShellBackend:本地 Shell 执行后端
    • 参数说明
      • root_dir=".":工作目录根路径,设置为当前目录
      • virtual_mode=True:启用虚拟模式,限制文件访问范围,提高安全性
    • 作用:允许 Agent 执行本地命令和脚本,扩展其能力范围

    创建 Deep Agent

    核心代码

    from deepagents import create_deep_agent
    
    agent = create_deep_agent(
        model="deepseek:deepseek-chat",
        system_prompt="你是于谦,捧话之王,说话坏简介明了直奔主题。",
        tools=[get_time, get_location],
        checkpointer=checkpointer1,
        middleware=[
            SummarizationMiddleware(
                model="deepseek:deepseek-chat",
                trigger=("tokens",1000),
                keep=("messages",3)
            )
        ],
        backend=LocalShellBackend(
            root_dir=".",
            virtual_mode=True
        ),
        skills=["./skills"],
    )
    

    跟传统的create_agent比,创建deepagent的方法对应的是create_deep_agent。大多数的传入参数基本都是一样的。

    参数详解

    参数 说明
    model 使用的模型名称,这里使用 DeepSeek 的 deepseek-chat 模型
    system_prompt 系统提示词,定义 Agent 的角色和行为风格
    tools 工具函数列表,Agent 可以调用这些函数
    checkpointer 记忆检查点,用于保存对话状态
    middleware 中间件列表,用于处理上下文摘要等任务
    backend 执行后端,用于运行本地命令
    skills 技能目录列表,加载自定义技能扩展

    对话循环

    智能体记忆的配置

    配合智能体的记忆功能使用的。

    config = {"configurable":{"thread_id":"1"}}
    

    这个config是需要放在后续的invoke里的。(其实这里没太想明白,为什么不放在构造deepagent的构造函数里。)

    代码讲解

    • thread_id:对话线程标识符
    • 相同的 thread_id 可以延续之前的对话
    • 不同的 thread_id 会开启新的对话线程

    主循环逻辑

    index=1
    
    while(True):
        print(f"### 第{index}轮对话 ###")
        query = input("请输入:")
        if query == "exit":
            print("退出")
            break
        results=agent.invoke(
            {"messages": [{"role": "user", "content": query}]},
            config=config
        )
    
        for message in results["messages"]:
            message.pretty_print()
    
        index+=1
    

    技能系统(Skills)

    这里我们演示一个HR的请假系统,相应的代码我们只定义了接口,为了演示方便不做具体的实现以及系统的对接,只返回固定的output。

    技能目录结构

    通常我们建议单独的一个skills目录保存所有skills,然后通过每个子目录来保存不同的skill。

    skills/
    └── HR/
        └── SKILL.md
    

    HR 技能的介绍

    文件路径skills/HR/SKILL.md

    技能元数据

    ---
    name: HR assistant
    description: HR 助手
    ---
    

    这两个配置是每个skill必须都要包含的,包括

    • name:技能的唯一标识符
    • description:技能的简短描述

    功能说明

    本技能是 HR 系统的 AI 助手,根据系统接口文档和用户的问题,直接调用相应的接口完成操作。当用户说"小赵"时触发。
    

    根据这段声明,我们定义了触发机制为

    • 触发词:"小赵"
    • 当用户在对话中提到"小赵"时,Agent 会自动调用此技能

    支持的功能

    这部分包含skill里都有什么功能

    ### 1. 请假
    - **功能**:创建新的请假申请
    - **需要确认的信息**  - 姓名
      - 请假开始时间
      - 请假结束时间
      - 请假类型(病假,事假,年假,婚假,丧假,产假,其他)
    - **操作流程**  1. 检查用户提供的信息是否完整
      2. 如果有任何信息不完整,依次向用户确认
      3. 所有信息确认后,让用户再次确认所有信息
      4. 调用 `create_leave_application` 函数创建请假申请
    

    根据这段描述我们可以看到,这里我们要求skill

    1. 信息收集

      • 主动询问缺失的信息
      • 一次只问一个问题,避免用户困惑
    2. 信息确认

      • 汇总所有信息展示给用户
      • 等待用户最终确认
    3. 执行操作

      • 调用具体的函数实现业务逻辑

    SILLL完整代码

    ---
    name: HR assistant
    description: HR 助手
    ---
    
    # HR 助手
    
    ## 功能说明
    
    本技能是HR系统的AI助手,根据系统接口文档和用户的问题,直接调用相应的接口完成操作。当用户说"小赵"时触发。
    
    ## 支持的功能
    
    ### 1. 请假
    - **功能**:创建新的请假申请
    - **需要确认的信息**  - 姓名
      - 请假开始时间
      - 请假结束时间
      - 请假类型(病假,事假,年假,婚假,丧假,产假,其他)
    - **操作流程**  1. 检查用户提供的信息是否完整
      2. 如果有任何信息不完整,依次向用户确认
      3. 所有信息确认后,让用户再次确认所有信息
      4. 调用 `create_leave_application` 函数创建请假申请
    
    
    ### 请假代码的具体实现
    ```python
    def create_leave_application(name, start_time, end_time, leave_type):
        """
        创建新的请假申请
        
        参数:
        - name: 姓名
        - start_time: 请假开始时间(格式:YYYY-MM-DD HH:MM)
        - end_time: 请假结束时间(格式:YYYY-MM-DD HH:MM)
        - leave_type: 请假类型(病假,事假,年假,婚假,丧假,产假,其他)
        
        返回:
        - 申请结果字典
        """
        #实现方法略
        return {
            "success": True,
            "message": "请假申请已创建",
            "data": {
                "name": name,
                "start_time": start_time,
                "end_time": end_time,
                "leave_type": leave_type,
                "status": "pending"
            }
        }
    ```
    

    技能使用示例

    用户对话流程

    以下是一段测试对话。

    我比较喜欢的一个点是,通过skill,以及智能体的加持,对于参数的输入就灵活了很多,在体验上更人性化。比如接口对日期格式的要求是yyyy-MM-dd,跟智能体对话的时候你只要说明天或者后天,那么智能体会根据接口的定义自动转换成符合要求的日期格式。

    用户:小赵,我想休一天的假
    Agent:好的,我是 HR 助手小赵。请问您的姓名是?
    
    用户:我是哈士奇
    Agent:好的,哈士奇。请问您想请哪一天的假?
    
    用户:后天
    Agent:好的,后天(2026-05-21)。请问您想请什么类型的假?
           可选类型:病假、事假、年假、婚假、丧假、产假、其他
    
    用户:年假
    Agent:请确认以下请假信息:
           - 姓名:哈士奇
           - 请假类型:年假
           - 开始时间:2026-05-21 09:00
           - 结束时间:2026-05-21 18:00
           以上信息是否正确?
    
    用户:正确
    Agent:✅ 请假申请已成功创建!
           申请状态:pending(待审批)
    

    关于上下文的汇总,由于需要比较多的智能体对话,所以测试结果不放在这里,感兴趣的朋友可以自己跑下代码,然后在输出部分看智能体是如何summarize历史对话记录的。

    关键技术点总结

    DeepAgents 架构优势

    1. 模块化设计

      • 工具(Tools):扩展 Agent 能力
      • 中间件(Middleware):处理上下文管理
      • 技能(Skills):实现特定领域功能
      • 后端(Backend):支持本地执行
    2. 记忆管理

      • 通过 Checkpointer 实现状态持久化
      • 支持多线程对话
      • 中间件自动摘要优化上下文
    3. 技能系统

      • 基于 Markdown 文件定义
      • 触发词机制自动激活
      • 支持复杂的多轮交互流程

    最佳实践

    1. 系统提示词设计

      • 明确角色定位
      • 定义语言风格
      • 简洁具体
    2. 工具函数编写

      • 函数名清晰描述功能
      • 添加文档字符串
      • 返回值格式统一
    3. 技能开发

      • 明确触发条件
      • 设计完整的交互流程
      • 提供详细的参数说明

    常见问题

    1. 中间件重复问题
      • 错误信息:AssertionError: Please remove duplicate middleware instances.
      • 原因:只要使用middleware那么就会报这个错,目前分析是版本的bug。以撰写此文的时间2026年5月18日为准,默认版本是0.6.1,经过我的测试,如果用0.5.1的版本的话就不会有这个问题。
      • 解决:pip卸载默认版本,安装0.5.1这个版本,具体方法可以参考我上一篇笔记。

    另目前很多vibe coding生成langchain的deepagents代码都会有很多问题,主要是因为这个东西太新了,很多智能体即使有再牛的上下文系统也不知道该如何去生成正确的代码,所以需要自己去鉴别一下。

    总结

    目前LangChain的DeepAgents已经支持了跟智能体相关的大多数方法,通过学习DeepAgents,我们也可以顺便理解目前市面上主流的智能体其背后是如何工作的。此文做阶段性的学习总结,方便后续的review以及供vibe coding参考。

    参考资料

  • 相关阅读:
    为什么团队的自动化没有效果?
    【动态规划】198. 打家劫舍、213. 打家劫舍 II、337. 打家劫舍 III
    次氯酸(HClO)荧光探针 激发波长567 nm
    Java 值传递与引用传递的简介说明
    jedis:使用事务开启watch监控
    计算机网络第3章(数据链路层)-----总结1
    【python自动化】03. pynput库自动操作键鼠(保姆级代码注释)
    【已解决】Operation timed out 问题
    css实现一个炫酷动画loading(四)
    【面试经典150 | 栈】有效的括号
  • 原文地址:https://www.cnblogs.com/aspnetx/p/20080374