LangChain之核心组件(消息与提示词模板)

1、消息(Messages)

1.1 LLM 消息结构

每条消息都有一个角色和内容,以及因 LLM 的不同而不同的附加元数据。

• 消息角色 (Role):用来区分对话中不同类型的消息,并帮助聊天模型了解如何响应给定的消息序 列。

角色 描述
system(系统角色) 用于告诉聊天模型如何行为并提供额外的上下文。并非所有聊天模型提供商都支持。
user(用户角色) 表示用户与模型交互的输入,通常以文本或其他交互式输入的形式。
assistant(助理角色) 表示来自模型的响应,其中可以包括文本或调用工具的请求。
tool(工具角色) 用于在检索外部数据或将工具调用的结果传递回模型的消息。与支持工具调用的聊天模型一起使用。

**• 消息内容 (Content):**表示多模态数据 (例如,图像、音频、视频)的消息文本或字典列表的内 容。内容的具体格式可能因底层不同的 LLM 而异。目前,大多数模型都支持文本作为主要内容类 型,对多模态数据的支持仍然有限。

• 消息其他元数据 (Additional metadata)

元数据 描述
ID 消息标识符。
Name 名称允许区分具有相同角色的不同实体。并非所有型号都支持此功能!
Metadata 有关消息的其他信息,例如时间戳、令牌使用情况等。
Tool Calls 模型发出的一个或多个工具的调用请求。

OpenAI 的格式消息列表:

python 复制代码
[
    {
"role": "user",
"content": "Hello, how are you?",
    },
    {
"role": "assistant",
"content": "I'm doing well, thank you for asking.",
     },
    {
"role": "user",
"content": "Can you tell me a joke?",
    }
]

LangChain 接受下面的格式作为聊天模型的输入 :

python 复制代码
chat_model.invoke([
    {
"role": "user",
"content": "Hello, how are you?",
    },
    {
"role": "assistant",
"content": "I'm doing well, thank you for asking.",
     },
    {
"role": "user",
"content": "Can you tell me a joke?",
    }
])

1.2 LangChain 消息

LangChain 提供了一种统一的消息格式 ,可以跨聊天模型使用 ,允许用户使用不同的聊天模型,而无需担心每个模型提供商使用的消息格式的具体细节

不同的供应商,对于其输入和输出,统一使用 LangChain 的消息格式。 LangChain 消息格式 主要分为五种,它们都是 LangChain BaseMessage 的子类 ,全部是作为 LangChain 聊天模型的输入和输出!!分别是:

消息类型 对应角色 描述
SystemMessage 对应 system 系统角色 用于启动 AI 模型的行为并提供额外的上下文 ,例如指示模型采用特定角色或设定对话的基调(例如,"你是一个后端开发的专家")。
HumanMessage 对应 user 用户角色 人类消息表示用户与模型交互的输入。大多数聊天模型都希望用户输入采用文本形式。
AIMessage 对应 assistant 助理角色 这是来自模型的响应, 其中可以包括文本或调用工具的请求。 它还可能包括**其他媒体类型,**如图像、音频或视频------尽管这目前仍然不常见。
AIMessageChunk 对应 assistant 助理角色,用于流式响应 通常在生成聊天模型时流式传输响应 ,因此用户可以实时看到响应,而不是等待生成整个响应后再显示。
ToolMessage 对应 tool 工具角色 这表示一条角色为"tool"的消息,其中包含调用工具的结果。

1.2.1 BaseMessage 抽象消息类

class langchain_core.messages.base.BaseMessage 是作为 LangChain 聊天模型的输 入和输出!!

BaseMessage 参数
参数 描述
content 消息的字符串内容。
additional_kwargs 与消息关联的其他有效负载数据。对于来自 AI 的消息,可能包括模型提供程序编码的工具调用。
response_metadata 响应元数据。例如:响应标头、logprobs、令牌计数、模型名称。
type 消息的类型。必须是消息类型唯一的字符串。此字段的目的是在对消息进行反序列化时方便地识别消息类型。
name 消息名称,为消息提供一个人类可读的名称。该字段的使用是可选的,是否使用它取决于模型实现。
id 消息的可选唯一标识符。理想情况下,这应该由创建消息的提供者/模型提供。
BaseMessage 内置方法
方法 描述
pretty_print() -> None 打印消息的漂亮表示。
pretty_repr(html: bool = False) -> str 获得消息的漂亮表示。参数 html 表示是否将消息格式化为 HTML(默认 False);返回消息的漂亮表示字符串。
text() -> str 获取消息的文本内容。
1.2.2 对话模式

大多数对话都以设置对话上下文的系统消息开始。 接下来是包含用户输入的用户消息 ,然后是包含模型响应的助手消息。

1.3 缓存历史消息

1.3.1 多轮对话

在与大型语言模型交互的过程中,我们常常体验到与智能助手进行连贯多轮对话的便利性。但目前我们的系统还不支持此功能,不支持更多轮的对话。但是只要将历史消息,重新发送给聊天模型,那么就可以实现多轮对话的功能。

1.3.2 内存缓存

