LangChain Deep Agents:System Prompt四层叠加机制深度解析
2026/9/16 3:07:27 网站建设 项目流程

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 的“出厂设置”。它不涉及任何具体任务,而是设定了模型的基础行为范式和能力边界。

  • 核心内容

    1. 身份声明:例如,“你是一个强大的人工智能助手”。
    2. 核心能力描述:强调模型在推理、规划、代码、知识等方面的通用能力。
    3. 基础行为准则:如“乐于助人”、“保持准确”、“在不确定时主动询问”。
    4. 输出格式引导:鼓励使用清晰、结构化的语言,如分点、列表。
  • 源码中的体现:这一层通常来源于 Agent 类型(AgentType)的默认配置,或是BaseChatModel自带的系统指令。在langchain.agentscreate_react_agentcreate_openai_tools_agent等函数中,你会找到一个基础的、通用的 Prompt 模板作为起点。

  • 设计意图:确立一个稳定、通用的基座。所有定制都基于这个“标准模型”进行,保证了行为的一致性下限。

注意:很多初学者会忽略这一层,直接堆砌任务指令,导致模型“人格”不稳定。一个稳固的基础层,能让后续的定制效果事半功倍。

3.2 第二层:任务与领域规范层

这一层将 Agent 从“通用助手”聚焦到“特定领域专家”。它定义了本次交互的核心任务范畴和领域知识要求。

  • 核心内容

    1. 任务目标:明确告知模型当前会话的核心目标是什么。例如,“你将协助用户进行数据分析,主要涉及 SQL 查询和数据可视化建议”。
    2. 领域知识边界:划定讨论范围,避免模型“越界”。例如,“本次对话仅围绕提供的数据库表结构进行,不讨论其他无关话题”。
    3. 领域特定术语与规则:解释该领域内的重要概念或默认规则。
  • 源码中的体现:这通常由开发者在创建 Agent 时,通过system_message参数或自定义的PromptTemplate传入。在AgentExecutor初始化时,这个自定义的系统消息会与基础层进行融合。

  • 设计意图:实现任务的聚焦。它像是一个“滤镜”,让模型的知识和推理能力集中到当前问题上,减少无关输出的噪音。

3.3 第三层:工具使用与行动规范层

这是 Agent 架构中最关键、最具特色的一层。它明确赋予了模型使用外部工具的能力,并规定了严格的行动格式。

  • 核心内容

    1. 工具列表与描述:以结构化方式列出所有可用的工具,包括每个工具的名称、描述、参数格式。这是模型能否正确调用工具的关键。
    2. 行动指令:明确告诉模型“如何思考”和“如何行动”。最经典的范式是 ReAct (Reasoning + Acting):Thought: (思考下一步)Action: (工具名)Action Input: (JSON格式输入)
    3. 输出解析指令:告诉模型在得到工具返回的Observation后,应该如何继续(继续思考,还是给出最终答案Final Answer)。
    4. 格式强制要求:严格规定模型必须使用Thought/Action/Action Input/Observation这样的标记语言,这对于后续的解析器(OutputParser)正常工作至关重要。
  • 源码中的体现:这一层的构建逻辑高度封装在langchain.agents的各种Agent类中。例如,ZeroShotAgentcreate_prompt方法会动态生成一个包含工具描述的 Prompt 模板。核心函数会遍历tools列表,将每个工具的名称和描述格式化后插入到预设的模板字符串中。

  • 设计意图:实现模型与环境的交互。它将大语言模型的“思考”能力,转化为可执行、可追踪的“行动”序列,是 Agent 智能的核心体现。

3.4 第四层:运行时状态与上下文注入层

