LangChain 学习笔记(四):Message 与提示词模板
本文基于《尚硅谷 LangChain 1.2》第4章整理,并结合实际开发经验进行补充。
本章不仅介绍 Message 的内部结构与四种消息类型,更重要的是理解 ChatPromptTemplate 如何取代传统的 PromptTemplate,成为现代 LangChain 1.0 应用的核心提示词工具。
一、本章学习目标
学习完本章,你应该能够:
- 理解 Message 的内部结构(role、content、metadata)
- 掌握四种消息类型:SystemMessage、HumanMessage、AIMessage、ToolMessage
- 区分 JSON 格式与对象格式,并了解各自适用场景
- 掌握所有消息对象的常用字段与参数
- 理解对话历史管理的核心规则(传递完整历史)
- 掌握对话历史优化策略(keep_recent_messages 模式)
- 实现完整的多轮对话聊天机器人
- 理解 content 的两种形式(字符串 vs 字典列表)与 content_blocks 的统一多模态标准
- 掌握 PromptTemplate 与 ChatPromptTemplate 的区别与演进
- 熟练使用 ChatPromptTemplate 的两种创建方式与三种调用方法
- 理解六种消息模板类型
- 掌握 partial() 预填充、MessagesPlaceholder、模板库、模板组合等高级特性
二、认识消息(Message)
1、为什么需要 Message?
大模型没有记忆。
它的输出只和输入模型的内容有关(上下文)。
很多大模型 API 服务也没有在服务端维护会话历史,是"无状态"的。
因此,如果应用需要"记住"对话历史,必须在程序中维护消息列表。
在 LangChain 中,Message(消息)是模型交互的最基本单元。
它既代表模型接收到的输入(Input) ,也代表模型生成的输出(Output)。
每一轮与大模型的对话,都由一条或多条 Message 构成。
每个 Message 不仅包含文字内容 ,还携带描述上下文状态的元信息(metadata)。
比如,模型在多轮交互中理解"谁在说话"、"说了什么"、"这条信息属于哪一轮对话"。
2、LangChain 1.0 的跨模型统一 Message 标准
LangChain 在 1.0 中提供了跨模型统一的 Message 标准。
无论你使用的是 OpenAI、Anthropic、Gemini 还是本地模型,这一标准都能保持一致的行为。
好处:
-
兼容性强:不同模型的消息格式自动对齐。
-
可扩展性高:方便添加多模态内容或自定义字段。
-
可追踪性好:为 LangSmith 等调试工具提供一致的上下文数据结构。
任何模型(OpenAI/Anthropic/Gemini)
│
↓
LangChain Message 统一标准
│
↓
格式自动对齐
多模态支持
可追踪上下文
三、消息的内部结构与类型
1、Message 的三种核心字段
LangChain 的消息对象包含三种字段:
Message 对象
│
├── Role(角色)
│ └── 消息所属的角色,如 system、user、assistant、tool
│
├── Content(内容)
│ └── 消息的文字内容或多模态数据
│
└── Metadata(元数据)
└── 可选,存储额外信息
如:消息ID、响应时间、token消耗量、消息标签等
| 字段 | 是否必须 | 说明 |
|---|---|---|
| role | 是 | 消息角色:system/user/assistant/tool |
| content | 是 | 消息内容,支持字符串或字典列表 |
| metadata | 否 | 元数据字典,用于存储额外上下文信息 |
2、四种消息类型
| 类型 | Role | 说明 | 方向 |
|---|---|---|---|
| SystemMessage | system | 系统提示词,设定 AI 的行为、角色、规则 | 输入 |
| HumanMessage | user | 用户输入,可以包含文本或多模态内容 | 输入 |
| AIMessage | assistant | AI 的回复,包含文本、工具调用、元数据等 | 输出 |
| ToolMessage | tool | 工具执行的结果,匹配 AIMessage 中的工具调用 ID | 输入 |
为什么使用不同的消息类型?
- 明确角色:清晰区分系统提示、用户输入和 AI 回复
- 控制行为:通过 SystemMessage 精确控制 AI 的行为
- 对话历史:构建完整的多轮对话上下文
- 调试友好:更容易追踪和调试对话流程
四种消息的 JSON 示例
json
// 1. 系统消息
{"role": "system", "content": "你是个精通编程的软件架构师"}
// 2. 用户消息
{"role": "user", "content": "你好啊~"}
// 3. 助手消息(纯文本回复)
{"role": "assistant", "content": "我也很高兴认识你"}
// 4. 助手消息(含工具调用)
{
"role": "assistant",
"content": "",
"tool_calls": [{
"name": "get_weather",
"args": {"location": "北京"},
"id": "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"
}]
}
// 5. 工具调用消息
{"role": "tool", "content": "今天天气很好", "tool_call_id": "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"}
四、消息格式:JSON 格式 vs 对象格式
LangChain 支持两种消息格式,它们完全等价,可以互相转换。
1、JSON 格式(字典格式)
直接使用 Python 字典,符合 OpenAI Chat API 标准。
python
# JSON格式的消息列表
messages = [
{"role": "system", "content": "你是个善解人意的助手"},
{"role": "user", "content": "你好啊~"},
{"role": "assistant", "content": "我也很高兴认识你"},
{"role": "tool", "content": "<工具输出>", "tool_call_id": "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"}
]
2、对象格式(Message 对象)
使用 LangChain 提供的 Message 类。
python
from langchain_core.messages import (
HumanMessage, # 用户消息
AIMessage, # AI 消息
SystemMessage, # 系统消息
ToolMessage # 工具返回消息
)
# 对象格式的消息列表
SystemMessage(content="你是个善解人意的助手")
HumanMessage(content="你好啊~")
AIMessage("我也很高兴认识你")
ToolMessage(
content="<工具输出>",
tool_call_id="call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s" # 一定要和AI消息中的调用ID匹配
)
3、两种格式的对比
| 对比维度 | JSON 格式 | 对象格式 |
|---|---|---|
| 写法 | {"role": "system", ...} |
SystemMessage(...) |
| 本质 | Python 字典 | LangChain 对象 |
| 序列化 | 天然支持(JSON) | 需要转换 |
| 额外属性 | 不直接支持 | 支持 id、response_metadata、tool_calls 等 |
| 类型安全 | 弱(依赖字符串匹配 role) | 强(IDE 自动补全与类型检查) |
| 适用场景 | 快速原型、HTTP 接口 | 正式项目、Agent 开发、LangGraph |
| LangChain 内部 | 会自动转换 | 原生支持 |
JSON 格式 ←------自动转换------→ 对象格式
(字典) (Message 类)
│ │
│ │
└────── 都可以传入 ───────┘
│
↓
model.invoke()
JSON 格式调用示例
python
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
# 通过JSON初始化
messages = [
{"role": "system", "content": "你是一个善于给出通俗易懂解释的AI助手"},
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!我能帮你什么?"},
{"role": "user", "content": "什么是机器学习"}
]
response = model.invoke(messages)
print(response.content)
对象格式调用示例
python
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
messages = [
SystemMessage("你是一个善于给出通俗易懂解释的AI助手"),
HumanMessage("你好"),
AIMessage("你好!我能帮你什么?"),
HumanMessage("什么是机器学习"),
]
response = model.invoke(messages)
print(response.content)
五、消息对象字段说明
1、SystemMessage 参数列表
python
SystemMessage("你是个善解人意的助手") # content 字段名可以省略
SystemMessage(content="你是个善解人意的助手") # 等价写法
SystemMessage 结构最简单,一般只需要 content 一个参数。
2、HumanMessage 参数列表
| 参数 | 是否必须 | 说明 |
|---|---|---|
| content | 是 | 消息内容,字段名可以省略 |
| name | 否 | 用户名,用于多人对话场景区分发言者 |
| id | 否 | message 的唯一 ID |
| metadata | 否 | 元数据字段,可自定义多个 |
python
# 基础用法
HumanMessage("你好啊~")
HumanMessage(content="你好啊~")
# 带 name 和 id
HumanMessage(
content="Hello!",
name="alice", # 可选,用户名
id="msg_123", # 可选,message 的 ID
)
注意 :
name和id都属于元数据字段。不是所有模型都支持这些字段,是否支持取决于模型供应商,需要查看官方手册。
例如:OpenAI 的 API 手册明确支持 name 作为元数据字段。
DeepSeek 的 API 官方文档也明确支持 name 作为元数据,但实测发现模型可能无法识别。
ChatOpenRouter 调用时可能没有将 name 正确传递给模型服务。
name 字段的实战应用(多人对话场景)
python
from langchain_core.messages import SystemMessage, HumanMessage
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
messages = [
SystemMessage("你是一个信息抽取器。你会收到多条来自不同发言者的 user 消息。"
"每条消息可能带有 name 字段。你的任务是:严格根据每条消息的 name "
"提取发言者及其观点,并输出JSON。禁止使用"第一个人/第二个人"这种相对称呼。"
"若某条消息没有 name,则输出 unknown。"
"输出格式:{\"speakers\":[{\"name\":\"...\",\"claim\":\"...\"}]}"),
HumanMessage(content="我认为1+1=2", name="Bob"),
HumanMessage(content="我认为1+1>2", name="Tom"),
HumanMessage(content="请列出谁说了什么,不要判断对错。", name="audience"),
]
response = model.invoke(messages)
print(response.content)
# 输出:{"speakers":[{"name":"Bob","claim":"我认为1+1=2"},
# {"name":"Tom","claim":"我认为1+1>2"},
# {"name":"audience","claim":"请列出谁说了什么,不要判断对错。"}]}
3、AIMessage 参数列表
| 参数 | 是否必须 | 说明 |
|---|---|---|
| content | 是 | 模型输出的原始内容,字段名可以省略 |
| response_metadata | 否 | 模型响应的附加元数据(token 用量、模型名称、finish_reason 等) |
| tool_calls | 否 | 工具调用信息列表,无工具调用时为空列表 |
| usage_metadata | 否 | 标准化的 token 用量信息 |
| id | 否 | message 的唯一 ID(通常是 run ID) |
python
# 基础用法
AIMessage("你好~")
AIMessage(content="你好~")
# 给出最终答案
AIMessage(content="北京今天晴天,温度15°C")
# 调用工具(content 为空,tool_calls 不为空)
AIMessage(
content="",
tool_calls=[{
'name': 'get_weather',
'args': {'city': '北京'},
'id': 'call_xxx'
}]
)
tool_calls 结构详解
python
tool_calls=[
{
'name': 'get_weather', # 应调用的工具名
'args': {'city': '杭州'}, # 调用工具的参数
'id': 'call_00_gIXYOD1Q1OkEXmdDBqXR1578', # 工具调用的唯一标识ID
'type': 'tool_call'
},
{
'name': 'get_news',
'args': {},
'id': 'call_01_jD3phD5PEaIZf0mVLhKt0861',
'type': 'tool_call'
}
]
AIMessage 完整输出示例
python
from rich import print as rprint
messages = [
SystemMessage("你叫小智,是一名助人为乐的助手。"),
HumanMessage("你好,好久不见,请介绍下你自己。")
]
response = model.invoke(messages)
rprint(response)
返回的 AIMessage 结构:
AIMessage(
content='你好,好久不见!我叫小智...',
additional_kwargs={'refusal': None},
response_metadata={
'token_usage': {
'completion_tokens': 118,
'prompt_tokens': 34,
'total_tokens': 152,
...
},
'model_provider': 'openai',
'model_name': 'gpt-5.4-mini-2026-03-17',
'finish_reason': 'stop',
...
},
id='lc_run--019e49a0-928b-7712-8000-3c4ceba64cff-0',
tool_calls=[],
invalid_tool_calls=[],
usage_metadata={
'input_tokens': 34,
'output_tokens': 118,
'total_tokens': 152,
'input_token_details': {'audio': 0, 'cache_read': 0},
'output_token_details': {'audio': 0, 'reasoning': 0}
}
)
AIMessage 对象各字段获取方式
python
# 获取回答内容
print(response.content)
# 获取 token 用量
print(response.usage_metadata)
# {'input_tokens': 34, 'output_tokens': 118, 'total_tokens': 152, ...}
# 获取响应元数据
print(response.response_metadata)
# 获取工具调用
print(response.tool_calls)
4、ToolMessage 参数列表
| 参数 | 是否必须 | 说明 |
|---|---|---|
| content | 是 | 工具执行的返回结果 |
| name | 否 | 工具名称 |
| tool_call_id | 是 | 工具调用唯一ID,必须与 AIMessage 中 tool_calls 的 id 匹配 |
python
ToolMessage(
content="<工具输出>",
name="get_weather",
tool_call_id="call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s" # 一定要和AI消息中的调用ID匹配
)
工具调用完整流程(JSON格式)
python
from langchain.chat_models import init_chat_model
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
def get_weather(city: str) -> str:
return "不错哦~"
# 模拟模型绑定工具
model_with_tools = model.bind_tools([get_weather])
# 模拟 AI 消息(含工具调用)
ai_message = {
"role": "assistant",
"content": "",
"tool_calls": [{
"name": "get_weather",
"args": {"location": "北京"},
"id": "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"
}]
}
# 模拟工具返回消息
tool_message = {
"role": "tool",
"content": "今天北京天气晴朗,万里无云~",
"tool_call_id": "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"
}
messages = [
{"role": "user", "content": "北京天气如何"},
ai_message,
tool_message
]
response = model.invoke(messages)
print(response.content) # 输出:今天北京天气晴朗,万里无云。
工具调用完整流程(对象格式)
python
from langchain_core.messages import AIMessage, ToolMessage, HumanMessage
ai_message = AIMessage(
content=[],
tool_calls=[{
"name": "get_weather",
"args": {"location": "北京"},
"id": "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"
}]
)
tool_message = ToolMessage(
content="今天北京天气晴朗,万里无云~",
tool_call_id="call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s"
)
messages = [
HumanMessage(content="北京天气如何"),
ai_message,
tool_message
]
response = model.invoke(messages)
print(response.content)
六、对话历史管理
1、关键规则:每次调用必须传递完整的对话历史!
大模型没有长期记忆。
每一次 model.invoke(...) 实际上都是重新发送整个 messages。
第1轮:
[system, user] → AI 回复 → 保存回复
第2轮:
[system, user, assistant, user] → AI 回复 → 保存回复
第3轮:
[system, user, assistant, user, assistant, user] → AI 回复
每次对话都要在原有的消息列表中添加新消息,不可重新创建新的列表。
2、错误做法汇总
python
# ❌ 错误举例1:每次只传当前问题,不传历史
response1 = model.invoke("我叫张三")
response2 = model.invoke("我叫什么?") # AI 不记得!
# ❌ 错误举例2:重新创建列表,丢失了历史
conversation = [{"role": "user", "content": "问题1"}]
response1 = model.invoke(conversation)
conversation = [{"role": "user", "content": "问题2"}] # 重新创建!
response2 = model.invoke(conversation) # 丢失了历史
# ❌ 错误举例3:忘记保存 AI 回复
conversation = []
conversation.append({"role": "user", "content": "问题1"})
response1 = model.invoke(conversation)
# 忘记保存 response1.content!
conversation.append({"role": "user", "content": "问题2"})
response2 = model.invoke(conversation) # AI 不知道之前的回答
3、正确做法 ✅ (★★★★★)
python
conversation = []
# 第一次
conversation.append({"role": "system", "content": "你是Python导师"})
conversation.append({"role": "user", "content": "我叫张三"})
response1 = model.invoke(conversation)
# 关键:保存 AI 回复
conversation.append({"role": "assistant", "content": response1.content})
# 第二次(传递完整历史)
conversation.append({"role": "user", "content": "我叫什么?"})
response2 = model.invoke(conversation) # AI 记得!
核心流程:
用户输入
│
↓
追加到 messages 列表
│
↓
传递完整 messages 给 invoke()
│
↓
获取 AI 回复
│
↓
追加 AI 回复到 messages 列表
│
↓
(循环)等待下一次用户输入
4、对话历史优化:keep_recent_messages 模式
问题:对话历史会越来越长,消耗大量 tokens 和成本。
解决方案:只保留最近 N 轮对话。
策略:
- 总是保留 system 消息(定义角色)
- 只保留最近 N 轮对话,丢弃更早的历史
python
def keep_recent_messages(messages, max_pairs=3):
"""
保留最近的 N 轮对话
max_pairs: 保留的对话轮数(每轮 = user + assistant)
"""
# 分离 system 和对话
system_msgs = [m for m in messages if m.get("role") == "system"]
conversation_msgs = [m for m in messages if m.get("role") != "system"]
# 只保留最近的
recent_msgs = conversation_msgs[-(max_pairs * 2):]
# 返回:system + 最近对话
return system_msgs + recent_msgs
测试对话历史优化
python
long_conversation = [
{"role": "system", "content": "你是Python导师"}
]
# 第1轮
long_conversation.append({"role": "user", "content": "什么是列表?用一句解释"})
r1 = model.invoke(long_conversation)
long_conversation.append({"role": "assistant", "content": r1.content})
# 第2轮
long_conversation.append({"role": "user", "content": "列表和元组有什么区别?用一句解释"})
r2 = model.invoke(long_conversation)
long_conversation.append({"role": "assistant", "content": r2.content})
# 第3轮
long_conversation.append({"role": "user", "content": "什么是字典呢?用一句解释"})
r3 = model.invoke(long_conversation)
long_conversation.append({"role": "assistant", "content": r3.content})
print(f"原始消息数: {len(long_conversation)}") # 7
# 优化:只保留最近 2 轮
optimized = keep_recent_messages(long_conversation, max_pairs=2)
print(f"优化后消息数: {len(optimized)}") # 5
print(f"保留的内容: system + 最近 2 轮对话")
# 添加新的用户问题
optimized.append({"role": "user", "content": "我第一个问题问的是什么?"})
response = model.invoke(optimized)
print(f"\nAI回复: {response.content}")
# AI回复: 你第一个问题问的是:"列表和元组有什么区别?用一句解释"
说明:优化后模型"忘记"了第一个问题,因为被丢弃了。
这正是预期的行为------用"上下文遗忘"换取"成本控制"。
5、多轮对话聊天机器人(★★★★★ 完整实战)
python
from langchain.chat_models import init_chat_model
import os
from dotenv import load_dotenv
load_dotenv(override=True)
# 1. 基础配置
MODEL_NAME = "gpt-5.4-mini"
MAX_PAIRS_HISTORY = 10
EXIT_WORD = "quit"
# 2. 初始化模型
model = init_chat_model(
model=MODEL_NAME,
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL")
)
# 3. 初始化消息列表
messages = [
{
"role": "system",
"content": "你是小谷姐姐,尚硅谷教育的数字员工,也是一名耐心、友好的智能助手。"
"我会用自然、清晰的方式回答用户问题。"
}
]
# 4. 启动提示
print(f"请输入问题,输入 {EXIT_WORD} 结束对话\n")
# 5. 多轮对话主循环
i = 1
while True:
print("\n", "=" * 10, f'-> 第 {i} 轮对话开始 <-', "=" * 10, "\n")
user_input = input("请输入:")
# 退出判断
if user_input.lower() == EXIT_WORD:
print("对话已结束,欢迎下次再来!")
break
# 追加用户消息
messages.append({"role": "user", "content": user_input})
# 优化历史记忆
memory_messages = keep_recent_messages(messages, max_pairs=MAX_PAIRS_HISTORY)
# 流式输出模型回复
print("小谷姐姐:", end="", flush=True)
reply_content = ""
for chunk in model.stream(memory_messages):
if chunk.content:
print(chunk.content, end="", flush=True)
reply_content += chunk.content
print("\n", "=" * 10, f'-> 第 {i} 轮对话结束 <-', "=" * 10, "\n")
i += 1
# 追加 AI 回复
messages.append({"role": "assistant", "content": reply_content})
关键设计要点:
- 使用
keep_recent_messages()控制发送给模型的消息长度 - 对流式输出使用
for chunk in model.stream()配合flush=True实现打字机效果 - 每次对话后必须将 AI 回复追加到 messages
- system 消息在循环外定义,始终保留
七、拓展 -- content 与 content_blocks
1、content 的两种形式
消息的 content 是弱类型的,支持两种形式:
形式1:字符串(纯文本)
python
from langchain.messages import HumanMessage
msg1 = HumanMessage(content="你好啊")
msg2 = HumanMessage("你好啊") # 可以省略参数名称
适用于纯文本对话。
形式2:字典列表(多模态内容)
当需要发送图片、音频等多模态内容时,使用字典列表形式。
字典内容遵循模型供应商的 API 规范。
python
import base64
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL")
)
def encode_image(img_path, img_type='jpeg'):
"""将本地图片转换成 Base64 编码的 Data URI 字符串"""
with open(img_path, "rb") as img_file:
return f"data:image/{img_type};base64,{base64.b64encode(img_file.read()).decode('utf-8')}"
img_path = "image_test.png"
base64_image = encode_image(img_path)
response = model.invoke(
[
HumanMessage(
content=[
{'type': 'text', 'text': '这张图里有什么?'},
{
'type': 'image_url',
"image_url": base64_image,
}
]
)
]
)
print(response.content)
| content 形式 | 使用场景 | 结构 |
|---|---|---|
| 字符串 | 纯文本对话 | "你好" |
| 字典列表 | 多模态内容(图文、音视频) | [{'type': 'text', ...}, {'type': 'image_url', ...}] |
2、content_blocks -- 跨模型统一的多模态标准
在 LangChain 1.x 中,content_blocks 是消息对象(BaseMessage)的一项重大升级。
它的核心目标是提供一种跨模型供应商、标准化的多模态数据结构。
为什么需要 content_blocks?
过去,处理图片、音频、甚至是模型生成的"思维链(Reasoning)"内容时,不同供应商(OpenAI、Anthropic、Google 等)的 API 格式各异。
开发者需要写大量的适配代码。
content_blocks 的出现终结了这种混乱。
content_blocks 的特点
- 数据结构:
list[TypedDict] - 统一格式:每个 block 都有一个
type字段,用于区分内容类型 - 支持类型:
text(文本)、image(图片)、audio(音频)、video(视频)、tool_call(工具调用)以及reasoning(推理/思维链) - 懒加载:调用时才会解析,性能友好
注意 :在 LangChain 1.2 中,
content属性依然存在(向前兼容),但新增了content_blocks属性,可以将 content 解析为标准、类型安全的表示。
输入格式化(多模态输入)
借助 content_blocks,可以用一套标准代码,无缝地在不同厂商的模型之间切换。
示例1:OpenAI 模型
python
import base64
from langchain.messages import HumanMessage
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL")
)
def encode_image(img_path):
with open(img_path, "rb") as img_file:
return base64.b64encode(img_file.read()).decode("utf-8")
img_path = "image_test.png"
base64_image = encode_image(img_path)
response = model.invoke(
[
# 推荐的统一写法
HumanMessage(
content_blocks=[
{'type': 'text', 'text': '这张图里有什么?'},
{
'type': 'image',
'base64': base64_image,
'mime_type': 'image/png',
}
]
)
]
)
print(response.content)
示例2:Anthropic 模型(同样的 content_blocks 代码,只需换模型名)
python
model = init_chat_model(
model="claude-haiku-4-5",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL")
)
# 完全相同的 content_blocks 写法!
response = model.invoke(
[
HumanMessage(
content_blocks=[
{'type': 'text', 'text': '这张图里有什么?'},
{
'type': 'image',
'base64': base64_image,
'mime_type': 'image/png',
}
]
)
]
)
关键优势:
OpenAI 原生格式 ──┐
│
Anthropic 原生格式 ─┼──→ content_blocks 统一格式 ──→ 任一模型
│
Gemini 原生格式 ──┘
一套代码,多模型切换,无需修改 content_blocks 部分。
content 格式 vs content_blocks 格式对比
| 对比维度 | content(字典列表) | content_blocks(统一格式) |
|---|---|---|
| 标准 | 各厂商私有格式(如 OpenAI 的 image_url) | LangChain 跨模型统一标准 |
| 跨模型兼容 | ❌ 需要写适配代码 | ✅ 一套代码所有模型通用 |
| 推荐程度 | 向后兼容保留 | ★★★★★ 新项目强烈推荐 |
| 图片类型标识 | 'type': 'image_url' |
'type': 'image' |
| 图片数据格式 | "image_url": base64_image |
'base64': base64_image, 'mime_type': 'image/png' |
输出格式化(提取思维链/推理内容)
content_blocks 还可以统一不同模型的输出格式。
以 DeepSeek 的 deepseek-v4-flash 为例,其思考内容位于 additional_kwargs 的 reasoning_content 字段下。
不同模型输出格式不同,切换模型时需要更改提取代码。
content_blocks 提供了统一的输出格式:
python
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
load_dotenv(override=True)
model = init_chat_model(
model="deepseek:deepseek-v4-flash",
extra_body={"thinking": {"type": "enabled"}},
)
response = model.invoke("你好,一句话回答")
print('=' * 20, '-> response.content <-', '=' * 20)
print(response.content)
# 输出:你好,请说出您的问题,我会用一句话回答。
print('=' * 20, '-> response.content_blocks <-', '=' * 20)
print(response.content_blocks)
# 输出:
# [{'type': 'reasoning', 'reasoning': '好的,用户说"一句话回答"...'},
# {'type': 'text', 'text': '你好,请说出您的问题,我会用一句话回答。'}]
开发建议 :优先检查
response.content_blocks而不是response.content,特别是当你需要获取"思维链"或者"引用(Citations)"信息时。
八、提示词模板(Prompt Templates)
1、为什么推荐提示词模板?
在 LangChain 开发中,构造提示词既可以直接使用 Python 字符串拼接(如 f-string、format() 或 +),也可以使用 LangChain 提供的 PromptTemplate 或 ChatPromptTemplate。
方法1:字符串拼接
python
topic = "Python"
difficulty = "初学者"
# 难以维护,容易出错
prompt_str = f"你是一个 {difficulty} 级别的编程导师。请用简单易懂的语言解释 {topic}。"
response = model.invoke(prompt_str)
方法2:提示词模板
python
from langchain.prompts import PromptTemplate
topic = "Python"
difficulty = "初学者"
template = PromptTemplate.from_template(
"你是一个 {difficulty} 级别的编程导师。请用简单易懂的语言解释 {topic}。"
)
# 使用模板生成提示词
prompt = template.format(difficulty=difficulty, topic=topic)
response = model.invoke(prompt)
两种方式的对比
| 对比维度 | 字符串拼接 | 提示词模板 |
|---|---|---|
| 可读性 | ❌ 变量多时混乱 | ✅ 结构清晰(变量占位) |
| 可维护性 | ❌ 修改容易出错 | ✅ 易维护、可复用 |
| 变量校验 | ❌ 无校验(容易漏/拼错) | ✅ 自动变量校验(更安全) |
| 复杂场景 | ❌ 难以支持多轮对话/RAG/Few-shot | ✅ 支持对话/RAG/Agent |
| LangChain 集成 | ❌ 无 | ✅ 可与 LangChain 生态无缝集成 |
| 日志追踪 | ❌ 不便 | ✅ 便于调试与日志追踪 |
| 学习成本 | ✅ 无 | ❌ 有一定学习成本 |
开发建议:
- 小项目/临时用 --> 字符串拼接
- 正式开发/AI 应用 --> 提示词模板(必选 ★★★★★)
2、提示词机制的演进:从旧时代到新时代
LangChain 1.0 的架构变革中,核心的演进之一体现在 Prompt 机制上:
一个结构化的、富含元数据的消息列表已经取代单一字符串,成为与模型交互的标准数据格式。
旧时代:LLM + PromptTemplate
输入:单一字符串
│
↓
PromptTemplate.format()
│
↓
字符串 → LLM(文本补全模型)
│
↓
输出:单一字符串
python
from langchain.prompts import PromptTemplate
prompt_template = PromptTemplate.from_template(
"请给我一个关于 {topic} 的 {type} 解释。"
)
prompt = prompt_template.format(type="详细", topic="量子力学")
print(prompt)
# 请给我一个关于 量子力学 的 详细 解释。
局限性:当需要用这种方式模拟多轮聊天时,开发者必须在字符串中手动拼接和伪造对话角色,例如:
python
"Human:你好 \nAI:你好!有什么我能帮忙的吗?\nHuman:..."
这种方式不仅导致 Prompt 的结构混乱、难以维护,也极易让模型混淆对话的边界与上下文,影响生成质量。
新时代:ChatModel + ChatPromptTemplate
输入:消息列表
│
↓
ChatPromptTemplate.invoke()
│
↓
List[BaseMessage] → ChatModel(聊天模型)
│
↓
输出:消息列表
python
from langchain_core.prompts import ChatPromptTemplate
prompt_template = ChatPromptTemplate([
("system", "你是一个AI开发工程师. 你的名字是 {name}."),
("human", "{user_input}")
])
prompt = prompt_template.invoke({"name": "小谷AI", "user_input": "你能帮我做什么?"})
print(prompt)
# messages=[SystemMessage(...), HumanMessage(...)]
现代聊天模型 API 已原生支持角色概念,不再接受单一字符串,而是要求输入一个结构化的消息列表。
PromptTemplate vs ChatPromptTemplate
| 特性 | PromptTemplate | ChatPromptTemplate |
|---|---|---|
| 输出格式 | 纯文本字符串 | 消息列表(ListBaseMessage) |
| 角色支持 | ❌ 无 | ✅ system/user/assistant |
| 对话历史 | ❌ 不支持 | ✅ 支持 |
| 适用场景 | 简单提示 | 聊天、对话、多轮交互 |
| 推荐程度 | 旧项目兼容保留 | ★★★★★ 现代 LangChain 首选工具 |
九、ChatPromptTemplate 的使用
在 LangChain 1.0 中,ChatPromptTemplate 是用于生成消息列表的核心组件。
它比普通 PromptTemplate 更适合处理多角色、多轮次的对话场景,支持 System/Human/AI 等不同角色的消息模板。
消息角色说明
| 角色字符串 | 含义 | 用途 |
|---|---|---|
| "system" | 系统消息 | 设定 AI 的行为、角色、规则 |
| "user" / "human" | 用户消息 | 用户的输入/问题 |
| "assistant" / "ai" | AI 消息 | AI 的回复(用于对话历史) |
1、两种实例化方式
方式1(推荐 ★★★★★):from_messages()
python
from langchain_core.prompts import ChatPromptTemplate
chat_template = ChatPromptTemplate.from_messages(
[
("system", "你是一个有帮助的AI机器人,你的名字是{name}。"),
("human", "你好,最近怎么样?"),
("ai", "我很好,谢谢!"),
("human", "{user_input}"),
]
)
# 格式化聊天提示词模板中的变量
prompt = chat_template.invoke({"name": "小明", "user_input": "你叫什么名字?"})
print(prompt)
# messages=[SystemMessage(content='你是一个有帮助的AI机器人,你的名字是小明。', ...),
# HumanMessage(content='你好,最近怎么样?', ...),
# AIMessage(content='我很好,谢谢!', ...),
# HumanMessage(content='你叫什么名字?', ...)]
流程:
from_messages([
("system", "你是{name}"),
("human", "你好"),
("ai", "我很好"),
("human", "{user_input}"),
])
│
↓
.invoke({"name": "小明", "user_input": "你叫什么?"})
│
↓
messages = [
SystemMessage("你是小明"),
HumanMessage("你好"),
AIMessage("我很好"),
HumanMessage("你叫什么?"),
]
方式2:使用初始化方法(init)
python
from langchain_core.prompts import ChatPromptTemplate
prompt_template = ChatPromptTemplate([
("system", "你是一个AI开发工程师. 你的名字是 {name}."),
("human", "你能开发哪些AI应用?"),
("ai", "我能开发很多AI应用, 比如聊天机器人, 图像识别, 自然语言处理等."),
("human", "{user_input}")
])
prompt = prompt_template.invoke({"name": "小谷AI", "user_input": "你能帮我做什么?"})
print(prompt)
说明 :
from_messages()的底层也是调用的类的__init__()方法,两种方式本质等价。
2、模板调用的三种方式
| 方法 | 返回值 | 适用场景 |
|---|---|---|
invoke() |
ChatPromptValue | 直接传给 model.invoke() ★★★★★ |
format() |
str(字符串) | 调试、日志、传给非 LangChain 组件 |
format_messages() |
list(消息列表) | 需要直接操作消息对象时 |
方式1:invoke()(推荐 ★★★★★)
python
from langchain_core.prompts import ChatPromptTemplate
prompt_template = ChatPromptTemplate([
("system", "你是一个AI开发工程师. 你的名字是 {name}."),
("human", "你能开发哪些AI应用?"),
("ai", "我能开发很多AI应用, 比如聊天机器人, 图像识别, 自然语言处理等."),
("human", "{user_input}")
])
prompt = prompt_template.invoke({"name": "小谷AI", "user_input": "你能帮我做什么?"})
print(type(prompt)) # <class 'langchain_core.prompt_values.ChatPromptValue'>
print(prompt)
print(len(prompt.messages)) # 4
返回 ChatPromptValue,可直接传给 model.invoke()。
方式2:format()
python
prompt = prompt_template.format(name="小谷AI", user_input="你能帮我做什么?")
print(type(prompt)) # <class 'str'>
print(prompt)
# System: 你是一个AI开发工程师. 你的名字是 小谷AI.
# Human: 你能开发哪些AI应用?
# AI: 我能开发很多AI应用, 比如聊天机器人, 图像识别, 自然语言处理等.
# Human: 你能帮我做什么?
返回人类可读的字符串,方便调试和日志查看。
方式3:format_messages()
python
prompt = prompt_template.format_messages(name="小谷AI", user_input="你能帮我做什么?")
print(type(prompt)) # <class 'list'>
print(prompt)
# [SystemMessage(content='你是一个AI开发工程师. 你的名字是 小谷AI.', ...),
# HumanMessage(content='你能开发哪些AI应用?', ...),
# AIMessage(content='我能开发很多AI应用...', ...),
# HumanMessage(content='你能帮我做什么?', ...)]
返回消息列表(list),可以直接追加到对话历史中。
三种方法对比
┌──────────────┐
│ChatPromptTemplate│
└──────┬───────┘
│
┌───────────┼───────────┐
│ │ │
↓ ↓ ↓
invoke() format() format_messages()
│ │ │
↓ ↓ ↓
ChatPromptValue str List[Message]
(可直接调用) (日志用) (手动拼接)
3、ChatPromptTemplate + LLM 集成(★★★★★)
python
from dotenv import load_dotenv
from langchain_core.prompts import ChatPromptTemplate
import os
from langchain.chat_models import init_chat_model
# 1、提供大模型
load_dotenv(override=True)
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL")
)
# 2、提供提示词
chat_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个数学家,你可以计算任何算式"),
("human", "{text}"),
])
# 3、输入提示
prompt_value = chat_prompt.invoke({
"text": "我今年18岁,我的舅舅今年38岁,我的爷爷今年72岁,我和舅舅一共多少岁了?"
})
# 4、结合提示词,调用大模型
output = model.invoke(prompt_value)
print(output.content)
# 你今年18岁,舅舅今年38岁。
# 一共是:18 + 38 = 56岁
# 所以,你和舅舅一共 56岁。
标准三步流程:
ChatPromptTemplate
│
↓ invoke()
ChatPromptValue(消息列表)
│
↓ model.invoke()
AIMessage(模型回复)
│
↓ .content
字符串结果
十、更丰富的初始化参数类型
ChatPromptTemplate 的 messages 参数是列表类型,列表中的元素支持多种类型。
python
# __init__ 源码签名
def __init__(self,
messages: Sequence[BaseMessagePromptTemplate | BaseMessage |
BaseChatPromptTemplate |
tuple[str | type, str | list[dict] | list[object]] |
str | dict[str, Any]],
*,
template_format: Literal["f-string", "mustache", "jinja2"] = "f-string",
**kwargs: Any) -> None
结论:列表的元素可以是字符串、字典、元组、消息类型、提示词模板类型、消息提示词模板类型等。
六种参数类型汇总
| 类型 | 示例 | 角色默认值 | 推荐程度 |
|---|---|---|---|
| str | "Hello, {name}!" |
human | 不推荐(无角色区分) |
| tuple | ("system", "你是{role}") |
元组第一项 | ★★★★★ |
| dict | {"role": "system", "content": "你是{role}"} |
字典指定 | ★★★★ |
| Message | SystemMessage(content="你是助手") |
Message 自带 | ★★★★ |
| MessagePromptTemplate | HumanMessagePromptTemplate.from_template(...) |
模板指定 | ★★★★★ |
| BaseChatPromptTemplate | 嵌套 ChatPromptTemplate | 子模板定义 | ★★★(组合用) |
类型1:str 列表类型(不推荐)
python
chat_template = ChatPromptTemplate.from_messages([
"Hello, {name}!" # 等价于 ("human", "Hello, {name}!")
])
messages = chat_template.invoke({"name": "小谷AI"})
print(messages)
# messages=[HumanMessage(content='Hello, 小谷AI!', ...)]
默认角色都是 human,无法区分 system 和 user,不推荐。
类型2:tuple 列表类型(★★★★★ 推荐)
python
prompt = ChatPromptTemplate.from_messages([
("system", "你的名字是 {role}."),
("human", "很高兴认识你"),
])
print(prompt.invoke({"role": "小智"}))
# messages=[SystemMessage(content='你的名字是小智.', ...),
# HumanMessage(content='很高兴认识你', ...)]
元组的第一项指定角色,第二项指定内容。
这是最常用、最简洁的写法。
类型3:dict 列表类型
python
prompt = ChatPromptTemplate.from_messages([
{"role": "system", "content": "你的名字是 {role}."},
{"role": "human", "content": "很高兴认识你"},
])
print(prompt.invoke({"role": "小智"}))
# messages=[SystemMessage(content='你的名字是小智.', ...),
# HumanMessage(content='很高兴认识你', ...)]
与 JSON Message 格式一致,直观明了。
类型4:Message 列表类型
python
from langchain_core.messages import SystemMessage, HumanMessage
chat_prompt_template = ChatPromptTemplate.from_messages([
SystemMessage(content="我是一个贴心的智能助手"),
HumanMessage(content="我的问题是: 人工智能英文怎么说?")
])
messages = chat_prompt_template.invoke({})
print(messages)
注意 :在 XxxMessage 中不能有占位符 (如 {var}),占位符不会被解析:
python
# ❌ 占位符不会被解析
SystemMessage(content="我是一个{role}智能助手") # 变量不会被替换!
# ✅ 如果需要占位符,使用类型5(MessagePromptTemplate)
类型5:MessagePromptTemplate 列表类型(★★★★★ 推荐带变量场景)
LangChain 提供三种 MessagePromptTemplate:
SystemMessagePromptTemplate:生成 SystemMessage 的模板HumanMessagePromptTemplate:生成 HumanMessage 的模板AIMessagePromptTemplate:生成 AIMessage 的模板
python
from langchain_core.prompts import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
SystemMessagePromptTemplate
)
# 创建消息模板
system_message_prompt = SystemMessagePromptTemplate.from_template("你是一个{role}")
human_message_prompt = HumanMessagePromptTemplate.from_template("给我解释{concept},用浅显易懂的语言")
# 组合成聊天提示模板
chat_prompt = ChatPromptTemplate.from_messages([
system_message_prompt,
human_message_prompt
])
# 格式化提示
formatted_messages = chat_prompt.invoke({"role": "物理学家", "concept": "相对论"})
print(formatted_messages)
# messages=[SystemMessage(content='你是一个物理学家', ...),
# HumanMessage(content='给我解释相对论,用浅显易懂的语言', ...)]
MessagePromptTemplate 的设计目的:简化用户输入消息的模板化构造,避免重复定义角色。
类型6:BaseChatPromptTemplate 列表类型(模板嵌套)
可以理解为 ChatPromptTemplate 里嵌套了 ChatPromptTemplate。
示例1:嵌套带参数的模板
python
from langchain_core.prompts import ChatPromptTemplate
nested_prompt_template1 = ChatPromptTemplate.from_messages([
("system", "我是一个人工智能助手,我的名字叫{name}")
])
nested_prompt_template2 = ChatPromptTemplate.from_messages([
("human", "很高兴认识你,我的问题是{question}")
])
prompt_template = ChatPromptTemplate.from_messages([
nested_prompt_template1, nested_prompt_template2
])
result = prompt_template.invoke({"name": "小智", "question": "你为什么这么帅?"})
print(result)
# ChatPromptValue(messages=[SystemMessage(content='我是一个人工智能助手,我的名字叫小智', ...),
# HumanMessage(content='很高兴认识你,我的问题是你为什么这么帅?', ...)])
示例2:嵌套不带参数的模板
python
nested_prompt_template1 = ChatPromptTemplate.from_messages([("system", "我是一个人工智能助手")])
nested_prompt_template2 = ChatPromptTemplate.from_messages([("human", "很高兴认识你")])
prompt_template = ChatPromptTemplate.from_messages([
nested_prompt_template1, nested_prompt_template2
])
prompt_template.invoke({})
# ChatPromptValue(messages=[SystemMessage(content='我是一个人工智能助手', ...),
# HumanMessage(content='很高兴认识你', ...)])
示例3:综合使用(Message + MessagePromptTemplate + BaseChatPromptTemplate)
python
from langchain_core.prompts import (
ChatPromptTemplate,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate,
)
from langchain_core.messages import SystemMessage, HumanMessage
# 示例1: 使用 BaseMessage(已实例化的消息)
system_msg = SystemMessage(content="你是一个AI工程师。")
human_msg = HumanMessage(content="你好!")
# 示例2: 使用 BaseMessagePromptTemplate
system_prompt = SystemMessagePromptTemplate.from_template("你是一个 {role}.")
human_prompt = HumanMessagePromptTemplate.from_template("{user_input}")
# 示例3: 使用 BaseChatPromptTemplate(嵌套的 ChatPromptTemplate)
nested_prompt = ChatPromptTemplate.from_messages([("system", "嵌套提示词")])
prompt = ChatPromptTemplate.from_messages([
system_msg, # MessageLike (BaseMessage)
human_msg, # MessageLike (BaseMessage)
system_prompt, # MessageLike (BaseMessagePromptTemplate)
human_prompt, # MessageLike (BaseMessagePromptTemplate)
nested_prompt, # MessageLike (BaseChatPromptTemplate)
])
prompt.invoke({"role": "人工智能专家", "user_input": "介绍一下大模型的应用场景"})
十一、高级特性
1、partial() -- 部分变量预填充
预填充某些固定不变的变量,创建模板的变体。
使用场景:
- 某些变量在所有调用中都相同
- 需要为不同用户/场景创建定制模板
python
from langchain_core.prompts import ChatPromptTemplate
# 原始模板
template = ChatPromptTemplate.from_messages([
("system", "你是{role},目标用户是 {audience}"),
("user", "{task}")
])
# 部分填充
customer_support_template = template.partial(
role="客服专员",
audience="普通用户"
)
# 现在只需要提供 task
messages = customer_support_template.invoke({"task": "解释退款政策"})
print(messages)
# messages=[SystemMessage(content='你是客服专员,目标用户是普通用户', ...),
# HumanMessage(content='解释退款政策', ...)]
为不同部门创建专用模板:
python
base_template = ChatPromptTemplate.from_messages([
("system", "你是{department}的 {role}"),
("user", "{task}")
])
# IT 部门
it_template = base_template.partial(
department="IT部门",
role="技术支持"
)
# 销售部门
sales_template = base_template.partial(
department="销售部门",
role="销售顾问"
)
sales_template.invoke({"task": "为什么每年年底汽车会促销"})
# ChatPromptValue(messages=[SystemMessage(content='你是销售部门的销售顾问', ...),
# HumanMessage(content='为什么每年年底汽车会促销', ...)])
partial() 的价值:
原始模板(3个变量)
│
├── partial(role="客服专员", audience="普通用户")
│ └── 客服模板(1个变量:task)
│
├── partial(role="销售顾问", audience="潜在客户")
│ └── 销售模板(1个变量:task)
│
└── partial(role="技术专家", audience="开发者")
└── 技术模板(1个变量:task)
2、MessagesPlaceholder -- 消息占位符
当你不确定消息提示模板使用什么角色,或者希望在格式化过程中插入消息列表时,使用消息占位符。
使用场景:多轮对话系统存储历史消息以及 Agent 的中间步骤处理。
方式1:JSON 形式
python
from langchain_core.prompts import ChatPromptTemplate
template = ChatPromptTemplate.from_messages([
("system", "你是一个有用的AI助手"),
("placeholder", "{conversation}"),
])
prompt_value = template.invoke({
"conversation": [
("human", "你好!"),
("ai", "今天我能帮你做什么?"),
("human", "你能给我做一个冰激凌吗?"),
("ai", "抱歉,我没有这样的能力"),
]
})
print(prompt_value)
方式2:MessagesPlaceholder 实例(★★★★★)
python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage
prompt_template = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant"),
MessagesPlaceholder("msgs")
])
prompt_template.invoke({"msgs": [HumanMessage(content="hi!")]})
# ChatPromptValue(messages=[SystemMessage(...), HumanMessage(content='hi!', ...)])
关键特性:如果我们传入了 5 条消息,那么总共会生成 6 条消息(系统消息加上传入的 5 条消息)。
这对于将一系列消息插入到特定位置非常有用。
存储对话历史内容
python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt_template = ChatPromptTemplate.from_messages([
("system", "你是一个非常友好的AI助手"),
MessagesPlaceholder(variable_name="history"),
("human", "{question}")
])
prompt_template.invoke({
"history": [
("human", "5 + 2 = ?"),
("ai", "5 + 2 = 7")
],
"question": "结果再乘以4呢?"
})
# ChatPromptValue(messages=[SystemMessage(...),
# HumanMessage(content='5 + 2 = ?', ...),
# AIMessage(content='5 + 2 = 7', ...),
# HumanMessage(content='结果再乘以4呢?', ...)])
MessagesPlaceholder 在模板中的位置:
ChatPromptTemplate.from_messages([
("system", "你是AI助手"),
MessagesPlaceholder("history"), ← 动态插入历史消息
("human", "{question}"), ← 当前问题
])
history 变量中的消息会在 MessagesPlaceholder 位置展开。
3、可复用模板库(★★★★★)
在实际项目中,建议创建模板库。
方式1:类封装
python
# templates.py
from langchain_core.prompts import ChatPromptTemplate
class PromptLibrary:
"""可复用的提示词模板库"""
TRANSLATOR = ChatPromptTemplate.from_messages([
("system", "你是专业翻译,精通 {source_lang} 和 {target_lang}"),
("user", "翻译以下文本:\n{text}")
])
CODE_REVIEWER = ChatPromptTemplate.from_messages([
("system", "你是{language} 代码审查专家,重点关注 {focus}"),
("user", "审查代码:\n```{language}\n{code}\n```")
])
SUMMARIZER = ChatPromptTemplate.from_messages([
("system", "你是内容摘要专家"),
("user", "将以下内容总结为 {num} 个要点:\n{content}")
])
TUTOR = ChatPromptTemplate.from_messages([
("system", "你是{subject} 导师,学生水平:{level}"),
("user", "{question}")
])
使用模板库:
python
from templates import PromptLibrary
messages = PromptLibrary.TRANSLATOR.format_messages(
source_lang="英语",
target_lang="中文",
text="Hello World"
)
方式2:模块化文件组织
templates/
├── __init__.py
├── common.py # 通用模板
├── translation.py # 翻译相关
└── coding.py # 编程相关
python
# common.py
from langchain_core.prompts import ChatPromptTemplate
FRIENDLY_ASSISTANT = ChatPromptTemplate.from_messages([
("system", "你是一个友好的助手"),
("user", "{input}")
])
4、模板组合
将多个模板片段组合成复杂的提示词。
方法1:字符串组合
python
# 定义可复用的部分
role_part = "你是一个 {domain} 专家。"
style_part = "回答风格:{style}。"
constraint_part = "限制:{constraint}。"
# 组合
full_system = role_part + style_part + constraint_part
template = ChatPromptTemplate.from_messages([
("system", full_system),
("user", "{question}")
])
方法2:使用 + 运算符(模板合并)
python
template1 = ChatPromptTemplate.from_messages([
("system", "你是助手")
])
template2 = ChatPromptTemplate.from_messages([
("user", "{input}")
])
# 组合(LangChain 1.0 支持)
combined = template1 + template2
+ 运算符的作用:
ChatPromptTemplate([("system", ...)])
+
ChatPromptTemplate([("user", "{input}")])
↓
ChatPromptTemplate([("system", ...), ("user", "{input}")])
两个模板的消息列表被合并为一个。
十二、本章总结
本章首先学习了 Message 的核心概念:
- Message 是模型交互的最基本单元,LangChain 1.0 提供了跨模型统一的标准。
- Message 包含三种核心字段:role、content、metadata。
- 四种消息类型:SystemMessage(设定规则)、HumanMessage(用户输入)、AIMessage(AI 回复)、ToolMessage(工具结果)。
- JSON 格式与对象格式完全等价,JSON 适合序列化传输,对象格式适合正式开发与 Agent。
- 各消息类型有丰富的参数字段(name、id、response_metadata、tool_calls、usage_metadata 等)。
然后学习了对话历史管理:
- 关键规则:每次调用必须传递完整的对话历史。
- 每次对话都要在原有消息列表中追加新消息,不可重新创建列表。
- keep_recent_messages 模式可在保留 system 的前提下只保留最近 N 轮对话,控制成本。
- 多轮聊天机器人 = 消息列表 + 流式输出 + 历史优化。
接着学习了 content 与 content_blocks:
- content 支持字符串(纯文本)和字典列表(多模态)两种形式。
- content_blocks 是 LangChain 1.x 的重大升级,提供跨模型供应商统一的多模态数据标准。
- content_blocks 可用于输入格式化(一套代码适配多种模型)和输出格式化(统一提取思维链/推理内容)。
最后深入学习了提示词模板:
- PromptTemplate(旧时代)输出字符串,ChatPromptTemplate(新时代)输出消息列表。
- ChatPromptTemplate 是 LanChain 1.0 的核心 Prompt 工具。
- 两种创建方式:from_messages()(推荐)和构造器方法。
- 三种调用方式:invoke()(返回 ChatPromptValue)、format()(返回 str)、format_messages()(返回 list)。
- 六种消息模板类型:str、tuple、dict、Message、MessagePromptTemplate、BaseChatPromptTemplate。
- 高级特性:partial() 预填充、MessagesPlaceholder 动态插入、可复用模板库、模板组合。
核心心法:
在 LangChain 1.0 中,一个结构化的、富含元数据的消息列表已经取代单一字符串,成为与模型交互的标准数据格式。
ChatPromptTemplate 也因此取代了 PromptTemplate,成为构建现代 LangChain 应用的首选工具。
本章知识点速查表
| 分类 | 知识点 | 推荐使用 | 说明 |
|---|---|---|---|
| 消息格式 | JSON 格式 | 快速原型、序列化场景 | {"role": "user", "content": "..."} |
| 消息格式 | 对象格式 | 正式项目 ★★★★★ | HumanMessage(content="...") |
| 消息类型 | SystemMessage | 每次对话开局 ★★★★★ | 设定 AI 角色与行为规则 |
| 消息类型 | HumanMessage | 每次用户输入 ★★★★★ | 支持 name 字段区分发言者 |
| 消息类型 | AIMessage | 每次 AI 输出 ★★★★★ | 包含 content、tool_calls、usage_metadata |
| 消息类型 | ToolMessage | 工具调用后 ★★★★★ | tool_call_id 必须与 AIMessage 中的 id 匹配 |
| 对话历史 | 追加模式 | 所有多轮对话 ★★★★★ | messages.append(...) 不可重建列表 |
| 对话历史 | keep_recent_messages | 长对话场景 ★★★★★ | 保留 system + 最近 N 轮,控制 tokens |
| 多模态内容 | content_blocks | 新项目强烈推荐 ★★★★★ | 跨模型统一的标准化多模态数据结构 |
| 提示词模板 | ChatPromptTemplate | 现代应用首选 ★★★★★ | 输出消息列表而非字符串 |
| 模板创建 | from_messages() | 推荐方式 ★★★★★ | 简洁直观的 tuple 列表写法 |
| 模板调用 | invoke() | 与 model 集成 ★★★★★ | 返回可直接传给 model 的 ChatPromptValue |
| 消息模板 | tuple 类型 | 最常用 ★★★★★ | ("system", "你是{role}") |
| 消息模板 | MessagePromptTemplate | 需要变量占位 ★★★★★ | HumanMessagePromptTemplate.from_template() |
| 高级特性 | partial() | 固定变量的场景 ★★★★ | 创建模板变体 |
| 高级特性 | MessagesPlaceholder | 动态插入历史 ★★★★★ | 多轮对话和 Agent 的必备工具 |
| 高级特性 | 模板库 + 模板组合 | 大型项目 ★★★★★ | 模块化 + 可复用 |
十三、面试常见问题
Q1:LangChain 为什么需要 Message?大模型 API 不是可以直接传字符串吗?
大模型没有记忆,每次调用都是"无状态"的。
Message 不仅包含文字内容,还携带角色(role)和元信息(metadata),让模型理解"谁在说话"、"说了什么"。
维护完整的消息列表是实现多轮对话的关键。
Q2:JSON 格式和 Message 对象格式有什么区别?什么时候用哪个?
本质上完全等价,可以互相转换。
JSON 格式是 Python 字典,天然支持序列化,适合 HTTP 接口和快速原型。
Message 对象格式支持更多属性(id、response_metadata、tool_calls 等),类型更安全,适合正式项目和 Agent 开发。
Q3:HumanMessage 的 name 字段有什么作用?所有模型都支持吗?
name 字段用于多人对话场景区分不同发言者。
不是所有模型都支持,取决于模型供应商。
OpenAI 官方支持,ChatOpenRouter 实测可能无法正确传递。
Q4:AIMessage 的 tool_calls 是什么?什么时候会用到?
tool_calls 是 AIMessage 的特有属性。
当 LLM 决定调用工具时,AIMessage 的 content 为空,tool_calls 包含调用的工具名、参数和唯一 ID。
后续的 ToolMessage 必须使用相同的 tool_call_id 与之匹配。
Q5:为什么每次调用模型都要传递完整的对话历史?
大模型本身没有长期记忆。
model.invoke() 实际上是发送整个 messages 列表给模型,模型只根据传入的内容生成回复。
如果不传历史,模型就不知道之前说过什么。
Q6:如何解决对话历史越来越长导致的 token 消耗问题?
使用 keep_recent_messages 模式:始终保留 system 消息,只保留最近 N 轮对话,丢弃更早的历史。
这是一种用"上下文遗忘"换取"成本控制"的权衡策略。
对于更复杂的需求,可以使用 LangChain 的记忆组件(Memory)或 LangGraph 的 Checkpoint。
Q7:PromptTemplate 和 ChatPromptTemplate 有什么区别?为什么 LangChain 1.0 推荐 ChatPromptTemplate?
PromptTemplate 输出单一字符串,适合早期的文本补全模型。
ChatPromptTemplate 输出消息列表(ListBaseMessage),支持多角色(system/user/assistant),适合现代聊天模型。
LangChain 1.0 的架构变革中,消息列表取代单一字符串成为标准数据格式,因此 ChatPromptTemplate 是首选工具。
Q8:ChatPromptTemplate 的 invoke()、format()、format_messages() 有什么区别?
invoke() 返回 ChatPromptValue(可直接传给 model.invoke()),是推荐方式。
format() 返回字符串,适合调试和日志。
format_messages() 返回 list,适合需要手动操作消息对象的场景。
Q9:content_blocks 和 content 有什么区别?为什么推荐使用 content_blocks?
content 是弱类型的,支持字符串和字典列表(各厂商私有格式)。
content_blocks 是 LangChain 1.x 新增的跨模型统一多模态标准,type 字段统一为 text、image、audio、video、tool_call、reasoning。
一套 content_blocks 代码可以在 OpenAI、Anthropic、Gemini 之间无缝切换,无需修改多模态部分代码。
Q10:MessagesPlaceholder 和 partial() 分别适用于什么场景?
MessagesPlaceholder 适用于格式化时动态插入消息列表(如对话历史、Agent 中间步骤)。
partial() 适用于预填充固定不变的变量,创建模板变体(如不同部门/角色的定制模板)。
Q11:在实际项目中,提示词模板应该怎么组织?
建议创建可复用的模板库,可以用类封装(如 PromptLibrary)或模块化文件(templates/目录)。
结合 partial() 创建不同场景的模板变体,使用 + 运算符或字符串拼接组合模板片段。
(第四章完)