那么对于历史消息的管理就显得尤为重要。在 LangChain 老版本中,可以使用RunnableWithMessageHistory 消息历史类来包装另一个 Runnable 并为其管理聊天消息历史记录。将跟踪模型的输入和输出 ,并将其存储在某个数据存储中 。未来的交互将加载这些消息 ,并 将其作为输入 的一部分传递给链

python 复制代码
# 导入所需模块
from langchain_openai import ChatOpenAI               # 导入 OpenAI 聊天模型
from langchain_core.messages import HumanMessage, AIMessage  # 导入消息类型
from langchain_core.chat_history import BaseChatMessageHistory, InMemoryChatMessageHistory  # 聊天历史基类及内存实现
from langchain_core.runnables.history import RunnableWithMessageHistory  # 带消息历史管理的可运行包装器

# 定义大模型(使用 OpenAI 的 GPT-4o-mini 模型)
model = ChatOpenAI(model="gpt-4o-mini")

# 存储不同会话的消息历史记录(字典:session_id -> 消息历史对象)
store = {}

# 根据 session_id 获取对应的消息历史对象
# 这个函数会被 RunnableWithMessageHistory 调用,用于管理每个会话的历史记录
def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        # 如果该会话还没有历史记录,则创建一个新的内存消息历史对象
        # InMemoryChatMessageHistory 将消息存储在内存列表中(适合测试/单次运行)
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

# 包装 model,使其能够自动管理聊天消息历史记录
# RunnableWithMessageHistory 会在每次调用时根据 config 中的 session_id 调用 get_session_history
with_message_history = RunnableWithMessageHistory(model, get_session_history)

# 配置信息,其中 session_id 用于区分不同的对话会话
config = {"configurable": {"session_id": "1"}}

# 第一次对话:用户发送消息 "Hi! I'm Bob"
with_message_history.invoke(
    [HumanMessage(content="Hi! I'm Bob")],  # 输入消息列表(这里只有一条用户消息)
    config=config,                         # 传入配置,指定 session_id = "1"
).pretty_print()  # 打印模型响应的漂亮格式

# 第二次对话:在同一会话(session_id = "1")中继续对话
with_message_history.invoke(
    [HumanMessage(content="What's my name?")],  # 用户询问自己的名字
    config=config,
).pretty_print()

class langchain_core.runnables.history.RunnableWithMessageHistory 类方法

说明:

• .invoke() 方法:此方法与其他 Runnable 实例的 .invoke() 方法相同。只不过注意其

config配置,需要配置成config={"configurable": {"session_id": ""}} ,让

RunnableWithMessageHistory 可以读取到会话id。

1.3.2.1 说明

从 LangChain 的 v0.3 版本开始,官方建议 LangChain 用户不要使用

RunnableWithMessageHistory ,而是利用 LangGraph 持久性来完成(见 LangGraph 章

节)。

原因是它们的功能有限,不太适合现实世界的对话式 AI 应用程序。这些内存抽象缺乏对多用户、多对话场景的内置支持,而这对于实际的对话式人工智能系统至关重要。这些实现中的大多数已在

LangChain 0.3.x 中被正式弃用,取而代之的是 LangGraph 持久性。LangGraph 持久性非常灵

活,可以支持比 RunnableWithMessageHistory 接口更广泛的用例。

1.4 管理历史消息

1.4.1 前置概念

1.4.1.1 上下文窗口

管理历史消息,无非就是理解如何"管理","管理"无非也就是一些 "CRUD"。那么在了解如何

管理消息之前,需要先了解下多轮对话的核心概念:上下文窗口

上下文窗口可以理解为模型的"短期工作记忆区",即 LLM 在**一次处理请求时,所能查看和处理的最大 Token 数量,**它包含了:

• 用户的输入
• 大模型的输出

• 有时还包括系统指令(SystemMessage)和对话历史。

Token

在自然语言处理(NLP)中,Token 是文本的基本单位。它不是完全等同于一个单词或一个汉字,而是一个更细粒度的划分。为什么用 Token?计算机无法直接理解文字,它需要将文本转换为数字(向量) 。Tokenization(令牌化)就是这个转换过程的第一步,将句子分解成模型可以理解和处理的碎片。
• 对于英文: 1个Token ~= 4个字符或0.75个单词,1000 个 Tokens 约等于 750 个英文单词。一个 Token 可以是一个单词(如 "apple" )、一个词根(如 "un" 在 "unlikely" 中),或者一个标点符号(如 "." )。例如, "ChatGPT is great!" 可能会被分成 ["Chat","G", "PT", " is", " great", "!"] 这 6 个 Token。
• 对于中文: 1个汉字 ~= 1.5-2个Tokens ,1000 个 Tokens 大约相当于 500-700 个汉字。常见

的词和字可能是一个 Token,生僻字或复杂词可能会被拆分成多个。

1.4.2 消息裁剪

多轮对话的实现原理,其实就是:
• 输入 = 系统消息 + 对话历史 + 最新用户问题

• 对于模型来说,并不真正"记忆",而是每次都将完整的上下文重新输入。

