☰
DeepAgent 深度解析:基于 LangGraph 构建可中断恢复的复杂任务 Agent
2026/11/3 14:12:05 网站建设 项目流程

1. DeepAgent 到底是个什么东西

第一次听到 DeepAgent 这个名字,很多人会下意识觉得它又是一个套壳的 Agent 框架,或者只是 LangChain 生态里换了个马甲的新概念。我一开始也这么想,直到真正把它的设计思路和 LangGraph、LangChain 的关系捋清楚之后,才发现它解决的是一个非常具体、非常痛的问题:让 Agent 从"能跑通一个 demo"进化到"能扛住真实业务里的长链路、多步骤、可中断、可恢复的复杂任务"。

DeepAgent 本质上是构建在 LangGraph 之上的一个高层 Agent 抽象。LangGraph 负责底层的图编排、状态管理和执行调度,LangChain 提供模型、工具、检索这些基础组件,而 DeepAgent 则把"一个能干活的 Agent 应该长什么样"这件事给标准化了。它内置了规划、子任务分解、工具调用、记忆管理、中间件拦截等能力,让你不用从零去拼装一个 Agent 的骨架。

它适合谁?如果你已经写过几个基于 LangChain 的简单 Agent,但一到多步骤任务就开始失控——比如工具调用顺序乱了、上下文爆了、中途报错整个流程就废了——那 DeepAgent 就是为你准备的。如果你是完全的新手,想直接上手一个结构清晰的 Agent 框架,它同样友好,因为它的抽象层次比裸写 LangGraph 要高得多。这篇文章我会从设计思路、核心机制、实操落地到踩坑排查,把 DeepAgent 讲透,让你看完就能动手搭一个属于自己的深度 Agent。

2. 为什么需要 DeepAgent:从 LangChain 到 LangGraph 再到 DeepAgent

2.1 LangChain 的天花板在哪里

LangChain 刚出来的时候,大家都很兴奋,因为它把 LLM 调用、Prompt 模板、工具、检索这些东西统一了接口。但用久了就会发现一个问题:LangChain 的 Chain 本质上是线性的、无状态的、一次性的。你调用一次 Chain,它跑完就结束了,中间发生了什么、状态怎么流转、能不能中断再恢复,这些它都不管。

举个实际例子。你要做一个"读取测试用例自动生成 UI 自动化测试脚本"的 Agent,流程大概是:读用例文件 → 解析步骤 → 生成 Playwright 脚本 → 校验语法 → 执行验证 → 失败则回退重试。这种任务用 LangChain 的 Chain 去写,你会发现自己要手动管理一大堆中间状态,而且一旦某一步失败,整个 Chain 就得从头再来。这就是 LangChain 的天花板——它擅长"一次调用",不擅长"多轮循环和状态流转"。

2.2 LangGraph 补上了哪块拼图

LangGraph 的出现就是为了解决状态和循环的问题。它把 Agent 的执行过程建模成一张有向图,节点是执行单元,边是流转条件,整个图共享一个 State。这个 State 可以在节点之间传递、累积、修改,而且 LangGraph 支持检查点(Checkpoint),也就是把每一步的状态持久化下来,这样任务中断后可以从断点恢复。

LangGraph 的核心概念其实不多,但每一个都很关键:

  • State:整个图的共享状态,通常是一个 TypedDict 或 Pydantic 模型,节点读写它。
  • Node:一个执行函数,接收 State,返回 State 的更新。
  • Edge:节点之间的连接,可以是普通边,也可以是条件边(根据 State 决定走哪条路)。
  • Checkpointer:状态持久化机制,支持内存、SQLite、Postgres 等后端。

有了这些,你就能写出"循环调用工具直到任务完成"这种逻辑。但问题是,裸写 LangGraph 依然很繁琐。你要自己定义 State 结构、自己写规划节点、自己处理工具调用的解析和错误、自己管理记忆。一个完整的 Agent 图,动辄几百行代码,而且每个项目都要重复一遍。

