LangChain 消息系统与提示词模板:构建高质量 LLM 交互的基石
前言
在 LangChain 的世界里,有两个概念贯穿始终:Message(消息) 和 Prompt Template(提示词模板)。前者是模型交互的基本单元,定义了"谁在说话、说了什么";后者是 Prompt 工程的核心工具,让你从混乱的字符串拼接中解脱出来。
这两者构成了 LangChain 中 Model I/O(模型输入输出)层的核心基础设施。本文将系统讲解消息系统的类型与多模态支持,以及从 PromptTemplate 到 ChatPromptTemplate 的完整进化路径。
一、认识 Message:模型交互的基本单元
1.1 核心认知
大模型本身没有记忆——它的输出完全取决于当前输入。LangChain 将每一次交互抽象为一条 Message,每条 Message 包含:
- Role(角色):谁在说话 ——
system、user、assistant、tool - Content(内容):说了什么 —— 纯文本或多模态内容
- Metadata(元数据):附加信息,如消息 ID、响应时间、Token 消耗等
LangChain 1.x 提供了跨模型供应商(OpenAI / Anthropic / Gemini / 本地模型)的统一 Message 标准。
1.2 四种消息类型
四种消息类型在整个 LLM 交互生命周期中各司其职:
| 类型 | 角色 | 作用 | 代码示例 |
|---|---|---|---|
| SystemMessage | system |
设定 AI 的行为、角色和规则 | SystemMessage("你是一个数学老师") |
| HumanMessage | user |
用户输入,支持文本和多模态 | HumanMessage("什么是微积分?") |
| AIMessage | assistant |
模型的回复,含文本、工具调用、元数据 | AIMessage("微积分是...") |
| ToolMessage | tool |
工具调用的返回结果 | ToolMessage(content="晴天", tool_call_id="...") |
1.3 两种表达格式
JSON 格式(简洁、跨语言友好):
{"role": "system", "content": "你是个善解人意的助手"}
{"role": "user", "content": "你好啊~"}
{"role": "assistant", "content": "我也很高兴认识你"}
{"role": "tool", "content": "今天北京天气晴朗", "tool_call_id": "call_xxx"}
对象格式(类型安全、IDE 友好):
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage, ToolMessage
SystemMessage(content="你是个善解人意的助手")
HumanMessage(content="你好啊~")
AIMessage("我也很高兴认识你")
ToolMessage(content="今天北京天气晴朗", tool_call_id="call_xxx")
1.4 各消息类型深入
SystemMessage
最简单的类型,只有一个核心参数 content:
SystemMessage("你是个善解人意的助手")
# 等价于
SystemMessage(content="你是个善解人意的助手")
HumanMessage
除了 content,还支持 name 和 id 等元数据字段,用于区分多发言者:
HumanMessage(
content="我认为 1+1=2",
name="Bob", # 发言者标识
id="msg_123", # 消息唯一ID
)
注意:
name属性 OpenAI 官方支持,但 DeepSeek 实测无法识别,ChatOpenRouter 也无法正确传递。使用时需验证目标模型是否兼容。
多发言者信息抽取实战:
from langchain_core.messages import SystemMessage, HumanMessage
messages = [
SystemMessage("你是一个信息抽取器。严格根据每条消息的 name 提取发言者及其观点。"),
HumanMessage(content="我认为 1+1=2", name="Bob"),
HumanMessage(content="我认为 1+1>2", name="Tom"),
HumanMessage(content="请列出谁说了什么,不要判断对错。", name="audience")
]
response = model.invoke(messages)
# 输出:{"speakers": [{"name":"Bob","claim":"我认为 1+1=2"}, ...]}
AIMessage
模型返回的消息类型,字段最丰富:
AIMessage(
content="你好!我是小智,一名助人为乐的AI助手。",
response_metadata={
'token_usage': {'prompt_tokens': 28, 'completion_tokens': 18, 'total_tokens': 46},
'model_name': 'gpt-5.4-mini-2026-03-17',
'finish_reason': 'stop',
},
tool_calls=[], # 工具调用列表
usage_metadata={ # 标准化 Token 统计
'input_tokens': 28, 'output_tokens': 18, 'total_tokens': 46
}
)
ToolMessage
工具调用结果的载体,tool_call_id 必须与 AIMessage 中的工具调用 ID 精确匹配:
# 模拟:模型决定调用工具
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)
二、对话历史管理:从"失忆"到"记住你"
2.1 核心规则
每次调用必须传递完整的对话历史。 模型本身不存储任何状态,上下文完全由你传入的 messages 决定。
三个常见错误:
# 错误1:没传历史
model.invoke("我叫张三")
model.invoke("我叫什么?") # AI 不记得!
# 错误2:每次重新创建列表(丢失之前的内容)
conversation = [{"role": "user", "content": "问题1"}]
response = model.invoke(conversation)
conversation = [{"role": "user", "content": "问题2"}] # 历史全丢了
# 错误3:忘记保存 AI 回复
conversation.append({"role": "user", "content": "问题1"})
response = model.invoke(conversation)
# 忘记 append assistant 回复!
conversation.append({"role": "user", "content": "问题2"})
model.invoke(conversation) # AI 不知道上一轮自己说了什么
正确实现:
conversation = []
# 第一次交互
conversation.append({"role": "user", "content": "我叫张三"})
response = model.invoke(conversation)
conversation.append({"role": "assistant", "content": response.content}) # 关键!
# 第二次交互(传递完整历史)
conversation.append({"role": "user", "content": "我叫什么?"})
response = model.invoke(conversation) # AI 能正确回答"张三"
2.2 历史优化:保留最近 N 轮
对话越来越长会带来两个问题:Token 消耗飙升,以及超出上下文窗口。
解决方案:始终保留 system 消息,只保留最近 N 轮对话。其处理逻辑如下:
def keep_recent_messages(messages, max_pairs=3):
"""保留最近 N 轮对话(每轮 = user + assistant)"""
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):]
return system_msgs + recent_msgs
2.3 完整的多轮聊天机器人
多轮对话机器人的完整执行流程:
from langchain.chat_models import init_chat_model
import os
from dotenv import load_dotenv
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"),
)
messages = [
{"role": "system", "content": "你是一个耐心友好的智能助手。"}
]
while True:
user_input = input("请输入:")
if user_input.lower() == "quit":
break
messages.append({"role": "user", "content": user_input})
# 用流式输出 + 历史裁剪
memory_messages = keep_recent_messages(messages, max_pairs=10)
reply = ""
for chunk in model.stream(memory_messages):
if chunk.content:
print(chunk.content, end="", flush=True)
reply += chunk.content
messages.append({"role": "assistant", "content": reply})
print()
三、多模态消息:文本之外的世界
3.1 content 的两种形态
content 字段是弱类型的,同时支持两种形态:
| 形态 | 适用场景 | 示例 |
|---|---|---|
| 字符串 | 纯文本交互 | "你好啊" |
| 字典列表 | 多模态(文本+图片+音频等) | [{"type":"text","text":"..."}, {"type":"image_url",...}] |
3.2 图片输入实战
import base64
from langchain.messages import HumanMessage
def encode_image(img_path, img_type='jpeg'):
with open(img_path, "rb") as img_file:
return f"data:image/{img_type};base64,{base64.b64encode(img_file.read()).decode('utf-8')}"
base64_image = encode_image("image_test.png")
response = model.invoke(
[HumanMessage(
content=[
{'type': 'text', 'text': '这张图里有什么?'},
{'type': 'image_url', 'image_url': base64_image},
]
)]
)
3.3 content_blocks:跨模型标准化
LangChain 1.x 新增 content_blocks,提供跨供应商的统一多模态数据结构,支持六种类型:
| type | 说明 |
|---|---|
text |
文本内容 |
image |
图片(base64 + mime_type) |
audio |
音频 |
video |
视频 |
tool_call |
工具调用 |
reasoning |
推理/思维链 |
content 与 content_blocks 两条路径的对比:
使用 content_blocks 输入图片(OpenAI):
response = model.invoke(
[HumanMessage(
content_blocks=[
{'type': 'text', 'text': '这张图里有什么?'},
{'type': 'image', 'base64': base64_image, 'mime_type': 'image/png'},
]
)]
)
使用 content_blocks 提取 DeepSeek 思维链:
model = init_chat_model(
model="deepseek:deepseek-v4-flash",
extra_body={"thinking": {"type": "enabled"}},
)
response = model.invoke("你好,一句话回答")
# content_blocks 中包含 reasoning 类型,轻松提取思维链
for block in response.content_blocks:
if block.get("type") == "reasoning":
print("思考过程:", block.get("reasoning"))
最佳实践:优先检查
response.content_blocks而非response.content,特别是需要获取思维链或引用信息时。
四、提示词模板:从字符串拼接到工程化
4.1 为什么要用模板?
来看一个对比:
字符串拼接(不推荐):
topic = "Python"
difficulty = "初学者"
prompt = f"你是一个{difficulty}级别的编程导师。请用简单易懂的语言解释{topic}。"
缺点:变量多时混乱、不易维护、无变量校验、不支持复杂场景。
提示词模板(推荐):
from langchain.prompts import PromptTemplate
template = PromptTemplate.from_template(
"你是一个{difficulty}级别的编程导师。请用简单易懂的语言解释{topic}。"
)
prompt = template.format(difficulty="初学者", topic="Python")
优点:结构清晰、易维护、自动变量校验、与 LangChain 生态无缝集成。
建议:临时 Demo 用字符串拼接,正式 AI 应用必须用提示词模板。
4.2 PromptTemplate vs ChatPromptTemplate
LangChain 提示词机制的演进路径:
| 特性 | PromptTemplate | ChatPromptTemplate |
|---|---|---|
| 输出格式 | 纯文本字符串 | 消息列表 |
| 角色支持 | 无 | system / user / assistant |
| 对话历史 | 不支持 | 支持 |
| 适用场景 | 简单提示 | 聊天、多轮对话、Agent |
旧时代(PromptTemplate):
from langchain.prompts import PromptTemplate
template = PromptTemplate.from_template("请给我一个关于{topic}的{type}解释。")
prompt = template.format(type="详细", topic="量子力学")
# 输出:纯文本字符串,无角色区分
新时代(ChatPromptTemplate):
from langchain_core.prompts import ChatPromptTemplate
template = ChatPromptTemplate.from_messages([
("system", "你是一个AI开发工程师,你的名字是{name}。"),
("human", "{user_input}")
])
prompt = template.invoke({"name": "小谷AI", "user_input": "你能帮我做什么?"})
# 输出:包含 SystemMessage 和 HumanMessage 的消息列表
4.3 ChatPromptTemplate 三种调用方式
三种调用方式返回不同的数据类型,适用于不同场景:
template = ChatPromptTemplate.from_messages([
("system", "你是一个{role}。"),
("human", "{question}")
])
# 方式1:invoke() → 返回 ChatPromptValue(推荐,直接喂给模型)
value = template.invoke({"role": "数学家", "question": "1+1=?"})
# 方式2:format() → 返回格式化字符串(适合调试和打印)
text = template.format(role="数学家", question="1+1=?")
# 输出:"System: 你是一个数学家。\nHuman: 1+1=?"
# 方式3:format_messages() → 返回消息对象列表
messages = template.format_messages(role="数学家", question="1+1=?")
4.4 完整调用链路
从模板定义到模型输出的完整数据流:
from langchain_core.prompts import ChatPromptTemplate
from langchain.chat_models import init_chat_model
# 1. 初始化模型
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. 调用模型(直接传入 ChatPromptValue)
output = model.invoke(prompt_value)
print(output.content)
五、高级特性
5.1 部分变量预填充:partial()
当某些变量在所有调用中都相同时,可以用 partial() 预填充,减少重复传参:
# 原始模板
template = ChatPromptTemplate.from_messages([
("system", "你是{role},目标用户是{audience}"),
("user", "{task}")
])
# 预填充角色和受众
support_template = template.partial(role="客服专员", audience="普通用户")
# 现在只需传 task
messages = support_template.invoke({"task": "解释退款政策"})
为不同部门创建专用模板:
base = ChatPromptTemplate.from_messages([
("system", "你是{department}的{role}"),
("user", "{task}")
])
it_template = base.partial(department="IT部门", role="技术支持")
sales_template = base.partial(department="销售部门", role="销售顾问")
5.2 消息占位符:MessagesPlaceholder
当需要在模板中动态插入一组消息时使用(如对话历史、Agent 中间步骤):
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
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呢?"
})
这是实现多轮对话、RAG、Agent 场景的核心工具。
5.3 可复用模板库
将常用模板抽离为类,实现一处定义、处处复用:
# 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}")
])
使用:
from templates import PromptLibrary
messages = PromptLibrary.TRANSLATOR.format_messages(
source_lang="英语", target_lang="中文", text="Hello World"
)
5.4 模板组合
字符串拼接:
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}")
])
使用 + 运算符(LangChain 1.x 支持):
t1 = ChatPromptTemplate.from_messages([("system", "你是助手")])
t2 = ChatPromptTemplate.from_messages([("user", "{input}")])
combined = t1 + t2
六、总结
| 模块 | 核心要点 |
|---|---|
| Message 类型 | SystemMessage / HumanMessage / AIMessage / ToolMessage,四种角色覆盖所有交互场景 |
| 对话历史 | 每次调用必须传递完整历史;长对话用 keep_recent_messages 裁剪 |
| 多模态 | content_blocks 统一支持文本/图片/音频/视频/思维链,跨模型兼容 |
| 提示词模板 | ChatPromptTemplate 替代字符串拼接,支持角色区分、对话历史、partial 预填充 |
| MessagesPlaceholder | 动态插入消息列表,是多轮对话和 Agent 的核心基础设施 |
| 模板工程化 | 模板库 + 模板组合,实现 Prompt 的集中管理和复用 |
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)