由于所有模型的上下文窗口大小都是有限的,这意味着作为输入的 Token 也是有限的。如果有累积了很长的消息历史记录,则需要管理传递给模型的消息的长度。trim_messages 可用于将聊天历史记录的大小减小指定的令牌计数或指定的消息计数。

1.4.2.1 基于输入 Token 数的修剪
python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, trim_messages

# 1. 定义大模型(使用 OpenAI 的 GPT-4o-mini)
model = ChatOpenAI(model="gpt-4o-mini")

# 2. 模拟一个包含多轮对话的历史消息列表
messages = [
    SystemMessage(content="you're a good assistant"),   # 系统消息:设定助手角色
    HumanMessage(content="hi! I'm bob"),                # 用户介绍自己
    AIMessage(content="hi!"),                           # 助手回复
    HumanMessage(content="I like vanilla ice cream"),   # 用户说喜欢香草冰淇淋
    AIMessage(content="nice"),                          # 助手回应
    HumanMessage(content="whats 2 + 2"),                # 用户问数学题
    AIMessage(content="4"),                             # 助手回答 4
    HumanMessage(content="thanks"),                     # 用户感谢
    AIMessage(content="no problem!"),                   # 助手说不客气
    HumanMessage(content="having fun?"),                # 用户问是否开心
    AIMessage(content="yes!"),                          # 助手回答是的
    HumanMessage(content="What's my name?"),            # 用户问自己的名字(需要上下文记忆)
]

# 3. 创建消息修剪器trim_messages,它是一个函数(方法)
#它返回一个 Runnable 对象。trimmer变量名,用来存储 trim_messages() 返回的那个 Runnable 对象。
# 作用:将消息列表修剪到不超过指定 token 数量,避免超出模型上下文长度
trimmer = trim_messages(
    max_tokens=65,                # 修剪后消息总 token 数的上限(可根据需要调整)
    strategy="last",              # 保留策略:"last" 保留最后的消息;"first" 保留最早的消息
    token_counter=model,          # 用于计算 token 数量的函数或模型(这里直接传入 model,它会自动使用 tokenizer)
    include_system=True,          # 是否始终保留系统消息(即使它不在"保留区域"内)
    allow_partial=False,          # 是否允许拆分消息内容(False 表示宁可多保留几条完整消息,也不截断单条消息)
    start_on="human",             # 确保修剪后第一条消息(不含系统消息)的类型为 "human",避免从助手消息开始
)

# 4. 应用修剪器,得到修剪后的消息列表
trimmed_messages = trimmer.invoke(messages)

# 可选:打印修剪前后的消息数量和内容,查看效果
print(f"原始消息数量: {len(messages)}")
print(f"修剪后消息数量: {len(trimmed_messages)}")
print("\n修剪后的消息内容:")
for msg in trimmed_messages:
    print(f"{msg.type}: {msg.content}")

# 5. 将修剪后的消息发送给模型并获取回复
response = model.invoke(trimmed_messages)
print("\n模型回复:")
response.pretty_print()
  • trim_messages 是 LangChain 提供的消息修剪工具,用于在发送给 LLM 之前,自动将消息历史压缩到指定的 token 限制内,防止因对话过长而超出模型上下文窗口。

  • 参数详解

    • max_tokens:目标 token 上限,工具会尽可能使修剪后的消息总 token 数不超过该值。

    • strategy:修剪策略,"last" 保留最近的消息,"first" 保留最早的消息。

    • token_counter:用于计算每个消息 token 数的可调用对象,可以直接传入 ChatOpenAI 实例(LangChain 会调用其 get_num_tokens 方法)。

    • include_system:是否强制保留所有系统消息,即使它们在时间上较早。

    • allow_partial:若设为 False,则不会截断某条消息的部分内容,而是完整保留或舍弃整条消息。

    • start_on:保证修剪后(忽略系统消息)的第一条消息类型为指定角色(如 "human"),避免对话以助手消息开头。

  • 执行流程 :先调用 trimmer.invoke(messages) 获取修剪后的消息列表,再将该列表传入 model.invoke() 获取模型回复。

注意事项

  • 使用 token_counter=model 需要安装 tiktoken 包,OpenAI 模型会自动使用它进行 token 计数。

  • trim_messages 返回的是一个 Runnable 对象,需要调用 .invoke() 才能实际执行修剪。

  • 建议在实际对话场景中,每次调用模型前都动态修剪历史消息,以灵活控制上下文长度。

1.4.2.2 基于消息数的修剪

除了基于 token 的修剪,还可以通过设置 token_counter=len 根据消息数修剪聊天记录。在这

种情况下, max_tokens 将控制最大消息数。示例如下:

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, trim_messages

# 1. 定义大模型(OpenAI GPT-4o-mini)
model = ChatOpenAI(model="gpt-4o-mini")

# 2. 历史消息记录(注意:原代码中列表定义有误,已修正为完整列表)
messages = [
    SystemMessage(content="you're a good assistant"),
    HumanMessage(content="hi! I'm bob"),
    AIMessage(content="hi!"),
    HumanMessage(content="I like vanilla ice cream"),
    AIMessage(content="nice"),
    HumanMessage(content="whats 2 + 2"),
    AIMessage(content="4"),
    HumanMessage(content="thanks"),
    AIMessage(content="no problem!"),
    HumanMessage(content="having fun?"),
    AIMessage(content="yes!"),
    HumanMessage(content="What's my name?"),
]