2.3 DeepAgent 的定位:把 Agent 的"标准件"封装好

DeepAgent 就是在这个背景下出现的。它把 LangGraph 上构建一个"深度 Agent"所需要的标准组件全部封装好了,你只需要关注业务逻辑。它的核心能力包括:

能力说明对应底层
任务规划自动把复杂任务拆成子任务LangGraph 图节点
工具调用标准化的工具绑定与调用循环LangChain Tools
中间件在 Agent 执行的关键节点插入拦截逻辑LangGraph 节点钩子
记忆管理短期对话记忆 + 长期知识记忆LangChain Memory + 向量库
状态持久化支持中断恢复、断点续跑LangGraph Checkpointer
子 Agent支持嵌套 Agent,分工协作LangGraph 子图

这里要特别说一下中间件这个概念,因为它是 DeepAgent 区别于普通 Agent 框架的关键。中间件允许你在 Agent 执行的各个阶段(比如"调用模型前""调用工具后""生成最终答案前")插入自定义逻辑。这有什么用?比如你想在每次工具调用前做一次权限校验,或者想在模型输出后做一次敏感词过滤,或者想在上下文太长时自动做摘要压缩——这些都可以通过中间件实现,而不需要改动 Agent 的核心逻辑。

提示:很多人会把 DeepAgent 和 LangGraph 对立起来看,觉得用了 DeepAgent 就不用学 LangGraph 了。这是个误区。DeepAgent 是建立在 LangGraph 之上的,遇到它封装不了的场景,你还是得下沉到 LangGraph 层去改。所以 LangGraph 的基础概念该学还得学。

3. DeepAgent 的核心架构拆解

3.1 执行循环:Agent 是怎么"想一步做一步"的

DeepAgent 的执行核心是一个循环,业界通常叫ReAct 循环(Reasoning + Acting)。它的基本流程是:

  1. 把当前状态(用户输入、历史对话、已有观察结果)喂给模型。
  2. 模型决定下一步:是调用某个工具,还是直接给出最终答案。
  3. 如果调用工具,执行工具,把结果作为"观察"追加到状态里。
  4. 回到第 1 步,直到模型给出最终答案或达到最大迭代次数。

这个循环看起来简单,但魔鬼在细节里。比如:模型输出的工具调用格式不对怎么办?工具执行报错了怎么办?循环次数太多导致上下文爆炸怎么办?DeepAgent 在这些地方都做了处理,但你需要知道它处理了什么,才能在出问题时快速定位。

我用一个生活化的类比来解释这个循环:它就像一个厨师做菜。厨师(模型)看着菜谱和现有食材(状态),决定下一步是切菜(调用工具)还是直接装盘(给答案)。切完菜之后,案板上多了切好的食材(观察结果),厨师再基于新的情况决定下一步。如果切菜时刀钝了(工具报错),厨师得想办法换刀或者换个切法,而不是直接放弃这道菜。

3.2 状态管理:State 里到底装了什么

DeepAgent 的 State 通常包含这几类信息:

  • messages:对话消息列表,包括用户输入、模型输出、工具调用请求、工具返回结果。这是最核心的部分。
  • plan:当前的任务计划,如果 Agent 做了规划的话。
  • scratchpad:草稿区,Agent 的中间推理过程可以放这里。
  • metadata:一些元信息,比如当前迭代次数、已用 token 数等。

这里有个关键点:messages 会不断增长。每一轮循环都会往里面追加消息,如果不做处理,几轮之后上下文就会超出模型的窗口限制。DeepAgent 通常提供几种应对策略:

  • 滑动窗口:只保留最近 N 条消息。
  • 摘要压缩:把早期消息用模型总结成一段摘要。
  • 消息裁剪:把工具返回的超长结果截断。

具体用哪种,取决于你的任务特点。如果是需要长期记忆的对话,摘要压缩更合适;如果是短任务但工具返回结果很长,裁剪更直接。

