零基础看懂 ChatPromptTemplate:从"手动拼字符串"到"专业模板"的进化之路

一句话总结:ChatPromptTemplate 是 LangChain 中专门用来组装聊天消息的"模板引擎",它能让你像填空题一样,把变量插进预设的对话结构里,告别手动拼接字符串的混乱时代。


一、为什么需要它?先看一个"翻车现场"

想象你要让 AI 扮演一个"暴躁的代码审查员",审查一段代码。最原始的做法是手动拼字符串:

# ❌ 原始人写法:手动拼接,极易出错
language = "Python"
code = "def add(a, b): return a + b"

prompt = f"""
你是一个暴躁的代码审查员。
请用{language}的视角审查以下代码:
{code}
要求:1. 指出问题 2. 骂得狠一点
"""

问题在哪?

  • 角色、指令、代码全混在一起,改一处可能牵一发而动全身
  • 如果对话有"历史记录",拼接起来更是灾难
  • 无法复用,每次都要重新写一遍格式

二、ChatPromptTemplate 是什么?

它是 LangChain 提供的"聊天消息组装器",核心思想:

把对话拆成"角色 + 内容"的消息块,留出变量占位符,最后统一填充。

from langchain.prompts import ChatPromptTemplate

# ✅ 现代写法:结构清晰,像填空题
template = ChatPromptTemplate.from_messages([
    ("system", "你是一个{personality}的代码审查员。"),      # 系统消息:设定角色
    ("human", "请用{language}的视角审查这段代码:\n{code}"),  # 人类消息:用户输入
])

对比一目了然:

维度 手动拼接字符串 ChatPromptTemplate
结构 一锅粥 分角色、分消息块
复用 复制粘贴 定义一次,多次填充
维护 改一处崩全局 改模板不影响调用逻辑
扩展 地狱难度 轻松加消息、加变量


三、核心概念:三种"消息角色"

聊天模型(如 GPT-4、Claude)只认三种身份牌:

  1. system —— “幕后导演”
    设定 AI 的身份、语气、规则。用户看不到,但 AI 全程照做。
from langchain.prompts import ChatPromptTemplate

template = ChatPromptTemplate.from_messages([
    ("system", "你是一位{style}的厨师,只做{ cuisine }菜。"),
    ("human", "我想学做{ dish }"),
])

# 填充变量,生成最终消息列表
messages = template.invoke({
    "style": "严厉",
    "cuisine": "川菜",
    "dish": "麻婆豆腐"
})

print(messages)
# 输出:
# [
#   SystemMessage(content='你是一位严厉的厨师,只做川菜。'),
#   HumanMessage(content='我想学做麻婆豆腐')
# ]
  1. human / user —— “提问的顾客”
    就是用户的输入。可以带变量,也可以纯文本。
template = ChatPromptTemplate.from_messages([
    ("system", "你是翻译官,把用户的话翻译成{target_lang}。"),
    ("human", "{user_input}"),  # 变量占位
])

result = template.invoke({
    "target_lang": "日语",
    "user_input": "今天天气真好"
})
# HumanMessage(content='今天天气真好')
  1. ai / assistant —— “假装 AI 已经说过的话”
    这是精髓! 用来构造"少样本示例(Few-shot)",让 AI 模仿特定格式。
template = ChatPromptTemplate.from_messages([
    ("system", "你是一个情感分析器,只输出'正面'或'负面'。"),
    
    # 👇 假装这是上一轮对话:用户问,AI 答
    ("human", "这部电影太棒了!"),
    ("ai", "正面"),
    
    ("human", "浪费了我两个小时。"),
    ("ai", "负面"),
    
    # 👇 真正的用户输入
    ("human", "{text}"),
])

result = template.invoke({"text": "主角演技炸裂"})
# 模型看到历史示例后,大概率输出:正面

四、四种创建方式(从简到繁)

方式 1:元组列表(最常用,推荐⭐)

from langchain.prompts import ChatPromptTemplate

template = ChatPromptTemplate.from_messages([
    ("system", "你是{role},擅长{skill}。"),
    ("human", "请帮我{task}"),
])

messages = template.invoke({
    "role": "前端专家",
    "skill": "React",
    "task": "优化一个渲染卡顿的组件"
})

方式 2:Message 对象(更灵活,可加额外参数)

from langchain.prompts import ChatPromptTemplate
from langchain.prompts.chat import SystemMessagePromptTemplate, HumanMessagePromptTemplate

system_template = SystemMessagePromptTemplate.from_template(
    "你是{role},语气要{tone}。"
)
human_template = HumanMessagePromptTemplate.from_template("帮我{task}")

chat_prompt = ChatPromptTemplate.from_messages([
    system_template,
    human_template
])

方式 3:字符串直接转(适合单轮简单场景)

from langchain.prompts import ChatPromptTemplate

# 只有一个消息时,默认当作 human 消息
template = ChatPromptTemplate.from_template("把这句话翻译成{lang}:{text}")
messages = template.invoke({"lang": "英语", "text": "你好世界"})

方式 4:混合使用(历史记录 + 新输入)

from langchain_core.messages import AIMessage, HumanMessage

# 从真实的聊天历史构造
history = [
    HumanMessage(content="你好"),
    AIMessage(content="你好!有什么可以帮你的吗?"),
]

template = ChatPromptTemplate.from_messages([
    ("system", "你是客服机器人。"),
    *history,  # 展开历史记录
    ("human", "{new_question}"),  # 最新问题
])

messages = template.invoke({"new_question": "怎么退款?"})

五、实战案例:从简单到复杂

案例 1:带格式的代码审查(基础用法)

from langchain.prompts import ChatPromptTemplate