# 3. 使用 trim_messages 基于消息数进行修剪
# 设置 token_counter=len 表示按消息条数计数,此时 max_tokens 表示最大保留的消息数量
trimmer = trim_messages(
    max_tokens=11,                # 最大保留的消息条数(包括可能的系统消息)
    strategy="last",              # 保留策略:"last" 保留最后的消息;"first" 保留最早的消息
    token_counter=len,            # 关键:使用 len 函数按消息条数计数(而非 token 数)
    include_system=True,          # 是否始终保留系统消息(设为 True 时系统消息不计入 max_tokens 限制?实际上会强制保留,但仍占用条数)
    allow_partial=False,          # 是否允许拆分消息内容(基于条数时无效,因为不会拆分消息)
    start_on="human",             # 确保修剪后第一条消息(非系统消息)类型为 "human"
)

# 4. 执行修剪并打印结果
trimmed = trimmer.invoke(messages)
print(trimmed)

# 可选:打印每条消息以查看效果
print(f"\n修剪后共 {len(trimmed)} 条消息:")
for msg in trimmed:
    print(f"{msg.type}: {msg.content}")
  • token_counter=len :这是实现"基于消息数裁剪"的核心。trim_messages 允许传入任意可调用对象来计算消息的"大小"。当传入 len 时,每条消息的计数就是 1,因此 max_tokens 就变成了最大消息数量。

  • max_tokens=11 :由于原始消息列表共有 12 条消息(包含系统消息),设置 max_tokens=11 会从末尾保留 11 条消息,丢弃最前面的一条(本例中会丢弃系统消息吗?实际上因为 include_system=True,系统消息会被强制保留,所以实际保留的消息会包含系统消息 + 最后 10 条非系统消息,总数为 11。)

  • strategy="last" :从末尾开始保留消息。若设为 "first" 则从开头保留。

  • start_on="human" :确保修剪后的第一条消息(忽略系统消息后)是 human 类型,避免对话以 assistant 消息开头导致 LLM 行为异常。

1.4.3 消息过滤

在更复杂的场景下,我们可能会使用消息列表来跟踪状态 ,例如我们可能只想将这个完整消息列表的子集传递模型调用,而不是所有的历史记录。filter_messages 方法则可以轻松地按类型、ID 或名称过滤 message。下面演示相关过滤示例,首先准备消息列表:

python 复制代码
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, filter_messages

# ========== 1. 准备消息列表 ==========
# 注意:SystemMessage, HumanMessage, AIMessage 的构造方法需要使用 content= 参数
messages = [
    SystemMessage(content="你是一个聊天助手", id="1"),      # id 用于唯一标识消息
    HumanMessage(content="示例输入", id="2"),
    AIMessage(content="示例输出", id="3"),
    HumanMessage(content="真实输入", id="4"),
    AIMessage(content="真实输出", id="5"),
]

# ========== 2. 示例一:按类型筛选 ==========
# 只保留 HumanMessage 类型的消息
filtered_by_type = filter_messages(messages, include_types="human")
# 或者使用链式写法:filter_messages(include_types="human").invoke(messages)

print("按类型筛选(只保留 human):")
for msg in filtered_by_type:
    print(f"{msg.type}: {msg.content} (id={msg.id})")

# 输出示例(实际运行结果):
# human: 示例输入 (id=2)
# human: 真实输入 (id=4)

# ========== 3. 示例二:按类型 + ID 筛选 ==========
# 包含 HumanMessage 和 AIMessage,但排除 id 为 "3" 的消息(即第二条 AIMessage)
filtered_by_type_and_id = filter_messages(
    messages,
    include_types=[HumanMessage, AIMessage],   # 可传入类型列表
    exclude_ids=["3"]                          # 要排除的消息 ID 列表
)

print("\n按类型(HumanMessage + AIMessage)并排除 id='3':")
for msg in filtered_by_type_and_id:
    print(f"{msg.type}: {msg.content} (id={msg.id})")

# 输出示例:
# human: 示例输入 (id=2)
# human: 真实输入 (id=4)
# ai: 真实输出 (id=5)
  1. filter_messages 的作用

    从消息列表中选择一个子集,支持按类型(include_types / exclude_types)、按 ID(include_ids / exclude_ids)、按名称(include_names / exclude_names)进行筛选。返回的是满足条件的消息列表。

  2. 参数说明

    • include_types:需要包含的消息类型,可以是字符串(如 "human")或类(如 HumanMessage),也可以是它们的列表。

    • exclude_ids:需要排除的消息 ID 列表。

    • 其他常用参数:

      • exclude_types:排除的消息类型

      • include_ids:只包含的 ID 列表

      • include_names / exclude_names:按消息的 name 属性筛选

  3. 调用方式

    • 可以直接调用 filter_messages(messages, ...) 返回筛选后的列表。

    • 也可以使用 Runnable 风格:filter_messages(...).invoke(messages),这在链式操作中更灵活。

