前言

在 LangChain 的世界里,有两个概念贯穿始终:Message(消息)Prompt Template(提示词模板)。前者是模型交互的基本单元,定义了"谁在说话、说了什么";后者是 Prompt 工程的核心工具,让你从混乱的字符串拼接中解脱出来。

这两者构成了 LangChain 中 Model I/O(模型输入输出)层的核心基础设施。本文将系统讲解消息系统的类型与多模态支持,以及从 PromptTemplateChatPromptTemplate 的完整进化路径。


一、认识 Message:模型交互的基本单元

1.1 核心认知

大模型本身没有记忆——它的输出完全取决于当前输入。LangChain 将每一次交互抽象为一条 Message,每条 Message 包含:

  • Role(角色):谁在说话 —— systemuserassistanttool
  • Content(内容):说了什么 —— 纯文本或多模态内容
  • Metadata(元数据):附加信息,如消息 ID、响应时间、Token 消耗等

LangChain 1.x 提供了跨模型供应商(OpenAI / Anthropic / Gemini / 本地模型)的统一 Message 标准。

1.2 四种消息类型

四种消息类型在整个 LLM 交互生命周期中各司其职:

SystemMessage: 角色/规则设定

LLM 模型推理

HumanMessage: 用户输入

AIMessage: 模型回复

是否工具调用?

ToolMessage: 工具返回结果

返回给用户

类型 角色 作用 代码示例
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,还支持 nameid 等元数据字段,用于区分多发言者:

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)

新方式 (content_blocks)

HumanMessage 输入

选择路径

content = list[dict]
{type, text/image_url}

content_blocks = list[dict]
{type, text/base64/mime_type}

各供应商各自解析
格式不统一

LangChain 统一格式化
自动适配 OpenAI / Anthropic / Gemini

使用 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 三种调用方式

三种调用方式返回不同的数据类型,适用于不同场景:

ChatPromptTemplate

invoke()

format()

format_messages()

ChatPromptValue
可直接传给 model.invoke()

str 字符串
适合调试和打印

list[BaseMessage]
消息对象列表

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 完整调用链路

从模板定义到模型输出的完整数据流:

1. 定义模板
ChatPromptTemplate
含 {变量} 占位符

2. 填充变量
invoke() / format()

3. ChatPromptValue
消息列表

4. model.invoke()
LLM 推理

5. AIMessage
content + metadata

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() 预填充,减少重复传参:

基础模板
变量: role, audience, task

partial(role=客服, audience=普通用户)

partial(role=技术支持, audience=开发者)

客服模板: 只需填 task

技术模板: 只需填 task

invoke({task: '解释退款'})

invoke({task: '排查故障'})

# 原始模板
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 中间步骤):

ChatPromptTemplate
system: 你是助手
MessagesPlaceholder
human: {question}

invoke()

传入 history = [
HumanMessage('5+2=?'),
AIMessage('5+2=7')
]

最终消息列表:
[SystemMessage,
HumanMessage(5+2=?),
AIMessage(5+2=7),
HumanMessage(结果x4?)]

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 的集中管理和复用
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