template = ChatPromptTemplate.from_messages([
    ("system", """你是一位{style}的{language}代码审查专家。
审查规则:
1. 检查是否有空指针风险
2. 检查是否有性能隐患
3. 用{style}的语气给出建议"""),
    
    ("human", """请审查以下代码:
```{language}
{code}
```"""),
])

# 使用
messages = template.invoke({
    "style": "温和但犀利",
    "language": "Java",
    "code": """
    public String getName(User user) {
        return user.getProfile().getName();
    }
    """
})

# 可以直接传给模型
# response = model.invoke(messages)

案例 2:动态 Few-shot 学习(让 AI 模仿格式)

假设你要让 AI 从非结构化文本中提取"人物-关系"对,并且严格按 JSON 格式输出:

template = ChatPromptTemplate.from_messages([
    ("system", "你是一个信息抽取助手。从文本中提取人物关系,只输出 JSON 数组,不要解释。"),
    
    # 示例 1
    ("human", "文本:小明是小红的哥哥,他们一起去了公园。"),
    ("ai", '[{"person1": "小明", "relation": "哥哥", "person2": "小红"}]'),
    
    # 示例 2
    ("human", "文本:张三是李四的老板,王五是他们公司的客户。"),
    ("ai", '[{"person1": "张三", "relation": "老板", "person2": "李四"}, {"person1": "王五", "relation": "客户", "person2": "公司"}]'),
    
    # 真实输入
    ("human", "文本:{input_text}"),
])

result = template.invoke({
    "input_text": "曹操是刘备的敌人,关羽是刘备的结拜兄弟。"
})
# AI 会模仿前面的 JSON 格式输出,而不是胡说八道

案例 3:多轮对话记忆(结合历史记录)

from langchain_core.messages import AIMessage, HumanMessage

# 假设这是从数据库取出的历史记录
chat_history = [
    HumanMessage(content="我想订一张去上海的机票"),
    AIMessage(content="好的,请问您想哪天出发?"),
    HumanMessage(content="明天"),
    AIMessage(content="明天上午 9 点有一班,可以吗?"),
]

template = ChatPromptTemplate.from_messages([
    ("system", "你是航空公司客服,叫小助手。"),
    *chat_history,
    ("human", "{input}"),
])

messages = template.invoke({"input": "可以,帮我订了吧"})
# 模型能根据上下文理解"可以"指的是"上午 9 点那班"

案例 4:条件分支(根据变量动态改变提示词)

template = ChatPromptTemplate.from_messages([
    ("system", "你是{level}教程作者,用{level}的语言解释概念。"),
    ("human", "请解释什么是{concept}"),
])

# 同一套模板,不同变量 = 完全不同的风格
beginner = template.invoke({
    "level": "零基础",
    "concept": "递归"
})
# 系统消息:你是零基础教程作者,用零基础的语言解释概念。

expert = template.invoke({
    "level": "资深工程师",
    "concept": "递归"
})
# 系统消息:你是资深工程师教程作者,用资深工程师的语言解释概念。

六、高级技巧

技巧 1:部分填充(Partial Variables)

有些变量是固定的,不想每次调用都传:

template = ChatPromptTemplate.from_messages([
    ("system", "你是{role},使用{language}回答问题。"),
    ("human", "{question}"),
])

# 先绑定固定变量
partial_template = template.partial(role="Python 专家", language="中文")

# 之后只需要传变化的 question
messages = partial_template.invoke({"question": "怎么写装饰器?"})

技巧 2:管道组合(Pipeline)

可以把模板和模型串成流水线:

from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4")

# 模板 + 模型 = 一个完整的链
chain = template | model

# 直接传入变量,自动经过模板格式化再传给模型
response = chain.invoke({
    "role": "诗人",
    "language": "中文",
    "question": "写一首关于春天的诗"
})

技巧 3:MessagePlaceholder(插入任意消息列表)

当你不知道历史记录有多少条时:

from langchain.prompts import MessagesPlaceholder

template = ChatPromptTemplate.from_messages([
    ("system", "你是客服机器人。"),
    MessagesPlaceholder(variable_name="history"),  # 这里会插入任意数量的消息
    ("human", "{input}"),
])

# history 可以传一个消息列表
messages = template.invoke({
    "history": [
        HumanMessage(content="你好"),
        AIMessage(content="您好!有什么可以帮您?"),
        HumanMessage(content="查一下订单"),
    ],
    "input": "订单号 12345"
})

七、常见坑与解决方案

坑 现象 解决
变量名写错 KeyError: 'langauge' 仔细检查占位符 {} 里的名字
忘记 invoke 得到的是模板对象,不是消息 必须调用 .invoke(variables)
单双引号混乱 JSON 里的引号和 f-string 冲突 用 from_messages 的元组写法,避免手动转义
历史记录顺序错 AI 回答错乱 确保是 human/ai/human/ai 交替
变量是列表/对象 直接插进去变成 <object> 先用 json.dumps() 转成字符串


八、总结:一张图看懂流程

┌─────────────────┐      ┌──────────────────┐      ┌─────────────┐
│   定义模板       │  →   │   填充变量        │  →   │  发给模型    │
│                 │      │                  │      │             │
│ system: 你是{role}│      │ role="医生"       │      │ SystemMessage│
│ human: 帮我{task} │      │ task="看报告"      │      │ HumanMessage │
│                 │      │                  │      │             │
│ ChatPromptTemplate│      │ .invoke()        │      │ model.invoke()│
└─────────────────┘      └──────────────────┘      └─────────────┘

核心口诀:

角色分清楚,变量留占位,调用 invoke 填,直接丢给模型算。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