LangChain 提示词模板:从拼接字符串到组合流水线

在构建 AI 应用时,提示词(Prompt) 的质量直接决定模型输出的效果。而如何高效、安全、可维护地管理提示词,是每个开发者都必须面对的课题。LangChain 提供了强大的提示词模板体系,帮助我们告别字符串拼接的混乱,走向模块化的工程实践。

本文将带你全面了解 LangChain 提示词模板的设计理念、基础用法和高级特性(包括组合模板),让你在项目中得心应手。


什么是提示词模板?

一张图告诉你

一、为什么需要提示词模板?

1. 字符串拼接的痛点

初学者通常直接用 Python 的 f-string 或 + 拼接提示词:

python 复制代码
name = "小明"
user_input = "今天天气如何?"
prompt = f"你是一个AI助手,叫{name}\n用户问:{user_input}"

这种方式在 Demo 阶段很方便,但面临以下问题:

  • 可读性差:变量一多,字符串乱成一锅粥。
  • 易出错:缺少变量校验,拼写错误难以察觉。
  • 难以复用:每个场景都要重新拼接。
  • 不支持复杂结构:多轮对话、少样本示例、RAG 等场景难以优雅表达。

2. 提示词模板的优势

LangChain 的 ChatPromptTemplate 提供了:

  • 结构清晰 :通过占位符 {variable} 分离模板与数据。
  • 自动校验:调用时检查所有变量是否提供,避免遗漏。
  • 高度复用:同一模板可应用于不同场景。
  • 生态集成:与 LangChain 的模型、链、代理无缝协作。
  • 调试友好:可打印完整的消息列表,便于追踪。

开发建议:临时脚本可用拼接,正式项目请务必使用模板。


二、ChatPromptTemplate 基础

1. 从 from_messages() 创建(推荐)

这是最常用、最直观的方式,通过传入消息元组列表构建模板:

python 复制代码
from langchain_core.prompts import ChatPromptTemplate

template = ChatPromptTemplate.from_messages([
    ("system", "你是一个AI助手,你的名字是{name}"),
    ("human", "你好,最近怎么样?"),
    ("ai", "我很好,谢谢!"),
    ("human", "{user_input}")
])

# 调用时传入变量
result = template.invoke({"name": "小智", "user_input": "你是谁?2+2等于几?"})
print(result)

输出

text 复制代码
messages=[SystemMessage(content='你是一个AI助手,你的名字是小智'), 
          HumanMessage(content='你好,最近怎么样?'), 
          AIMessage(content='我很好,谢谢!'), 
          HumanMessage(content='你是谁?2+2等于几?')]

💡 支持的角色类型:systemhumanai(或 assistant),也可用 user 代替 human

2. 直接实例化

你也可以直接创建 ChatPromptTemplate 对象,效果相同:

python 复制代码
template = ChatPromptTemplate([
    ("system", "你是一个AI助手,你的名字是{name}"),
    ("human", "{user_input}")
])

三、模板的调用方式

创建模板后,我们有三种方式"填充"变量并得到结果。

1. invoke() ------ 返回 ChatPromptValue

python 复制代码
value = template.invoke({"name": "小智", "user_input": "你好"})
print(value)          # 完整的消息对象
print(value.messages) # 消息列表

2. format() ------ 返回纯字符串

python 复制代码
text = template.format(name="小智", user_input="你好")
print(text)
# 输出:
# System: 你是一个AI助手,你的名字是小智
# Human: 你好
print(type(text))  # <class 'str'>

注意format() 返回的是纯字符串,适合用于非结构化场景。

3. format_messages() ------ 返回消息列表

python 复制代码
messages = template.format_messages(name="小智", user_input="你好")
print(messages)
# [SystemMessage(...), HumanMessage(...)]
print(type(messages))  # <class 'list'>

推荐 :在与模型交互时,尽量使用 format_messages()invoke(),因为它们保留了消息角色信息,更精确。


四、与 LLM 结合使用

将模板与模型串联,即可完成一个完整的调用链:

python 复制代码
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate

load_dotenv(override=True)

# 初始化模型(示例使用 OpenRouter 接入模型)
model = init_chat_model(
    model="xiaomi/mimo-v2.5",
    model_provider="openai",
    api_key=os.getenv("OPENROUTER_API_KEY"),
    base_url=os.getenv("OPENROUTER_BASE_URL")
)

