一、提示词:Agent 的"岗位说明书"
上一篇结尾预告了本篇主题。创建 Agent 时,model 决定"大脑多聪明",tools 决定"手脚能干什么",而提示词决定"这个人是谁、按什么规矩办事"。
在 LangChain 中,提示词为 Agent 提供任务背景、行为准则和操作指南,本质上定义了 Agent 的"角色"和"使命"。同一个模型、同一组工具,换一份提示词,就是换了一个 Agent——客服助手和数据分析助手的核心差异,往往只在 system_prompt 那几百字。
对 Java 工程师,可以把它类比成 Spring Bean 的初始化配置:静态提示词像 @Value 注入的固定配置,动态提示词像运行时按请求上下文路由的策略模式——本文正是围绕这两层展开。
二、基础设置:一个参数定角色
基础用法最简单:create_agent 时传入 system_prompt 参数,支持字符串或 SystemMessage 两种类型:
from langchain.agents import create_agent
from langchain_core.messages import SystemMessage
agent = create_agent(
model=deepseek_llm,
tools=[add_numbers],
# 写法一:直接传字符串
# system_prompt="你是一个数学助手,可以实现两数相加。",
# 写法二:传 SystemMessage 对象
system_prompt=SystemMessage(content="你是一个数学助手,可以实现两数相加。"),
)
resp = agent.invoke({
"messages": [{
"role": "user", "content": "10加上20再加上30是多少?"}]})
print(resp["messages"][-1].content) # 10加上20再加上30的和是60。
两种写法效果等价,字符串是 SystemMessage 的简写形式。日常用字符串足够;需要在消息里携带附加元数据时才用 SystemMessage。
这份"岗位说明书"一旦设定,Agent 整个生命周期行为不变。这就够了吗?看一个真实矛盾:电商客服系统里,普通用户期望"简单直接、别绕弯子",VIP 用户期望"专业严谨、深度分析"。一份静态提示词无法同时讨好两类人——这就是动态提示词的用武之地。
三、动态设置:按运行时身份换"人格"
动态提示词通过中间件实现:@dynamic_prompt 装饰器标记的函数,会在每次推理前根据运行时上下文实时生成系统提示词。
实现分三步。
第一步:TypedDict 定义上下文结构。 声明 Agent 运行时可以接收哪些动态数据:
from typing import TypedDict
class AgentContext(TypedDict):
query_type: str # 'normal' 普通用户 / 'vip' VIP用户
# user_id: int # 可扩展:用户ID、会员等级等
第二步:编写动态提示词中间件。 从 request.runtime.context 取上下文,按身份返回不同提示词:
from langchain.agents.middleware import dynamic_prompt, ModelRequest
@dynamic_prompt
def dynamic_support_prompt(request: ModelRequest) -> str:
query_type = request.runtime.context.get("query_type", "normal")
base = "你是一名专业的电商客服助手。请根据工具查询结果,准确、清晰地回答用户问题。"
if query_type == "vip":
return f"""{base}
当前角色:高级支持专员
工作要求:1.深度分析:识别潜在根本问题;
2.精准分类:明确归类(物流/产品质量/售后申请);
3.方案规划:工具能解决给步骤,需人工则告知流程。语言专业严谨。"""
else:
return f"""{base}
当前角色:一线客服助手
工作要求:1.快速响应:优先用工具查订单/政策;
2.直接解答:明确问题直接给答案;
3.简洁友好:避免复杂术语。"""
第三步:创建时挂载中间件并关联上下文。
agent = create_agent(
model=deepseek_llm,
tools=[query_order_info, search_faq],
middleware=[dynamic_support_prompt], # 挂载动态提示词中间件
context_schema=AgentContext, # 关联上下文结构
)
# 调用时通过 context 参数传入身份数据
result = agent.invoke(
{
"messages": [{
"role": "user", "content": "我的订单ORD654321已签收,但物品坏了怎么办?"}]},
context={
"query_type": "normal"}, # 必须符合 AgentContext 定义
)
四、效果对比:同一个问题,两种人格
同一句"我的订单已签收但物品坏了",两种模式的回复差异显著。
普通模式输出:先说明订单状态异常(显示待付款但已签收),然后给出三步建议——拍照取证、联系人工客服、售后处理提示,整体友好直白,像一线客服的口吻。
VIP 模式输出:结构化拆解为"问题分类:产品质量问题 → 当前情况分析 → 建议处理方案(核实信息/标准售后流程/立即行动)→ 后续处理路径",专业严谨,像高级专员的工单报告。
关键认知:两个模式的模型、工具完全相同,差异只来自提示词。模型能力是底座,提示词决定了这份能力以什么"人格"表达出来。这也是提示词工程的杠杆所在——改几十个字,就能切换服务的深度与风格。
五、机制剖析:动态提示词在链路中的位置
动态提示词是怎么生效的?放进 Agent 执行链路看。

流程拆解:
agent.invoke(..., context={"query_type": "vip"})携带上下文进入执行循环;- 每轮推理调用模型前,
@dynamic_prompt中间件触发,从request.runtime.context读取 query_type,生成对应的系统提示词; - 生成的提示词作为 SystemMessage 注入消息流首位,与用户消息、历史消息、工具结果一起构成模型输入;
- 模型基于"当前人格"决策;循环继续,每轮都重新走一遍中间件。
注意两个要点:其一,中间件每轮推理都会执行——如果上下文在多轮间变化,提示词也随之变化;其二,context_schema 是契约——TypedDict 定义了哪些字段可传,invoke 时传入的数据必须符合结构,这相当于给运行时上下文上了一道类型检查。
对照 Java 生态:这套机制 ≈ Interceptor 拦截器 + 策略模式。dynamic_prompt 是拦截器钩子,context 是请求属性(request attributes),按身份路由不同策略,而这一切对业务代码零侵入。
六、提示词工程四要素
无论静态还是动态,一份好的 Agent 提示词通常包含四要素,客服案例全部覆盖:
| 要素 | 作用 | 案例对应 |
|---|---|---|
| 角色 | 定身份、定语气 | "一线客服助手" / "高级支持专员" |
| 边界 | 说什么不说什么 | "只回答天气相关的问题" |
| 规则 | 怎么办事的步骤约束 | "优先用工具查订单,再直接解答" |
| 输出 | 格式与风格要求 | "简洁友好,避免复杂术语" |
写提示词时的实用建议:
- 指令具体化——"回答要简洁"不如"回复控制在 100 字内,先给结论再给步骤";
- 工具使用前置——明确"遇到订单/政策问题先调工具再回答",可显著减少模型凭空编造;
- 拒绝策略写明——超出边界时怎么回应("直接回答:我不清楚这问题答案"),避免模型硬编;
- 动态部分隔离——基础指令放 base_instruction,身份差异部分按分支拼接,避免两份提示词大片重复、难以维护。
七、小结
- 基础设置:
system_prompt传字符串或 SystemMessage,一次定义、全程不变,适合角色单一的场景; - 动态设置:
@dynamic_prompt中间件 +context_schema上下文契约,按用户身份/问题类型实时生成提示词,适合多角色、个性化的生产系统; - 杠杆认知:模型与工具相同时,提示词是 Agent 行为差异的唯一来源;把提示词当配置管理,而不是当文案随手写。
技术说明
本文代码基于 LangChain 1.2 + Python 3.13 环境验证,完整案例(含两个客服工具定义与双模式输出对比)可在公众号后台回复关键词获取;API 细节以官方文档(docs.langchain.com)为准。