03 · ChatPromptTemplate:从手写消息列表到模板的必然性
本片目标:回答”为什么要模板”——当消息列表需要动态拼装、插入历史、复用系统提示时,手写列表会变得脆弱;模板把”消息结构”与”每次的数据”分离。 新增规定性:3(拼消息用ChatPromptTemplate,历史用MessagesPlaceholder) 数据字典:ChatPromptTemplate / MessagesPlaceholder / PromptTemplate / PromptValue。 进程线程模型:无变化(同步阻塞,模板 invoke 是纯内存操作,毫秒级)。 网络模型:模板本身不联网;它把消息拼好后,交给模型 invoke 才发 HTTP。
1. 上集回顾
第 02 篇我们学会了构造SystemMessage + HumanMessage 列表传给模型。但现在遇到两个场景,手写列表开始难受:
1要插入历史消息:多轮对话时,消息列表是 [system, 历史user, 历史ai, 当前user]——历史是动态的、可长可短。
2要动态填变量:"你是{company}的助手,请回答关于{product}的问题"——每次拼字符串吗?f-string 拼多了就乱。
本质问题:消息列表的结构(哪些是 system、哪里插历史、哪里放当前问题)是固定的,变的是数据(问题内容、公司名、历史消息)。把”固定结构”和”变化数据”混在一起写,必然导致重复代码和拼错。
模板 = 把结构写一次,数据每次填进去。
2. 数据字典:模板类
2.1 ChatPromptTemplate(聊天模板,本系列主力)
MessageLikeRepresentation(模板里每个元素,三种写法):
2.2 MessagesPlaceholder(历史槽位——关键)
为什么需要它(规定性 3 的核心):普通
{question} 只能塞一个字符串。而历史是一组消息对象。如果不用占位符而是 ("human", "{history}"),传列表进去会变成字符串字面量 "['AIMessage(...)', 'AIMessage(...)']"——模型看到的是 Python 列表的 repr,不是消息!
2.3 PromptValue(invoke 的返回)
2.4 PromptTemplate(单条字符串模板,偶尔用)
3. 关键方法
4. 进程线程模型
5. 网络模型
type 字段("system"/"human"/"ai")在序列化时被映射成协议的 role(system/user/assistant)。
6. 验证:跑起来
配套代码code/03_prompt.py:
- 建一个带 system 变量 + history 槽位 + question 的模板;
- 第一轮(空历史)invoke,打印拼出的消息列表;
- 第二轮(有历史)invoke,打印历史如何被展开插入;
- 演示
{history}普通变量 vsMessagesPlaceholder的差异(传列表会变成字面量字符串)。
7. 边界
- 模板不是唯一方式——
create_agent内部就不用模板(直接列表拼接)。但本系列目标是 RAG,模板是拼 prompt 的正道。 {变量}只塞字符串——塞列表会变成 repr 字符串。塞消息组必须用MessagesPlaceholder。- f-string 语法限制——模板里别写裸
{}(如 JSON 示例),要么转义{{,要么用template_format="jinja2"。 - system 提示也可以插值——
("system", "你是{company}的助手")完全合法,变量在 invoke 时填。
推荐资料(延伸阅读)
- LangChain 官方文档 · 提示词模板 —— 模板与 MessagesPlaceholder 权威说明
- langchain-core API 参考 · ChatPromptTemplate —— 模板类的完整方法签名
8. 未完待续
现在”输入”这块齐了:模型 + 消息 + 模板。但输出还有问题:invoke返回的是 AIMessage,我想要纯文本——每次手动.content?- 模型回答慢,用户等 2 秒才看到整段文字——能不能流式输出,像 ChatGPT 那样一个字一个字蹦?