template = ChatPromptTemplate.from_messages([
    ("system", "你是一个AI助手,你的名字是{name}"),
    ("human", "{user_input}")
])

# 生成消息列表
messages = template.format_messages(name="小智", user_input="2+2等于几?")

# 调用模型
response = model.invoke(messages)
print(response.content)

输出示例:

text 复制代码
你好!我是小智,2+2等于4。有什么我可以帮你的吗?

五、更丰富的初始化方式

from_messages() 支持多种参数类型,灵活适应不同场景。

1. 字符串列表(只有 human 消息)

python 复制代码
template = ChatPromptTemplate.from_messages([
    "你好,我是{name}"
])
# 等同于 ("human", "你好,我是{name}")

2. 元组列表(最常用)

python 复制代码
template = ChatPromptTemplate.from_messages([
    ("system", "你是一个助手"),
    ("human", "我的名字是{name}")
])

3. 字典列表

python 复制代码
template = ChatPromptTemplate.from_messages([
    {"role": "system", "content": "你是一个助手"},
    {"role": "human", "content": "我是{name}"}
])

4. 消息对象列表(⚠️ 注意:变量不会被替换)

python 复制代码
from langchain_core.messages import SystemMessage, HumanMessage

template = ChatPromptTemplate.from_messages([
    SystemMessage(content="你是一个助手"),
    HumanMessage(content="你好,我是{name}")  # 这里的 {name} 不会替换!
])
result = template.invoke({"name": "小明"})
# 输出中 HumanMessage 内容是 "你好,我是{name}" 字面值

因此,如果需要变量替换,不要直接传入消息对象,应使用模板字符串。

5. 使用 SystemMessagePromptTemplate 等子模板

python 复制代码
from langchain_core.prompts import SystemMessagePromptTemplate, HumanMessagePromptTemplate

sys_template = SystemMessagePromptTemplate.from_template("你是{role}")
human_template = HumanMessagePromptTemplate.from_template("你好,{name}")

template = ChatPromptTemplate.from_messages([sys_template, human_template])
result = template.invoke({"role": "导游", "name": "游客"})

6. 组合已有的 ChatPromptTemplate 实例

python 复制代码
template1 = ChatPromptTemplate.from_messages([("system", "你是助手")])
template2 = ChatPromptTemplate.from_messages([("human", "我是{name}")])

combined = ChatPromptTemplate.from_messages([template1, template2])
result = combined.invoke({"name": "小明"})

六、高级特性(上):部分变量预填充与消息占位符