1.4.4 消息合并

若我们的消息列表存在连续某种类型相同的消息,但实际上某些模型不支持传递相同类型的连续消

息。意思是:消息列表里有时会出现两个相同角色的消息连在一起 (例如连续条 HumanMessage 或连续两条 AIMessage)。

但有些大模型的 API 要求消息必须是交替的(user / assistant / user / assistant ...),不允许连续出现相同角色。为了解决这个问题,LangChain 提供了 merge_message_runs ,它会把相邻的相同角色消息拼接成一条 ,保证发给模型的消息是交替的。使用 merge_message_runs 方法轻松合并相同类型的连续消息。

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, merge_message_runs

# 1. 定义大模型(以 OpenAI 为例)
model = ChatOpenAI(model="gpt-4o-mini")

# 2. 原始消息列表(存在连续相同类型的消息)
messages = [
    SystemMessage(content="你是一个聊天助手。"),
    SystemMessage(content="你总是以笑话回应。"),           # 连续两条 SystemMessage
    HumanMessage(content="为什么要使用 LangChain?"),
    HumanMessage(content="为什么要使用 LangGraph?"),      # 连续两条 HumanMessage
    AIMessage(content="因为你试图让你的代码更有条理时,LangGraph 会让你感到"节点"是个好主意!"),
    AIMessage(content="不过别担心,它不会"分散"你的注意力!"),  # 连续两条 AIMessage
    HumanMessage(content="选择 LangChain 还是 LangGraph?"),
]

# 3. 合并相同角色的连续消息
merged = merge_message_runs(messages)

print("合并后的消息列表:")
for msg in merged:
    print(f"{msg.type}: {msg.content}")

# 输出示例:
# system: 你是一个聊天助手。\n你总是以笑话回应。
# human: 为什么要使用 LangChain?\n为什么要使用 LangGraph?
# ai: 因为你试图让你的代码更有条理时,LangGraph 会让你感到"节点"是个好主意!\n不过别担心,它不会"分散"你的注意力!
# human: 选择 LangChain 还是 LangGraph?

# 4. 调用大模型的两种方式

# 方式一:先合并再直接 invoke
merged_messages = merge_message_runs(messages)
response1 = model.invoke(merged_messages)
print("\n方式一 模型回复:")
response1.pretty_print()

# 方式二:将 merge_message_runs 和 model 组合成一个链(推荐写法)
merger = merge_message_runs()      # 创建一个可运行的合并器
chain = merger | model             # 使用管道符组合,先合并后调用模型
response2 = chain.invoke(messages) # 直接传入原始消息列表,链会自动先合并再调用
print("\n方式二 模型回复:")
response2.pretty_print()
  1. merge_message_runs 的行为

    • 仅合并相邻且角色相同的消息。

    • 不同角色之间的顺序保持不变。

    • 系统消息(SystemMessage)也会合并(如果连续出现)。

  2. 两种调用模型的方式

    • 方式一 :手动调用 merge_message_runs 得到合并后的列表,再传给 model.invoke

    • 方式二 :创建一个 merge_message_runs() 可运行对象,然后用 | 与模型组合成链。直接调用链时可以传入原始消息,链会自动完成合并 + 调用模型

2. 提示词模板(Prompt Template)

关于提示词模板。这里只挑拣几个简单讲解,更多的去官方文档查询使用方法即可

2.1 概念

提示词模板(Prompt Template)是 LangChain 的核心抽象之一,它被广泛应用于构建大语言模型

(LLM)应用的各个环节。

简单来说,只要是需要动态、批量、或有结构地向大语言模型【发送请求】的地方 ,几乎都会用到提示词模板。

一个简单的例子,假设我们想根据一个城市名询问 LLM 其历史,按照之前的做法,我们可以定义

HumanMessage("请介绍上海的历史") 、HumanMessage("请介绍西安的历史") 消息等等。可

以发现每次询问都会描写重复的消息内容: 请介绍xxx的历史。

在 LangChain 中,针对这种情况,可以定义一个模板:
• 固定文本(模板): "请介绍{city}的历史。"
• 输入变量: ["city"]

定义好后,可以使用该模板:

• 当我们需要查询北京时,就将 city 变量赋值为 "北京"。模板引擎会生成: "请介绍北京的历

史。"

• 当我们需要查询上海时,就将 city 变量赋值为 "上海"。模板引擎会生成: "请介绍上海的历

史。"

提示词模板 就是一个可复用的提示词蓝图 ,它允许我们动态地生成提示词,而不是每次都手动编写完整的提示词。

它类似于编程中的字符串格式化功能 。你创建一个带有"占位符"的模板 ,然后在运行时,用具体的值(变量)填充这些占位符, 从而生成一个最终发送给 LLM 的完整提示词。

提示词模板解决了以下几个核心问题:

  1. 可复用性: 只需定义一个模板,就可以用于无数个类似的查询。

  2. 关注点分离: 将提示词的结构和逻辑(工程)与具体的内容和数据分离开。提示工程师可以专注于

