13-提示词与消息模板
13 - 提示词与消息模板
本章课程目标:
- 理解 Prompt 是什么,知道它为什么会从“一段字符串”逐步演化成“多角色消息 + 模板 + 占位符”。
- 掌握 LangChain 中与输入组织最相关的三块内容:消息类型(Message)、模型调用方式(invoke / stream / batch)、提示词模板(PromptTemplate / ChatPromptTemplate)。
- 会运行并理解本章全部案例:输入类型、同步与异步调用、文本模板、对话模板、消息占位符、从 JSON / YAML 加载提示词,为后续 输出解析器、LCEL 与链式调用、记忆与对话历史 打基础。
学习建议: 读这一章时,拿一条普通用户问题练手,把它改造成消息列表:系统消息放规则,用户消息放任务,历史消息放上下文。先理解模型到底吃进去什么,再看 PromptTemplate、ChatPromptTemplate、MessagesPlaceholder 怎么帮你复用这些结构。读完后最好能判断:什么时候写死提示词,什么时候抽成模板,什么时候需要把模板放到文件里。
官方文档与资源:详见 工具导航与参考资料索引 - 提示词与结构化输出。
1、Prompt 简介
本章对应 第 11 章 Model I/O 里的 输入格式化(Format) 与 模型调用(Predict) 两部分。模型并不是“凭空思考”,它始终是在读取我们提供的输入后再生成输出;而 Prompt、Message、Template,正是组织这份输入的核心工具。
如果你已经学过 1-2 提示词工程基础,那么本章可以直接理解为它的代码实现版:前一章讲“怎么把角色、任务、上下文、输入、输出和约束说清楚”,这一章讲“这些内容进入 LangChain 后,应该以什么输入形态存在、如何复用、如何插入多轮历史、如何外置到文件”。
1.1 定义
Prompt(提示词),就是你发给大模型的输入内容。
最简单的 Prompt,是一句自然语言,比如“什么是 LangChain?”;再进一步,你会给它增加角色,比如“你是一名法律顾问,请用 50 字介绍广告法”;再往后,为了让代码可复用、可维护、可协作,你会把这段输入写成模板,并把会变化的部分改成占位符。这就是从“随手提问”走向“工程化输入管理”的过程。
入门阶段可先把握一个基本判断:Prompt 不只是把一句话写漂亮,而是把模型输入组织清楚。
在真实项目中,Prompt 通常承担几件事:
- 告诉模型它是谁,要扮演什么角色。
- 告诉模型当前任务是什么,回答边界是什么。
- 告诉模型输出格式应该长什么样。
- 把用户问题、历史对话、检索结果、工具结果组合成一次完整输入。
这也是为什么随着项目复杂度提高,Prompt 会从“一个字符串”逐步演化成“多角色消息 + 模板 + 占位符 + 外部配置文件”。