3.3 中间件机制:在正确的时机插入正确的逻辑

中间件是 DeepAgent 最值得深挖的部分。它的设计思路借鉴了 Web 框架里的中间件概念——在请求处理的关键节点插入一层可复用的逻辑。

DeepAgent 的中间件通常可以挂载在这些时机:

  • before_model:调用模型之前。适合做上下文压缩、Prompt 注入、权限检查。
  • after_model:模型返回之后。适合做输出解析、格式校验、敏感词过滤。
  • before_tool:调用工具之前。适合做参数校验、权限控制、限流。
  • after_tool:工具返回之后。适合做结果清洗、错误处理、日志记录。

我举个实际场景。假设你在做一个企业内部的 Agent,需要控制成本。你可以在 before_model 中间件里统计当前会话已经消耗的 token 数,超过阈值就强制走一个更便宜的模型,或者直接返回"本次会话预算已用完"。这种逻辑如果写在主流程里,会污染业务代码;用中间件就非常干净。

再比如,你做了一个能操作数据库的 Agent,出于安全考虑,你想禁止某些危险操作(比如 DROP TABLE)。你可以在 before_tool 中间件里检查工具名和参数,发现危险操作直接拦截并返回一个错误提示给模型,让模型换个方式。这就是中间件的价值——把横切关注点从业务逻辑里剥离出来。

3.4 子 Agent 与任务分解:大任务怎么拆

复杂任务往往需要多个"专家"协作。比如一个"帮我分析这份财报并生成 PPT"的任务,可以拆成:数据提取 Agent、财务分析 Agent、PPT 生成 Agent。DeepAgent 支持通过子图的方式定义子 Agent,主 Agent 负责调度,子 Agent 负责具体执行。

这种设计的优势是职责隔离。每个子 Agent 有自己的工具集、自己的 Prompt、自己的状态,互不干扰。主 Agent 只需要知道"什么时候该调用哪个子 Agent",不需要关心子 Agent 内部怎么干活。

但这里有个坑:子 Agent 之间的状态传递。如果子 Agent A 的输出需要作为子 Agent B 的输入,你得在主 Agent 层面做好数据的整理和传递。我见过不少人在这里翻车,子 Agent 各自跑得很好,但拼在一起就乱了,因为数据格式对不上。

4. 手把手搭一个 DeepAgent:从环境到跑通

4.1 环境准备与依赖安装

先把基础环境搭起来。我假设你已经有了 Python 环境(建议 3.10 以上),下面是核心依赖:

pip install langchain langchain-openai langgraph deepagents

如果你要用本地的向量库做记忆,还需要:

pip install chromadb langchain-chroma

如果你打算用 Ollama 跑本地模型(比如做本地知识库场景),再装:

pip install langchain-ollama

注意:DeepAgent 相关的包在不同时期可能叫不同的名字,有的版本里它作为 langgraph 的一个扩展模块存在,有的版本是独立包。装之前先确认一下你用的版本对应的导入路径,别照着老教程硬套。

4.2 最小可运行示例:一个会查天气的 Agent

先跑一个最简单的,确认环境没问题。这个 Agent 只有一个工具:查天气。

from langchain_openai import ChatOpenAI from langchain_core.tools import tool from deepagents import create_deep_agent @tool def get_weather(city: str) -> str: """查询指定城市的天气""" # 实际项目里这里调真实 API,这里用假数据演示 fake_data = { "北京": "晴,25度", "上海": "多云,28度", "广州": "小雨,30度", } return fake_data.get(city, f"暂时查不到{city}的天气") llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) agent = create_deep_agent( model=llm, tools=[get_weather], system_prompt="你是一个天气助手,用户问天气时调用工具查询。", ) result = agent.invoke({"messages": [("user", "北京今天天气怎么样?")]}) print(result["messages"][-1].content)