优化模板,而应用程序则负责提供变量值。

  1. 一致性: 确保发送给LLM的提示词结构统一,这有助于获得更稳定、可预测的输出结果。

  2. 可维护性: 如果需要修改提示词的风格或结构,只需修改一个模板文件,而不用在代码的无数个地

方进行修改。

2.2 用法

2.2.1 字符串模板

LangChain 提供了 PromptTemplate 类来轻松实现这一功能。**PromptTemplate 实现了标准的
Runnable 接口。**示例如下:

python 复制代码
from langchain_core.prompts import PromptTemplate

# 1. 定义模板:使用 {language} 作为占位符,运行时会被实际值替换
#    from_template() 是类方法,根据模板字符串创建一个 PromptTemplate 实例
prompt_template = PromptTemplate.from_template("Translate the following into {language}")

# 2. 实例化模板:调用 invoke 方法,传入一个字典,键为占位符名称,值为要填充的内容
#    返回的是一个 PromptValue 对象,其 .text 属性或 str() 可得到最终字符串
result = prompt_template.invoke({"language": "Chinese"})

print(result)          # 直接打印 PromptValue 对象会显示 text='...'
print(result.text)     # 获取纯文本:Translate the following into Chinese
参数 描述
template 提示模板字符串,包含用 {} 包裹的变量名。
input_variables 模板中所有变量名的列表(通常自动从模板提取,也可手动指定)。

内置方法

方法 描述
from_template(template: str) 类方法,根据模板字符串创建 PromptTemplate 实例。
invoke(input: dict) 接收一个字典(变量名 -> 值),返回填充后的 PromptValue 对象。

更完整的示例(便于理解)

  • PromptTemplate 实现了 LangChain 的 Runnable 接口,可以与模型通过 | 管道组合:prompt | model

  • 如果模板中使用了变量但未在 invoke 传入,会抛出 KeyError

python 复制代码
# 定义包含多个变量的模板
template = """
请根据以下信息回答问题:
问题:{question}
背景:{context}
"""
prompt = PromptTemplate.from_template(template)

# 填充变量
filled = prompt.invoke({
    "question": "LangChain 是什么?",
    "context": "LangChain 是一个用于构建 LLM 应用的框架。"
})

print(filled.text)
# 输出:
# 请根据以下信息回答问题:
# 问题:LangChain 是什么?
# 背景:LangChain 是一个用于构建 LLM 应用的框架。

2.2.2 聊天消息模板

ChatPromptTemplate 模板:专为 LangChain 聊天模型设计。可以方便地构建包含
SystemMessage 、HumanMessage 、AIMessage 的消息模板

python 复制代码
# ==================== 1. 基础用法:创建包含 system 和 user 消息的模板 ====================
from langchain_core.prompts import ChatPromptTemplate

# 1.1 设置模板:使用元组列表定义消息序列,每个元组为 (角色, 内容模板)
# 支持的角色类型通常为 "system", "user", "ai"
prompt_template = ChatPromptTemplate(
    [
        ("system", "Translate the following into {language}."),   # 系统消息模板
        ("user", "{text}")                                        # 用户消息模板
    ]
)

# 注释:在 langchain_core 0.2.24 版本后可直接使用 ChatPromptTemplate() 构造函数;
#       旧版本需要使用 ChatPromptTemplate.from_messages() 方法。

# 1.2 实例化模板:传入具体变量值,得到 PromptValue 对象
messages_value = prompt_template.invoke(
    {
        "language": "Chinese",
        "text": "what is your name?"
    }
)

# 1.3 将 PromptValue 转换为消息列表(包含 SystemMessage 和 HumanMessage 对象)
messages = messages_value.to_messages()
print(messages)
# 输出:
# [SystemMessage(content='Translate the following into Chinese.', ...),
#  HumanMessage(content='what is your name?', ...)]

# ==================== 2. 进阶用法:与模型和输出解析器组合成链 ====================
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

# 2.1 定义大模型(以 OpenAI GPT-4o-mini 为例)
model = ChatOpenAI(model="gpt-4o-mini")

# 2.2 定义输出解析器:将模型响应转换为字符串
parser = StrOutputParser()

# 2.3 构建链:模板 -> 模型 -> 解析器
#    注意:ChatPromptTemplate 可以直接 invoke 返回消息列表,也可以直接放入链中
chain = prompt_template | model | parser

# 2.4 执行链:传入变量,自动完成模板填充 -> 模型调用 -> 解析
result = chain.invoke({"language": "Chinese", "text": "what is your name?"})
print(result)   # 输出:你的名字是什么?
  1. ChatPromptTemplate 的作用

    专为聊天模型设计,可以方便地定义多轮对话的消息模板(系统、用户、助手角色)。每个消息模板是一个 (role, template_string) 元组。

  2. 两种调用方式

    • 单独使用:prompt_template.invoke(variables)PromptValue,再调用 .to_messages() 得到消息列表,传递给模型。

    • 链式调用:prompt_template | model | parser,直接传入变量,自动完成模板填充、模型调用和输出解析。

  3. 输入变量的自动推断
    ChatPromptTemplate 会自动扫描所有模板字符串中的 {variable} 作为输入变量,无需手动指定 input_variables

  4. 常见角色类型

    • "system": 系统消息,设定模型行为。

    • "user": 用户消息。

    • "ai": 助手消息(通常用于少样本示例或对话历史)。

    • 也可使用 HumanMessage, AIMessage 等对象直接添加,但元组形式更简洁。

  • 第一个 print(messages) 输出的是 SystemMessageHumanMessage 对象的列表,包含完整元数据。

  • 链式调用最终打印的是模型翻译后的中文结果:你的名字是什么?