1.2 Prompt 的作用
很多人在学 LangChain 时,会把注意力都放在“接哪个模型”“换哪个平台”“怎么配 API Key”上,但真正到了项目里,最容易让系统效果不稳定的,往往不是模型接入,而是输入组织得不够清楚。
下面这些真实开发场景,都离不开本章内容:
- 智能客服:需要系统提示词规定语气、身份和拒答策略。
- 企业知识库问答:需要把“检索出来的上下文 + 用户问题”组合成一条清晰提示。
- 多轮聊天:需要把历史对话插回当前输入,而不是每轮都从头问。
- 结构化输出:需要提前在 Prompt 里写清楚输出格式要求,方便后面交给解析器处理。
- 团队协作与 A/B 测试:需要把 Prompt 模板从代码里抽出来,放到 JSON / YAML 中做版本管理。
一句话:模型能力决定上限,Prompt 设计决定你能不能稳定接近这个上限。
1.3 本章在项目中的位置
本章对应仓库中的 案例与源码-2-LangChain框架/04-prompt 目录,按学习顺序可以分成 4 类:
| 目录 | 作用 | 你会学到什么 |
|---|---|---|
invoke/ |
模型调用方式 | invoke、stream、batch 及异步版本 |
prompt_templates/ |
文本模板 | PromptTemplate 的创建、格式化、复用 |
chat_prompt_template/ |
对话模板 | ChatPromptTemplate、消息参数、消息占位符 |
load_external/ |
外部文件加载 | 如何从 JSON / YAML 加载 Prompt |
这一章不是零散的 API 介绍,而是在回答一个问题:开发 LLM 应用时,怎样把 1-2 提示词工程基础 里学到的写法和原则,组织成一套可复用、可维护、可扩展的结构?
2、调用大模型的入参类型
初学者容易误以为:调用聊天模型时,输入永远只能是“一段字符串”。实际上,LangChain 聊天模型为了适配真实对话场景,通常支持多种输入形态。这些写法表面不同,核心都一样:把 1-2 章 里讲过的角色、任务、上下文、输入和约束,以合适的消息结构交给模型。
2.1 入参形态总览
同一次 invoke,左侧可以是多种类型的输入,中间由聊天模型处理,右侧典型返回值是 AIMessage。这也是为什么你在 第 11 章 会经常看到“正文一般通过 .content 读取”。