1. 部分变量预填充(partial()

有时候,某些变量在多个请求中固定不变,可以预先填充,避免重复传入。

python 复制代码
template = ChatPromptTemplate.from_messages([
    ("system", "你是{role},你的目标人群是{audience}"),
    ("user", "{task}")
])

# 预设 role 和 audience
final_template = template.partial(role="公务员", audience="人民群众")

res1 = final_template.invoke({"task": "为人民服务"})
res2 = final_template.invoke({"task": "为中国人民谋幸福"})

print(res1)  # System: 你是公务员,你的目标人群是人民群众
print(res2)  # System: 你是公务员,你的目标人群是人民群众

partial() 非常适合构建特定场景的子模板,例如在代码中固定 role 后,后续只需关注 task 变量。


2. 消息占位符(用于多轮对话)

当我们需要在模板中插入不确定数量的历史消息 时,可以使用 MessagesPlaceholder

方式一:使用 "placeholder" 元组

python 复制代码
template = ChatPromptTemplate.from_messages([
    ("system", "你是一个AI助手"),
    ("placeholder", "{conversation}")   # 占位名称为 conversation
])

result = template.invoke({
    "conversation": [
        ("human", "天气怎么样?"),
        ("ai", "今天是晴天。"),
        ("human", "明天呢?")
    ]
})

方式二:使用 MessagesPlaceholder 对象(更明确)

python 复制代码
from langchain_core.prompts import MessagesPlaceholder

template = ChatPromptTemplate.from_messages([
    ("system", "你是一个AI助手"),
    MessagesPlaceholder(variable_name="history")
])

实际应用:在 RAG 或聊天机器人中,将历史消息动态传入,而当前问题作为单独的变量:

python 复制代码
template = ChatPromptTemplate.from_messages([
    ("system", "你是一个AI助手"),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{question}")
])

result = template.invoke({
    "history": [
        ("human", "你好"),
        ("ai", "你好!有什么可以帮你?")
    ],
    "question": "帮我查一下明天的天气"
})

这样,历史消息和最新问题被清晰地区分开来,模型能够理解完整的对话上下文。


七、高级特性(下):PipelinePromptTemplate 组合模板(模块化利器)

在实际的复杂业务中,一个提示词往往由多个逻辑模块拼接而成,例如:

  • 角色设定(你是谁)
  • 背景知识(上下文)
  • 风格约束(回答语气/格式)
  • 任务指令(具体要做什么)
  • 少样本示例(Few-shot)

如果把这些全部塞进一个巨大的模板字符串,变量会非常多,可读性和维护性都会急剧下降。此时,PipelinePromptTemplate 就是你的救星。

它允许你将一个"终极提示词"拆分成若干个"子模板",每个子模板负责生成其中一部分内容,最后再统一组装。

1. 基本用法

我们通过一个实际的例子来看:假设我们需要让 AI 扮演一位"职业规划顾问",面向不同人群给出建议,并要求特定的输出格式。

python 复制代码
from langchain_core.prompts import PipelinePromptTemplate, PromptTemplate

# 1. 定义子模板:每个子模板只关注自己的逻辑
role_prompt = PromptTemplate.from_template("你的角色是:{role}")
audience_prompt = PromptTemplate.from_template("你的咨询对象是:{audience},他们面临的问题是:{pain_point}")
style_prompt = PromptTemplate.from_template("请使用{style}的语气进行回答")
format_prompt = PromptTemplate.from_template("回答格式要求:{format_rule}")

# 2. 定义最终模板:这里用 {sub_template_name} 占位引用子模板的输出
final_prompt = PromptTemplate.from_template(
    "【系统设定】\n{role}\n{audience}\n{style}\n\n"
    "【具体要求】\n{format}\n\n"
    "【任务】\n请根据以上设定,回答用户的问题:{question}"
)

# 3. 组装管道:将子模板和最终模板绑定
pipeline = PipelinePromptTemplate(
    final_prompt=final_prompt,
    pipeline_prompts=[
        ("role", role_prompt),       # 对应 final_prompt 中的 {role}
        ("audience", audience_prompt), # 对应 {audience}
        ("style", style_prompt),     # 对应 {style}
        ("format", format_prompt)    # 对应 {format}
    ]
)

# 4. 一次性传入所有原始变量,管道会自动逐层处理
result = pipeline.invoke({
    "role": "资深职业规划师",
    "audience": "应届毕业生",
    "pain_point": "不知道选大厂还是考公务员,非常焦虑",
    "style": "温和而理性,多用数据说话",
    "format_rule": "1. 先分析利弊 2. 给出决策建议 3. 结尾附一句鼓励的话",
    "question": "我该如何选择?"
})

# 查看生成的完整提示词
print(result.to_string())

输出效果

text 复制代码
【系统设定】
你的角色是:资深职业规划师
你的咨询对象是:应届毕业生,他们面临的问题是:不知道选大厂还是考公务员,非常焦虑
请使用温和而理性,多用数据说话的语气进行回答

【具体要求】
回答格式要求:1. 先分析利弊 2. 给出决策建议 3. 结尾附一句鼓励的话

【任务】
请根据以上设定,回答用户的问题:我该如何选择?

2. 为什么推荐这种方式?

特性 单个大模板 Pipeline组合模板
可维护性 修改一处需全局查看 各模块解耦,修改互不影响
可测试性 难以单独测试某部分 可单独测试子模板逻辑
复用性 几乎无法复用片段 子模板可在不同场景重复使用
可读性 变量混杂,容易漏填 变量归属清晰,校验自动完成
团队协作 所有人都改同一个字符串 不同成员可各自维护子模板

3. 与 ChatPromptTemplate 的配合使用

⚠️ 注意PipelinePromptTemplate 原生设计用于 PromptTemplate(纯字符串)。如果你需要组合 ChatPromptTemplate(带角色消息),依然可以使用之前介绍的 4.6 节 组合方式(ChatPromptTemplate.from_messages([template1, template2]))。两者适用场景不同:

  • ChatPromptTemplate.from_messages([...]) :适合组合不同角色(system/human/ai)的消息块
  • PipelinePromptTemplate :适合按逻辑功能(角色/任务/格式)切分内容。

在实战中,你可以将两者结合使用:外层用 ChatPromptTemplate 定义消息角色,内层用 PipelinePromptTemplate 精雕细琢某一段内容的生成逻辑。

python 复制代码
# 假设我们先通过 Pipeline 生成复杂的 system 消息内容
# 注意:这里需要将 pipeline 的输出作为变量传入
system_part_template = PromptTemplate.from_template(
    "你是{role}。你的背景:{background}。你的风格:{style}。"
)
# ... 构建 pipeline 省略 ...

# 再将其嵌入 ChatPromptTemplate 中
final_chat_template = ChatPromptTemplate.from_messages([
    ("system", "{system_content}"),  # 这里接收 pipeline 生成的完整字符串
    ("human", "{question}")
])

# 更优雅的方式:使用 LCEL 链式调用(后续文章会深入)

八、可复用模板库(工程化建议)

在实际项目中,建议将常用模板集中在一个 Python 文件中,例如 prompts.py

python 复制代码
# prompts.py
from langchain_core.prompts import ChatPromptTemplate

# 通用聊天模板
CHAT_TEMPLATE = ChatPromptTemplate.from_messages([
    ("system", "你是{role},请用{style}风格回答"),
    ("human", "{question}")
])

# RAG 检索增强模板
RAG_TEMPLATE = ChatPromptTemplate.from_messages([
    ("system", "根据以下上下文回答问题:\n{context}"),
    ("human", "{question}")
])

# 复杂流水线模板(需结合 PipelinePromptTemplate)
# 此处建议封装成函数,便于参数管理

然后在主程序中导入复用,大大提升代码整洁度。


九、总结与选型建议

特性 字符串拼接 提示词模板 组合模板
可读性 ❌ 混乱 ✅ 清晰 ✅ 模块化极强
变量校验 ❌ 无 ✅ 自动 ✅ 自动
复用性 ❌ 差 ✅ 高 ✅ 极高(子模块复用)
多轮对话 ❌ 难 ✅ 占位符支持 ✅ 可嵌套支持
调试 ❌ 困难 ✅ 可打印结构 ✅ 可逐层打印
学习成本 中等 较高
适用场景 临时脚本 正式 AI 应用 超复杂业务系统

最终开发建议

  1. 临时脚本、快速实验 → 字符串拼接够用。
  2. 正式项目、单轮简单问答ChatPromptTemplate + from_messages()
  3. 多轮对话/RAG/Agent → 引入 MessagesPlaceholder 管理历史。
  4. 大型复杂系统(>5个逻辑模块) → 果断使用 PipelinePromptTemplate 进行分层设计。

LangChain 的提示词模板不仅提升了开发体验,更为后续集成链(Chain)、检索增强(RAG)和智能代理(Agent)打下了坚实基础。掌握这些技巧,你就掌握了 LangChain 开发的半壁江山。


延伸阅读

相关推荐
夜影风2 小时前
智能体开发的“脚手架“:主流框架(LangChain、AutoGen等)选型指南
人工智能·langchain·ai agent
lxyker2 小时前
AI常用概念名词
llm·ai编程
带刺的坐椅3 小时前
用 Solon TeamAgent 落地供应链异常管理与自动补货
java·ai·llm·agent·solon·供应链
ZhengEnCi13 小时前
LLM02-Context Priming:利用强模型上下文提升弱模型能力的策略
llm
先吃饱再说13 小时前
Skill 是什么?一个让 AI 从“现想”变成“照做”的操作手册
llm·ai编程
冬奇Lab13 小时前
MCP 系列(08):企业治理——Registry、路由与可观测性
人工智能·llm·mcp
海上彼尚13 小时前
Nodejs也能写Agent - 16.LangGraph篇 - 条件分支与循环
前端·后端·langchain·node.js
To_OC14 小时前
从 “卡死半天” 到 “打字机效果”:大模型流式输出前端实现全记录
前端·javascript·llm
Darling噜啦啦18 小时前
从 URL 到 Chunk:网页爬取与语义切割实战,构建 RAG 知识库的第一步
langchain