这是最动态的一层,在每次调用模型时实时生成并插入。它包含了当前会话的具体状态信息。

  • 核心内容

    1. 对话历史:当前轮次之前的用户输入和模型(Agent)输出。这是实现多轮对话的基础。
    2. 中间步骤:对于已经执行过的Thought-Action-Observation循环,会将其完整记录并注入,让模型知道“我已经做了什么,得到了什么结果”。
    3. 当前目标/问题:用户当前轮次的具体输入。
    4. 临时指令或元数据:例如,本次调用需要特别注意的临时规则。
  • 源码中的体现:这一层的组装发生在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_promptconstruct_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是驱动循环的引擎。在每一步,它要做的是:

  1. 准备第四层(状态层):从intermediate_steps中提取之前所有的(AgentAction, Observation)对,并将其格式化成一段文本(对于 ReAct 格式)或一系列消息(对于 OpenAI Tools 格式),填入agent_scratchpad这个占位符。
  2. 调用模型:将组装好的 Prompt(已包含一、二、四层)和绑定的 Tools(第三层)一起发给 LLM。
  3. 解析输出:根据模型返回的格式(可能是包含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

组装顺序总结

  1. 静态模板定义:开发者通过ChatPromptTemplate定义框架,包含第二层(system)和第四层占位符(human input, scratchpad)。
  2. 工具绑定:通过llm.bind_tools(tools)将第三层(工具规范)以 API 原生方式附加到 LLM 调用上。
  3. 运行时填充AgentExecutor在每次迭代时,将具体的input和格式化后的intermediate_steps(第四层动态内容)填入模板。
  4. 模型接收: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 高级定制:实现动态系统提示

有时,我们需要系统提示根据运行状态动态变化。这超出了标准四层的静态组合。实现方法如下:

  1. 自定义 Agent 类:继承Agent基类,重写create_promptplan方法。
  2. 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:系统提示过于强势。如果系统消息里说“你是一个知识渊博的助手,请直接回答问题”,模型可能被鼓励跳过思考步骤。
    • 排查:审查第二层(任务规范)和第一层(基础角色)的指令,是否包含了抑制工具使用的隐含信息。
    • 解决:在指令中明确要求模型进行思考和使用工具。例如,“对于需要查询数据、进行计算或执行操作的问题,你必须首先规划步骤并调用相应的工具。”
  • 可能原因 3:agent_scratchpad格式错误。模型无法正确解析历史步骤,导致它认为当前是第一次思考,而非继续。
    • 排查:打印出agent_scratchpad的最终内容,检查其格式是否与 Agent 类型要求的完全一致。
    • 解决:确保construct_scratchpad方法输出正确。

6.2 问题:Agent 陷入循环,重复调用同一工具

  • 可能原因 1:Observation 信息不足或模糊。工具返回的结果没有给模型提供足够的决策依据,导致它重复尝试。
    • 排查:检查工具的返回结果。是否清晰?是否包含了模型做出下一步判断所需的所有信息?
    • 解决:优化工具的输出,使其更结构化、信息更完整。例如,查询无结果时,返回“未找到符合条件的数据”,而不是空列表或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(agent=agent, tools=tools, 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 到 LangGraph:System 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 优化langgraphcompile过程会将工作流图编译成高效的执行逻辑。在这个过程中,系统有机会对各个节点的 Prompt 进行静态分析和优化,比如合并重复的指令,提前验证工具绑定等。

实践建议:当你的 Agent 逻辑变得复杂,涉及多步骤、多角色或复杂状态转移时,就应该考虑从单纯的AgentExecutor升级到LangGraph。它将 System Prompt 的“层”的概念,扩展到了工作流的“空间”和“时间”维度,提供了更强大的控制能力。

理解 LangChain Deep Agents 中 System Prompt 的四层叠加,不仅仅是读懂了一段代码,更是掌握了一种设计复杂 AI 智能体的思维模型。它教会我们如何通过分层、组合的方式来管理日益复杂的指令集,如何在灵活性与可控性之间找到平衡。下次当你调试 Agent 行为时,不妨从这四层逐一审视:基础角色稳不稳?任务聚焦准不准?工具描述清不清?历史状态全不全?相信你会更快地找到问题的钥匙。

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

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

立即咨询