2.2.3 消息占位符

在上面的 ChatPromptTemplate 中,我们看到了如何格式化两条消息,每条消息都是一个字符串。但如果我们希望将消息插入特定位置怎么办?使用 MessagesPlaceholder :负责在特定位置添加消息列表。

python 复制代码
# ==================== 方式一:使用 MessagesPlaceholder(推荐) ====================
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage

# 1. 定义模板:包含系统消息和一个占位符(用于插入任意消息列表)
prompt_template = ChatPromptTemplate([
    ("system", "你是一个聊天助手"),
    MessagesPlaceholder("msgs")   # 参数为占位符变量名,运行时会将对应消息列表嵌入此处
])

# 2. 准备要插入的消息列表(通常是对话历史或动态生成的连续消息)
messages_to_pass = [
    HumanMessage(content="中国首都是哪里?"),
    AIMessage(content="中国首都是北京。"),
    HumanMessage(content="那法国呢?")
]

# 3. 调用 invoke,将消息列表赋值给变量 "msgs",生成最终 PromptValue
formatted_prompt = prompt_template.invoke({"msgs": messages_to_pass})

# 4. 打印结果:可以看到系统消息后面依次插入了三条消息
print(formatted_prompt)
# 输出效果(简化):
# messages=[
#     SystemMessage(content='你是一个聊天助手', ...),
#     HumanMessage(content='中国首都是哪里?', ...),
#     AIMessage(content='中国首都是北京。', ...),
#     HumanMessage(content='那法国呢?', ...)
# ]

# ==================== 方式二:使用 ("placeholder", "{var}") 的缩写形式 ====================
# 不显式使用 MessagesPlaceholder 类,直接用元组 ("placeholder", 变量名) 效果相同
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import HumanMessage, AIMessage

prompt_template = ChatPromptTemplate([
    ("system", "You are a helpful assistant"),
    ("placeholder", "{msgs}")      # 等价于 MessagesPlaceholder("msgs")
])

messages_to_pass = [
    HumanMessage(content="中国首都是哪里?"),
    AIMessage(content="中国首都是北京。"),
    HumanMessage(content="那法国呢?")
]

formatted_prompt = prompt_template.invoke({"msgs": messages_to_pass})
print(formatted_prompt)   # 输出结果与方式一完全一致
  1. 什么是 MessagesPlaceholder

    它是一个特殊的占位符,允许你在聊天模板的任意位置嵌入一个完整的消息列表(而不是普通字符串)。通常用于:

    • 插入多轮对话历史记录。

    • 动态添加任意数量的上下文消息(如检索到的示例、用户的操作记录等)。

  2. 为什么需要消息占位符

    普通的字符串占位符(如 {text})只能填充文本内容,而 MessagesPlaceholder 可以直接填充 BaseMessage 列表(HumanMessageAIMessage 等),保留每条消息的角色和元数据。

  3. 两种写法的区别与选择

    • 推荐写法MessagesPlaceholder("variable_name"),语义清晰,IDE 支持良好。

    • 缩写写法("placeholder", "{variable_name}"),更简洁,但不易发现可用的参数(如 optionaln_messages 等扩展参数)。

    从 LangChain 0.2+ 开始,MessagesPlaceholder 还支持额外参数:optional=True(允许变量缺失而不报错)、n_messages=5(限制插入的消息数量)等,这些在缩写形式中无法使用。

  4. 与普通模板的区别

    • 普通模板:("user", "{text}") → 填充的是纯文本,生成一条 HumanMessage

    • 消息占位符:MessagesPlaceholder("msgs") → 将整个消息列表"展开"后嵌入,列表中的每条消息保持原样插入。

  5. 实际应用场景

    python 复制代码
    # 场景:需要将检索到的示例对话作为上下文插入到系统消息之后
    prompt_template = ChatPromptTemplate([
        ("system", "你是一个代码助手"),
        MessagesPlaceholder("few_shot_examples"),   # 插入几个例子
        ("user", "{user_input}")
    ])
    
    examples = [
        HumanMessage("如何反转列表?"),
        AIMessage("使用 list[::-1] 即可。"),
        HumanMessage("如何合并两个字典?"),
        AIMessage("使用 {**dict1, **dict2}。")
    ]
    final_prompt = prompt_template.invoke({
        "few_shot_examples": examples,
        "user_input": "如何创建字典推导式?"
    })

扩展:MessagesPlaceholder 的更多参数(LangChain 0.3+)

python 复制代码
MessagesPlaceholder(
    variable_name="history",
    optional=True,        # 若 history 未提供,不会报错,直接忽略该占位符
    n_messages=5          # 最多只取历史中的最后 5 条消息插入
)

2.3 使用 LangChain Hub 的提示词模板


