LangChain Deep Agents:System Prompt四层叠加机制深度解析 📅 2026/8/11 8:57:00 1. 项目概述从“手撕”到“理解”看到“手撕源码”这个标题很多开发者朋友会心一笑这背后是一种直接、硬核的学习态度。今天我们要拆解的是 LangChain Deep Agents 中一个看似基础实则暗藏玄机的部分System Prompt 的组装。如果你正在构建基于大语言模型的智能体Agent或者在使用 LangChain、LangGraph 时感觉对 Agent 的行为控制力不足那么这篇文章就是为你准备的。简单来说System Prompt 就是给大模型LLM的“角色设定”和“工作指令”。它决定了模型以什么身份思考遵循什么规则输出什么格式。在 Deep Agents 的架构里这个指令并非简单的一句话而是通过一个四层叠加机制精心构建出来的。理解这个机制你就能从“调用 API”的层面跃升到“设计智能体思维”的层面真正掌控 Agent 的行为逻辑。无论是想解决 Agent 的“胡说八道”问题还是想让它更精准地调用工具、遵循复杂流程核心钥匙都握在 System Prompt 的组装逻辑里。2. 核心需求解析为什么需要四层叠加在深入代码之前我们必须先回答一个根本问题一个简单的 System Prompt 不行吗为什么 LangChain Deep Agents 要设计得如此复杂这源于生产环境中 Agent 面临的几个核心挑战2.1 挑战一职责分离与模块化一个复杂的 Agent 可能同时承担多种职责它需要理解用户意图、规划执行步骤、安全地调用工具、格式化输出结果。如果把所有指令都塞进一个巨大的 Prompt 里会带来维护灾难。任何细微的改动都可能产生不可预知的副作用。四层叠加的本质是关注点分离。每一层负责一个明确的职责通过组合而非修改来构建最终指令这使得系统更健壮、更易维护。2.2 挑战二动态上下文与状态管理Agent 在运行过程中是有状态的。例如在一个多轮对话中它需要记住之前的交互历史在一个执行链条中它需要知道当前步骤和后续规划。这些动态信息状态需要被实时地注入到给模型的指令中。静态的 Prompt 无法做到这一点。叠加机制中的某些层就是专门为了动态注入运行时状态而设计的。2.3 挑战三安全与可控性直接让模型自由发挥是危险的。我们需要定义边界哪些工具能用参数格式是什么输出必须遵守什么模板如何防止 Prompt 注入攻击这些约束性、安全性的规则需要被明确、强制地传达给模型。通过一个独立的层来承载这些“硬性规定”可以确保无论其他指令如何变化安全基线始终稳固。2.4 挑战四灵活性与可定制性不同的应用场景对 Agent 的要求天差地别。一个数据分析 Agent 和一个客服 Agent 的“人设”和规则完全不同。叠加机制提供了一种“乐高积木”式的定制方式。开发者可以通过启用、禁用或替换某一层快速组装出适合特定场景的 Agent而无需重写整个 Prompt 逻辑。因此这四层叠加并非炫技而是为了解决上述实际工程问题而演化出的最佳实践。接下来我们就一层层剥开它的神秘面纱。3. 四层 System Prompt 结构全解构LangChain Deep Agents 的 System Prompt 组装可以形象地理解为给模型穿上一层层“制服”和“装备”。每一层都赋予了模型特定的能力和约束。通常这四层从基础到具体依次是3.1 第一层基础角色与能力层这是最底层定义了 Agent 的“出厂设置”。它不涉及任何具体任务而是设定了模型的基础行为范式和能力边界。核心内容身份声明例如“你是一个强大的人工智能助手”。核心能力描述强调模型在推理、规划、代码、知识等方面的通用能力。基础行为准则如“乐于助人”、“保持准确”、“在不确定时主动询问”。输出格式引导鼓励使用清晰、结构化的语言如分点、列表。源码中的体现这一层通常来源于 Agent 类型AgentType的默认配置或是BaseChatModel自带的系统指令。在langchain.agents的create_react_agent或create_openai_tools_agent等函数中你会找到一个基础的、通用的 Prompt 模板作为起点。设计意图确立一个稳定、通用的基座。所有定制都基于这个“标准模型”进行保证了行为的一致性下限。注意很多初学者会忽略这一层直接堆砌任务指令导致模型“人格”不稳定。一个稳固的基础层能让后续的定制效果事半功倍。3.2 第二层任务与领域规范层这一层将 Agent 从“通用助手”聚焦到“特定领域专家”。它定义了本次交互的核心任务范畴和领域知识要求。核心内容任务目标明确告知模型当前会话的核心目标是什么。例如“你将协助用户进行数据分析主要涉及 SQL 查询和数据可视化建议”。领域知识边界划定讨论范围避免模型“越界”。例如“本次对话仅围绕提供的数据库表结构进行不讨论其他无关话题”。领域特定术语与规则解释该领域内的重要概念或默认规则。源码中的体现这通常由开发者在创建 Agent 时通过system_message参数或自定义的PromptTemplate传入。在AgentExecutor初始化时这个自定义的系统消息会与基础层进行融合。设计意图实现任务的聚焦。它像是一个“滤镜”让模型的知识和推理能力集中到当前问题上减少无关输出的噪音。3.3 第三层工具使用与行动规范层这是 Agent 架构中最关键、最具特色的一层。它明确赋予了模型使用外部工具的能力并规定了严格的行动格式。核心内容工具列表与描述以结构化方式列出所有可用的工具包括每个工具的名称、描述、参数格式。这是模型能否正确调用工具的关键。行动指令明确告诉模型“如何思考”和“如何行动”。最经典的范式是 ReAct (Reasoning Acting)Thought: 思考下一步Action: 工具名Action Input: JSON格式输入。输出解析指令告诉模型在得到工具返回的Observation后应该如何继续继续思考还是给出最终答案Final Answer。格式强制要求严格规定模型必须使用Thought/Action/Action Input/Observation这样的标记语言这对于后续的解析器OutputParser正常工作至关重要。源码中的体现这一层的构建逻辑高度封装在langchain.agents的各种Agent类中。例如ZeroShotAgent的create_prompt方法会动态生成一个包含工具描述的 Prompt 模板。核心函数会遍历tools列表将每个工具的名称和描述格式化后插入到预设的模板字符串中。设计意图实现模型与环境的交互。它将大语言模型的“思考”能力转化为可执行、可追踪的“行动”序列是 Agent 智能的核心体现。3.4 第四层运行时状态与上下文注入层这是最动态的一层在每次调用模型时实时生成并插入。它包含了当前会话的具体状态信息。核心内容对话历史当前轮次之前的用户输入和模型Agent输出。这是实现多轮对话的基础。中间步骤对于已经执行过的Thought-Action-Observation循环会将其完整记录并注入让模型知道“我已经做了什么得到了什么结果”。当前目标/问题用户当前轮次的具体输入。临时指令或元数据例如本次调用需要特别注意的临时规则。源码中的体现这一层的组装发生在AgentExecutor的_call或_atake_next_step等方法中。代码会从agent_state或intermediate_steps中提取历史步骤和对话然后将其填充到已经组装好前三层的 Prompt 模板的特定位置通常是{chat_history}、{agent_scratchpad}或{intermediate_steps}这样的占位符。设计意图提供连续性和记忆。它确保了 Agent 不是“金鱼记忆”而是能够基于完整的上下文进行连贯的推理和行动规划。四层叠加的最终效果就像是一份不断演进的“任务简报”基础层是士兵手册规范层是本次作战的战场地图工具层是配发的武器装备和使用说明书而状态层就是实时更新的敌情通报和已完成的战术动作记录。模型拿到这份完整的简报才能做出最符合预期的决策。4. 源码追踪组装逻辑的实战拆解理论讲完了我们直接进入langchain源码以当前主流版本为例看看这四层是如何在代码中具体实现的。我们将追踪一个标准 OpenAI Tools Agent 的创建和执行流程。4.1 入口create_openai_tools_agent函数这是最常见的创建方式。我们关注其prompt参数。# 简化示意非逐行源码 from langchain.agents import create_openai_tools_agent from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder # 第二层任务规范和第四层上下文占位通常在这里定义 prompt ChatPromptTemplate.from_messages([ (“system”, “你是一个专业的编程助手。”), # 第二层任务规范 (“human”, “{input}”), # 第四层的一部分当前用户输入 MessagesPlaceholder(variable_name“agent_scratchpad”), # 第四层中间步骤关键 ])这里我们手动定义了第二层“你是一个专业的编程助手。”并为第四层预留了位置agent_scratchpad。那么第一层和第三层去哪了4.2 核心组装OpenAIToolsAgent的内部逻辑当调用create_openai_tools_agent时它会实例化一个OpenAIToolsAgent。这个类的create_prompt或construct_scratchpad方法负责真正的组装。第一层基础层对于 OpenAI 模型其本身就有很强的内置系统指令。LangChain 的ChatOpenAI在调用时可能会将我们传入的systemmessage 与模型默认的指令结合。在某些 Agent 实现中会有一个最基础的模板例如强调“用 Thought/Action/Action Input 格式响应”。第三层工具层的注入这是最精妙的部分。OpenAIToolsAgent会利用 OpenAI 最新的function calling能力。它不会像 ReAct 那样将工具描述以文本形式硬塞进 Prompt而是将tools列表转换为 OpenAI API 支持的tools(原functions) 参数在 API 调用时单独传入。# 类似逻辑的示意 llm_with_tools llm.bind_tools(tools) # 关键步骤将工具绑定到LLM这对应第三层bind_tools方法内部会将每个工具的函数签名名称、描述、参数 schema转换成 OpenAI 要求的格式。当模型收到用户输入和系统指令后它可以“看到”这些可用的函数并决定是否、如何调用它们。这比旧的文本注入方式更可靠、更标准化。4.3 动态填充AgentExecutor的工作AgentExecutor是驱动循环的引擎。在每一步它要做的是准备第四层状态层从intermediate_steps中提取之前所有的(AgentAction, Observation)对并将其格式化成一段文本对于 ReAct 格式或一系列消息对于 OpenAI Tools 格式填入agent_scratchpad这个占位符。调用模型将组装好的 Prompt已包含一、二、四层和绑定的 Tools第三层一起发给 LLM。解析输出根据模型返回的格式可能是包含tool_calls的 AIMessage决定下一步是执行工具还是返回最终答案。# 在 AgentExecutor 的某个步骤中组装最终输入 def _prepare_inputs(self, state): # state 中包含 input, intermediate_steps, chat_history 等 inputs {“input”: state[“input”]} # 将历史步骤格式化为 scratchpad inputs[“agent_scratchpad”] self.agent.construct_scratchpad(state[“intermediate_steps”]) # 如果 prompt 中有 chat_history 占位符也在此处填充 if “chat_history” in self.agent.prompt.input_variables: inputs[“chat_history”] state[“chat_history”] return inputs组装顺序总结静态模板定义开发者通过ChatPromptTemplate定义框架包含第二层system和第四层占位符human input, scratchpad。工具绑定通过llm.bind_tools(tools)将第三层工具规范以 API 原生方式附加到 LLM 调用上。运行时填充AgentExecutor在每次迭代时将具体的input和格式化后的intermediate_steps第四层动态内容填入模板。模型接收LLM 最终收到的是一个完整的消息列表包含系统消息、历史消息、当前问题以及可用的工具列表。5. 潜规则与高级定制技巧理解了标准流程我们才能玩转那些“潜规则”进行高级定制。这些是官方文档不会明说但实践中至关重要的经验。5.1 潜规则一层的覆盖与优先级当不同层的内容发生冲突时谁生效虽然没有明文规定但实践表明越具体的层优先级越高。第四层具体输入和历史的影响力最大其次是第三层工具指令然后是第二层任务规范最后是第一层基础角色。模型会更关注最新的、最具体的指令。系统消息System Message的合并如果你通过prompt参数传入了一个system消息它通常会覆盖或深度融合模型默认的基础系统指令第一层。这意味着你需要承担起定义基础行为的责任。实操心得如果你想强化某个行为比如“必须用中文回答”最好在第二层任务规范中明确写出而不是依赖可能被覆盖的基础层。对于关键约束甚至可以在第四层的当前用户输入前以Human消息的形式再次强调。5.2 潜规则二agent_scratchpad的格式是胜负手agent_scratchpad的格式化方式直接决定了模型能否理解历史动作。不同的 Agent 类型AgentType有不同的格式。ReAct 格式将(AgentAction, Observation)转换成Thought: ... Action: ... Action Input: ... Observation: ...的纯文本。这要求模型必须严格按照此格式输出。OpenAI Tools 格式将历史转换为AIMessage包含tool_calls和ToolMessage的列表。这是更现代、更结构化的方式与 API 原生格式对齐可靠性更高。自定义技巧如果你发现 Agent 总是忘记历史或格式出错可以去检查Agent.construct_scratchpad方法。你可以继承并重写它输出更符合你需求或特定模型偏好的历史格式。5.3 潜规则三工具描述的“艺术”工具的描述description和参数模式args_schema是第三层的核心写得好坏直接影响工具调用的准确率。描述要具体、包含意图不要只写“查询数据库”要写“根据用户提供的自然语言描述生成相应的 SQL 查询语句并执行返回查询结果。用户描述可能涉及筛选、排序、聚合等操作。”参数 schema 要严谨使用 Pydantic 模型定义参数并写好每个字段的description。这会被转换成 JSON Schema 供模型理解。字段描述要清晰例如date_range: str Field(description“时间范围格式为 ‘YYYY-MM-DD:YYYY-MM-DD’”)。控制工具数量一次性给模型太多工具比如超过10个会显著增加其认知负荷可能导致混乱。根据当前任务上下文动态加载工具集是更优方案。5.4 高级定制实现动态系统提示有时我们需要系统提示根据运行状态动态变化。这超出了标准四层的静态组合。实现方法如下自定义 Agent 类继承Agent基类重写create_prompt或plan方法。在AgentExecutor的步骤中注入通过自定义pre_running钩子或修改_prepare_inputs方法在运行时根据state动态修改或添加system消息。# 简化示例在运行时添加动态指令 class DynamicPromptAgentExecutor(AgentExecutor): async def _atake_next_step(self, state): # 根据状态判断是否需要特殊指令 if state.get(“is_critical_phase”): dynamic_instruction “注意当前处于关键阶段请务必在最终答案前进行双重验证。” # 将动态指令插入到消息列表的 system 部分或作为新的 human 消息 # ... 修改 self.agent.prompt 或 inputs ... return await super()._atake_next_step(state)6. 常见问题排查与调试实录在实际使用中你一定会遇到 Agent 行为不符合预期的情况。下面是我踩过坑后总结的排查清单。6.1 问题Agent 不调用工具直接给出答案可能原因 1工具描述不清。模型不理解工具是干什么的或者认为不需要工具就能解决问题。排查检查工具的description是否足够清晰、具体。模拟用户思考“看到这个描述我知道该在什么时候用它吗”解决重写描述强调工具的必需性和专用性。例如加上“你必须使用本工具来获取XXX信息”。可能原因 2系统提示过于强势。如果系统消息里说“你是一个知识渊博的助手请直接回答问题”模型可能被鼓励跳过思考步骤。排查审查第二层任务规范和第一层基础角色的指令是否包含了抑制工具使用的隐含信息。解决在指令中明确要求模型进行思考和使用工具。例如“对于需要查询数据、进行计算或执行操作的问题你必须首先规划步骤并调用相应的工具。”可能原因 3agent_scratchpad格式错误。模型无法正确解析历史步骤导致它认为当前是第一次思考而非继续。排查打印出agent_scratchpad的最终内容检查其格式是否与 Agent 类型要求的完全一致。解决确保construct_scratchpad方法输出正确。6.2 问题Agent 陷入循环重复调用同一工具可能原因 1Observation 信息不足或模糊。工具返回的结果没有给模型提供足够的决策依据导致它重复尝试。排查检查工具的返回结果。是否清晰是否包含了模型做出下一步判断所需的所有信息解决优化工具的输出使其更结构化、信息更完整。例如查询无结果时返回“未找到符合条件的数据”而不是空列表或None。可能原因 2缺少终止条件或最终答案指令。模型不知道什么时候应该停止“思考-行动”循环。排查检查系统提示中是否明确说明了何时给出Final Answer。解决在 Prompt 中强化最终答案的指令。例如“当你拥有足够的信息来直接、准确地回答用户问题时请立即给出Final Answer:。”6.3 问题工具调用参数格式错误可能原因 1参数 Schema 定义不兼容。模型的函数调用功能对 JSON Schema 有特定要求。排查使用llm_with_tools llm.bind_tools(tools)后检查llm_with_tools.tools属性查看自动生成的 Schema 是否合理。解决确保工具函数的参数有明确的类型注解并使用 Pydantic 的Field提供描述。避免使用过于复杂的嵌套类型。可能原因 2模型“幻觉”出不存在的参数。排查模型有时会根据工具描述“脑补”参数。对比模型尝试调用的参数与 Schema 定义。解决在工具描述中明确参数的约束和示例。对于可选参数要说明其默认行为。6.4 调试技巧透视 Prompt 的最终形态最有效的调试方法是查看发送给模型的最终消息列表。# 方法一设置 LangChain 的调试回调 from langchain.callbacks import StdOutCallbackHandler agent_executor AgentExecutor(agentagent, toolstools, callbacks[StdOutCallbackHandler()]) # 运行时会打印详细的链式调用信息包括最终的 Prompt。 # 方法二在自定义代码中打印 # 在 AgentExecutor 的 _prepare_inputs 方法后或直接 monkey-patch LLM 的调用 original_generate llm._generate def debug_generate(messages, **kwargs): print(“ 发送给模型的最终消息 ) for msg in messages: print(f”{msg.type}: {msg.content}“) print(” 绑定的工具 ) print(kwargs.get(”tools“, [])) return original_generate(messages, **kwargs) llm._generate debug_generate通过查看这个最终形态你可以清晰地看到四层内容是如何叠加在一起的从而精准定位是某一层缺失、冲突还是格式错误。7. 从 Deep Agents 到 LangGraphSystem Prompt 的演进LangGraph 是 LangChain 中用于构建有状态、多智能体工作流的框架。在 LangGraph 中System Prompt 的管理思想一脉相承但有了更结构化的体现。7.1 节点Node作为 Prompt 的作用域在 LangGraph 中每个节点通常是一个函数或一个Runnable可以有自己的“上下文”。你可以为不同的节点设置不同的系统提示从而实现分阶段、分角色的 Prompt 管理。例如一个“分析员”节点和一个“决策者”节点可以拥有完全不同的系统指令。7.2 状态State作为 Prompt 的驱动源LangGraph 的State对象是工作流的核心。System Prompt 的第四层运行时状态可以更优雅地从State中获取。你可以在节点的逻辑中根据State的当前值动态构造或选择不同的系统提示。7.3 编译Compilation时的 Prompt 优化langgraph的compile过程会将工作流图编译成高效的执行逻辑。在这个过程中系统有机会对各个节点的 Prompt 进行静态分析和优化比如合并重复的指令提前验证工具绑定等。实践建议当你的 Agent 逻辑变得复杂涉及多步骤、多角色或复杂状态转移时就应该考虑从单纯的AgentExecutor升级到LangGraph。它将 System Prompt 的“层”的概念扩展到了工作流的“空间”和“时间”维度提供了更强大的控制能力。理解 LangChain Deep Agents 中 System Prompt 的四层叠加不仅仅是读懂了一段代码更是掌握了一种设计复杂 AI 智能体的思维模型。它教会我们如何通过分层、组合的方式来管理日益复杂的指令集如何在灵活性与可控性之间找到平衡。下次当你调试 Agent 行为时不妨从这四层逐一审视基础角色稳不稳任务聚焦准不准工具描述清不清历史状态全不全相信你会更快地找到问题的钥匙。