这段代码跑通之后,你会看到 Agent 自动调用了 get_weather 工具,然后基于返回结果生成了回答。这就是最基本的 ReAct 循环。

4.3 加入规划能力:让 Agent 先想再做

上面的 Agent 是"走一步看一步",遇到复杂任务容易乱。我们给它加上规划能力。DeepAgent 通常提供一个 planning 中间件或者规划节点,让 Agent 在动手前先生成一个任务计划。

from deepagents import create_deep_agent from deepagents.middleware import PlanningMiddleware agent = create_deep_agent( model=llm, tools=[get_weather], system_prompt="你是一个助手,复杂任务先制定计划再执行。", middleware=[PlanningMiddleware()], )

加了规划之后,Agent 面对"帮我规划一个三天的北京旅行,考虑天气"这种任务时,会先生成一个计划(比如:1. 查北京三天天气 2. 根据天气安排室内外活动 3. 输出行程),然后按计划逐步执行。这个"先规划"的动作,对复杂任务的完成质量提升非常明显。

4.4 接入记忆:让 Agent 记住之前聊过什么

短期记忆靠 messages 就够了,但如果你想让 Agent 跨会话记住用户偏好,就需要长期记忆。通常的做法是接一个向量库。

from langchain_chroma import Chroma from langchain_openai import OpenAIEmbeddings vectorstore = Chroma( collection_name="agent_memory", embedding_function=OpenAIEmbeddings(), persist_directory="./memory_db", ) # 把向量库作为检索工具挂给 Agent retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

然后在中间件里做两件事:对话结束后把关键信息写入向量库,对话开始前从向量库检索相关记忆注入上下文。这样 Agent 就能"记得"用户上次说过喜欢靠窗座位、对花生过敏之类的信息。

4.5 用中间件做上下文压缩

长对话最容易出的问题就是上下文超限。我写一个简单的压缩中间件示例:

from deepagents.middleware import BaseMiddleware class ContextCompressMiddleware(BaseMiddleware): def __init__(self, max_messages=20, llm=None): self.max_messages = max_messages self.llm = llm def before_model(self, state): messages = state["messages"] if len(messages) <= self.max_messages: return state # 保留最近的消息,把早期的总结成一段 recent = messages[-self.max_messages:] old = messages[:-self.max_messages] summary = self.llm.invoke( f"用一段话总结以下对话的关键信息:\n{old}" ).content state["messages"] = [ ("system", f"以下是之前对话的摘要:{summary}") ] + recent return state

这个中间件在每次调用模型前检查消息数量,超了就压缩。实测下来,这个简单的策略能把长对话的 token 消耗降低 60% 以上,而且基本不损失关键信息。

5. 实操中一定会遇到的问题与排查

5.1 工具调用格式错误:模型不按套路出牌

这是最常见的问题。模型有时候不返回标准的工具调用格式,而是把工具名和参数写在普通文本里。表现就是 Agent 卡住不动,或者报"无法解析工具调用"。

排查思路:先看模型的原始输出是什么。如果模型本身不支持 function calling,那你就得用 Prompt 引导它输出特定格式,再自己解析。如果模型支持但偶尔抽风,可以在 after_model 中间件里加一层校验,格式不对就重新让模型生成一次。

实操心得:用支持 function calling 的模型能省掉 80% 的格式问题。如果非要用不支持的小模型,建议在 system prompt 里给一个非常明确的输出格式示例,并且用 few-shot 的方式多给几个例子。

5.2 无限循环:Agent 一直在调工具停不下来

Agent 陷入循环,反复调用同一个工具,或者在不同工具之间来回横跳。原因通常是:工具返回的结果没有让模型满意,模型以为没成功,就再试一次。

解决办法有几个:一是设置最大迭代次数(硬性兜底);二是在工具返回里明确告诉模型"操作已成功";三是在中间件里检测重复调用,连续 N 次调用同一个工具且参数相同就强制中断。

