LangGraph 架构避坑:智能体职责拆分与流式回调透传机制剖析

LangGraph 架构避坑:智能体职责拆分与流式回调透传机制剖析
背景本章节主要针对使用 LangChain/LangGraph 框架设计智能体可能会遇到的问题并剖析其原理。不考虑 subgraph一、问题背景许多开发者在设计智能体时都会关注 Token / First Token / 耗时 / 长对话幻觉问题。 这些问题的本质其实是一个智能体即 LangChain 的create_agentAPI上加载了太多系统提示词sys_prompt和tool。原因LangChain 的智能体在运行时会把系统提示词和工具的描述统一加载到上下文中。太长的描述性文字、约定性文字、强制的规则致使模型无法做出优先级评判或是无法针对指定场景做出指定回应。可能产生的影响Token 飙增对于对话任务来说未经拆分职责的智能体所加载的提示词Tool 的描述也会加载过长。若用户只询问了“11几”的简单问题场景智能体不必要加载业务上的复杂提示词。模型幻觉大量提示词中对不同场景下约定模型的输出规则产生重叠或指代不清对于私有化部署的小模型而言会放大这一现象导致工具调用混乱或不调用工具从而产生幻觉。耗时同理大量 Token 加载后模型单个任务需要处理的耗时显著增加。...为了解决这些问题我们通常会考虑对智能体进行职责拆分。二、拆分的场景2.1 类 skill 技能的拆分LLM 智能路由原理将不同职责的提示词、工具等统一定义为一个 langchain-agentcreate_agent并封装为 skill 节点。路由时通过 LLM 判断进入到哪个节点。将二者都编排到 LangGraph 图中。思想graph 图编排——外层首先过一个 LLM依据提示词指定输出用户意图节点的标识完成路由随后 graph 进入对应节点执行。2.2 包装 Tool 拆分依靠主 Agent 完成路由原理将不同职责的提示词、工具等统一定义为一个 agentcreate_agent随后将调用智能体的方法/函数暴露出来定义针对该智能体的 LangChain-Tool描述中交代模型何时调用该 Tool。此 Tool 挂在外层与用户交互的主 Agent 上完成拆分。思想职责智能体封装为工具主 Agent 依靠提示词sys Tool 描述完成路由执行。注意以上是常见的两种设计思路在落地实现时可能稍有不同不过原理都大同小异。不考虑 subgraph 的前提三、为什么这样设计我们先说一下包装 Tool 做拆分。这个思路是什么其实是利用了 LangChain 官方推荐的思想模型封装工具。这个很大的好处是在对 LangGraph 的了解不太深入的前提下只利用简单的 LangChain-Tool-API 完成同时也能优化上述提到的那些问题。 但这种方法只适用于初期设计智能体开发的场景。首先主 Agent 无法控制进入 Tool 之后的 query 是什么因为与用户交互的是主 Agent内部分析后判断需要调用工具但无法直接透传用户交互的原生语言。使得主 Agent 只能通过上下文进行判断。这就导致了在整个环节模型内部的运行始终对我们不可见以及区分记忆的问题。捡了西瓜又丢了西瓜职责智能体内部的提示词虽然好维护但仍需关注它被包装的 Tool 的描述以及主 Agent 路由提示词的设计。这在某些用户交互的精细化场景下可能对 Prompt 字数反而没有好的提升。接下来我们再讨论一下类 skill 模式 LLM 路由的场景。这种场景其实可以通过代码实现编排但逻辑较为复杂我们通常会使用 LangGraph保证用户交互的流程是可追溯的。乍一看这种场景其实也能完成我们想要的效果。事实上我通过测试发现确实性能、Token 都维持在接受范围内。但唯一一个不好的是在使用 LangGraph 后整体的流程几乎被打散了因为我们引入了LLM 智能路由。很多人对路由这个问题都有不同的见解。比如规则引擎、同义词匹配等方式也都能路由到正确的节点。其实我的理解最好还是使用 LLM这一点会很大程度上降低模型的幻觉问题因为它可以按照提示词输出我们想要的标识从而进入该节点完成对应职责。对于幻觉而言我认为这种是最好的解决思路。那它有什么问题呢图节点因为自定义智能体导致消息机制被阻断这个问题比较难理解。因为 LangGraph 本身是有一套消息传播机制的尤其是涉及到多智能体的场景。使用 LLM 并编排进 graph 节点后此时我们创建的智能体create_agent被编排到 LLM 路由后的节点。这将导致消息流在我们创建的智能体处被打断。四、LangGraph 的流传播机制为什么消息流会被打断我们需要深入剖析 LangGraph 的流传播机制。在 LangGraph 中图的状态流转和事件冒泡是强依赖于节点边界的。当一个节点被定义为一个普通的 Python 异步函数时对于主图来说这个函数就是一个“黑盒”。如上图所示当我们在Skill_1节点内部使用create_agent并通过await agent.ainvoke()调用时会发生以下情况回调上下文未透传顶层astream(stream_modemessages)生成的流式回调监听器并没有自然地注入到create_agent的底层模型中。因为主图只负责把状态传给节点函数而节点函数内部又建立了一个独立的执行环境。状态更新掩盖流式输出由于使用了ainvoke阻塞调用节点必须等待内部 Agent 彻底生成完毕才会把最终结果作为一个完整的AIMessage通过return返回给主图。这就导致主图收到的是一次性的状态更新updates模式而不是细粒度的逐 Token 流messages模式。消息流被打断的表象用户在前端体验到的就是“打字机效果失效”AI 的回复要么是一大段突然跳出要么因为状态合并机制把历史上下文错误地暴露出来。总结LangGraph 的流式传播机制依赖于图节点的原生特性。在“不考虑 subgraph 纯嵌套”的前提下如果我们强行在图节点内部通过函数包裹并ainvoke一个独立的 Agent就会破坏 LangGraph 默认的回调透传链路导致消息机制被阻断。这是我们在采用“类 skill 拆分 LLM 路由”架构时必须面对并通过额外手段如显式透传config、或改写为astream手动消费去修补的核心痛点。