零基础看懂 ChatPromptTemplate:从"手动拼字符串"到"专业模板"的进化之路
一句话总结:ChatPromptTemplate 是 LangChain 中专门用来组装聊天消息的"模板引擎",它能让你像填空题一样,把变量插进预设的对话结构里,告别手动拼接字符串的混乱时代。
一、为什么需要它?先看一个"翻车现场"
想象你要让 AI 扮演一个"暴躁的代码审查员",审查一段代码。最原始的做法是手动拼字符串:
# ❌ 原始人写法:手动拼接,极易出错language="Python"code="def add(a, b): return a + b"prompt=f""" 你是一个暴躁的代码审查员。 请用{language}的视角审查以下代码:{code}要求:1. 指出问题 2. 骂得狠一点 """问题在哪?
- 角色、指令、代码全混在一起,改一处可能牵一发而动全身
- 如果对话有"历史记录",拼接起来更是灾难
- 无法复用,每次都要重新写一遍格式
二、ChatPromptTemplate 是什么?
它是 LangChain 提供的"聊天消息组装器",核心思想:
把对话拆成"角色 + 内容"的消息块,留出变量占位符,最后统一填充。
fromlangchain.promptsimportChatPromptTemplate# ✅ 现代写法:结构清晰,像填空题template=ChatPromptTemplate.from_messages([("system","你是一个{personality}的代码审查员。"),# 系统消息:设定角色("human","请用{language}的视角审查这段代码:\n{code}"),# 人类消息:用户输入])对比一目了然:
维度 手动拼接字符串 ChatPromptTemplate
结构 一锅粥 分角色、分消息块
复用 复制粘贴 定义一次,多次填充
维护 改一处崩全局 改模板不影响调用逻辑
扩展 地狱难度 轻松加消息、加变量
三、核心概念:三种"消息角色"
聊天模型(如 GPT-4、Claude)只认三种身份牌:
system—— “幕后导演”
设定 AI 的身份、语气、规则。用户看不到,但 AI 全程照做。
fromlangchain.promptsimportChatPromptTemplate template=ChatPromptTemplate.from_messages([("system","你是一位{style}的厨师,只做{ cuisine }菜。"),("human","我想学做{ dish }"),])# 填充变量,生成最终消息列表messages=template.invoke({"style":"严厉","cuisine":"川菜","dish":"麻婆豆腐"})print(messages)# 输出:# [# SystemMessage(content='你是一位严厉的厨师,只做川菜。'),# HumanMessage(content='我想学做麻婆豆腐')# ]human/user—— “提问的顾客”
就是用户的输入。可以带变量,也可以纯文本。
template=ChatPromptTemplate.from_messages([("system","你是翻译官,把用户的话翻译成{target_lang}。"),("human","{user_input}"),# 变量占位])result=template.invoke({"target_lang":"日语","user_input":"今天天气真好"})# HumanMessage(content='今天天气真好')ai/assistant—— “假装 AI 已经说过的话”
这是精髓! 用来构造"少样本示例(Few-shot)",让 AI 模仿特定格式。
template=ChatPromptTemplate.from_messages([("system","你是一个情感分析器,只输出'正面'或'负面'。"),# 👇 假装这是上一轮对话:用户问,AI 答("human","这部电影太棒了!"),("ai","正面"),("human","浪费了我两个小时。"),("ai","负面"),# 👇 真正的用户输入("human","{text}"),])result=template.invoke({"text":"主角演技炸裂"})# 模型看到历史示例后,大概率输出:正面四、四种创建方式(从简到繁)
方式 1:元组列表(最常用,推荐⭐)
fromlangchain.promptsimportChatPromptTemplate template=ChatPromptTemplate.from_messages([("system","你是{role},擅长{skill}。"),("human","请帮我{task}"),])messages=template.invoke({"role":"前端专家","skill":"React","task":"优化一个渲染卡顿的组件"})方式 2:Message 对象(更灵活,可加额外参数)
fromlangchain.promptsimportChatPromptTemplatefromlangchain.prompts.chatimportSystemMessagePromptTemplate,HumanMessagePromptTemplate system_template=SystemMessagePromptTemplate.from_template("你是{role},语气要{tone}。")human_template=HumanMessagePromptTemplate.from_template("帮我{task}")chat_prompt=ChatPromptTemplate.from_messages([system_template,human_template])方式 3:字符串直接转(适合单轮简单场景)
fromlangchain.promptsimportChatPromptTemplate# 只有一个消息时,默认当作 human 消息template=ChatPromptTemplate.from_template("把这句话翻译成{lang}:{text}")messages=template.invoke({"lang":"英语","text":"你好世界"})方式 4:混合使用(历史记录 + 新输入)
fromlangchain_core.messagesimportAIMessage,HumanMessage# 从真实的聊天历史构造history=[HumanMessage(content="你好"),AIMessage(content="你好!有什么可以帮你的吗?"),]template=ChatPromptTemplate.from_messages([("system","你是客服机器人。"),*history,# 展开历史记录("human","{new_question}"),# 最新问题])messages=template.invoke({"new_question":"怎么退款?"})五、实战案例:从简单到复杂
案例 1:带格式的代码审查(基础用法)
fromlangchain.promptsimportChatPromptTemplate 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:多轮对话记忆(结合历史记录)
fromlangchain_core.messagesimportAIMessage,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="中文")# 之后只需要传变化的 questionmessages=partial_template.invoke({"question":"怎么写装饰器?"})技巧 2:管道组合(Pipeline)
可以把模板和模型串成流水线:
fromlangchain_openaiimportChatOpenAI model=ChatOpenAI(model="gpt-4")# 模板 + 模型 = 一个完整的链chain=template|model# 直接传入变量,自动经过模板格式化再传给模型response=chain.invoke({"role":"诗人","language":"中文","question":"写一首关于春天的诗"})技巧 3:MessagePlaceholder(插入任意消息列表)
当你不知道历史记录有多少条时:
fromlangchain.promptsimportMessagesPlaceholder 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 填,直接丢给模型算。