LangChain:Message 与提示词模板
理解消息角色、工具响应配对、多轮历史、内容块与提示词模板的组合方式。
正文目录
消息组织角色与交互历史,提示词模板将固定指令与动态输入组合起来。本章介绍消息结构、工具响应配对、多轮历史和模板的使用方式。
适用版本:LangChain 1.2.12。示例中的
model为已初始化的聊天模型,创建方式见第 2 章。
1. Message 是交互的基本单位
消息不只是文字,还包含角色、内容、身份标识、工具调用与响应元信息。聊天模型用角色区分谁在说话,用当前输入中的消息序列理解上下文。普通无状态 API 不会自动保存并重新发送应用历史。
| 角色 | 字典写法 | 消息对象 | 职责 |
|---|---|---|---|
| 系统 | {"role": "system", "content": "..."} |
SystemMessage |
定义角色、任务边界与回答要求 |
| 用户 | {"role": "user", "content": "..."} |
HumanMessage |
表达用户输入 |
| 助手 | {"role": "assistant", "content": "..."} |
AIMessage |
模型回复,也可能携带工具调用 |
| 工具 | {"role": "tool", "content": "...", "tool_call_id": "..."} |
ToolMessage |
返回某次工具执行的结果 |
消息字典列表是可序列化为 JSON 的 Python 对象。调用时应按接口要求传入对象,区分消息列表与已经编码的 JSON 字符串。
from langchain.messages import SystemMessage, HumanMessage, AIMessage, ToolMessage
messages = [ SystemMessage(content="你是 Python 学习助手,用简短示例解释问题。"), HumanMessage(content="什么是列表?"),]response = model.invoke(messages)messages.append(response)2. 消息字段与工具配对
2.1 关键字段
content:字符串或内容块列表。name:可选名称;适配器是否传递、模型是否利用它,取决于提供商,不能作为可靠的身份认证机制。id:消息标识,有助于状态合并、更新与删除。response_metadata:响应相关信息;usage_metadata在 AIMessage 上提供标准化用量。tool_calls:AIMessage 中解析后的调用列表,元素通常含name、args、id、type。tool_call_id:ToolMessage 必须关联的调用 ID。
通用“元信息”不等于所有信息都放在一个名为 metadata 的消息字段中;消息元数据、Runnable 的追踪 metadata 和模型请求字段要分开理解。
2.2 完整工具消息链
HumanMessage:查询北京天气AIMessage:tool_calls=[{name: get_weather, args: {...}, id: call_1}]ToolMessage:content=查询结果,tool_call_id=call_1AIMessage:结合工具结果回复用户模型提出调用的 AIMessage 必须保留,然后追加匹配的 ToolMessage,再交给模型。一次 AIMessage 可提出多个调用,应为每个调用补全对应结果;不能只追加工具结果而丢掉调用请求,也不能混入不相关消息破坏协议顺序。
3. 管理多轮历史
对无状态模型调用,应用维护历史的基本过程是:追加本轮用户消息 → 调用模型 → 追加模型回复 → 下一轮继续。
history = [SystemMessage(content="你是耐心的学习助手。")]
def ask(question: str): history.append(HumanMessage(content=question)) answer = model.invoke(history) history.append(answer) return answer.content
print(ask("我正在学习 LangChain。"))print(ask("我刚才说在学什么?"))这段示例只适合单用户的内存演示。多用户应用需要隔离历史;程序退出后列表消失;长历史需要裁剪或摘要。
多轮对话需要保留足够的有效上下文。 可以按 Token 预算保留系统规则、近期消息和关键摘要,避免历史无限增长。包含工具调用时,简单截取最后 N 条可能留下孤立的 ToolMessage,应按有效消息序列裁剪。
流式聊天中需要把块合并为完整回复后写入历史;不要把每个碎片都当作独立的一轮助手消息。
4. content 与 content_blocks
| 属性 | 理解方式 | 使用建议 |
|---|---|---|
content |
原始文本或提供商格式的内容 | 纯文本场景最直观,先判断类型 |
content_blocks |
按标准 type 组织的内容视图 |
多模态、引用、推理等跨厂商处理 |
标准内容块可以包含 text、image、audio、video、tool_call、reasoning 等类型,但并非每个模型都支持全部输入输出。标准化会在访问时解析可识别内容,不会凭空增加模型能力,也不会暴露模型没有返回的内部推理。
for block in response.content_blocks: if block["type"] == "text": print(block["text"])图像输入要按模型要求提供 URL 或正确编码的图像数据。把本地文件路径写进提示词,不等于模型已经读取图像;远程服务也不能直接访问本机路径。
5. 提示词模板
5.1 模板的作用
模板把固定指令和运行时变量分离,便于复用、检查缺失变量、记录版本和组合多角色消息。
PromptTemplate:构造文本提示;仍适合单段文本任务。ChatPromptTemplate:构造包含明确角色的聊天消息。
二者不是简单的“旧版不能用、新版必须用”。聊天模板也不会自动保存历史,变量校验更不等于防止提示词注入或保证模型服从。
5.2 创建与调用
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([ ("system", "你是{subject}教师,适合{level}学习者。"), ("human", "请解释:{question}"),])values = {"subject": "Python", "level": "初学", "question": "装饰器"}
prompt_value = prompt.invoke(values)answer = model.invoke(prompt_value)print(answer.content)也可以用 ChatPromptTemplate([...]) 构造。常见的三种渲染方法:
| 方法 | 输入 | 返回 |
|---|---|---|
invoke({...}) |
变量字典 | ChatPromptValue,可交给模型 |
format(**values) |
关键字参数 | 拼成的字符串,适合展示 |
format_messages(**values) |
关键字参数 | 消息对象列表 |
模板支持字符串、角色元组、角色字典、消息对象、消息模板和子聊天模板等输入。学习时优先使用角色元组,结构最清楚。HumanMessage(content="{question}") 是已经构造好的消息,里面的花括号不会自动变成模板变量;应改用 ("human", "{question}")。
5.3 与模型组合
chain = prompt | modelresult = chain.invoke(values)这里的管道表达“模板输出交给模型”,不是旧版 LLMChain 对象。模板还能用字符串片段或 + 组合;组合后仍应检查消息顺序、重复系统指令与变量名冲突。
6. 高级模板能力
6.1 partial() 预填变量
python_prompt = prompt.partial(subject="Python", level="初学")result = (python_prompt | model).invoke({"question": "生成器"})预填适合角色、语言、风格等重复配置;剩余变量在每次调用时传入。需要随时间变化的值,不要在创建模板时固定成过期常量。
6.2 MessagesPlaceholder 插入历史
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
chat_prompt = ChatPromptTemplate.from_messages([ ("system", "你是学习助手,结合已提供的历史回答。"), MessagesPlaceholder(variable_name="history", optional=True), ("human", "{question}"),])value = chat_prompt.invoke({ "history": [ HumanMessage(content="我刚学完函数。"), AIMessage(content="下一步可以学习装饰器。"), ], "question": "下一步学什么?",})占位符插入的是一组完整消息,保留各自角色;{question} 等普通变量填入单条消息文本。占位符只负责放置消息,不会自动检索或存储历史。简写 ("placeholder", "{history}") 也可表达消息占位符。
6.3 模板库
把常用模板集中到 templates.py 等模块,业务代码传入变量;为不同任务建立清晰名称,并记录必要的版本。模板里需要输出字面量 JSON 花括号时,默认 f-string 风格通常要写 {{ 和 }}。
7. 本章要点
- 消息列表、单条 AIMessage 和 PromptValue 是不同对象,不要把字符串化后的历史当成仍有角色结构的消息列表。
- 多轮调用要保留必要上下文;裁剪时注意工具调用与结果配对。
- 模板变量负责格式化,消息占位符负责插入消息列表,两者不能互换。
思考题
format_messages()与invoke()返回类型是什么?- 已有消息对象里的
{name}会不会被替换? - 为什么 ToolMessage 需要
tool_call_id?