class LoopGuardMiddleware(BaseMiddleware): def __init__(self, max_repeat=3): self.max_repeat = max_repeat self.call_history = [] def before_tool(self, state, tool_name, tool_args): self.call_history.append((tool_name, str(tool_args))) recent = self.call_history[-self.max_repeat:] if len(recent) == self.max_repeat and len(set(recent)) == 1: raise RuntimeError(f"检测到重复调用 {tool_name},强制中断") return state

5.3 上下文爆炸:token 消耗失控

前面提过,messages 会不断增长。除了压缩,还有一个技巧是工具返回结果截断。很多工具(比如网页抓取、文件读取)返回的内容特别长,直接塞进上下文非常浪费。可以在 after_tool 中间件里对结果做截断或摘要。

5.4 常见问题速查表

问题现象可能原因排查方向解决手段
Agent 卡住不动工具调用格式错误看模型原始输出换模型或加格式校验中间件
无限循环工具结果不被认可看工具返回内容加最大迭代 + 循环检测
上下文超限messages 无限增长统计 token 数压缩中间件 + 结果截断
子 Agent 数据错乱状态传递格式不一致检查子 Agent 输入输出统一数据契约
记忆检索不准向量库 embedding 质量差看检索结果换 embedding 模型或调 k 值
工具报错后流程中断没有错误处理看异常栈after_tool 里捕获并返回友好提示

5.5 几个我踩过的坑

第一个坑是过度依赖规划。不是所有任务都需要规划,简单任务加规划反而增加延迟和 token 消耗。我的经验是:任务步骤超过 3 步、或者步骤之间有依赖关系时才启用规划。

第二个坑是中间件顺序。多个中间件叠加时,执行顺序会影响结果。比如压缩中间件和日志中间件,如果日志在前压缩在后,你看到的日志就是压缩前的,容易误判。建议在中间件命名上体现顺序,或者用显式的优先级参数。

第三个坑是子 Agent 的工具集重叠。如果主 Agent 和子 Agent 有同名工具,调用时容易混淆。我的做法是给子 Agent 的工具加前缀,比如db_query、web_search,一眼就能看出是哪个 Agent 的能力。

6. DeepAgent 的能力边界与选型建议

6.1 它擅长什么,不擅长什么

DeepAgent 擅长的是结构化的、多步骤的、需要工具协作的任务。比如自动化测试脚本生成、数据分析报告、多轮信息检索整合。这些任务的共同点是:有明确的步骤、需要调用外部能力、中间结果需要累积。

它不擅长的是需要极低延迟的实时交互。因为 Agent 的每一轮循环都要调用模型,延迟天然比单次调用高。如果你的场景是"用户说一句话,100ms 内必须响应",那 Agent 架构就不合适,老老实实用单次模型调用。

6.2 和直接写 LangGraph 怎么选

如果你的任务流程非常固定,比如就是"检索 → 生成 → 校验"三步,那直接写 LangGraph 更轻量,没必要上 DeepAgent。但如果你的任务需要动态规划、需要根据中间结果决定下一步、需要记忆和中间件,那 DeepAgent 能帮你省掉大量重复代码。

我的判断标准很简单:如果你发现自己在多个项目里重复写类似的 Agent 骨架代码,那就该用 DeepAgent 了。

6.3 后续可以怎么扩展

跑通基础版本之后,我建议按这个顺序扩展:先加记忆(让 Agent 有连续性),再加中间件(做安全和成本控制),最后加子 Agent(做任务分解)。每加一层都要重新测一遍,确保没有引入新的问题。尤其是子 Agent,复杂度是成倍上升的,不要一上来就搞多 Agent 协作,先把单 Agent 跑稳。

最后分享一个我在实际项目里的小技巧:给 Agent 加一个"调试模式",开启后把每一轮的 State、模型输入输出、工具调用详情都打到日志里。排查问题时,这个日志比任何猜测都管用。我一般用环境变量控制,生产环境关掉,本地开发打开,非常方便。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询