文章目录
- [1. LangChain 核心组件(Components)](#1. LangChain 核心组件(Components))
-
- [1.1 消息(Messages)](#1.1 消息(Messages))
-
- [1.1.1 LLM消息结构](#1.1.1 LLM消息结构)
- [1.1.2 LangChain消息](#1.1.2 LangChain消息)
- [1.1.3 对话模式](#1.1.3 对话模式)
- [1.2 缓存历史消息](#1.2 缓存历史消息)
-
- [1.2.1 多轮对话](#1.2.1 多轮对话)
- [1.2.2 内存缓存](#1.2.2 内存缓存)
- [1.3 管理历史消息](#1.3 管理历史消息)
-
- [1.3.1 相关概念](#1.3.1 相关概念)
-
- [1.3.1.1 上下文窗口](#1.3.1.1 上下文窗口)
- [1.4.1.2 Token](#1.4.1.2 Token)
- [1.3.2 消息裁剪](#1.3.2 消息裁剪)
-
- [1.3.2.1 基于输入Token数的修剪](#1.3.2.1 基于输入Token数的修剪)
- [1.3.2.2 基于消息数的修剪](#1.3.2.2 基于消息数的修剪)
- [1.3.3 消息过滤](#1.3.3 消息过滤)
- [1.3.4 消息合并](#1.3.4 消息合并)
- [2. 提示词模板(Prompt Template)](#2. 提示词模板(Prompt Template))
-
- [2.1 概念](#2.1 概念)
- [2.2 用法](#2.2 用法)
-
- [2.2.1 字符串模板](#2.2.1 字符串模板)
- [2.2.2 聊天消息模板](#2.2.2 聊天消息模板)
- [2.2.3 消息占位符](#2.2.3 消息占位符)
- [2.3 使用LangChain Hub的提示词模板](#2.3 使用LangChain Hub的提示词模板)
1. LangChain 核心组件(Components)
1.1 消息(Messages)
消息是聊天模型中的通信单位,用于表示聊天模型的输入和输出,以及可能与对话关联的任何其他上下文或元数据。
1.1.1 LLM消息结构
每条消息都有一个角色role和内容content,以及因LLM的不同而不同的附加元数据。
消息角色(Role):
用来区分对话中不同类型的消息,并帮助聊天模型了解如何响应给定的消息序列。
| 角色 | 描述 |
|---|---|
| system(系统角色) | 用于告诉聊天模型如何行为并提供额外的上下文。并非所有聊天模型提供商都支持。 |
| user(用户角色) | 表示用户与模型交互的输入,通常以文本或其他交互式输入的形式。 |
| assistant(助理角色) | 表示来自模型的响应,其中可以包括文本或调用工具的请求。 |
| tool(工具角色) | 用于检索外部数据或将工具调用的结果传递回模型的消息。与支持工具调用的聊天模型一起使用。 |
消息内容(Content):
表示多模态数据(例如,图像、音频、视频)的消息文本或字典列表的内容。内容的具体格式可能因底层不同的LLM而异。目前,大多数模型都支持文本作为主要内容类型,对多模态数据的支持仍然有限。
消息其他元数据(Additional metadata)
| 元数据 | 描述 |
|---|---|
| ID | 消息标识符。 |
| Name | 名称允许区分具有相同角色的不同实体。并非所有型号都支持此功能! |
| Metadata | 有关消息的其他信息,例如时间戳、令牌使用情况等。 |
| Tool Calls | 模型发出的一个或多个工具的调用请求 |
下面展示一个OpenAI的格式消息列表:
json
[
{
"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.1.2 LangChain消息
LangChain提供了一种统一的消息格式,可以跨聊天模型使用,允许用户使用不同的聊天模型,而无需担心每个模型提供商使用的消息格式的具体细节。例如:
python
openai_model = init_chat_model("gpt-4o-mini", model_provider="openai")
anthropic_model = init_chat_model("claude-3-5-sonnet-latest", model_provider="anthropic")
deepseek_model = init_chat_model("deepseek-chat", model_provider="deepseek")
google_genai_model = init_chat_model("gemini-2.5-flash", model_provider="google_genai")
model = init_chat_model(...)
这些模型提供商不同,但对于其输入和输出,统一使用LangChain的消息格式。LangChain消息格式主要分为五种,分别是:
| 消息类型 | 对应角色 | 描述 |
|---|---|---|
| SystemMessage | 对应system系统角色 | 用于启动AI模型的行为并提供额外的上下文,例如指示模型采用特定角色或设定对话的基调(例如,"你是一个后端开发的专家")。 |
| HumanMessage | 对应user用户角色 | 人类消息表示用户与模型交互的输入。大多数聊天模型都希望用户输入采用文本形式。 |
| AIMessage | 对应assistant助理角色 | 这是来自模型的响应,其中可以包括文本或调用工具的请求。它还可能包括其他媒体类型,如图像、音频或视频------尽管这目前仍然不常见。 |
| AIMessageChunk | 对应流式assistant助理角色 | 通常在生成聊天模型时流式传输响应,因此显示。可以实时看到响应,而不是等待生成整个响应。 |
| ToolMessage | 对应tool工具角色 | 这表示一条角色为"tool"的消息,其中包含调用工具的结果。 |
五种消息类型全部都是LangChain BaseMessage 的子类,全部作为LangChain聊天模型的输入和输出。
BaseMessage抽象消息类
langchain_core.messages.base.BaseMessage 是作为LangChain聊天模型的输入和输出:
参数如下:
content:消息的字符串内容。additional_kwargs:与消息关联的其他有效负载数据。对于来自AI的消息,可能包括模型提供程序编码的工具调用。response_metadata:响应元数据。例如:响应标头、logprobs、令牌计数、模型名称。type:消息的类型。必须是消息类型唯一的字符串。此字段的目的是在对消息进行反序列化时方便地识别消息类型。name:消息名称,为消息提供一个人类可读的名称。该字段的使用是可选的,是否使用它取决于模型实现。id:消息的可选唯一标识符。理想情况下,这应该由创建消息的提供者/模型提供。
内置方法:
pretty_print() -> None:打印消息的漂亮表示。pretty_repr(html: bool = False) -> str:获得消息的漂亮表示。- 请求:是否将消息格式化为HTML。如果为True,则消息将使用HTML标记进行格式化。默认值为False。
- 响应:这是消息的漂亮表示。
text() -> str:获取消息的文本内容。
测试代码:
python
# 消息内置方法演示
from langchain_core.messages import HumanMessage, SystemMessage
msg = HumanMessage(content="你好,今天天气怎么样?")
sys_msg = SystemMessage(content="你是一个有帮助的助手。")
# 1. pretty_print() → 终端里带颜色、格式化打印消息,方便调试
msg.pretty_print()
sys_msg.pretty_print()
# 2. pretty_repr() → 返回格式化的字符串(不直接打印),html=True 可生成 HTML
print(msg.pretty_repr()) # 普通文本格式的漂亮表示
print(msg.pretty_repr(html=True))# HTML 格式的漂亮表示
# 3. text() → 直接取消息的纯文本内容(等同于 .content,但更语义化)
print(msg.text()) # 输出: 你好,今天天气怎么样?
1.1.3 对话模式
大多数对话都以设置对话上下文的系统消息开始。接下来是包含用户输入的用户消息,然后是包含模型响应的助手消息。如下图所示:

1.2 缓存历史消息
1.2.1 多轮对话
在与大型语言模型交互的过程中,常常体验到与智能助手进行连贯多轮对话的便利性。但目以下的代码还不支持:
python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")
# 第一次对话
result = model.invoke([HumanMessage(content="Hi! I'm Bob")])
result.pretty_print()
# 第二次对话
result = model.invoke([HumanMessage(content="What's my name?")])
result.pretty_print()
打印结果:
============================ Ai Message
Hi Bob! How can I assist you today?
============================ Ai Message
I'm sorry, but I don't have access to personal information about you unless you share it with me. What would you like me to call you?
如果在消息列表中带上完整的提示信息,那么ai就可以正确给出答案:
python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")
# 记录消息
messages = [
HumanMessage(content="Hi! I'm Bob"),
AIMessage(content="Hello Bob! How can I assist you today?"),
HumanMessage(content="What's my name?"),
]
model.invoke(messages).pretty_print()
打印结果:
============================ Ai Message
Your name is Bob! How can I help you today, Bob?
只要将历史消息,重新发送给聊天模型,那么就可以实现根据上下文进行多轮对话的功能。
1.2.2 内存缓存
那么对于历史消息的管理就显得尤为重要。在LangChain老版本中,可以使用RunnableWithMessageHistory消息历史类来包装另一个Runnable并为其管理聊天消息历史记录。它将跟踪模型的输入和输出,并将其存储在某个数据存储中。未来的交互将加载这些消息,并将其作为输入的一部分传递给链。
代码如下:
python
from langchain_core.chat_history import BaseChatMessageHistory, InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage
from langchain_core.runnables import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="gpt-5.4",
temperature=0,
api_key="sk-XXX",
base_url="https://www.packyapi.com/v1",
)
# 这里来实现多轮对话,根据对话id来查询会话里的消息列表
store = {} # 用于存储对话消息,key为对话id,value为消息列表
# 根据会话id查找会话的历史记录
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory() # 如果没有这个会话id,就创建一个新的InMemoryChatMessageHistory实例
return store[session_id] # 返回这个会话id对应的InMemoryChatMessageHistory实例
# 包装了model,让model具备存储历史消息的能力
with_history_message_model=RunnableWithMessageHistory(model, get_session_history)
# 第一轮对话
response1 = with_history_message_model.invoke(
input=[HumanMessage("你好,我是一名程序员")],
config={"configurable": {"session_id": "1"}}
)
print("第一轮:", response1.content)
# 第二轮对话(同一个 session_id,模型能记住上一轮说过的话)
response2 = with_history_message_model.invoke(
input=[HumanMessage("我刚才说我的职业是什么?")],
config={"configurable": {"session_id": "1"}}
)
print("第二轮:", response2.content)
class langchain_core.runnables.history.RunnableWithMessageHistory 类初始化参数说明:
runnable:被包装Runnable实例,这里就是我们定义的聊天模型get_session_history:返回类型为BaseChatMessageHistory的函数,传入后作为回调函数。此函数接受一个session_id字符串类型,并返回相应的聊天消息历史记录实例。
class langchain_core.runnables.history.RunnableWithMessageHistory 类方法说明:
.invoke()方法:此方法与其他Runnable实例的.invoke()方法相同。只不过注意其config配 置,需要配置成config={"configurable": {"session_id": ""}},让RunnableWithMessageHistory可以读取到会话id,以确保是在同一个对话。
最终打印结果:
plain
第一轮: 你好!很高兴一起写代码。
我可以帮你看代码、改 bug、加功能、写脚本、补测试、做重构,或者一起设计方案。
你可以直接给我这些内容之一:
- 需求描述
- 报错信息
- 一段代码或文件路径
- 想实现的功能
- 想优化的性能/结构
如果你愿意,现在就把你的问题贴过来。
第二轮: 你刚才说你是一名程序员。
从LangChain的v0.3版本开始,官方建议LangChain用户不要使用RunnableWithMessageHistory,而是利用LangGraph持久性来完成(见LangGraph章节)。原因是它们的支持有限,不太适合现实世界,这些内存抽象缺乏对多用户、多对话场景的内置支持,而这对于实际的对话式AI应用至关重要。这些实现中的大多数已在LangChain 0.3.x中被正式弃用,取而代之的是LangGraph持久性。这些LangGraph持久性非常灵活,可以支持比RunnableWithMessageHistory接口更广泛的用例。
1.3 管理历史消息
1.3.1 相关概念
1.3.1.1 上下文窗口
先了解在每一次处理请求时,所能查看和处理的最大Token数量,它包含了:
- 用户的输入
- 大模型的输出
- 有时还包括系统指令(SystemMessage)和对话历史。

不同大模型支持的上下文窗口大小不同,例如:
- OpenAI下GPT-5模型上下文窗口为400000(最大Token数量)
- GPT-4.1模型上下文窗口为1047576(最大Token数量)
- 其他模型上下文窗口可参考对应模型官网说明,如OpenAI下模型可以参考这里。
1.4.1.2 Token
在自然语言处理(NLP)中,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.3.2 消息裁剪
有了上下文窗口和Token的认知,再来看多轮对话的实现原理,其实就是:
- 输入 = 系统消息 + 对话历史 + 最新用户问题
- 对于模型来说,并不真正"记忆",而是每次都将完整的上下文重新输入。
由于所有模型的上下文窗口大小都是有限的,这意味着作为输入的Token也是有限的。如果有累积了很长的消息历史记录,则需要管理传递给模型的消息的长度。
trim_messages 可用于将聊天历史记录的大小减小为指定的令牌计数或指定的消息计数。
1.3.2.1 基于输入Token数的修剪
下面演示一个通过 trim_messages 裁剪消息的示例(基于输入Token数的修剪)。
先不做任何输入限制聊天:
python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, trim_messages
# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")
# 历史消息记录
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?"),
]
print(model.invoke(messages))
打印结果:
json
content='Your name is Bob.'
additional_kwargs={'refusal': None}
response_metadata={
'token_usage': {
'completion_tokens': 5,
'prompt_tokens': 88,
'total_tokens': 93,
'completion_tokens_details': {
'accepted_prediction_tokens': 0,
'audio_tokens': 0,
'reasoning_tokens': 0,
'rejected_prediction_tokens': 0
},
'prompt_tokens_details': {
'audio_tokens': 0,
'cached_tokens': 0
}
},
'model_name': 'gpt-4o-mini-2024-07-18',
'system_fingerprint': 'fp_51db84afab',
'id': 'chatcmpl-C8KeAy4qX9DaILGRYV6I3v6i0VSjM',
'service_tier': 'default',
'finish_reason': 'stop',
'logprobs': None
}
usage_metadata={
'input_tokens': 88,
'output_tokens': 5,
'total_tokens': 93,
'input_token_details': {
'audio': 0,
'cache_read': 0
},
'output_token_details': {
'audio': 0,
'reasoning': 0
}
}
LLM还认识我们,且共输入了88 tokens。接下来让对消息进行裁剪,只希望将来输入时,最多输入65 tokens,超出的需要按照一定的"规则"进行裁剪,代码如下:
python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, trim_messages
# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")
# 历史消息记录
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?"),
]
# 使用 trim_messages 减少发送给模型的消息数量
trimmer = trim_messages(
max_tokens=65, # 修剪消息的最大令牌数,根据你想要的谈话长度来调整
strategy="last", # 修剪策略:
# "last"(默认):保留最后的消息。
# "first":保留最早的消息。
token_counter=model, # 传入一个函数或一个语言模型(因为语言模型有消息令牌计数方法)
include_system=True, # 如果始终保留初始系统消息,可以指定
allow_partial=False, # 是否允许拆分消息的内容
start_on="human", # 如果需要确保我们的第一条消息(不包括系统消息)始终是特定类型,可以指定 start_on
)
chain = trimmer | model
print(chain.invoke(messages))
打印结果:
json
content="I don't know your name. Would you like to share it with me?"
additional_kwargs={'refusal': None}
response_metadata={
'token_usage': {
'completion_tokens': 16,
'prompt_tokens': 60,
'total_tokens': 76,
'completion_tokens_details': {
'accepted_prediction_tokens': 0,
'audio_tokens': 0,
'reasoning_tokens': 0,
'rejected_prediction_tokens': 0
},
'prompt_tokens_details': {
'audio_tokens': 0,
'cached_tokens': 0
}
},
'model_name': 'gpt-4o-mini-2024-07-18',
'system_fingerprint': 'fp_8068f50c16',
'id': 'chatcmpl-6KtOEHfixpZ2afz6rW5ecXjnjM',
'service_tier': 'default',
'finish_reason': 'stop',
'logprobs': None
}
usage_metadata={
'input_tokens': 60,
'output_tokens': 16,
'total_tokens': 76,
'input_token_details': {
'audio': 0,
'cache_read': 0
},
'output_token_details': {
'audio': 0,
'reasoning': 0
}
}
可以看见,此时我们的输入message已经被修剪了。被修剪了哪些消息呢?来看下:
python
trimmer = trim_messages(
max_tokens=65,
strategy="last",
token_counter=model,
include_system=True,
allow_partial=False,
start_on="human",
)
# 打印经过裁剪器裁剪后的结果
print(trimmer.invoke(messages))
结果如下:
[
SystemMessage(content="you're a good assistant", additional_kwargs={}, response_metadata={}),
HumanMessage(content="whats 2 + 2", additional_kwargs={}, response_metadata={}),
AIMessage(content="4", additional_kwargs={}, response_metadata={}),
HumanMessage(content="thanks", additional_kwargs={}, response_metadata={}),
AIMessage(content="no problem!", additional_kwargs={}, response_metadata={}),
HumanMessage(content="having fun?", additional_kwargs={}, response_metadata={}),
AIMessage(content="yes!", additional_kwargs={}, response_metadata={}),
HumanMessage(content="What's my name?", additional_kwargs={}, response_metadata={})
]
从结果来看,确实是按照我们给定的裁剪"规则"来完成的。修剪聊天记录后,生成的聊天记录(输入)应该有效,需遵循对话模式原则:
- 聊天记录以
HumanMessage或SystemMessage开头,后跟HumanMessage。这可以通过设置start_on="human"来实现。 - 聊天记录以
HumanMessage或ToolMessage结尾。这可以通过设置ends_on=("human", "tool")来实现。 ToolMessage只能出现在涉及工具调用的AIMessage之后。- 如果原始聊天历史记录中存在
SystemMessage,则新聊天历史记录应包括SystemMessage,因为SystemMessage包含对聊天模型的特殊说明。SystemMessage总是历史记录中的第一条消息(如果存在)。这可以通过设置include_system=True。
1.3.2.2 基于消息数的修剪
除了基于token的修剪,还可以通过设置 token_counter=len 根据消息数修剪聊天记录。在这种情况下,max_tokens 将控制最大消息数。示例如下:
python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, trim_messages
# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")
# 历史消息记录
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?"),
]
# 使用 trim_messages 减少发送给模型的消息数量
trimmer = trim_messages(
max_tokens=11, # 最大消息数
strategy="last", # 修剪策略:
# "last"(默认):保留最后的消息。可获取消息列表中的最后一个 max_tokens
# "first":保留最早的消息。
token_counter=len, # 根据消息数裁剪
include_system=True, # 如果想始终保留初始系统消息,可以指定
allow_partial=False, # 是否允许拆分消息的内容
start_on="human", # 如果需要确保我们的第一条消息(不包括系统消息)始终是特定类型,可以指定 start_on
)
print(trimmer.invoke(messages))
结果如下:
[
SystemMessage(content="you're a good assistant", additional_kwargs={}, response_metadata={}),
HumanMessage(content="I like vanilla ice cream", additional_kwargs={}, response_metadata={}),
AIMessage(content="nice", additional_kwargs={}, response_metadata={}),
HumanMessage(content="whats 2 + 2", additional_kwargs={}, response_metadata={}),
AIMessage(content="4", additional_kwargs={}, response_metadata={}),
HumanMessage(content="thanks", additional_kwargs={}, response_metadata={}),
AIMessage(content="no problem!", additional_kwargs={}, response_metadata={}),
HumanMessage(content="having fun?", additional_kwargs={}, response_metadata={}),
AIMessage(content="yes!", additional_kwargs={}, response_metadata={}),
HumanMessage(content="What's my name?", additional_kwargs={}, response_metadata={})
]
1.3.3 消息过滤
在更复杂的场景下,可能会使用消息列表来跟踪状态,例如只想将这个完整消息列表的子集传递模型调用,而不是所有的历史记录。
filter_messages 方法则可以轻松地按类型、ID或名称过滤 message。
下面演示相关过滤示例,首先准备消息列表:
python
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, filter_messages
# 历史消息记录
messages = [
SystemMessage("你是一个聊天助手", id="1"),
HumanMessage("示例输入", id="2"),
AIMessage("示例输出", id="3"),
HumanMessage("真实输入", id="4"),
AIMessage("真实输出", id="5"),
]
- 按类型进行筛选:
python
print(filter_messages(messages, include_types="human"))
# 注意写法等价于:
# print(filter_messages(include_types="human").invoke(messages))
# 结果如下:
# [
# HumanMessage(content='示例输入', additional_kwargs={}, response_metadata={}, name='example_user', id='2'),
# HumanMessage(content='真实输入', additional_kwargs={}, response_metadata={}, name='bob', id='4')
# ]
- 按类型+ID进行筛选:
python
print(filter_messages(messages, include_types=[HumanMessage, AIMessage], exclude_ids=["3"]))
# 结果如下:
# [
# HumanMessage(content='示例输入', additional_kwargs={}, response_metadata={}, name='example_user', id='2'),
# HumanMessage(content='真实输入', additional_kwargs={}, response_metadata={}, name='bob', id='4'),
# AIMessage(content='真实输出', additional_kwargs={}, response_metadata={}, name='ai_message', id='5')
# ]
1.3.4 消息合并
若消息列表存在连续某种类型相同的消息,但实际上某些模型不支持传递相同类型的连续消息。因此对于这种情况,可以使用 merge_message_runs 方法轻松合并相同类型的连续消息。
示例如下:
python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, merge_message_runs
# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")
# 历史消息记录
messages = [
SystemMessage("你是一个聊天助手。"),
SystemMessage("你总是以笑话回应。"),
HumanMessage("为什么要使用 LangChain?"),
AIMessage("因为当你试图让你的代码更有条理时,LangGraph 会让你感到"节点"是个好主意!"),
AIMessage("不过别担心,它不会分散你的注意力!"),
AIMessage("选择LangChain还是LangGraph?"),
]
merged = merge_message_runs(messages)
# 打印合并后的每个消息
print("\n".join([repr(x) for x in merged]))
合并结果:
SystemMessage(content='你是一个聊天助手。\n你总是以笑话回应。', additional_kwargs={}, response_metadata={}),
HumanMessage(content='为什么要使用 LangChain?\n为什么要使用 LangGraph?', additional_kwargs={}, response_metadata={}),
AIMessage(content='因为当你试图让你的代码更有条理时,LangGraph 会让你感到"节点"是个好主意!\n不过别担心,它不会分散你的注意力!', additional_kwargs={}, response_metadata={}),
HumanMessage(content='选择LangChain还是LangGraph?', additional_kwargs={}, response_metadata={})
调用大模型:
python
# 方式一:
merged = merge_message_runs(messages)
model.invoke(merged).pretty_print()
# 方式二:
merger = merge_message_runs()
chain = merger | model
chain.invoke(messages).pretty_print()
2. 提示词模板(Prompt Template)
2.1 概念
提示词模板(Prompt Template)是LangChain的核心抽象之一,它被广泛应用于构建大语言模型(LLM)应用的各个环节。简单来说,只要是需要动态、批量、或有结构地向大语言模型发送请求的地方,几乎都会用到提示词模板。
举个简单的例子,假设想根据一个城市名询问LLM其历史,按照之前武汉的做法,可以每次询问都编写重复的消息内容:"请介绍xx的历史。"。现在我们可以在LangChain中,针对这种情况,可以定义一个模板:
- 固定文本(模板):"请介绍{city}的历史。"
- 输入变量: "city"
定义好后,可以使用该模板:
当我们需要查询北京时,就将city变量赋值为"北京"。模板引擎会生成:"请介绍北京的历史。"
当我们需要查询上海时,就将city变量赋值为"上海"。模板引擎会生成:"请介绍上海的历史。"
由此可得:提示词模板就是一个可复用的提示词蓝图,允许动态地生成提示词,而不是每次都手动编写完整的提示词。它类似于编程中的字符串格式化功能。你创建一个带有"占位符"的模板,然后在运行时,用具体的值(变量)填充这些占位符,从而生成一个最终发送给LLM的完整提示词。
提示词模板主要解决了以下几个核心问题:
- 可复用性:只需定义一个模板,就可以用于无数个类似的查询。
- 关注点分离:将提示词的结构和逻辑(工程)与具体的内容和数据分离开。提示工程师可以专注于优化模板,而应用程序则负责提供变量值。
- 一致性:确保发送给LLM的提示词结构统一,这有助于获得更稳定、可预测的输出结果。
- 可维护性:如果需要修改提示词的风格或结构,只需修改一个模板文件,而不用在代码的无数个地方进行修改。
2.2 用法
2.2.1 字符串模板
LangChain提供了 PromptTemplate 类来实现这一功能。PromptTemplate 实现了标准的Runnable接口。示例如下:
python
from langchain_core.prompts import PromptTemplate
# 1. 定义模板
prompt_template = PromptTemplate.from_template("Translate the following into {language}")
# 2. 实例化模板
print(prompt_template.invoke({"language": "Chinese"}))
打印结果:
text='Translate the following into Chinese'
说明:
class langchain_core.prompts.prompt.PromptTemplate 类,其参数如下:
template:提示模板input_variables:需要其值作为提示输入的变量的名称列表。
内置方法:
from_template():从模板定义提示模板。方法返回了一个PromptTemplate实例
因此除了上面示例中 PromptTemplate.from_template 定义提示模板的方式外,下面这种方法也可以直接初始化模板:
python
prompt_template = PromptTemplate(
input_variables=["language"],
template="Translate the following into {language}",
)
2.2.2 聊天消息模板
ChatPromptTemplate 模板:专为LangChain聊天模型设计。可以方便地构建包含 SystemMessage、HumanMessage、AIMessage 的消息模板。如下代码所示:
python
from langchain_core.prompts import ChatPromptTemplate
# 1. 设置模板
prompt_template = ChatPromptTemplate(
[
("system", "Translate the following into {language}."),
("user", "{text}")
]
)
# 说明:
# 在 0.2.24 版本后可以直接使用ChatPromptTemplate()来初始化模板
# 在 0.2.24 版本前,需要使用 ChatPromptTemplate.from_messages() 来初始化模板
# 2. 实例化模板,获取消息实例
messagesValue = prompt_template.invoke(
{
"language": "Chinese",
"text": "what is your name?"
}
)
messages = messagesValue.to_messages()
print(messages)
打印结果:
[
SystemMessage(content='Translate the following into Chinese.', additional_kwargs={}, response_metadata={}),
HumanMessage(content='what is your name?', additional_kwargs={}, response_metadata={})
]
现在可以将该结果发送给任何一个LLM来获取答案。如下所示:
由于 ChatPromptTemplate 同样实现了标准的Runnable接口,可以通过链来完成调用。如下所示:
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 定义大模型
model = ChatOpenAI(model="gpt-4o-mini")
# 1. 设置模板
prompt_template = ChatPromptTemplate(
[
("system", "Translate the following into {language}."),
("user", "{text}")
]
)
# 2. 定义输出解析器
parser = StrOutputParser()
# 3. 定义链
chain = prompt_template | model | parser
for token in chain.stream(
{
"language": "English",
"text": "你好,我叫斯蒂芬,很高兴认识你"
}
):
print(token, end="|")
打印结果:
|Hello|,| my| name| is| Stephen|,| nice| to| meet| you|!|
2.2.3 消息占位符
将消息插入特定位置?使用 MessagesPlaceholder。代码如下:
python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage
prompt_template = ChatPromptTemplate([
("system", "你是一个聊天助手"),
MessagesPlaceholder("msgs") # 消息占位符
])
messages_to_pass = [
HumanMessage(content="中国首都是哪里?"),
AIMessage(content="中国首都是北京。"),
HumanMessage(content="那法国呢?"),
]
formatted_prompt = prompt_template.invoke({"msgs": messages_to_pass})
print(formatted_prompt)
打印结果:
[
SystemMessage(content='你是一个聊天助手', additional_kwargs={}, response_metadata={}),
HumanMessage(content='中国首都是哪里?', additional_kwargs={}, response_metadata={}),
AIMessage(content='中国首都是北京。', additional_kwargs={}, response_metadata={}),
HumanMessage(content='那法国呢?', additional_kwargs={}, response_metadata={})
]
在不显式使用 MessagesPlaceholder 类也可以完成该能力:
python
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import HumanMessage, AIMessage
prompt_template = ChatPromptTemplate(
[
("system", "You are a helpful assistant"),
("placeholder", "msgs"),
]
)
messages_to_pass = [
HumanMessage(content="中国首都是哪里?"),
AIMessage(content="中国首都是北京。"),
HumanMessage(content="那法国呢?"),
]
formatted_prompt = prompt_template.invoke({"msgs": messages_to_pass})
print(formatted_prompt)
2.3 使用LangChain Hub的提示词模板
LangChain Hub 是一个用于上传、浏览、拉取和管理提示词(prompts)的地方。随着 LLM 的发展,提示变得越来越重要。LangChain 正在打造一个与像 GitHub 这样的传统平台,GitHub长期以来一直是共享和协作代码的首选平台。于是推出了 LangChain Hub 平台。
LangChain Hub 创建一个分享和发现 Prompt 的平台,使得开发者可以更容易地发现新用例和精炼提示。这一举措使提示工程师更容易合作,重复使用现有的提示,并对其进行微调以实现特定的结果,从而加速对话代理和其他基于语言的应用程序的开发和部署。早期的时候 LangChain Hub 有 Prompt、Chain、Agent,现在只有Prompt。传送门
hardkothari/prompt-maker为例,演示一下如何使用 LangChain Hub 上的提示。

Prompt Maker 模板是一个【提示生成器】,它可以自动化优化提示的过程,从而提高语言模型在各种应用中的质量和效果。
要想使用该能力,需要先申请并配置 LangSmith 环境变量:LANGSMITH_API_KEY="你的LangSmith API Key"。接着,需要从 hub 拉取相应的提示,并使用,示例代码如下:
python
# Create a LangSmith API in Settings > API Keys
# Make sure API key env var is set:
# import os; os.environ["LANGSMITH_API_KEY"] = "<your-api-key>"
from langsmith import Client
client = Client()
prompt = client.pull_prompt("hardkothari/prompt-maker")
python
# 导入依赖
from langchain_openai import ChatOpenAI
from langsmith import Client
# 从 hub 拉取 "hardkothari/prompt-maker" 提示词模板。
client = Client()
prompt = client.pull_prompt("hardkothari/prompt-maker", include_model=True)
# 定义模型
model = ChatOpenAI(model="gpt-4o-mini")
# 定义链
chain = prompt | model
while True:
task = input("\n你的任务是什么?(输入 quit 退出聊天) \n")
if task == 'quit':
break
lazy_prompt = input("\n你当前的提示是什么?(输入 quit 退出聊天) \n")
if lazy_prompt == 'quit':
break
print("\n Response:")
chain.invoke({'lazy_prompt': lazy_prompt, 'task': task}).pretty_print()
输入交互流程与输出:
python
你的任务是什么?(输入 quit 退出聊天)
写一个快排代码
你当前的提示是什么?(输入 quit 退出聊天)
开发专家,需中文回复
Response:
============================ Ai Message
============================
As a coding expert specialized in algorithms and data structures, please write a detailed implementation of the Quick Sort algorithm in Python.
### Instructions:
Your code should be well-structured, with clear comments explaining each step of the algorithm. Additionally, include an example of how to use the function to sort a list of integers and print the sorted result.
### Context:
The implementation should focus on efficiency and clarity, ideally spanning no more than 20 lines of code. Ensure that the prompt is in Chinese, and structure your response to be easily understandable for readers with a basic knowledge of programming.
Example:
```python
def quick_sort(arr):
# 快速排序函数实现
...
return sorted_arr
# 使用示例
unsorted_list = [34, 7, 23, 32, 5, 62]
print(quick_sort(unsorted_list))
通过使用这个模板,可以大大减少手动调整提示所需的工作量,从而节省时间和资源。