**LangChain Hub 是一个用于上传、浏览、拉取和管理提示词(prompts)的地方。**随着 LLM 的发展,提示变得越来越重要。LangChain 正在打造一个与像 GitHub 这样的传统平台,GitHub长期以来一直是共享和协作代码的首选平台。于是推出了 LangChain Hub 平台。

LangChain Hub 创建一个分享和发现 Prompt 的平台,使得开发者可以更容易地发现新用例和精炼提示。 这一举措使提示工程师更容易合作,重复使用现有的提示,并对其进行微调以实现特定的结果,从而加速对话代理和其他基于语言的应用程序的开发和部署。早期的时候 LangChain Hub 有Prompt、Chain、Agent,现在只有Prompt。

LangChain Hub 官网地址:中心 - LangSmith。通过登录到 Hub 来探索所有现有提示。

这里以提示词模板:hardkothari/prompt-maker 为示例,演示一下如何使用 LangChain Hub 上的提示。Prompt Maker 模板是一个【提示生成器】 ,它可以自动化优化提示的过程,从而提高语言模型在各种应用中的质量和效果。

要想使用该能力,需要先申请并配置LangSmith 环境变量 : LANGSMITH_API_KEY="你的LangSmith API Key" 。接着,需要从 hub 拉取相应的提示,并使用

python 复制代码
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langsmith import Client

# ==================== 1. 设置 LangSmith API 环境变量 ====================
# 在代码中设置(不推荐生产环境使用,仅演示),或者提前在系统环境变量中配置好 LANGSMITH_API_KEY
# os.environ["LANGSMITH_API_KEY"] = "你的 LangSmith API Key"

# ==================== 2. 初始化 LangSmith 客户端 ====================
client = Client()  # 自动从环境变量 LANGSMITH_API_KEY 中读取 API Key

# ==================== 3. 从 LangSmith Hub 拉取提示词模板 ====================
# 从 LangSmith Hub 拉取 hardkothari/prompt-maker 提示词模板
# include_model=False 表示只拉取提示词本身的定义,不附带关联的模型配置。
# 如果设置为 True,则会尝试同时加载模板中指定的模型配置(需要额外设置)
prompt = client.pull_prompt("hardkothari/prompt-maker", include_model=False)

# 4. 定义 LLM 模型:此处使用 OpenAI 的 GPT-4o-mini 模型
model = ChatOpenAI(model="gpt-4o-mini")

# 5. 构建处理链:提示词模板 → 大模型调用(此时模板还未"注入"用户输入)
chain = prompt | model

# 6. 交互式循环:接收用户输入的"任务"和"当前提示词",生成优化后的提示词并打印
while True:
    # 6.1 获取用户当前的任务描述
    task = input("\n你的任务是什么?(输入 quit 退出聊天)\n")
    if task == 'quit':
        break

    # 6.2 获取用户当前的提示词/原始提示
    lazy_prompt = input("\n你当前的提示是什么?(输入 quit 退出聊天)\n")
    if lazy_prompt == 'quit':
        break

    # 6.3 将两个变量以字典形式传入链中调用,生成模型的回复并美化打印
    print("\n Response:")
    # 将 task 和 lazy_prompt 传递给链进行调用,注意字典的键名要与模板期望的变量名完全匹配
    chain.invoke({'lazy_prompt': lazy_prompt, 'task': task}).pretty_print()

通过使用这个模板,可以大 减少手动调整提示所需的工作量,从而节省时间和资源。Prompt Maker通过分析初始提示的结构和内容,然后应用一组预定规则或算法来优化提示,以提高响应质量、清晰度和相关性。这在提示的质量对模型的输出有很大影响的场景中特别有用,比如客户服务机器人、对话代理或数据分析任务。

相关推荐
Aaron15884 小时前
27DR/47DR/67DR技术对比及应用分析
人工智能·算法·fpga开发·硬件架构·硬件工程·信息与通信·基带工程
星爷AG I4 小时前
20-2 工作记忆(AGI基础理论)
人工智能·agi
码途漫谈4 小时前
Easy-Vibe开发篇阅读笔记(十三)——附录之用 Dify 搭建知识库问答系统
笔记·ai·开源·ai编程
AI进化营-智能译站4 小时前
Jazzy ROS2入门指南系列04-如何在ROS2中创建工作空间,怎么编译ROS2项目
ai
博.闻广见4 小时前
AI_概率统计-3.统计量
人工智能
工作log4 小时前
10分钟搭建本地语音识别服务 (Whisper large-v3-turbo)
人工智能·whisper·语音识别
烟雨江南7854 小时前
苟富拒绝方言“滑铁卢”:语音识别本地部署中的领域增量微调(Fine-tuning)与样本自动标注全流程贵受到广泛发生过施工方
人工智能·语音识别
Zzj_tju4 小时前
大语言模型技术指南:RAG 为什么能补知识盲区?检索、切块、重排与生成参数详解
人工智能·语言模型·自然语言处理
昨夜见军贴06164 小时前
供应链合规正在“前移审查”:AI报告审核与IACheck如何重构供应商资质的精准校验逻辑
人工智能·重构