1 历史记录的剪裁 trimmed_messages
from langchain_core.messages import (
AIMessage,
HumanMessage,
SystemMessage,
trim_messages,
)
from langchain_openai import ChatOpenAI
messages = [
SystemMessage("you're a good assistant, you always respond with a joke."),
HumanMessage("i wonder why it's called langchain"),
AIMessage(
'Well, I guess they thought "WordRope" and "SentenceString" just didn\'t have the same ring to it!'
),
HumanMessage("and who is harrison chasing anyways"),
AIMessage(
"Hmmm let me think.\n\nWhy, he's probably chasing after the last cup of coffee in the office!"
),
HumanMessage("what do you call a speechless parrot"),
]
trim_messages(
messages,
max_tokens=45,
strategy="last",
token_counter=ChatOpenAI(model="gpt-4o-mini"),
)
输出:
[AIMessage(content="Hmmm let me think.\n\nWhy, he's probably chasing after the last cup of coffee in the office!", additional_kwargs={}, response_metadata={}),
HumanMessage(content='what do you call a speechless parrot', additional_kwargs={}, response_metadata={})]
这段代码演示了如何使用LangChain的消息管理功能控制对话长度,确保不超过大语言模型的Token限制。以下是逐部分解析:
1. 消息类型导入
from langchain_core.messages import (
AIMessage, # AI生成的消息
HumanMessage, # 用户输入的消息
SystemMessage, # 系统角色设定消息
trim_messages # 消息修剪函数
)
- SystemMessage: 设定对话背景(如角色设定)
- HumanMessage: 用户输入内容
- AIMessage: AI的历史回复
trim_messages
:核心工具函数,用于智能裁剪对话历史
2. 大语言模型初始化
from langchain_openai import ChatOpenAI
# 实际使用时需替换为有效模型名如"gpt-4"
token_counter = ChatOpenAI(model="gpt-4o-mini")
- 此处创建了一个用于计算Token的模型实例
- ⚠️注意:
gpt-4o-mini
可能是笔误,OpenAI官方模型应为gpt-4-0125-preview
等
3. 对话历史构建
messages = [
SystemMessage("you're a good assistant..."), # 系统角色设定
HumanMessage("i wonder why..."), # 用户第一次提问
AIMessage('Well, I guess...'), # AI第一次回复
HumanMessage("and who is..."), # 用户第二次提问
AIMessage("Hmmm let me think..."), # AI第二次回复
HumanMessage("what do you call...") # 待回答的新问题
]
对话结构可视化:
System: 你是一个爱讲笑话的助手
User: 为什么叫LangChain?
AI: 因为"WordRope"不好听...
User: Harrison在追什么?
AI: 可能在追最后一杯咖啡!
User: 怎么称呼不会说话的鹦鹉?(待回答)
4. 消息修剪执行
trimmed_messages = trim_messages(
messages,
max_tokens=45, # 最大Token限制
strategy="last", # 修剪策略
token_counter=token_counter # Token计数器
)
关键参数解析
参数 | 作用 | 典型值 |
---|---|---|
max_tokens | 保留内容的最大Token数 | 根据模型上限调整 |
strategy | 修剪策略: - last :优先保留最新消息- first :优先保留旧消息 | “last”/“first” |
token_counter | 指定Token计算方式(需匹配模型) | 模型实例 |
执行过程
- 从最新消息开始逆向计算Token总数
- 当累计Token超过45时,丢弃最旧的消息
- 保留能放入限额内的最长连续消息序列
假设每条消息Token消耗:
SystemMessage: 15 tokens
HumanMessage1: 10
AIMessage1: 12
HumanMessage2: 8
AIMessage2: 15
HumanMessage3: 10
总Tokens = 15+10+12+8+15+10 = 70 → 超过45
修剪过程(保留最后N条):
- 保留最后5条 → 10+15+8+15+10 = 58 ❌
- 保留最后4条 → 8+15+10+10 = 43 ✅
最终保留内容:
[
HumanMessage("and who is..."),
AIMessage("Hmmm let me think..."),
HumanMessage("what do you call...")
]
5. 典型应用场景
# 生成最终回复时使用修剪后的消息
llm = ChatOpenAI(model="gpt-4")
response = llm.invoke(trimmed_messages)
- 保证请求不超过模型的Token上限(如GPT-4的8,192 Tokens)
- 维持尽可能长的对话记忆
6. 开发注意事项
-
策略选择:
last
:适合短期记忆重要的场景(如追问细节)first
:适合需要保留初始设定的场景(如角色扮演)
-
Token计算:
- 不同模型的Token计算方式不同(如GPT-3与Claude)
- 中文通常消耗更多Token(一个汉字≈1.5 Token)
-
最佳实践:
# 动态计算max_tokens(留出回答空间) max_tokens = model_max_limit - response_token_buffer
-
常见问题:
- 过度修剪导致丢失关键上下文
- 未考虑特殊格式Token(如Markdown语法)
- 混合多模型时Token计算方式不统一
通过这种消息管理机制,开发者可以在大模型有限的上下文窗口内,智能平衡历史记忆与当前问题的关联性。实际应用中常结合向量数据库实现长期记忆存储。
# 保留 system prompt
# 作用 默认值 典型场景
# 强制保留系统消息(即使超出Token限制) False 需要始终保持角色设定/系统提示
trim_messages(
messages,
max_tokens=45,
strategy="last",
token_counter=ChatOpenAI(model="gpt-4o-mini"),
include_system=True,
allow_partial=True,
)
输出:
[SystemMessage(content="you're a good assistant, you always respond with a joke.", additional_kwargs={}, response_metadata={}),
HumanMessage(content='what do you call a speechless parrot', additional_kwargs={}, response_metadata={})]
2 过滤带标签的历史记录 filter_messages
from langchain_core.messages import (
AIMessage,
HumanMessage,
SystemMessage,
filter_messages,
)
messages = [
SystemMessage("you are a good assistant", id="1"),
HumanMessage("example input", id="2", name="example_user"),
AIMessage("example output", id="3", name="example_assistant"),
HumanMessage("real input", id="4", name="bob"),
AIMessage("real output", id="5", name="alice"),
]
filter_messages(messages, include_types="human")
输出:
[HumanMessage(content='example input', additional_kwargs={}, response_metadata={}, name='example_user', id='2'),
HumanMessage(content='real input', additional_kwargs={}, response_metadata={}, name='bob', id='4')]
filter_messages(messages, exclude_names=["example_user", "example_assistant"])
输出:
[SystemMessage(content='you are a good assistant', additional_kwargs={}, response_metadata={}, id='1'),
HumanMessage(content='real input', additional_kwargs={}, response_metadata={}, name='bob', id='4'),
AIMessage(content='real output', additional_kwargs={}, response_metadata={}, name='alice', id='5')]
filter_messages(messages, include_types=[HumanMessage, AIMessage], exclude_ids=["3"])
[HumanMessage(content='example input', additional_kwargs={}, response_metadata={}, name='example_user', id='2'),
HumanMessage(content='real input', additional_kwargs={}, response_metadata={}, name='bob', id='4'),
AIMessage(content='real output', additional_kwargs={}, response_metadata={}, name='alice', id='5')]
这段代码展示了如何使用 filter_messages
函数从对话历史中筛选特定类型的消息。以下是对代码的详细讲解及该函数的应用场景分析:
代码讲解
-
导入模块和函数:
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, filter_messages
- 导入了三种消息类型:
SystemMessage
(系统消息)、HumanMessage
(用户消息)、AIMessage
(AI回复)。 filter_messages
是用于过滤消息列表的函数。
- 导入了三种消息类型:
-
定义消息列表:
messages = [ SystemMessage("you are a good assistant", id="1"), HumanMessage("example input", id="2", name="example_user"), AIMessage("example output", id="3", name="example_assistant"), HumanMessage("real input", id="4", name="bob"), AIMessage("real output", id="5", name="alice"), ]
- 消息列表包含 5 条对话历史记录,每条消息都有
id
和name
属性用于标识来源。
- 消息列表包含 5 条对话历史记录,每条消息都有
-
调用过滤函数:
filtered = filter_messages(messages, include_types="human")
filter_messages
会根据include_types
参数过滤消息。此处include_types="human"
表示只保留HumanMessage
类型的消息。- 输出结果:
filtered
将包含两条用户消息(id=2 和 id=4)。
filter_messages
的工作原理
-
按类型过滤:
- 根据消息的类型(
SystemMessage
、HumanMessage
、AIMessage
)进行筛选。 include_types
接受字符串或列表(如["human", "system"]
),表示需要保留的类型。- 类型名称通常对应类名的小写形式(如
HumanMessage → "human"
)。
- 根据消息的类型(
-
支持的参数:
include_types
: 包含的消息类型。exclude_types
: 排除的消息类型。include_names
/exclude_names
: 根据name
属性进一步过滤。
思考:你能想出这个功能的一个使用场景吗
应用场景
-
提取用户输入:
- 当需要单独分析用户的提问或指令时(如统计高频问题),过滤出所有
HumanMessage
。
- 当需要单独分析用户的提问或指令时(如统计高频问题),过滤出所有
-
构建对话上下文:
- 在聊天机器人中,可能只需将用户的历史输入(
HumanMessage
)和AI的回复(AIMessage
)作为上下文,忽略系统消息。
- 在聊天机器人中,可能只需将用户的历史输入(
-
数据清洗:
- 在训练模型时,可能需要移除系统提示或特定角色的消息,保留有效对话数据。
-
权限控制:
- 若某些消息类型仅供内部使用(如
SystemMessage
),可通过过滤避免泄露给终端用户。
- 若某些消息类型仅供内部使用(如
-
路由处理:
- 根据消息类型分发到不同处理模块(如用户消息转分析服务,AI消息转日志服务)。
示例扩展
若想同时保留用户和AI的消息:
filter_messages(messages, include_types=["human", "ai"])
若想排除名为 bob
的用户消息:
filter_messages(messages, include_types="human", exclude_names="bob")
通过 filter_messages
,开发者可以灵活处理对话数据,适应不同业务场景的需求。