LangChain 学习笔记(四):Message 与提示词模板

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
)

注意nameid 都属于元数据字段。

不是所有模型都支持这些字段,是否支持取决于模型供应商,需要查看官方手册。

例如: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_kwargsreasoning_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 字段统一为 textimageaudiovideotool_callreasoning

一套 content_blocks 代码可以在 OpenAI、Anthropic、Gemini 之间无缝切换,无需修改多模态部分代码。

Q10:MessagesPlaceholder 和 partial() 分别适用于什么场景?

MessagesPlaceholder 适用于格式化时动态插入消息列表(如对话历史、Agent 中间步骤)。

partial() 适用于预填充固定不变的变量,创建模板变体(如不同部门/角色的定制模板)。

Q11:在实际项目中,提示词模板应该怎么组织?

建议创建可复用的模板库,可以用类封装(如 PromptLibrary)或模块化文件(templates/目录)。

结合 partial() 创建不同场景的模板变体,使用 + 运算符或字符串拼接组合模板片段。


(第四章完)

相关推荐
hsjiasb2 小时前
FreeRTOS学习(二十六)——动态内存管理heap_1到heap_5
stm32·单片机·学习·学习笔记·freertos
崇子嵘2 小时前
基于zynqMP15eg的linux驱动学习
学习
知识分享小能手4 小时前
线性代数学习教程,从入门到精通,向量组的线性相关性 — 完整知识点梳理(7)
学习·线性代数·机器学习
Shell运维手记4 小时前
Linux 常用基础命令学习笔记
linux·运维·笔记·学习·算法·github
FakeOccupational5 小时前
【电路笔记 STM32】Cortex-M7 内核上的数据缓存(D-Cache)结构+MPU+DMA&Cache+STM32CubeMX配置
笔记·stm32·缓存
JaydenAI5 小时前
[基于OpenEvals的自动化评估-10]针对Agent对话的评估[上篇]
ai·langchain·agent·evaluation·openevals
动词ing5 小时前
【学习笔记】C语言(数组指针与指针数组+字符数组+函数+参数传递+字符串作为形参+递归函数+指针函数+回调函数+结构体嵌套+内存动态分配函数)
c语言·笔记·学习
z落落7 小时前
C# Modbus-ASCII 超详细完整笔记(协议原理+LRC算法+读写源码全拆解)
笔记
小O的算法实验室7 小时前
IEEE TII,学习为多目标深度学习生成偏好
人工智能·深度学习·学习