常见对应关系如下:
| 入参形态 | 调用时大致长什么样 | 适合场景 |
|---|---|---|
str |
model.invoke("请解释什么是 LangChain") |
单轮、轻量、快速试接口 |
PromptTemplate.format(...) 后的字符串 |
model.invoke(prompt_str) |
固定句式 + 少量变量替换 |
| 消息对象列表 | model.invoke([SystemMessage(...), HumanMessage(...)]) |
推荐写法,适合系统提示、多轮对话 |
(role, content) 元组列表 |
[("system", "..."), ("user", "...")] |
简洁、贴近 ChatPromptTemplate 风格 |
{"role": "...", "content": "..."} 字典列表 |
[{"role":"system","content":"..."}, ...] |
贴近 OpenAI 风格 JSON 数据 |
【案例源码】案例与源码-2-LangChain框架/04-prompt/invoke/LLM_Invoke_InputTypes.py
2.2 写法一:纯字符串
最简单的输入方式,把整段说明写在一个字符串里交给模型,适合快速试接口、或单轮一句话任务。
1 | resp = model.invoke("用一句话解释什么是 LangChain") |
不过在真实项目里,纯字符串也有明显局限:不方便表达系统角色与用户问题的边界;不方便插入历史对话;不利于后期维护和多人协作。
所以,纯字符串适合起步,不适合复杂对话场景长期使用。
2.3 写法二:模板 + 占位符
如果一句 Prompt 里只有少数变量会变化,就没必要每次手写整段长文本。更合理的做法,是把固定部分写成模板,把变化部分留成占位符。
这一节本质上仍然是“字符串输入”,只是我们把字符串的生成过程工程化了。
1 | from langchain_core.prompts import PromptTemplate |
也就是说:
PromptTemplate负责生产输入model.invoke(...)负责把输入发给模型
它和 2.2 的区别不在于模型收到了不同类型,而在于我们是手写字符串,还是用模板生成字符串。
2.4 写法三:多角色消息列表
当你开始做聊天机器人、问答助手、企业知识库、代码助手时,最推荐的入参方式通常不是字符串,而是消息列表。
原因很简单:聊天模型更擅长理解“谁在说话”。把输入拆成 SystemMessage、HumanMessage、AIMessage 等不同角色,模型更容易正确理解上下文结构,而不是把所有东西都当成一大段平铺文本。
1 | from langchain_core.messages import SystemMessage, HumanMessage, AIMessage |
在实际项目里,这种写法特别常见:
- 系统提示词放在
SystemMessage - 用户问题放在
HumanMessage - 历史回复可放回
AIMessage - 工具执行结果后续可用
ToolMessage
如果你后面要做多轮对话、Agent、RAG,这种消息列表思维会反复用到。
还有一种等价包装是 ChatPromptValue:链式编排里更常见的是由 ChatPromptTemplate.invoke(...) 得到 ChatPromptValue;下面直接构造对象,便于理解「PromptValue → to_messages() → model.invoke」的衔接。
1 | from langchain_core.messages import SystemMessage, HumanMessage, AIMessage |
2.5 写法四:元组列表与字典列表
除了显式使用 SystemMessage、HumanMessage 等类,LangChain 聊天模型通常还支持两种常见简写。
- 元组列表:每项写成
(role, content) - 字典列表:每项写成
{"role": "...", "content": "..."}
这两种写法与消息对象列表在语义上基本等价,只是更接近手写列表或 OpenAI 风格的数据结构。
1 | import os |
关于选择策略,可按下面的思路理解:
- 想学得最清楚:优先用
Message类 - 想写得最简洁:可以用元组列表
- 想和 OpenAI 风格请求体对齐:可以用字典列表
无论哪种方式,同步 invoke 的返回值通常仍是 AIMessage。
2.6 扩展:Java 生态中的多角色
“System / User / Assistant / Tool” 这套思路并不是 LangChain Python 独有的,Java 生态中的 LangChain4J、Spring AI 也有类似设计。理解这一点很有价值,因为它说明:
多角色消息并不是某个框架的语法技巧,而是现代聊天模型交互的一种通用抽象。
LangChain4J:
1 | package dev.langchain4j.data.message; |
Spring AI:
1 | package org.springframework.ai.chat.messages; |
3、入参的消息类型
当你把输入组织成“消息列表”之后,就需要知道每种消息类型代表什么。在 LangChain 官方语境里,最核心的几类消息是:SystemMessage、HumanMessage、AIMessage、ToolMessage。
文档:https://docs.langchain.com/oss/python/langchain/messages (英文);https://docs.langchain.org.cn/oss/python/langchain/messages (中文)
3.1 四类核心消息
| 类型 | 说明 | 项目里最常见的用途 |
|---|---|---|
SystemMessage |
系统消息,通常用于规定角色、风格、边界、输出格式 | 设定人设、回答规则、拒答策略 |
HumanMessage |
用户消息,对应用户当前输入 | 放用户问题、补充条件、后续追问 |
AIMessage |
模型回复消息 | 保存上一轮回复,支持多轮上下文 |
ToolMessage |
工具执行结果消息 | 把外部工具/函数的返回结果回传给模型 |
需要注意两个细节:
**LangChain 的类名叫
HumanMessage,但很多平台的角色字段写的是user。**这不是冲突,而是不同层的命名习惯。你可以把它们理解成同一个角色。**旧版本资料里可能会看到
FunctionMessage。**在 LangChain 1.x 语境下,更常见的是ToolMessage。如果你阅读旧教程或旧代码,看到这类差异,先把它理解为工具调用结果的旧命名即可。
3.2 SystemMessage 的作用
很多新手会把系统提示词和用户问题混在一段字符串里写,这样虽然也能跑,但可维护性很差。更稳妥的做法是把“规则”和“问题”拆开:
- SystemMessage 负责定义系统层规则
- HumanMessage 负责承载当前用户问题
例如:
- “你是一个法律助手,只回答法律问题”
- “输出请控制在 80 字内”
- “如果超出范围,请明确拒答”
这些内容更适合放在 SystemMessage 里,因为它们属于“长期规则”,而不是某一轮具体问题。
这正对应了 1-2 提示词工程基础 里强调的那条原则:**稳定约束和动态输入要拆开写。**在 LangChain 里,这条原则最直接的落地方式就是把稳定规则放进 SystemMessage,把当前任务放进 HumanMessage。
在真实项目里,SystemMessage 往往决定:
- 回答口吻是否专业
- 是否允许推测
- 是否必须引用上下文
- 是否必须按 JSON 输出
- 是否需要保守拒答
这也是为什么 Prompt 工程里经常会说:系统提示词是行为边界,用户提示词是当前任务。
3.3 ToolMessage 什么时候会出现
ToolMessage 不会在普通问答里频繁出现,它更常见于函数调用、工具调用、Agent 编排场景。
简单理解就是:
- 模型先在
AIMessage里表达“我想调用某个工具” - 代码真的去执行工具
- 工具结果再以
ToolMessage的形式回传给模型
这样模型才能基于工具结果继续回答。
下面是一个简化示例:
1 | from langchain_core.messages import SystemMessage, HumanMessage, AIMessage, ToolMessage |
对当前章节来说,你只需要先建立基本印象:最常用的是 System / Human / AI 三类;ToolMessage 是后续 Agent、工具调用章节的重要铺垫。
4、调用大模型的调用方式
当输入组织好之后,下一步就是把它交给模型。LangChain 聊天模型常见的调用方式有四类:普通调用、流式调用、批量调用,以及它们各自的异步版本。
这部分看起来像 API 记忆题,其实可以用一个更直观的方式理解:
- invoke / ainvoke:一次发一条
- stream / astream:一边生成一边返回
- batch / abatch:一次发很多条
4.1 普通调用(invoke / ainvoke)
【案例源码】案例与源码-2-LangChain框架/04-prompt/invoke/LLM_Invoke.py、LLM_aInvoke.py
invoke:同步调用,最常用,适合单轮问答与脚本演示。ainvoke:异步调用,适合异步 Web 服务、并发任务和高吞吐场景。
1 | import os |
实际项目里怎么选:
- 命令行脚本、教学示例、简单后台任务:优先
invoke - FastAPI、异步服务、并发请求:优先
ainvoke - 本地开源模型(Ollama 等):实例化与端点配置见 第 12 章 Ollama 本地部署与调用,本章的
invoke/stream/batch用法同样适用。
4.2 流式调用(stream / astream)
【案例源码】案例与源码-2-LangChain框架/04-prompt/invoke/LLM_Stream.py、LLM_aStream.py
- stream:同步流式输出
- astream:异步流式输出
流式的最大价值不是“更快算完”,而是更快把正在生成的内容展示给用户。在聊天机器人、报告生成、代码生成等场景里,用户体验会明显更好。
1 | messages = [ |
真实项目里,stream / astream 很常用于:
- 聊天界面的“打字机效果”
- 长回答提前回显
- 减少用户等待焦虑
4.3 批处理(batch / abatch)
【案例源码】案例与源码-2-LangChain框架/04-prompt/invoke/LLM_Batch.py、LLM_aBatch.py
- batch:一次提交多条输入,统一获得多条结果
- abatch:异步批处理
它特别适合离线任务,而不是交互式聊天。例如:
- 批量摘要一批文档
- 批量清洗问答数据
- 批量评估 Prompt 效果
- 批量为商品、评论、工单做标签分类
1 | questions = [ |
4.4 小结
| 场景 | 同步 | 异步 | 适合什么情况 |
|---|---|---|---|
| 单条调用 | invoke |
ainvoke |
单轮问答、接口服务 |
| 流式输出 | stream |
astream |
聊天 UI、长文本生成 |
| 批量处理 | batch |
abatch |
离线任务、批量评估 |
如果你一时记不住,也没关系,先记住这条基本规则:
先会 invoke,再学 stream,最后再补 batch 和异步版本。
5、提示词模板概览
5.1 提示词简介
在真正的项目里,Prompt 几乎不可能永远写死在代码中。原因很现实:
- 用户问题会变
- 角色设定会变
- 输出要求会变
- 业务策略会调整
- 团队成员需要一起维护
如果每次都手写一整段 Prompt,不仅容易重复,还非常难维护。提示词模板的作用,就是把固定部分沉淀下来,把变化部分改成变量,从而让同一套提示逻辑可以反复复用。
这和 Python 的 f-string 很像:
1 | def hello(name: str) -> None: |
这里的 {name} 就像 Prompt 模板里的占位符,直接把它看成“先留坑,后填值”就行。
5.2 提示词模板类型
LangChain 中常见的提示词模板主要有下面几类,本课程重点掌握前两种即可:
| 类型 | 说明 | 本课程定位 |
|---|---|---|
| PromptTemplate | 面向纯文本模板,填值后通常得到一条字符串 | 重点掌握 |
| ChatPromptTemplate | 面向聊天模型的多角色模板,填值后得到多条消息 | 重点掌握 |
| FewShotPromptTemplate | 把若干“示例输入-输出”嵌入提示词 | 了解即可 |
| PipelinePrompt | 把多个子提示按顺序组合 | 了解即可 |
入门阶段可以先作一个简化理解:
- 单条文本任务,先看
PromptTemplate - 聊天模型、多角色、多轮对话,重点看
ChatPromptTemplate
5.3 Few-shot 模板
Few-shot 的意思是:不要只告诉模型“按什么规则做”,还给它几组“输入应该怎么变成输出”的示例。
它适合下面这类场景:
- 分类标签容易混,需要给几个标准样例
- 输出风格有要求,单靠文字说明不够稳
- 任务规则不复杂,但希望模型模仿固定格式
入门阶段不用急着背 FewShotPromptTemplate 的所有参数,先记住一句话:**Few-shot 是把示例变成提示词的一部分,让模型照着样子做。**等你后面做分类、抽取、客服话术生成时,再把它作为提高稳定性的手段即可。
6、文本提示词模板(PromptTemplate)
6.1 简介
PromptTemplate 是 LangChain 中最基础的模板类,适合把一段文本 Prompt 做成“固定骨架 + 动态变量”的形式。
它最适合的场景是:
- 摘要、改写、翻译、分类等单轮文本任务
- 还不需要明确区分 system / user 角色
- 需要频繁替换少量变量
如果你把它和第 2 节联系起来看,会更容易理解:PromptTemplate 的结果通常还是字符串,只不过这条字符串不再靠手写,而是由模板生成。
6.2 常用参数
| 参数 | 说明 |
|---|---|
template |
模板字符串,内部可包含 {变量名} 占位符 |
input_variables |
调用时需要传入的变量名列表 |
partial_variables |
在模板创建阶段就预先固定的一部分变量 |
其中,partial_variables 尤其值得理解。它的作用很直接:
把那些“经常不变”的变量先固定住,后续每次只传真正会变化的部分。
典型例子:
- 系统角色长期固定为“Python 工程师”
- 但用户问题每次都不同
这样一来,你就不用每次都重复传 role="Python 工程师"。
6.3 常用方法
| 方法 | 返回值 | 适合场景 | 案例源码 |
|---|---|---|---|
format(...) |
str |
最常用,拿到字符串后直接给模型或自己继续拼接 | PromptTemplate_FormatMethod.py |
invoke({...}) |
PromptValue |
需要接入 LangChain 链时更自然 | PromptTemplate_InvokeMethod.py |
partial(...) |
新的 PromptTemplate |
先固定部分变量,再多次复用 | PromptTemplate_PartialMethod.py |
1 | from langchain_core.prompts import PromptTemplate |
对初学者的实用建议是:
- 刚入门时优先用
format - 做 LCEL 或链式调用时再逐渐理解
invoke - 同一模板长期复用时再考虑
partial
【案例源码】
format:案例与源码-2-LangChain框架/04-prompt/prompt_templates/method/PromptTemplate_FormatMethod.py
invoke:案例与源码-2-LangChain框架/04-prompt/prompt_templates/method/PromptTemplate_InvokeMethod.py
partial:案例与源码-2-LangChain框架/04-prompt/prompt_templates/method/PromptTemplate_PartialMethod.py
PromptTemplate_FormatMethod.py
PromptTemplate_InvokeMethod.py
PromptTemplate_PartialMethod.py
6.4 创建方式
PromptTemplate 常见有两种创建方式:
- 构造函数:手动指定
template和input_variables from_template(...):由 LangChain 自动推断变量名
1 | from langchain_core.prompts import PromptTemplate |
从经验上看,可按下面的方式选择:
- 模板简单:优先
from_template(...) - 你想显式表达变量名:用构造函数
【案例源码】构造函数:案例与源码-2-LangChain框架/04-prompt/prompt_templates/PromptTemplate_Constructor.py
【案例源码】from_template:案例与源码-2-LangChain框架/04-prompt/prompt_templates/PromptTemplate_FromTemplate.py
PromptTemplate_FromTemplate.py
除了创建方式,本章还保留了两个补充案例。
第一,组合多个模板。
当一个 Prompt 由多个子部分拼起来时,可以通过 + 组合模板,而不是手写超长字符串。真实项目里,这在“角色说明 + 业务规则 + 当前任务”这种分段组织里很有用。
第二,比较 partial_variables 与 partial()。
二者都能做到“先固定一部分变量,后续只传剩余变量”,但一个发生在模板创建阶段,一个发生在已有模板基础上。
PromptTemplate_PartialVariables.py
7、对话提示词模板(ChatPromptTemplate)
7.1 简介
如果说 PromptTemplate 更适合“单条文本输入”,那么 ChatPromptTemplate 就是为“聊天模型场景”准备的模板类。
它比 PromptTemplate 更贴合真实项目,因为真实聊天应用往往不只是“一句话”,而是:
- 一条系统设定
- 一条或多条用户消息
- 可能还有历史 AI 回复
- 可能再插入工具结果或历史上下文
这时,把所有内容写成一大段纯文本虽然也能跑,但不如把它们拆成带角色的消息来得清晰。
1 | from langchain_core.messages import SystemMessage, HumanMessage, AIMessage |
所以你可以把 ChatPromptTemplate 简单理解成:“面向多角色消息的模板系统”。
7.2 常用参数
ChatPromptTemplate 的核心不是单个 template 字符串,而是一组“消息模板”。每一项都可以是下面这些形式:
| 类型 | 说明 | 案例源码 |
|---|---|---|
| 元组 | ("system", "你是{name}") |
ChatPromptTemplate_TupleParam.py |
| 字典 | {"role": "system", "content": "你是{name}"} |
ChatPromptTemplate_DictParam.py |
| Message 类 | SystemMessage(content="你是{name}") |
ChatPromptTemplate_MessageParam.py |
MessagesPlaceholder |
在模板中预留一段“消息列表占位” | 见 7.5 节 |
三种常规写法里,没有绝对的谁对谁错,可以按下面的经验来选:
- 元组:最简洁,教学和业务代码里都很常见
- 字典:更贴近 OpenAI 风格 JSON,方便和网关数据结构对齐
- Message 类:最显式,角色最清楚,适合教学和复杂场景
【案例源码】
元组:案例与源码-2-LangChain框架/04-prompt/chat_prompt_template/parameter/ChatPromptTemplate_TupleParam.py
字典:案例与源码-2-LangChain框架/04-prompt/chat_prompt_template/parameter/ChatPromptTemplate_DictParam.py
Message 类:案例与源码-2-LangChain框架/04-prompt/chat_prompt_template/parameter/ChatPromptTemplate_MessageParam.py
ChatPromptTemplate_TupleParam.py
ChatPromptTemplate_DictParam.py
ChatPromptTemplate_MessageParam.py
下面这个基础示例能帮助你直观看懂三种写法的共同点:它们都是在定义“系统说什么、用户说什么、哪些部分由运行时填值”。
1 | from langchain_core.messages import SystemMessage, HumanMessage |
7.3 常用方法
| 方法 | 返回值 | 使用建议 |
|---|---|---|
format_messages(...) |
List[BaseMessage] |
最直观,得到消息列表后交给模型 |
invoke({...}) |
ChatPromptValue |
适合与 LangChain 链条衔接,也可直接交给模型 |
format(...) |
str |
适合查看最终拼接效果,不推荐作为聊天主写法 |
这三个方法最容易混淆,建议这样理解:
format_messages:我要的是“消息列表”invoke:我要的是“PromptValue 对象”format:我要的是“纯字符串”
1 | from langchain_core.prompts import ChatPromptTemplate |
对实际项目来说,建议优先采用这两种:
format_messages(...) -> model.invoke(messages)invoke({...}) -> model.invoke(prompt_value)
因为这两种方式都能保留清晰的消息角色结构。
【案例源码】案例与源码-2-LangChain框架/04-prompt/chat_prompt_template/ChatPromptTemplate_FormatMessages.py
ChatPromptTemplate_FormatMessages.py
7.4 创建方式
ChatPromptTemplate 常见也有两种创建方式:
ChatPromptTemplate.from_messages([...])ChatPromptTemplate([...])
它们的核心差别不大,本质上都是把一组消息模板交给 ChatPromptTemplate。
1 | from langchain_core.prompts import ChatPromptTemplate |
经验上更推荐优先使用 from_messages(...),因为可读性更好,也更符合官方文档和社区示例的主流写法。
【案例源码】案例与源码-2-LangChain框架/04-prompt/chat_prompt_template/ChatPromptTemplate_Constructor.py
ChatPromptTemplate_Constructor.py
7.5 MessagesPlaceholder
MessagesPlaceholder(消息占位符)这是本章最重要的知识点之一,很多人一开始会觉得它抽象,但一旦理解,就会发现它几乎是多轮对话、记忆、历史上下文拼接的关键。
先说结论:MessagesPlaceholder 的作用,就是在模板里先留出一段“消息列表的位置”,等真正调用时再把历史对话整块塞进去。
为什么它重要?因为真实项目里的“历史对话”往往不是固定写死的:
- 有时只有 2 轮
- 有时有 10 轮
- 有时还要先裁剪、总结、过滤
如果没有占位符,你就只能在代码里手动拼接 HumanMessage、AIMessage,又乱又难维护。
它常见有两种写法:
- 显式写法:
MessagesPlaceholder("memory") - 隐式写法:
("placeholder", "{memory}")
二者的核心思想完全一样,只是语法风格不同。
典型结构通常是:
- 系统设定
- 历史消息占位
- 当前用户问题
1 | from langchain_core.messages import HumanMessage, AIMessage |
这段代码的价值在于,它让模板本身变得非常稳定,而把“历史有几轮、具体内容是什么”延迟到运行时再决定。后面学 第 16 章 记忆与对话历史 时,你会频繁看到这种模式。
【案例源码】
显式:案例与源码-2-LangChain框架/04-prompt/chat_prompt_template/placeholder/ChatPromptTemplate_ExplicitPlaceholder.py
隐式:案例与源码-2-LangChain框架/04-prompt/chat_prompt_template/placeholder/ChatPromptTemplate_ImplicitPlaceholder.py
ChatPromptTemplate_ExplicitPlaceholder.py
ChatPromptTemplate_ImplicitPlaceholder.py
8、从文件加载提示词
当 Prompt 还很短时,把它直接写在 Python 代码里问题不大;但只要你开始做真实项目,就会很快遇到几个问题:Prompt 越来越长,代码越来越乱;产品、运营、算法同学希望一起改 Prompt;需要保留多个版本做 A/B 测试;想把 Prompt 与代码逻辑分离。
这时候,把 Prompt 放到 JSON / YAML 等外部文件中,就会更工程化。LangChain 提供了 load_prompt(...),可以根据文件内容加载为模板对象。对于本章案例来说,最常见的是 _type: "prompt",即加载为 PromptTemplate。
8.1 从 JSON 加载
1 | { |
1 | from langchain_core.prompts import load_prompt |
8.2 从 YAML 加载
YAML 与 JSON 的核心思路完全相同,只是文件格式更适合人工阅读,也更方便写注释。对团队协作来说,很多场景会更喜欢 YAML。
运行这类案例时,先确认当前工作目录,否则相对路径可能找不到文件。这一点和 第 10 章 HelloWorld 中的 .env 读取问题本质很像,都是“脚本运行位置”和“相对路径”的关系。
【案例源码】
JSON:案例与源码-2-LangChain框架/04-prompt/load_external/PromptLoadDemo01.py
YAML:案例与源码-2-LangChain框架/04-prompt/load_external/PromptLoadDemo02.py
配套文件:prompt.json、prompt.yaml
到这里你可以看到,本章知识已经形成了一个比较完整的工程化闭环:
- 消息类型 解决“输入按什么角色组织”
- 调用方式 解决“输入如何发给模型”
- PromptTemplate / ChatPromptTemplate 解决“输入如何复用”
- MessagesPlaceholder 解决“历史对话如何动态插入”
- JSON / YAML 外置文件 解决“模板如何协作与版本管理”
章节思考题:
普通字符串 Prompt 和消息模板最大的工程差别是什么?
参考思路: 普通字符串适合临时调用,消息模板更适合长期维护。它把系统规则、用户输入、历史消息和变量占位分清楚,后续改规则、换输入、接多轮历史都会更稳。
什么时候应该用
MessagesPlaceholder,而不是把历史对话拼成一大段字符串?参考思路: 需要保留消息角色、顺序和多轮结构时,应使用
MessagesPlaceholder。拼字符串会丢掉角色信息,也不利于后续和记忆、工具调用、消息对象体系衔接。如果一个模板变量越来越多,你会如何判断它是不是该拆分?
参考思路: 看变量是否服务同一个任务、是否来自同一层上下文、是否经常一起变化。如果一个模板同时管规则、业务输入、检索结果、历史消息和输出格式,可能就该拆成更小的模板或链路节点。
把提示词放到外部文件有什么好处和风险?
参考思路: 好处是便于版本管理、运营调整和复用;风险是变量名、格式和代码调用容易不一致。外置后要配套校验、示例输入和变更记录,不能只把文本搬出去。
本章小结:
- Prompt 的本质:Prompt 不是“随便写一句话”,而是对模型输入进行结构化组织。随着项目复杂度提升,输入会从纯字符串演化成多角色消息,再进一步演化成模板、占位符与外部配置文件。
- 消息与调用:聊天模型常见输入包括
str、消息对象列表、元组列表、字典列表;常见调用方式包括invoke / ainvoke、stream / astream、batch / abatch。返回值通常是AIMessage,正文一般通过.content读取。 - 模板与工程化:
PromptTemplate适合文本模板,ChatPromptTemplate适合聊天模型与多角色场景,MessagesPlaceholder是多轮历史拼接的关键;将 Prompt 放入 JSON / YAML 更适合真实项目中的版本管理、多人协作与 A/B 测试。 - 学完本章后,你至少应该能区分四类输入组织方式:纯字符串、消息列表、模板 + 占位符、外部文件加载;也要知道
PromptTemplate、ChatPromptTemplate、MessagesPlaceholder分别适合什么场景。
建议下一步: 继续学习 第 14 章 输出解析器,把本章的“输入组织”与下一章的“输出结构化”连起来;再配合 第 15 章 LCEL 与链式调用,就能形成 LangChain 中最核心的“输入 -> 模型 -> 输出 -> 链式编排”主线。如果你读到这里,仍然对“为什么要这样拆 Prompt”不够踏实,也可以回看 1-2 提示词工程基础 中的六要素、Few-shot 和结构化组织方式部分。