Agents 核心能力 [ 2 ]

指定提示词
接下来我们继续学习指定提示词相关内容,提示词分为两大类:静态系统提示词、动态系统提示词。
我们可以通过 system_prompt 参数控制 Agent 的初始行为,支持静态字符串和动态生成两种方式。
静态系统提示
先讲静态系统提示词,也就是代码里的system_prompt。静态提示词的逻辑很简单:我们在创建、构建 Agent 实例的时候,直接给system_prompt参数按照字符串形式或 SystemMessage 形式:直接传入提示文本。
传入一段固定描述文本,以此设定系统指令,这种一次性写死、运行全程不会变更的配置方式,就叫做静态系统提示词设定。
agent = create_agent(model, tools, system_prompt="你是一位乐于助人的助手。请简洁准确地表达。")
from langchain.messages import SystemMessage
agent = create_agent(
model="anthropic:claude-sonnet-4-5",
system_prompt=SystemMessage(
content=[
{"type": "text", "text": "你是一名负责分析文学作品的人工智能助手。"},
]
)
)
若未提供 system_prompt,Agent 会根据输入消息自行推断任务。
动态系统提示
除了静态写法之外,还有动态提示词的实现方案。我们先举一个业务场景方便大家理解:假设用户提问 “解释一下机器学习”,Agent 该用通俗大白话讲解,还是使用专业术语深度拆解?这个可以根据用户身份自动区分。 如果用户身份是初学者,我们就要求模型用简单易懂的大白话解释概念,避开专业术语;如果用户身份是行业专家,我们就要求模型使用专业名词,输出详尽完整的技术拆解。 不管是哪种输出风格,都需要一段对应的系统提示词来告知模型用户身份、回答规范,这就需要根据运行时场景动态生成提示词。
本次案例我们只用用户身份作为判断条件,把身份信息存放到运行上下文里,再读取身份做分支判断生成提示词;但这只是其中一种示例,只要是能从request中读取到的参数,不管是对话阶段、用户权限、会话状态,都可以作为动态生成提示词的判断依据。
下面通过完整实现流程来实现代码:我们先创建 Agent 实例,基础配置依旧先指定模型 gpt-4o-mini/deepseek。这里需要新增 context_schema 配置,用来规范上下文的数据结构。
我们自定义上下文类,有两种实现方式:注解写法 @dataclass 或者 继承 TypedDict。本次选择继承 TypedDict,内部定义 user_role 字段,用来存储用户身份标识。
from typing import TypedDict
from langchain_deepseek import ChatDeepSeek
model = ChatDeepSeek(
model="deepseek-chat",
temperature=0.0
)
class Context(TypedDict):
user_role: str
再看 Agent 调用逻辑,执行agent.invoke()时分为两块传参:
-
第一层字典是输入状态 input,里面存放用户对话消息,本次消息设置为 “解释一下机器学习”;
-
第二层独立参数
context,专门存放运行上下文,我们在这里传入{"user_role": "初学者"},用来模拟当前用户身份。
result = agent.invoke(
{"messages": [{"role": "user", "content": "解释机器学习"}]},
context={"user_role": "初学者"}
)
这里有个关键点:使用动态提示词时,创建 Agent 的参数里不能再写固定的system_prompt,如果写了固定文本就变成静态提示词,动态逻辑不会生效。想要实现动态生成,必须依靠专属中间件。【重点注意事项】
为什么不能同时存在? 因为框架内部的执行顺序是:构造参数中的 system_prompt 会优先于中间件执行。一旦检测到有静态值,框架就会直接使用它并跳过中间件的注入逻辑。所以,只要写了固定文本,动态逻辑就不会生效——这是 LangChain 官方明确的设计约束,而非 bug。
这里穿插一下:如果要使用 Graph 呢?如何实现动态提示词?
动态提示词的本质需求是在运行时注入上下文变量。在 LangGraph 架构下,我们不在 messages 数组上直接做物理覆写,因为消息列表遵循不可变追加原则,强行替换 messages[0] 会破坏状态一致性,尤其在多轮工具调用场景下容易引发历史错乱。
工程上的标准做法是在 State 中独立维护 system_prompt 字段(或 instructions),在真正调用 LLM 的节点前,由节点内部根据当前 State 动态拼接生成完整的 messages 列表再传给模型。
至于 Agent 和 Graph 的关系,Agent 本就是 Graph 的上层封装,它把提示词组装逻辑放在了预置节点里。但正因为 Graph 暴露了完整的节点编排能力,我们根本不需要依赖中间件在启动前拦截——直接在 Graph 的 State 流转中完成动态注入,比任何拦截方案都更干净、更可观测。
现在回过主题:这里给大家介绍一款全新的装饰器钩子,和之前学的before_model、before_agent、@wrap_model_call、@wrap_tool_call都不一样,它是专门用来生成动态系统提示词的专属装饰器 ——@dynamic_prompt。
我们基于这个装饰器定义处理函数,函数命名为user_role_prompt,用来根据用户身份拼接提示词:
-
函数入参仅接收一个
ModelRequest类型的 request 对象; -
函数返回值为字符串
str,这个返回的字符串就是最终生效的系统提示词;【也可以是 SystemMessage】 -
从 request 对象中可以读取模型、对话消息、运行时上下文、会话状态等全部参数,所有可读数据都能作为生成提示词的判断条件。
本次案例我们从运行上下文读取用户身份,读取逻辑: 从request.runtime.context中通过get方法读取user_role,同时设置默认值 “初学者”,如果上下文没有传入身份,默认按初学者处理。
@dynamic_prompt
def user_role_prompt(request: ModelRequest) -> str | SystemMessage:
"""根据用户角色生成系统提示词"""
user_role = request.runtime.context.get("user_role", "初学者")
base_prompt = "你是以为乐于助人的助手。"
if user_role == "专家":
return f"{base_prompt} 提供详尽的技术问答。"
elif user_role == "初学者":
return f"{base_prompt} 用简单易懂的语言解释概念,避免使用专业术语。"
return base_prompt;
接着定义基础提示词base_prompt,内容为 “你是一位乐于助人的专家。”,作为所有身份共用的基础指令。 然后做分支判断:
-
如果读取到
user_role等于 “初学者”:在基础提示词后追加约束,完整提示词为 “你是一位乐于助人的专家。用简单易懂的语言描述概念,避免使用专业术语。”,直接 return 拼接后的字符串; -
如果读取到
user_role等于 “专家”:追加专业输出约束,完整提示词为 “你是一位乐于助人的专家。尽量提供详细的技术解答,需要使用专业术语。”,return 拼接结果; -
兜底 else 分支:既不是初学者也不是专家,直接返回基础提示词
base_prompt。
定义好这个被@dynamic_prompt修饰的函数后,把它添加到 Agent 的middleware中间件列表中。后续每次执行 Agent,框架都会先执行这个中间件函数,读取上下文里的用户身份,自动生成匹配的系统提示词,再交给模型执行推理。
完整代码:
from typing import TypedDict
from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest
from langchain_core.messages import SystemMessage
from langchain_deepseek import ChatDeepSeek
model = ChatDeepSeek(
model="deepseek-chat",
temperature=0.0
)
class Context(TypedDict):
user_role: str
@dynamic_prompt
def user_role_prompt(request: ModelRequest) -> str | SystemMessage:
"""根据用户角色生成系统提示词"""
user_role = request.runtime.context.get("user_role", "初学者")
base_prompt = "你是以为乐于助人的助手。"
if user_role == "专家":
return f"{base_prompt} 提供详尽的技术问答。"
elif user_role == "初学者":
return f"{base_prompt} 用简单易懂的语言解释概念,避免使用专业术语。"
return base_prompt;
agent = create_agent(
model=model,
middleware=[user_role_prompt],
context_schema=Context
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "解释机器学习"}]},
# context={"user_role": "初学者"}
context = {"user_role": "专家"}
)
for msg in result.get("messages", []):
msg.pretty_print()
我们分两次运行测试验证效果:
第一次测试上下文传入user_role="初学者",执行完成后读取返回消息列表里最后一条 AI 回复,输出内容全程没有专业术语,使用生活化比喻讲解机器学习,完全符合初学者对应的动态提示词约束。
================================ Human Message =================================
解释机器学习
================================== Ai Message ==================================
想象一下,你教一个小孩认识苹果。你给他看很多苹果的图片,告诉他“这是苹果”。看多了之后,他就能自己认出新的苹果,哪怕这个苹果长得和之前看到的有点不一样。
机器学习差不多就是这个道理。它不是直接告诉电脑“苹果是红色的、圆形的”这种规则,而是给电脑看大量的例子(比如苹果的图片),让它自己从这些例子中找出规律。看多了,电脑就能学会判断一个新东西是不是苹果。
简单来说,机器学习就是让电脑通过“看例子”来学习,而不是靠人给它写死板的规则。
第二次把上下文身份改为user_role="专家",重新执行,模型输出会包含完整专业名词、英文全称、训练集、输入输出映射关系等深度技术内容,严格匹配专家专属提示词。
================================ Human Message =================================
解释机器学习
================================== Ai Message ==================================
机器学习是人工智能的一个核心分支,它使计算机能够从数据中学习模式,而无需进行明确的编程。简单来说,就是让机器通过“经验”(数据)自动提升性能。
核心思想是:**构建一个模型,利用算法从数据中学习规律,然后用这个模型对新的数据进行预测或决策。**
---
### 核心要素
一个典型的机器学习系统包含三个关键部分:
1. **数据 (Data)**:学习的“原材料”。通常分为:
- **特征 (Features)**:输入变量,用于描述数据。例如,预测房价时,面积、卧室数量、位置就是特征。
- **标签 (Labels)**:输出变量,即我们想要预测的目标。例如,房价本身。
2. **模型 (Model)**:学习到的“知识”或“规律”。它是一个数学函数或结构,将输入特征映射到输出预测。
3. **算法 (Algorithm)**:学习“方法”。它定义了如何从数据中调整模型参数,使模型预测更准确。
### 主要类型
机器学习通常分为三大类:
#### 1. 监督学习 (Supervised Learning)
- **特点**:使用**有标签**的数据进行训练。即每个训练样本都包含特征和对应的正确答案。
- **目标**:学习从特征到标签的映射关系,以便对新的、未见过的数据进行预测。
- **常见任务**:
- **分类 (Classification)**:预测离散的类别。例如:垃圾邮件检测(是/否)、图像识别(猫/狗)。
- **回归 (Regression)**:预测连续的数值。例如:房价预测、股票价格预测。
- **常用算法**:线性回归、逻辑回归、支持向量机 (SVM)、决策树、随机森林、神经网络。
#### 2. 无监督学习 (Unsupervised Learning)
- **特点**:使用**无标签**的数据进行训练。模型需要自己发现数据中的结构或模式。
- **目标**:探索数据的内部结构,如聚类、降维。
- **常见任务**:
- **聚类 (Clustering)**:将相似的数据点自动分组。例如:客户分群、新闻主题分类。
- **降维 (Dimensionality Reduction)**:在保留重要信息的同时,减少特征的数量。例如:数据可视化、压缩。
- **常用算法**:K-Means 聚类、层次聚类、主成分分析 (PCA)、t-SNE。
#### 3. 强化学习 (Reinforcement Learning)
- **特点**:智能体 (Agent) 在环境 (Environment) 中通过**试错**来学习。它根据行动获得奖励或惩罚,目标是最大化累积奖励。
- **目标**:学习一个策略,告诉智能体在每种状态下应该采取什么行动。
- **常见应用**:游戏AI(如AlphaGo)、机器人控制、自动驾驶。
- **常用算法**:Q-Learning、深度Q网络 (DQN)、策略梯度。
### 工作流程
一个典型的机器学习项目通常遵循以下步骤:
1. **问题定义**:明确要解决什么问题,是分类、回归还是聚类?
2. **数据收集**:获取足够、相关、高质量的数据。
3. **数据预处理**:清洗数据(处理缺失值、异常值)、转换数据(标准化、归一化)、特征工程(创建新特征)。
4. **模型选择**:根据问题类型和数据特点选择合适的算法。
5. **模型训练**:将训练数据输入算法,让模型学习。
6. **模型评估**:使用测试数据评估模型性能(如准确率、精确率、召回率、均方误差等)。
7. **模型调优**:调整模型超参数(如学习率、树深度)以提升性能。
8. **模型部署**:将训练好的模型集成到实际系统中,用于对新数据进行预测。
9. **监控与维护**:持续监控模型性能,并根据新数据定期重新训练。
### 简单例子:预测房价
- **问题**:根据房屋面积预测房价。
- **数据**:收集100套房屋的面积(特征)和成交价(标签)。
- **模型**:选择线性回归模型 `房价 = w * 面积 + b`。
- **训练**:算法自动寻找最佳的 `w` 和 `b`,使得模型在训练数据上的预测误差最小。
- **预测**:输入一套新房屋的面积(如120平米),模型输出预测价格。
### 总结
| 特性 | 监督学习 | 无监督学习 | 强化学习 |
| :--- | :--- | :--- | :--- |
| **数据标签** | 需要 | 不需要 | 不需要(通过奖励信号) |
| **目标** | 预测标签 | 发现结构 | 学习策略 |
| **反馈** | 直接(标签) | 无 | 延迟(奖励/惩罚) |
| **典型应用** | 分类、回归 | 聚类、降维 | 游戏、机器人 |
机器学习是一个庞大且快速发展的领域,以上是基础框架。如果你想深入了解某个特定算法或应用场景,可以随时提问。
最后补充拓展:本次演示只使用了运行时context上下文作为判断依据,实际上我们也可以从request.state会话状态中读取自定义参数,基于状态里的数据完成动态提示词生成,灵活适配各类业务场景。 到这里,Agent 指定静态、动态系统提示词的完整逻辑、代码编写、测试验证就全部讲解完毕。
指定结构化输出策略
接下来我们一起学习 Agent 指定结构化输出策略这项能力,先提前跟大家说明:这一小节我们不需要上手写大量业务代码,核心目标是搞懂结构化输出底层的两套实现策略、理解背后原理即可。
我们之前实操过结构化输出的基础用法:构建 Agent 的时候,自定义一个继承 Pydantic BaseModel 的类,把你想要的返回字段、数据格式全部定义好,再传给response_format参数,框架就能自动约束模型输出结构化数据。 但这种直接传 Pydantic 模型的便捷写法,是 LangChain 1.0 版本之后才推出的;在 1.0 版本之前,实现结构化输出必须显式指定两种底层策略中的一种。虽然新版本简化了调用方式,但底层执行逻辑没变,依旧是靠这两套策略支撑,所以我们要分开拆解理解。
两套底层策略分别是:提供者策略 ProviderStrategy、工具策略 ToolStrategy。
LangChain 通过 create_agent 中的 response_format 参数提供了实现此功能的策略。核心步骤:
- 使用 Pydantic 定义期望的输出格式(
BaseModel)。 - 在创建 Agent 时,将
response_format参数设置为对应的策略,并传入定义好的格式模型。
策略说明
LangChain 提供了两种主要策略,可以根据模型的支持情况和具体需求进行选择。
| 策略 | 原理 | 适用场景 | 特点 |
|---|---|---|---|
| ToolStrategy(工具策略) | 利用模型的工具调用(Tool Calling)能力,通过创建一个 “虚拟工具” 来迫使模型以调用该工具参数的形式输出结构化数据。 | 任何支持工具调用的模型。当模型不支持原生结构化输出或原生输出不可靠时使用。 | 通用性强,兼容性好。 |
| ProviderStrategy(提供者策略) | 直接使用模型提供商提供的原生结构化输出功能。 | 仅限支持原生结构化输出的模型(如 GPT-4o、Claude 3 等)。 | 更可靠、效率更高,是首选方案。 |
提供者策略 ProviderStrategy
提供者策略的核心逻辑:部分大模型厂商原生就内置了结构化输出能力,这个能力不是 LangChain 框架模拟出来的,是大模型本身原生支持的功能,这就是 “提供者策略” 名字的由来。 举个例子:GPT-4o、Claude 3 系列等模型,厂商原生开放结构化输出接口,能够直接按照指定 JSON / 对象格式返回数据。
老版本框架的写法要求:创建 Agent 配置结构化输出时,必须显式构造ProviderStrategy对象,再把我们定义好的 Pydantic 格式模型传入进去;而且绑定的模型必须是原生支持结构化输出的型号,否则这套策略无法生效。 现在新版本不用手动构造这个对象,框架会自动判断,但底层能走原生能力时,依旧会优先执行这套逻辑。
工具策略 ToolStrategy
如果我们选用的大模型原生不支持结构化输出,只能自由返回纯自然文本,这种场景下就需要 LangChain 框架介入,使用工具策略兜底实现结构化输出。
我们用流程对比两种策略的区别,方便大家理解底层逻辑:
提供者策略流程:用户输入 → 原生支持结构化的大模型 → 直接输出符合规范的结构化对象,全程由模型自身能力完成格式化;
工具策略流程:用户输入 → 原生仅能输出纯文本的大模型 → LangChain 底层自动生成一个虚拟假工具,通过工具调用机制强制格式化数据。
详细拆解工具策略的底层原理: 工具本身都具备固定参数 schema,LangChain 会创建一个无实际执行逻辑的虚拟工具,我们定义的 Pydantic 字段会直接映射成这个虚拟工具的入参规范。 模型原本只会输出自由文本,框架会引导模型:把本次要返回的全部信息,转换成调用这个虚拟工具所需的参数;而虚拟工具的参数结构,刚好和我们定义的结构化格式完全匹配。 模型输出文本后,框架会解析本次虚拟工具调用的入参,直接组装成我们需要的结构化对象,相当于靠一层虚拟工具做中转、转换,实现格式化输出。
老版本框架写法:原生不支持结构化的模型,配置response_format时,必须手动构造ToolStrategy对象,传入格式模型才能生效。
从 LangChain 1.0 版本开始,官方简化了 API,我们不用手动创建ProviderStrategy/ToolStrategy实例,只需要把写好的 Pydantic 模型直接传给response_format即可。 框架内部会自动做判断:
-
第一步优先校验当前使用的模型是否原生支持结构化输出,如果满足条件,自动使用提供者策略;
-
如果模型不支持原生结构化输出,自动降级兜底,使用工具策略;
这套自动判断机制,既简化了开发者的代码书写,同时兼容市面上所有大模型,兼顾了开发便捷性与全场景兼容性。
有一条硬性限制需要重点记住:预绑定工具的模型,无法和结构化输出功能搭配使用。 给大家解释含义: 我们之前写过单独实例化模型的代码,比如ChatOpenAI,实例化完成后可以调用.bind_tools()方法,提前给这个模型对象绑定好工具列表。 如果我们提前执行了bind_tools预绑定工具,再把这个处理过的模型传入create_agent,同时配置response_format结构化输出,这套组合是框架不支持的,会直接报错。
如果业务场景既要动态选择模型、又要使用结构化输出,必须保证传入中间件、Agent 的原始模型,没有提前调用过bind_tools做工具预绑定,规避这个冲突限制。
下面,我们演示一下上面解释的过程!
示例:从一段文本中提取联系人信息(姓名、邮箱、电话)。
第一步:定义输出格式
from pydantic import BaseModel
class ContactInfo(BaseModel):
name: str
email: str
phone: str
第二步:使用 ToolStrategy
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
# 创建Agent,指定使用ToolStrategy
agent = create_agent(
model="gpt-4o-mini", # 任何支持工具调用的模型
tools=[], # 此处为简化,可以无工具或传入其他工具
response_format=ToolStrategy(ContactInfo) # 传入ToolStrategy
)
# 调用Agent
result = agent.invoke({
"messages": [{"role": "user", "content": "从以下信息中提取联系详情: 约翰·多伊,john@example.com,(555) 123-4567"}]
})
# 获取结构化结果
structured_response = result["structured_response"]
print(structured_response)
# 输出:ContactInfo(name='约翰·多伊', email='john@example.com', phone='(555) 123-4567')
第三步:使用 ProviderStrategy
from langchain.agents.structured_output import ProviderStrategy
# 创建Agent,指定使用ProviderStrategy
agent = create_agent(
model="gpt-4o", # 必须使用支持原生结构化输出的模型
response_format=ProviderStrategy(ContactInfo) # 传入ProviderStrategy
)
# 调用方式与ToolStrategy完全相同
result = agent.invoke({
"messages": [{"role": "user", "content": "从以下信息中提取联系详情: 约翰·多伊,john@example.com,(555) 123-4567"}]
})
print(result["structured_response"])
简化写法与默认行为
从 langchain 1.0 开始,提供了一个便捷的简化写法。可以直接将定义好的 Pydantic 模型传给 response_format。
# 简化写法: 直接传递模型
agent = create_agent(
model="gpt-4.1",
response_format=ContactInfo # 直接传入模型,而不是策略对象
)
默认行为:
当直接传入模型(如 ContactInfo)时,LangChain 会自动处理:
-
优先尝试使用
ProviderStrategy(如果模型支持原生结构化输出)。 -
若不支持,则自动回退使用
ToolStrategy。
这种 “自动选择” 的策略兼顾了便捷性和兼容性。
重要注意事项: 预绑定工具(pre-bound)的模型不支持与结构化输出一起使用。如果需要动态模型选择与结构化输出结合,请确保传入中间件的模型没有预先调用 bind_tools。
定义 State
接下来我们继续学习在 Agent 中定义state状态相关内容。
我们之前已经接触过状态相关概念:LangChain 内部的 Agent 自带一套默认状态基类AgentState,这是所有状态定义的基础。 默认内置状态自带三类内置属性:
class AgentState(TypedDict, Generic[ResponseT]):
"""State schema for the agent."""
messages: Required[Annotated[list[AnyMessage], add_messages]]
jump_to: NotRequired[Annotated[JumpTo | None, EphemeralValue, PrivateStateAttr]]
structured_response: NotRequired[Annotated[ResponseT, OmitFromInput]]
# 详细注释说明版本:
from typing import Annotated, Generic, NotRequired, Required, TypedDict
from langgraph.graph.message import add_messages
from langgraph.managed import EphemeralValue, PrivateStateAttr
class AgentState(TypedDict, Generic[ResponseT]):
"""
Agent 状态模式定义。
该类通过 LangGraph 的状态管理机制,定义了 Agent 在执行过程中
需要维护的所有状态字段。每个字段都使用了特定的注解来控制其
更新行为、可见性和生命周期。
设计原则:
- 显式声明:所有状态字段都明确标注类型和行为
- 最小暴露:使用 PrivateStateAttr 和 OmitFromInput 控制字段可见性
- 不可变追加:消息列表采用追加模式,避免状态冲突
- 生命周期管理:临时字段自动清理,避免状态膨胀
Attributes:
messages: 对话历史消息列表(必填)。
使用 add_messages 合并函数,支持追加式更新而非覆盖式更新,
确保在多节点并行或重试场景下消息历史不会丢失。
jump_to: 流程控制跳转目标(可选,仅在当前步有效)。
用于在多个 Agent 节点或子图之间进行动态路由。
使用 EphemeralValue 管理,确保每次执行步后自动清空,
避免影响后续步骤的状态判断。
structured_response: 结构化工单响应数据(可选,不暴露给 LLM)。
存储 Agent 最终需要输出的结构化结果(如工单 ID、错误码等)。
使用 OmitFromInput 明确标记不参与 Prompt 渲染,
防止大量结构化数据污染上下文窗口。
"""
# ============ 核心对话层 ============
messages: Required[Annotated[list[AnyMessage], add_messages]]
"""
对话历史消息列表。
这是 Agent 最核心的状态字段,存储了用户、AI 和工具之间的完整对话历史。
关键行为:
- 使用 add_messages 作为合并函数:当多个节点同时向 messages 追加消息时,
不会相互覆盖,而是自动合并成完整的消息序列。
- 必填字段(Required):所有 Agent 启动时至少包含一条用户消息。
使用场景:
- LLM 调用节点:将 messages 转换为模型可接受的输入格式
- 工具节点:将工具执行结果追加为 ToolMessage
- 历史回溯:支持多轮对话中的上下文保持
注意事项:
- 不要直接在节点中修改 messages 的已有元素(如 messages[0] = ...),
应使用追加方式(如 messages.append(new_msg))或返回新的消息列表。
- 系统提示词不在 messages 中硬编码,应在调用 LLM 前动态拼接。
示例:
# 在节点中追加消息
def chat_node(state: AgentState):
return {"messages": [AIMessage(content="Hello!")]}
"""
# ============ 流程控制层 ============
jump_to: NotRequired[Annotated[JumpTo | None, EphemeralValue, PrivateStateAttr]]
"""
流程跳转目标(运行时临时字段)。
用于实现动态路由,让 Agent 在执行过程中根据当前状态决定下一步跳转到哪个节点。
该字段在每次 Graph 步骤执行后会被自动清空。
关键行为:
- EphemeralValue:字段生命周期仅限当前执行步,执行完成后自动清除。
这确保了跳转指令不会意外残留到后续步骤,避免路由混乱。
- PrivateStateAttr:该字段不会参与 State 的序列化/反序列化,
在 Checkpointer 持久化或子图调用时不会被传递。
- NotRequired:非必填字段,只有当需要动态路由时才设置。
使用场景:
- 路由节点:根据 LLM 的意图识别结果,设置 jump_to 指向特定子图
- 循环控制:在某个条件下跳回之前的节点重新处理
- 异常处理:检测到错误时跳转到降级处理节点
注意事项:
- 配合 Conditional Edge 使用:在路由条件函数中读取 state["jump_to"]
- 设置后需确保目标节点存在,否则会导致运行时错误
- 多个节点同时设置时,以最后执行的节点为准
示例:
# 路由节点中设置跳转目标
def router_node(state: AgentState):
intent = detect_intent(state["messages"])
if intent == "create_ticket":
return {"jump_to": JumpTo("create_ticket_subgraph")}
elif intent == "query_ticket":
return {"jump_to": JumpTo("query_ticket_subgraph")}
return {"jump_to": JumpTo("fallback_node")}
"""
# ============ 业务数据层 ============
structured_response: NotRequired[Annotated[ResponseT, OmitFromInput]]
"""
结构化工单响应数据(最终输出结果)。
存储 Agent 处理完用户请求后,需要返回给上游系统的结构化业务数据。
例如:创建的工单 ID、工单状态、错误码、查询结果等。
关键行为:
- OmitFromInput:该字段不会出现在 LLM 调用的输入上下文中。
这是为了避免将大量结构化数据(如几十个工单字段)传递给 LLM,
造成 Token 浪费和上下文干扰。
- NotRequired:只有需要返回结构化数据时才设置,不是所有请求都需要。
- Generic[ResponseT]:支持自定义响应类型,提供更好的类型安全。
使用场景:
- 创建工单节点:工单创建成功后,将工单 ID 存入 structured_response
- 查询工单节点:查询结果存入 structured_response,供上游系统消费
- 错误处理节点:将错误码和错误信息结构化存储,方便上游做异常处理
注意事项:
- 该字段是最终输出,不应该在中间节点被多次覆写(除非明确需要)
- 上游系统在调用 Agent 后,直接从 state["structured_response"] 取结果
- 如果 Agent 需要流式返回结构化数据,建议配合 stream_mode="updates"
示例:
# 节点中设置结构化响应
def create_ticket_node(state: AgentState) -> dict:
ticket_id = ticket_service.create(...)
return {
"structured_response": TicketResponse(
ticket_id=ticket_id,
status="created",
created_at=datetime.now()
)
}
# 上游获取结果
async for event in agent.astream(input_data):
if event.get("structured_response"):
return event["structured_response"]
"""
-
完整可追加的消息列表
messages,用来存储全部对话历史; -
jump_to跳转标识,用于控制流程跳转; -
结构化返回存储字段,用来存放格式化输出结果。
除框架自带的默认状态外,我们可以继承AgentState,拓展字段实现自定义状态,用来存储对话历史之外的临时信息,比如用户偏好、临时标记、计算中间结果等。自定义状态一共有两种实现方案,第一种是官方推荐的中间件绑定方式,第二种是创建 Agent 时直接传参绑定。
核心概念
自定义 State 是 Agent 在执行周期内的运行时内存载体,用于在节点间传递和共享数据。它的生命周期默认与单次 Graph 执行绑定——执行结束即销毁。若需支持会话级的跨步骤恢复,可配合 Checkpointer 将 State 持久化到外部存储;若需跨会话的长期记忆(如用户偏好、知识库),则应使用 LangGraph 的 BaseStore 接口,两者职责不同,不可混淆。
在类型定义上,LangGraph 推荐使用 TypedDict 声明 State 结构。你可以直接继承框架提供的 AgentState(已内置 messages、jump_to、structured_response 三个通用字段)快速上手,也可以完全自定义一个独立的 TypedDict——两者均合法,取决于你是否需要框架预设字段。
两种实现方式
| 方式 | 适用场景 | 特点 |
|---|---|---|
| 通过 Middleware 定义 | 自定义状态需要在中间件钩子或特定工具中被访问时使用。 | 推荐方式。作用域清晰,状态与相关中间件、工具绑定。 |
通过 state_schema 参数定义 |
自定义状态仅需在工具中使用,无需复杂中间件逻辑时的简化用法。 | 快速实现,但作用域较广。仅用于向后兼容,不推荐新项目使用。 |
通过 Middleware 定义(推荐)
第一种是优先推荐的写法,依托中间件、钩子hook绑定自定义状态。
实现逻辑:自定义状态类必须继承AgentState,定义为TypedDict;随后在中间件 / 钩子上通过state_schema绑定我们写好的自定义状态类型,绑定完成后,在钩子函数的入参里就能直接读取、修改状态里的所有字段;最后不要忘记在 create_agent 时通过 middleware 参数传入该中间件。
这里有一条强制版本规范需要重点记住:从 LangChain 1.0 新版本起,自定义状态只能使用TypedDict类型,不再兼容 Pydantic BaseModel、dataclass 这类旧写法,类型格式有硬性要求。
# ❌ 错误写法 1:使用 Pydantic BaseModel(LangChain 1.0+ 不再支持)
from pydantic import BaseModel
class InvalidState(BaseModel): # ❌ 会报错
messages: list
user_id: str
# ❌ 错误写法 2:使用 dataclass(LangChain 1.0+ 不再支持)
from dataclasses import dataclass
@dataclass # ❌ 会报错
class InvalidState:
messages: list
user_id: str
# ✅ 唯一正确写法:TypedDict
from typing import TypedDict
class ValidState(AgentState): # ✅ 正确
user_id: str
# ✅ 正确写法详细演示:继承 AgentState,使用 TypedDict
class MyCustomState(AgentState):
"""自定义状态 - 在 AgentState 基础上扩展业务字段"""
# 框架预设字段(AgentState 已包含 messages)
# messages: Annotated[list[AnyMessage], add_messages] # 已继承
# ⬇️ 以下是自定义扩展字段
user_id: str # 用户标识
user_name: str # 用户姓名
retry_count: int # 重试次数
ticket_id: str | None # 工单ID(可为空)
is_authorized: bool # 是否已授权
conversation_summary: str | None # 对话摘要
中间件分为两种编写形式,底层绑定状态逻辑完全一致:
装饰器@wrap_model_call这类钩子写法:直接在装饰器参数里配置state_schema绑定状态;
类继承AgentMiddleware写法:自定义中间件类,在类属性中直接赋值state_schema = 自定义状态类完成关联。 两种写法绑定状态后,所有钩子方法的入参都会自动注入我们定义的state对象,读取、修改状态字段的操作方式完全统一,作用域清晰,状态只在绑定的中间件、工具内生效,不会全局污染,所以官方优先推荐这种方案。
官方推荐优先使用通过 Middleware 定义的方式,因为它能将状态的扩展与特定中间件、工具的作用域绑定,结构更清晰。
中间件绑定的 state_schema 只影响该中间件的钩子方法的入参类型,不会改变 Graph 全局的 State 结构。每个中间件只能看到自己绑定的 State 中定义的字段,看不到其他中间件的私有字段——这就是'作用域隔离、不会全局污染'的真实含义。数据虽然共享在同一个底层 dict 里,但类型层面的隔离让不同中间件各司其职,互不干扰。
| 组件 | 能否感知自定义 State |
|---|---|
中间件钩子(before_agent/after_agent) |
✅ 能,因为绑了 state_schema |
| 工具节点(ToolNode) | ✅ 能,Graph 执行时 State 会传递给它 |
| LLM 节点 | ⚠️ 不能直接感知(LLM 只看到 messages,看不到自定义字段) |
| 图外部的调用方 | ❌ 不能(除非从 State 里取) |
代码示例
from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import AgentMiddleware
from typing import Any
# 1. 定义自定义状态
class CustomState(AgentState):
user_preferences: dict
# 2. 创建中间件,关联状态
class CustomMiddleware(AgentMiddleware):
state_schema = CustomState
tools = [] # 可选:为该中间件绑定工具
def before_model(self, state: CustomState, runtime) -> dict[str, Any] | None:
# 在模型调用前可以访问和修改 state
print(f"User preferences: {state.get('user_preferences')}")
return None
# 3. 创建Agent并传入中间件
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[],
middleware=[CustomMiddleware()] # 关键:通过middleware注入自定义状态
)
# 调用时传入额外状态
result = agent.invoke({
"messages": [{"role": "user", "content": "什么是大模型?"}],
"user_preferences": {"style": "技术性", "verbosity": "详细的"},
})
通过 state_schema 参数定义
第二种是简化快捷写法,不需要额外编写中间件类,在调用create_agent创建 Agent 实例时,直接传入state_schema=自定义状态类参数,一次性完成状态绑定。 这种写法上手更快、代码更少,但缺点是状态作用域更广,整个 Agent 所有工具、所有钩子都能访问这个状态,缺少作用域隔离,官方说明该方式仅用于旧项目向后兼容,新项目不推荐优先使用。
步骤
-
定义一个继承自
AgentState的TypedDict。 -
在
create_agent时直接通过state_schema参数传入该类型。
代码示例
from langchain.agents import AgentState, create_agent
# 1. 定义自定义状态
class CustomState(AgentState):
user_preferences: dict
# 2. 创建Agent时传入 state_schema
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[], # 工具可以访问此状态
state_schema=CustomState # 快捷定义
)
# 调用时传入额外状态
result = agent.invoke({
"messages": [{"role": "user", "content": "什么是大模型?"}],
"user_preferences": {"style": "技术性", "verbosity": "详细的"},
})
通过灵活运用自定义状态,可以为 Agent 赋予更丰富的短期记忆能力,使其在多轮交互中表现更智能、更贴合用户需求。
两种方案都能成功定义、使用自定义状态,区别仅在于配置绑定的位置、状态生效的作用域不同。
更多推荐



所有评论(0)