# LangChain Prompt 组件实践:模板、消息占位符与提示词工程化
Prompt 是 LLM 应用里最容易被低估的工程资产。很多项目一开始只是把用户输入拼进一段字符串里,等需求复杂起来后,才发现提示词已经散落在接口、服务、工具函数和测试脚本中:变量不统一、格式不可控、版本不可追踪、输出不稳定,最后每次改 Prompt 都像在拆一个没有测试的线上逻辑。
LangChain 的 Prompt 组件解决的不是“怎么写一句提示词”,而是“怎么把提示词变成可组合、可测试、可复用、可迭代的工程模块”。它让 Prompt 可以声明变量、格式化输入、组织聊天消息、插入历史上下文、拼接多个模板,并和 Model、Parser、Retriever、Agent 组合起来。
# Prompt 为什么需要组件化
直接拼字符串当然可以:
question = "SQLAlchemy Session 应该在哪里 commit?"
prompt = f"你是一个 Python 后端专家,请回答这个问题:{question}"
2
但真实项目很快会遇到这些问题:
- 系统角色、业务规则、用户输入混在一起。
- Prompt 变量越来越多,漏传时运行期才报错。
- 多轮对话需要插入历史消息,普通字符串不好表达。
- RAG 需要插入检索上下文、引用来源和回答约束。
- 结构化输出需要固定格式说明。
- 不同业务场景需要复用同一段系统提示词。
- Prompt 修改缺少版本、评审和测试。
Prompt 组件化的核心价值是:把提示词从临时字符串提升为应用的一部分,让它有清晰输入、组合边界和可验证行为。
# PromptTemplate:文本提示模板
PromptTemplate 适合传统文本输入场景。它通过变量占位符生成最终字符串。
from langchain_core.prompts import PromptTemplate
prompt = PromptTemplate.from_template(
"请用 {language} 解释一下 {subject},要求简洁准确。"
)
result = prompt.invoke({
"language": "中文",
"subject": "LangChain PromptTemplate",
})
print(result.to_string())
2
3
4
5
6
7
8
9
10
11
12
13
如果只是给普通 LLM 或一个文本处理链路构造输入,PromptTemplate 足够简单。
但现在大多数模型应用都使用 Chat Model,更推荐使用 ChatPromptTemplate,因为它可以明确区分 system、human、ai、tool 等消息角色。
# ChatPromptTemplate:聊天消息模板
ChatPromptTemplate 用来生成消息列表。它比纯文本模板更适合现代 Chat Model。
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个严谨的 Python 后端工程师。"),
("human", "{question}"),
])
prompt_value = prompt.invoke({
"question": "Flask 项目如何管理数据库迁移?"
})
print(prompt_value.to_messages())
2
3
4
5
6
7
8
9
10
11
12
13
这段代码生成的是消息结构,而不是简单字符串。消息结构的好处是角色清晰:
- system:定义模型身份、边界、规则。
- human:用户输入。
- ai:历史模型回复或 few-shot 示例。
- tool:工具返回结果。
在生产项目里,角色边界非常重要。系统规则应该放在 system message,用户输入应该放在 human message,不要把它们全部拼成一段无结构文本。
# MessagesPlaceholder:插入对话历史
多轮对话里,历史消息往往不是固定长度,也不是固定内容。MessagesPlaceholder 可以在 Prompt 中预留一个消息列表位置。
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个技术助手,回答要简洁。"),
MessagesPlaceholder("chat_history"),
("human", "{question}"),
])
prompt_value = prompt.invoke({
"chat_history": [
HumanMessage(content="你是谁?"),
AIMessage(content="我是一个技术助手。"),
],
"question": "继续解释一下 LangChain 的 Prompt 组件。",
})
print(prompt_value.to_messages())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
使用 MessagesPlaceholder 时要注意:传入的必须是消息列表或可转换为消息的对象。如果传错类型,LangChain 会报 prompt input 相关错误。
生产中不要无限塞历史消息。对话历史应该有策略:
- 只保留最近 N 轮。
- 对长历史做摘要。
- 抽取稳定用户偏好。
- 对敏感内容做过滤。
- 对 token 数做上限控制。
Prompt 只是插入历史的入口,真正的上下文治理要结合 memory、middleware 或 LangGraph 状态管理。
# partial:提前固定部分变量
有些变量每次调用都一样,或者由系统在构建 Prompt 时确定,可以用 partial 固定。
from datetime import datetime
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个技术助手。当前时间:{now}"),
("human", "{question}"),
]).partial(now=datetime.now().isoformat())
prompt_value = prompt.invoke({
"question": "今天应该如何安排数据库迁移上线?"
})
2
3
4
5
6
7
8
9
10
11
12
partial 适合这些场景:
- 固定业务角色。
- 固定输出语言。
- 注入当前时间。
- 注入租户名称。
- 注入 Prompt 版本。
- 固定通用格式说明。
不要把所有变量都变成 partial。用户输入、检索上下文、会话历史这些运行时变化的数据,仍然应该在 invoke 时显式传入。
# Prompt 的组合
Prompt 可以拆成多个片段,再组合起来。这样做有利于复用系统规则、业务约束和用户输入模板。
from langchain_core.prompts import ChatPromptTemplate
system_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业、克制、准确的技术助手。"),
])
task_prompt = ChatPromptTemplate.from_messages([
("human", "请回答这个问题:{question}"),
])
prompt = system_prompt + task_prompt
prompt_value = prompt.invoke({
"question": "LangChain Prompt 组件适合解决什么问题?"
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
组合 Prompt 时,最重要的是边界清楚:
- 全局规则单独维护。
- 业务任务单独维护。
- 输出格式单独维护。
- few-shot 示例单独维护。
- RAG 上下文单独维护。
不要把所有内容写进一个超长模板。超长模板后期很难 review,也很难判断是哪一段影响了模型行为。
# Few-shot 示例
Few-shot prompting 是提升模型稳定性的重要方式。它通过示例告诉模型“什么样的输入应该产生什么样的输出”。
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你负责把用户问题分类为 bug、feature、question 三类。"),
("human", "登录后页面一直转圈"),
("ai", "bug"),
("human", "能不能加一个导出 Excel 的功能"),
("ai", "feature"),
("human", "{input}"),
])
2
3
4
5
6
7
8
9
10
11
Few-shot 适合:
- 意图识别。
- 分类任务。
- 格式模仿。
- 工单摘要。
- SQL 生成约束。
- 特定风格写作。
示例不宜过多。太多示例会增加 token 成本,也可能让模型过拟合示例风格。生产中应该用评估集验证示例是否真的提升效果。
# RAG 场景里的 Prompt
RAG Prompt 通常要包含三类信息:
- 角色和回答规则。
- 检索到的上下文。
- 用户问题。
示例:
from langchain_core.prompts import ChatPromptTemplate
rag_prompt = ChatPromptTemplate.from_messages([
(
"system",
"""
你是企业知识库问答助手。
请只根据给定上下文回答问题。
如果上下文中没有答案,请明确说不知道。
不要编造不存在的事实。
上下文:
{context}
""",
),
("human", "{question}"),
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
RAG Prompt 的关键不是把上下文塞进去就完了,而是要明确模型行为边界:
- 只能根据上下文回答。
- 不知道时要承认不知道。
- 尽量引用来源。
- 不要泄露无关上下文。
- 不要把检索片段当成用户指令执行。
最后一点很重要。检索文档里可能包含恶意文本,例如“忽略之前所有规则”。生产 RAG 要把外部文档当成不可信上下文,而不是系统指令。
# 结构化输出 Prompt
当业务需要 JSON、枚举或 Pydantic 模型时,Prompt 要和结构化输出策略配合,而不是只写一句“请返回 JSON”。
更推荐使用模型或 Agent 的结构化输出能力。如果只是 Prompt 约束,也要写清楚格式:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
(
"system",
"""
你是工单分类器。
只返回 JSON,不要输出额外解释。
字段:
- intent:create_ticket、query_status、other
- priority:low、medium、high
- summary:20 字以内摘要
""",
),
("human", "{user_input}"),
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
生产中还必须做输出校验。Prompt 约束不是类型系统,模型仍然可能返回不合法 JSON、缺字段或枚举值错误。正确做法是:Prompt 约束 + schema 校验 + 失败重试 + 降级处理。
# Prompt 输入校验
Prompt 模板中的变量必须和调用时传入的变量一致。
prompt = ChatPromptTemplate.from_messages([
("human", "请解释 {subject}"),
])
prompt.invoke({"topic": "LangChain"})
2
3
4
5
这里会失败,因为模板需要 subject,但传入的是 topic。
项目里建议:
- 给 Prompt 变量命名建立规范。
- 避免同义变量混用,例如
query、question、input到处混杂。 - 对业务入参先做 Pydantic 校验。
- 为重要 Prompt 写最小单元测试。
Prompt 测试不一定要调用模型。至少可以测试模板能否正常 format、变量是否完整、消息角色是否符合预期。
# 大括号转义
LangChain 默认常用 f-string 风格模板。模板里如果要展示 JSON 示例,要注意大括号转义。
错误示例:
prompt = ChatPromptTemplate.from_template("""
请返回如下 JSON:
{
"answer": "...",
"confidence": 0.9
}
问题:{question}
""")
2
3
4
5
6
7
8
这里 JSON 的 {} 可能被当成模板变量。应该写成:
prompt = ChatPromptTemplate.from_template("""
请返回如下 JSON:
{{
"answer": "...",
"confidence": 0.9
}}
问题:{question}
""")
2
3
4
5
6
7
8
这是 Prompt 模板里非常常见的坑。
# Prompt 与 Agent
在 Agent 场景里,Prompt 不只是告诉模型“你是谁”,还要定义工具使用边界。
一个好的 Agent system prompt 应该说明:
- Agent 的职责。
- 可以使用哪些工具。
- 什么时候必须调用工具。
- 什么时候不能猜测。
- 敏感操作是否需要确认。
- 输出格式要求。
- 错误或信息不足时如何处理。
示例:
from langchain.agents import create_agent
from langchain.tools import tool
@tool
def get_order_status(order_id: str) -> str:
"""查询订单状态。"""
return f"订单 {order_id} 已发货"
SYSTEM_PROMPT = """
你是一个订单客服助手。
规则:
- 查询订单状态时必须调用订单查询工具。
- 不要猜测订单状态。
- 涉及退款、取消订单、修改地址时,只能解释流程,不能直接执行。
- 如果用户没有提供订单号,请先追问。
"""
agent = create_agent(
model="openai:gpt-4o-mini",
tools=[get_order_status],
system_prompt=SYSTEM_PROMPT,
)
result = agent.invoke({
"messages": [
{"role": "user", "content": "帮我查一下订单 A1001 的状态"}
]
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
静态规则适合放进 system_prompt。如果系统提示词需要根据用户、租户、权限、当前状态动态变化,更适合使用 middleware 或 LangGraph state 注入,而不是在业务函数里临时拼一个大字符串。
Agent 的效果很大程度取决于 harness:Prompt、tools、middleware、memory 和控制流。Prompt 只是其中一层,但它决定了模型如何理解自己的职责边界。长短期记忆也不要简单地全部塞回 Prompt,要结合 state、store、summary 和上下文窗口预算来设计。
# Prompt 版本管理
生产项目里,Prompt 应该像代码一样管理。
建议记录:
- Prompt 名称。
- Prompt 版本。
- 适用场景。
- 输入变量。
- 输出格式。
- 修改原因。
- 评估结果。
一个简单做法是把 Prompt 单独放在模块里:
# internal/ai/prompts/ticket.py
from langchain_core.prompts import ChatPromptTemplate
TICKET_CLASSIFIER_PROMPT_VERSION = "2026-08-06-v1"
ticket_classifier_prompt = ChatPromptTemplate.from_messages([
("system", "你是工单分类器,只返回结构化分类结果。"),
("human", "{user_input}"),
])
2
3
4
5
6
7
8
9
10
更成熟的团队可以把 Prompt 放进数据库、配置中心或 Prompt 管理平台,配合 LangSmith 做版本对比和评估。
# 项目目录建议
不要把 Prompt 散落在 route 或 service 里。推荐集中管理:
internal/
ai/
prompts/
common.py
chat.py
rag.py
ticket.py
chains.py
models.py
parsers.py
2
3
4
5
6
7
8
9
10
示例:
# internal/ai/prompts/common.py
BASE_TECH_ASSISTANT_SYSTEM_PROMPT = """
你是一个严谨的技术助手。
回答要准确、简洁。
不确定时要说明不确定,不要编造。
"""
2
3
4
5
6
# internal/ai/prompts/chat.py
from langchain_core.prompts import ChatPromptTemplate
from internal.ai.prompts.common import BASE_TECH_ASSISTANT_SYSTEM_PROMPT
chat_prompt = ChatPromptTemplate.from_messages([
("system", BASE_TECH_ASSISTANT_SYSTEM_PROMPT),
("human", "{question}"),
])
2
3
4
5
6
7
8
9
这样后续调整全局风格、RAG 规则、结构化输出规则时,都能找到明确位置。
# 常见坑
# Prompt 过长
Prompt 越长,不一定效果越好。过长的 Prompt 会增加成本、延迟和注意力干扰。应该把规则压缩到必要内容,复杂知识交给检索,不要把所有业务文档塞进 system prompt。
# 规则互相冲突
例如同时写“回答要简洁”和“必须详细解释每一步”。模型遇到冲突规则时会不稳定。Prompt review 时要检查规则是否一致。
# 用户输入污染系统规则
不要把用户输入拼进 system message。用户输入应该作为 human message,外部文档应该作为 context,并明确它不是指令来源。
# 没有输出校验
Prompt 写得再好,也不能保证模型永远按格式输出。关键业务必须做 parser 和 schema 校验。
# 没有评估集
Prompt 优化不能只靠主观感觉。至少准备一批典型问题和预期结果,每次修改后对比效果。
# 小结
LangChain 的 Prompt 组件把提示词从字符串变成了可组合的工程对象。PromptTemplate 适合文本模板,ChatPromptTemplate 适合聊天模型,MessagesPlaceholder 适合插入动态历史,partial 适合固定部分变量,Prompt 组合和 few-shot 可以提高复用性和稳定性。
生产项目里,Prompt 要有边界、有变量规范、有版本、有测试、有评估。它不是随手写在代码里的文案,而是 LLM 应用的核心控制面。Model 决定能力上限,Prompt 决定能力如何被约束和释放。