拓冰建站拓冰建站
首页 / 资讯中心 / 正文

DeepAgent实战:理清LangChain、LangGraph与Harness的三层分工

这两天把 DeepAgent 相关的几个概念从里到外捋了一遍发现最容易卡住的不是模型调用而是 Harness、LangChain、LangGraph 这三层到底怎么分工。这篇教程就按我实际跑通一个企业级 Agent 的顺序来写先说结论DeepAgent 这类框架核心不是“能调用大模型”而是把大模型、工具、记忆、流程和工程控制绑在一起变成一条可运行、可观测、可恢复的任务流。适合谁看准备做 Agent 应用开发、刚接触 LangChain 和 LangGraph、或者已经被“Agent 跑起来容易、跑稳很难”折腾过的人。最值得关注的点是搞清楚 Harness 在 Agent 里的真实作用不要只盯着模型名称和 Prompt。现在很多讨论把三个概念混在一起结果一上来就陷入“该学 LangChain 还是 LangGraph”的争论。我的做法是先把它们放回各自的位置LangChain 是一堆成熟组件的集合LangGraph 是负责流程编排的图执行器Harness 则是包裹在整个 Agent 外面的工程控制层。分开理解之后再做企业级落地会轻松很多。1. 先分清三层Harness、LangChain、LangGraph 各管什么1.1 LangChain组件库不是灵魂LangChain 解决的是“和大模型交互时经常重复造轮子”的问题。它把模型封装、Prompt 模板、Tool 包装、文档加载、向量库对接、记忆管理这些常见零件做成了统一接口。比如你接一个 OpenAI 兼容接口或者接一个本地模型服务在 LangChain 里通常只需要换一个 ChatModel 类后面调用逻辑不用大改。很多教程容易把 LangChain 讲成一个巨大的框架好像什么都能做。实际跑下来LangChain 的核心价值是组件库和抽象层它确实能减少重复代码但不会替你解决所有业务问题。业务逻辑、工具边界、异常处理还是要自己写。经常有人问“LangChain 是不是过时了”。我的看法是它作为组件层仍然有使用价值但作用边界在变化。现在更主流的做法是把 LangChain 里的模型调用、工具封装拿过来把流程编排交给 LangGraph而不是让 LangChain 的链式结构承担所有复杂控制逻辑。顺便说一个容易混淆的点LangChain、vLLM、PyTorch 不是同一层的东西。PyTorch 是底层深度学习框架vLLM 是推理服务LangChain 是应用层开发框架。选型时要看自己缺哪一层而不是把它们放到一个维度里比优劣。1.2 LangGraph用图把流程固定下来LangGraph 的核心思想很简单把 Agent 的执行流程定义成一张图图里有节点、边、条件路由。每个节点处理一段状态边决定下一步走向条件边按函数判断走哪个分支。相比传统 Chain 的线性调用LangGraph 的优点是循环、分支、多轮任务都能显式控制。一个 Agent 需要调用工具工具返回后又把结果喂回模型模型再决定继续调用还是结束。这个循环如果用 Python 手写 while 循环短期能用一旦加入多分支、记忆、失败重试、并发控制代码会很快变得不可维护。LangGraph 把这些控制逻辑建模成图状态一目了然。需要强调一点LangGraph 不是来替代 LangChain 的。它是编排层内部节点仍然可以使用 LangChain 的模型封装和 Tool 定义。它更像把原本松散的组织方式重新变成一张结构化的路线图。准备 LangChain 面试或做技术选型时这个区别要能讲清楚。1.3 Harness给 Agent 套上一件可控外壳Harness 这个词直译是“线束”或“捆绑”在 Agent 场景里指的是运行时的工程约束层。它的作用包括输入校验、超时控制、重试策略、日志捕获、结果格式校验、敏感信息过滤、调用次数限制。目的只有一个让 Agent 的运行过程可预测、可控制、可回到正常状态。现在搜索引擎里能翻到大量叫“xxx harness”的项目比如某个模型名加 harness或者某类工具名加 harness。这些项目通常代表作者在一个特定运行环境里把控制逻辑做通了但这不代表它能直接搬到你的生产环境。使用前先看它锁定了哪个模型、依赖哪些库、是否还在维护尤其要区分它是模型部署工具还是 Agent 编排工具。我一般会把 Harness 和 Agent 本体拆开想Agent 本体负责“思考”和“调用”Harness 负责“拦住明显错误”和“在出错时留下足够信息”。这种拆法在排查问题时非常好用很多线上问题并不是模型不会答而是外层控制没有做好。分层主要职责常见替代或配套方案LangChain模型封装、Tool 定义、Prompt 模板、文档处理直接写模型调用 SDKLangGraph状态图编排、条件路由、循环、持久化记忆手写 while 循环、状态机Harness超时、重试、日志、输入输出校验、限流Web 服务框架、外部任务队列2. 环境准备和最小可运行 Demo2.1 本地环境怎么搭在动手写代码之前先把环境准备好。我不建议一上来就搭全量的大模型本地部署环境学习阶段最重要的是把框架逻辑跑通。通用步骤是python -m venv .venv source .venv/bin/activate # Windows 使用 .venv\Scripts\activate pip install langchain langgraph langchain-openai这里的依赖版本没有固定值不同时期安装到的版本可能不一样。建议安装后先确认 import 是否正常再继续往下走。如果你准备接本地模型可以确认一下本地推理服务是否暴露了 OpenAI 兼容接口如果没有也可以先不接真实模型用一个返回固定文本的函数代替把编排流程跑通后再换真实模型。为什么一定要先确认接口兼容性因为 LangChain 的 ChatModel 抽象本质上都是标准化的invoke方法。只要你的模型来源能封装成这个接口后面所有编排代码都不用动。2.2 最小 Agent 示例下面这一段是示意代码重点是把“模型调用节点”的最小结构展示出来。不同版本 API 会有细节差异落地时以你实际安装版本的官方文档为准。from typing import TypedDict class AgentState(TypedDict): messages: list next_step: str def call_llm(state: AgentState): # 这里假设 llm 已经定义好 # llm ChatOpenAI(modelyour-model-name) response llm.invoke(state[messages]) return { messages: state[messages] [response], next_step: end }注意这个示例还没有把真实模型调用写进去只是告诉你每个节点要返回一份状态增量。后面我们会把call_llm注册到图里。我习惯先跑通单节点再看多节点。原因很简单单节点只牵涉模型的输入输出如果这里就报错大概率是模型封装、API Key、网络地址或消息结构的问题这些问题不解决后面加多少节点都会叠加更多干扰项。2.3 跑通后先记录四件事第一次跑通 Demo 后不要急着加功能。先把四个东西记下来状态对象的结构messages 里存的是什么类型是字符串还是消息对象。每个节点的返回字段哪些字段会被后续节点读取哪些只是临时数据。单次调用的耗时和日志路径日志是打到控制台还是文件能否定位到具体节点。失败时的报错信息报错发生在模型调用前还是节点返回后。这四条信息是后续加 Harness 的输入来源。没有这些基线数据后面出了问题只能靠猜。3. 企业级 Agent 的四个核心机制3.1 模型调用和工具调用Agent 的价值一半在工具调用一半在模型理解。模型负责判断“要不要调用工具、调用哪个工具、传什么参数”工具负责真正执行外部操作。二者必须稳定结合否则流程再好看也没用。工具调用的稳定性主要看三点参数绑定是否精确模型生成的工具参数能不能被正确解析成 JSON并匹配到函数入参。返回结果是否能被模型理解工具输出如果太长、太乱模型可能读不到关键信息。失败时是否有明确的错误信息工具抛出的异常能不能转换成模型能理解的描述而不是直接中断整个流程。现在工具接入也在走向标准化MCP 这类协议想解决的问题就是让工具描述和调用方式更统一。对于刚开始做 Agent 的人来说不要急着追每一个协议先把一个普通函数的调用跑通再考虑是否接入标准协议。3.2 记忆与状态管理多轮对话的 Agent 必须处理记忆问题。最直接的做法是把历史消息一起传给模型但这种方式有上限上下文窗口是有限的消息一多成本、延迟、准确率都会变差。更稳妥的做法是分层管理短期记忆当前任务轮次内需要保留的消息和状态。长期记忆需要持久化到外部存储的关键信息比如用户偏好、任务结果、中间摘要。LangGraph 里做记忆管理的常用方式是把记忆字段放进 State再通过 Checkpointer 或外部存储持久化。State 是图的全局状态每个节点都能读但最好只改自己负责的那一段字段。这样多轮对话时Agent 能知道自己在第几轮、已经拿到什么结果而不是每次从零开始。3.3 任务规划与条件路由早期 LangChain 里常见的 Agent 范式是 ReAct模型思考一下该调用什么工具调用完看结果再思考下一步。这个模式在简单任务上够用但在复杂任务里容易失控模型可能反复调用同一个工具或者在一个分支里越走越远。LangGraph 的改进是让流程显式化。你可以单独设计一个“规划节点”让模型把任务拆成步骤再走条件边判断下一步执行哪个节点。比如一个节点负责生成 SQL一个节点负责执行查询一个节点负责解释结果。条件边根据当前状态决定走查询节点还是直接结束。这么做不是限制模型能力而是把不确定的控制流尽量变成确定的分支。线上排查时只要看状态走到了哪个节点、条件边是怎么判断的就能定位问题。3.4 重试、超时与可观测性这是 Harness 的核心控制项。模型调用、工具调用、外部接口都可能失败不要把所有策略都塞进 Prompt。Prompt 负责引导模型行为Harness 负责兜底。实践中我会为每个调用环节单独配置超时时间避免一个工具或模型调用卡住整个流程。重试次数对偶发网络问题有用但不要无限重试。最大迭代轮数防止模型循环调用工具。日志记录记录每次调用的输入、输出、耗时、错误信息。如果做接口还要考虑并发数和限流。默认参数适合入门但生产任务必须单独压测找到当前机器和模型服务能扛住的并发上限。4. LangGraph 编排实战一个带条件分支的 Agent4.1 定义状态和节点下面这个例子展示一个很简单的流程模型先判断是否需要调用工具如果需要就进入工具节点否则直接结束。代码是示意风格具体的 API 以你安装的 LangGraph 版本为准。from typing_extensions import TypedDict from langgraph.graph import StateGraph, START, END class AgentState(TypedDict): messages: list need_tool: bool def call_llm(state: AgentState): # 这里用 llm.invoke 拿到模型返回 # 根据返回内容判断 need_tool 是 True 还是 False new_message {role: assistant, content: 我需要查询订单状态} return { messages: state[messages] [new_message], need_tool: True } def call_tool(state: AgentState): # 模拟调用外部工具 tool_result {role: tool, content: 订单已发货} return { messages: state[messages] [tool_result], need_tool: False }这里的关键点是每个节点返回的字段都要能在 State 里找到对应定义。节点之间不直接互相调用它们只负责更新状态。图会根据状态决定下一步走向。4.2 构建图和条件边def route_after_llm(state: AgentState): if state[need_tool]: return call_tool return END graph StateGraph(AgentState) graph.add_node(call_llm, call_llm) graph.add_node(call_tool, call_tool) graph.add_edge(START, call_llm) graph.add_conditional_edges(call_llm, route_after_llm) graph.add_edge(call_tool, END)条件边是 LangGraph 里最值得花时间理解的部分。route_after_llm函数读一下当前状态返回下一个节点的名字。这里就有判断逻辑工具是否需要被调用、当前任务是否已经完成、失败时是否要走兜底节点。注意不同版本的 LangGraph 对add_conditional_edges的写法可能有调整有的版本需要传映射字典有的版本直接返回节点名即可。遇到报错时第一件事不是改逻辑而是去确认当前版本的 API 签名。4.3 编译和调用app graph.compile() result app.invoke({ messages: [{role: user, content: 我的订单到哪了}], need_tool: False }) print(result[messages])调用compile()之后图就变成一个可执行对象。第一次运行后建议打印一下result的结构确认messages里累积了哪些消息need_tool是否按预期变化。如果输出和预期不一致不用急着改图。先在节点里加打印或者日志看看每个节点返回的状态增量到底是什么。状态传递如果出错问题往往出在某个节点的返回字段上而不是图结构本身。4.4 从单轮走向多轮单轮跑通之后再加多轮和持久化。LangGraph 里可以通过 Checkpointer 保存状态快照让同一份图在不同会话之间恢复历史状态。具体做法是编译时传入一个持久化对象调用时带上线程 ID 或会话 ID。不要一上来就设计非常复杂的记忆系统。先把“状态能够保存、能够恢复”跑通再考虑哪些字段该长期保存、哪些字段该清理。记忆设计过于复杂很容易变成后续排查的负担。5. 底层原理为什么 Agent 会乱跑怎么把它拉回来5.1 状态传递的边界LangGraph 的底层可以理解成一个图执行器每次调用从 START 节点进入按边走向下一个节点每个节点更新 State图再根据新状态决定是否继续。状态传递看起来是自动的但有个隐藏问题如果某个节点返回的字段类型和图定义不一致或者更新了未定义的字段后续节点拿到的 State 可能就是脏数据。最常见的情况是 messages 列表里混入了字符串和消息对象导致模型调用时直接报格式错误。我的建议是State 类型定义要尽量精简。只放流程真正需要的字段不要图方便把所有临时变量都塞进去。临时计算放在节点内部不放到全局 State。5.2 工具调用的确定性和异常工具调用一旦接入真实系统问题会立刻变多。外部 API 可能超时数据库可能连接失败文件路径可能不存在权限可能不足。很多看起来像模型问题的报错实际是工具层抛出来的。为了让流程可控我一般会在工具层做三层保护工具入口做参数校验先检查必要字段是否传入。调用外部服务时设置超时不把整个 Agent 的寿命绑定在一个网络请求上。工具异常统一转换为结构化错误比如返回{error: database timeout}而不是直接抛一个让模型看不明白的堆栈。工具调用尽量保持幂等。同一个参数执行一次和五次结果应该一致或者至少不会产生破坏性副作用。否则重试机制本身就变成风险。5.3 日志与追踪很多 Agent 看起来是“跑完了”但你不知道它内部经历了什么。排查问题的前提是能看到每一步发生了什么。不需要一开始就上重型的可观测系统。先做两件事在关键节点记录结构化日志节点名、输入摘要、输出摘要、耗时、错误信息。给每次调用打上 trace_id 或 request_id让同一次请求的所有日志可以串起来查。如果用了 LangChain 或 LangGraph 的官方回调机制也能从中拿事件。但回调只是管道真正有价值的是你在关键节点埋了什么日志。埋点时注意日志内容不要包含敏感信息比如密钥、完整手机号、用户隐私内容。5.4 资源占用怎么判断学习阶段一个普通笔记本就能跑。进入生产化之后资源占用必须量化判断。常见指标包括单次请求的往返耗时。模型调用占用的显存或内存。并发上升时CPU、内存、网络 IO 的变化。工具调用阻塞 Agent 的最大时间。低配置环境不是不能试但要把参数降下来减少并发数、缩短上下文长度、限制工具数量、避免大文档一次性放入上下文。我的经验是先用小样本把流程逻辑验证清楚再按资源上限慢慢加压不要一上来就开最大并发。6. 三层排查链路6.1 模型层报错、空输出、格式乱模型层的问题最容易误导人因为报错可能出现在调用之后也可能出现在调用之前。排查顺序先看原始模型返回内容不要只看最终结果。确认 API Key、模型名、base_url 是否正确。确认消息结构是否符合模型要求比如 role 字段是否合法。确认上下文长度有没有超过模型限制。确认返回内容是否被工具解析器正确读取。如果模型返回为空先看是不是 Prompt 要求不明确再看是不是上下文被截断最后再考虑模型本身的问题。不要一上来就责怪“模型能力不行”。6.2 工具层参数绑定、权限、路径工具层出问题时错误信息通常很直白但容易被忽略。典型问题工具名拼写不一致。模型生成的工具参数是字符串但函数需要数字或对象。工具执行目录不对相对路径找不到文件。工具运行环境缺少权限读不了文件或连不上服务。工具返回内容过大导致后续模型读取困难。排查时先把工具单独拿出来测一遍用固定参数调用确认工具本身没问题再把 Agent 接回来。这样能快速区分是模型理解的问题还是工具实现的问题。6.3 编排层卡住、循环、状态丢失编排层的问题往往是最隐蔽的因为代码没报错但流程就是不对。常见现象和判断方法现象优先排查点Agent 一直调用同一个工具条件边是否失效最大迭代轮数是否配置多轮之后忘记之前的内容State 字段是否被覆盖记忆是否持久化流程走到不期望的节点条件判断读取的字段是否被更新状态恢复后结果不一致Checkpointer 和线程 ID 是否配对偶发失败是否有超时、重试、日志是否完整遇到编排层问题我会先把整个图的节点和边打印出来确认图结构不是从网上复制后随意改造出来的。然后看状态流转到哪个节点开始偏离预期最后在偏离节点里加详细日志。注意不要同时修改多个参数再跑测试。一次只改一个变量否则出了问题很难定位是哪次修改引入的。7. 3小时学习路线和落地边界7.1 学习路线怎么安排按标题说的 3 小时可以这样拆第一个小时LangChain 核心组件。重点看模型封装和 Tool 定义跑通一个“模型调用 工具调用”的最小示例。不要在这阶段研究所有文档目标是知道每次调用输入输出长什么样。第二个小时LangGraph 状态图。把线性链改成图理解 State、节点、条件边。跑通带条件分支的流程多打印几次状态变化感受图执行和普通函数调用的区别。第三个小时Harness 工程控制。给 Agent 加超时、重试、日志、输入输出校验。把一个只能“跑通”的 Demo改造成一个能“看到每次调用发生了什么”的可控程序。这三个小时结束后你应该建立一条主线组件从哪拿、流程怎么编排、工程控制怎么做。剩下的是查文档、看源码、踩业务坑这些是持续积累的部分。7.2 什么情况不需要这套框架不是所有项目都需要 LangGraph 加 Harness。如果只是做一个单轮问答、固定提示词的接口调用直接用普通 Chain 或者直接调用模型 SDK 反而更清晰。以下情况可以考虑简化流程是固定的线性步骤没有分支和循环。工具数量很少且调用顺序固定。请求量和风险都很低不需要复杂重试和追踪。团队成员对 LangGraph 不熟悉维护成本高于收益。框架的最大价值在复杂流程里体现。简单任务强行上框架只会增加样板代码和排查负担。7.3 生产化之前必须补的工程项如果确定要上生产下面这几项建议放在功能开发之前考虑输入输出校验用户输入不可信工具返回不可信都要做类型和长度校验。敏感信息过滤日志、错误信息里不要出现密钥、手机号、身份证等敏感字段。失败重试和任务队列长时间任务或高频任务不能只靠同步调一次。版本锁定LangChain、LangGraph、模型封装库的版本要固定不然升级一次就可能出现 API 不兼容。压测用小并发、中并发、目标并发分别跑一轮确认资源曲线和失败率。最后留一个我自己的判断习惯先把单任务跑稳再考虑批量和接口。很多项目失败不是因为 Agent 不聪明而是前置环境、输入格式和失败重试没有处理干净。DeepAgent 这套框架真正落地时最该盯住的不是功能列表而是状态传递是否干净、工具调用是否稳定、日志是否够用。按这个顺序做至少能让你在 3 小时之后不仅“听说过”这套技术栈还能自己动手改出一个可靠版本。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门