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
)

注意 :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() 创建不同场景的模板变体,使用 + 运算符或字符串拼接组合模板片段。


(第四章完)

相关推荐
卿卿的产品经理日记4 小时前
【AI产品经理实战】Day 37|拆开 JSON 与 EXE:数据都有格式
人工智能·笔记·python·json·产品经理
辣知4 小时前
辣知·化智80 《道德经》和光同尘的智慧
学习
wdfk_prog4 小时前
LWIP教程 15:IPv4 与 IPv6——从网络层工作原理到嵌入式工程取舍
运维·开发语言·网络·笔记·学习·智能路由器·ip
chde2Wang4 小时前
numpy学习3
学习·numpy
坤坤子吖4 小时前
Linux 进程信号入门——从 Ctrl+C 到 kill
linux·开发语言·c++·笔记·学习
for_ever_love__4 小时前
LangChain RunnableConfig 详解:运行时传参、并发控制与回调注入
语言模型·langchain·llm·prompt·config·invoke·运行时参数
Gigavision4 小时前
SEED-V数据集下载 时频联合编码和空间、时间并行注意力学习脑电表示
学习
for_ever_love__4 小时前
LangChain 模型参数调优:temperature、max_tokens 这些到底怎么设
python·语言模型·langchain·aigc·模型参数·temperature·max_tokens
LONGZHIQIN4 小时前
Python学习笔记
笔记·python·学习
czt_java5 小时前
网络基础入门:IPv4、IPv6、私网公网、MAC、以太网帧与 DNS 学习笔记
网络·学习·macos