前面已经学习了本地模型、OpenAI 兼容接口、消息、提示词模板和输出解析器,但每个示例基本都只完成一次调用。
真正的聊天机器人还要解决一个问题:用户说完第一句话后,模型能不能在第二轮继续理解前面的内容?
例如:
用户:我叫小明。
助手:你好,小明。
用户:我叫什么名字?
助手:小明。
第二个问题中没有再次出现“小明”。模型要回答正确,就必须同时收到第一轮对话。本文将前面学过的组件组合起来,完成一个支持内存历史、SQLite 持久化和历史摘要的聊天机器人。
1. 聊天机器人的整体结构
无论历史保存在内存还是 SQLite,RunnableWithMessageHistory 的职责都一样:根据 session_id 读取历史、调用聊天链,再把本轮消息写回同一个会话。

这套聊天机器人的数据流可以分成五步:
- 用户提交当前问题。
- 程序根据 session_id 找到当前会话。
- LangChain 读取该会话的历史消息。
- 提示词模板把历史消息和当前问题一起交给 Qwen3。
- 新的用户消息和模型回答继续写入历史记录。
这里所说的“记忆”不是模型永久学会了新知识,而是应用程序在每次调用时重新把相关历史发送给模型。
2. 准备运行环境
本章代码位于:
source/_posts/llm_learning/langchain/p07_chatbot_with_memory/
进入项目并激活 Python 3.12 虚拟环境:
cd ~/Documents/git/blog/source/_posts/llm_learning
source .venv/bin/activate
python -m pip install -r requirements.txt
启动本地 Qwen3 服务:
"$VIRTUAL_ENV/bin/python" -m mlx_lm server \
--model model \
--host 127.0.0.1 \
--port 18080 \
--chat-template-args '{"enable_thinking": false}'
另开终端确认服务返回模型列表:
curl --noproxy '*' http://127.0.0.1:18080/v1/models
本文使用的核心版本为:
langchain==1.0.3
langchain-core==1.0.2
langchain-openai==1.0.1
langchain-community==0.4.1
SQLAlchemy==2.0.45
3. 从没有记忆的单轮聊天开始
先看最普通的聊天链:
# 这个文件演示最基础的单轮聊天。
# 两次调用彼此独立,因此第二个问题不会自动得到第一次调用的内容。
import httpx
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 连接项目根目录已经启动的 Qwen3 OpenAI 兼容服务。
llm = ChatOpenAI(
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed",
model="default_model",
temperature=0,
max_tokens=96,
http_client=httpx.Client(trust_env=False),
)
# 每次调用只会收到当前填入 question 的问题。
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个中文聊天助手,请简短回答。"),
("human", "{question}"),
]
)
chain = prompt | llm
first = chain.invoke({"question": "我最喜欢的水果是苹果,请记住。"})
print("第一次回答:", first.content)
# 这里没有把第一次对话传回模型,所以模型不知道“我”喜欢什么。
second = chain.invoke({"question": "我最喜欢的水果是什么?"})
print("第二次回答:", second.content)
运行:
python langchain/p07_chatbot_with_memory/01_single_turn_chatbot.py
真实输出:
第一次回答: 好的,已记住你最喜欢的水果是苹果。
第二次回答: 你最喜欢的是哪种水果呢?可以告诉我,我很想了解哦!
第一次回答中的“已记住”只是自然语言。第二次调用只有当前问题,没有第一轮消息,所以模型实际并没有得到“苹果”这个信息。
4. 使用内存保存多轮对话
4.1 给提示词增加历史消息位置
聊天提示词中需要增加一个 MessagesPlaceholder:
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个中文聊天助手,请根据历史消息简短回答。"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
)
最终发送给模型的顺序是:
SystemMessage
历史 HumanMessage
历史 AIMessage
当前 HumanMessage
4.2 使用 RunnableWithMessageHistory
完整代码如下:
# 这个文件演示如何用 RunnableWithMessageHistory 保存内存中的多轮对话。
# 消息只存在当前 Python 进程中,脚本结束后就会消失。
import httpx
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed",
model="default_model",
temperature=0,
max_tokens=96,
http_client=httpx.Client(trust_env=False),
)
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个中文聊天助手,请根据历史消息简短回答。"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
)
chain = prompt | llm
store: dict[str, InMemoryChatMessageHistory] = {}
def get_session_history(session_id: str) -> InMemoryChatMessageHistory:
"""取得指定会话的消息历史,没有时就创建一份。"""
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
chatbot = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
xiaoming_config = {"configurable": {"session_id": "xiaoming"}}
other_config = {"configurable": {"session_id": "other-user"}}
answer1 = chatbot.invoke({"input": "我叫小明。"}, config=xiaoming_config)
print("小明第 1 轮:", answer1.content)
answer2 = chatbot.invoke({"input": "我叫什么名字?只回答名字。"}, config=xiaoming_config)
print("小明第 2 轮:", answer2.content)
answer3 = chatbot.invoke({"input": "我叫什么名字?"}, config=other_config)
print("另一个会话:", answer3.content)
真实输出:
小明第 1 轮: 你好,小明!很高兴认识你。有什么我可以帮你的吗?
小明第 2 轮: 小明
另一个会话: 你还没有告诉我你的名字呢。
这里有三个重要参数:
- input_messages_key=”input”:当前用户输入使用哪个字段。
- history_messages_key=”history”:历史消息应该放入提示词的哪个占位符。
- session_id:本次调用属于哪个会话。
相同 session_id 可以读取相同历史,不同 session_id 之间互相隔离。
5. 使用 SQLite 持久化聊天历史
内存历史有一个明显问题:Python 进程停止后,字典中的消息全部消失。要让程序重新启动后仍能找到历史,可以使用 SQLChatMessageHistory。
两种历史实现都通过 session_id 隔离会话,主要差别在数据生命周期:

session_id 只是历史记录的分组键,不是登录认证。实际应用仍然要由自己的用户系统生成并保护会话标识,不能直接信任浏览器随意提交的值。
5.1 创建 SQLite 历史
from pathlib import Path
from langchain_community.chat_message_histories import SQLChatMessageHistory
project_root = Path(__file__).resolve().parents[2]
database_path = project_root / "data" / "chat_history.db"
database_path.parent.mkdir(parents=True, exist_ok=True)
connection = f"sqlite:///{database_path}"
def get_session_history(session_id: str) -> SQLChatMessageHistory:
return SQLChatMessageHistory(session_id=session_id, connection=connection)
本文使用的 langchain-community==0.4.1 推荐传入 connection=。一些旧资料使用 connection_string=,运行时会出现弃用提示。
5.2 验证重新创建聊天链后仍能回忆
完整示例位于 03_sqlite_chatbot.py。核心调用如下:
def create_chatbot() -> RunnableWithMessageHistory:
return RunnableWithMessageHistory(
prompt | llm,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
session_id = "sqlite-demo"
config = {"configurable": {"session_id": session_id}}
get_session_history(session_id).clear()
chatbot = create_chatbot()
first = chatbot.invoke({"input": "我家养了一只叫豆豆的猫。"}, config=config)
print("第 1 轮:", first.content)
# 重新创建对象,历史仍然从 SQLite 中读取。
chatbot = create_chatbot()
second = chatbot.invoke({"input": "我的猫叫什么名字?只回答名字。"}, config=config)
print("重新创建聊天链后的回答:", second.content)
history = get_session_history(session_id)
print("数据库消息数量:", len(history.messages))
真实输出:
第 1 轮: 豆豆真可爱,它有什么特别的地方吗?
重新创建聊天链后的回答: 豆豆
数据库消息数量: 4
两轮对话会保存两条 HumanMessage 和两条 AIMessage,所以数据库中共有 4 条消息。
6. 历史越来越长怎么办
多轮历史不能无限加入提示词。消息越多,占用的上下文越大,推理时间和内存消耗也会增加。
常见处理方式包括:
- 只保留最近几轮消息。
- 对较早的消息生成摘要。
- 完整消息仍保存在数据库中,摘要只用于当前模型输入。
本文把后两种方式组合起来:旧消息生成摘要,最近消息保留原始对象,而 SQLite 继续保存全部消息。这样既能控制本次模型上下文,也不会因为生成摘要而丢失原始记录。
6.1 生成旧消息摘要
summary_prompt = ChatPromptTemplate.from_messages(
[
("system", "请把下面的聊天历史压缩成一段简短事实摘要,不要遗漏姓名、喜好和地点。"),
("human", "{history_text}"),
]
)
summary_chain = summary_prompt | llm | StrOutputParser()
这里使用上一章学习的 StrOutputParser,因为摘要链只需要普通字符串,而不是带完整元数据的 AIMessage。
6.2 只压缩本次模型输入
recent_message_count = 4
def prepare_context(values: dict) -> dict:
history = values.get("history", [])
if len(history) <= recent_message_count:
return {"input": values["input"], "summary": "暂无。", "history": history}
old_messages = history[:-recent_message_count]
recent_messages = history[-recent_message_count:]
summary = summary_chain.invoke({"history_text": messages_to_text(old_messages)})
print("生成的历史摘要:", summary)
return {"input": values["input"], "summary": summary, "history": recent_messages}
完整链为:
answer_chain = RunnableLambda(prepare_context) | answer_prompt | llm
chatbot = RunnableWithMessageHistory(
answer_chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
执行顺序是:
- RunnableWithMessageHistory 从 SQLite 读取完整历史。
- prepare_context 把旧消息转换成摘要,只保留最近 4 条原始消息。
- answer_prompt 组合摘要、最近消息和当前问题。
- Qwen3 生成回答。
- RunnableWithMessageHistory 把本轮问题和回答继续写入 SQLite。
需要特别区分“模型本次看到的上下文”和“数据库实际保存的历史”:

摘要链只处理较早的消息,回答链看到的是“摘要 + 最近消息 + 当前问题”。调用结束后,本轮完整的 HumanMessage 和 AIMessage 仍然追加到 SQLite。
6.3 真实运行结果
运行:
python langchain/p07_chatbot_with_memory/04_chatbot_with_summary.py
关键输出:
第 1 轮用户:我叫小明。
第 2 轮用户:我最喜欢的水果是苹果。
第 3 轮用户:我现在住在杭州。
生成的历史摘要: 用户小明与助手进行了初次交流,助手向小明问好并表示愿意提供帮助。
第 4 轮用户:请说出我的名字、喜欢的水果和居住城市。
第 4 轮助手:你的名字是小明,喜欢的水果是苹果,现在居住在杭州。
SQLite 中保留的完整消息数量: 8
摘要中保留了姓名,最近消息中保留了水果和城市,因此模型能回答完整。更重要的是,数据库仍然有 8 条原始消息。
7. 查看数据库中的完整历史
运行:
python langchain/p07_chatbot_with_memory/05_inspect_chat_history.py
输出中的消息顺序为:
1. HumanMessage:我叫小明。
2. AIMessage:你好,小明!很高兴认识你。
3. HumanMessage:我最喜欢的水果是苹果。
4. AIMessage:苹果确实是个很棒的选择!
5. HumanMessage:我现在住在杭州。
6. AIMessage:杭州是个非常美丽的地方。
7. HumanMessage:请说出我的名字、喜欢的水果和居住城市。
8. AIMessage:你的名字是小明,喜欢的水果是苹果,现在居住在杭州。
这说明摘要没有替换或删除数据库中的消息。
8. 本章完整代码
下面的代码与 llm_learning 项目中的脚本保持一致。
01_single_turn_chatbot.py
# 这个文件演示最基础的单轮聊天。
# 两次调用彼此独立,因此第二个问题不会自动得到第一次调用的内容。
import httpx
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 连接项目根目录已经启动的 Qwen3 OpenAI 兼容服务。
llm = ChatOpenAI(
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed",
model="default_model",
temperature=0,
max_tokens=96,
http_client=httpx.Client(trust_env=False),
)
# 每次调用只会收到当前填入 question 的问题。
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个中文聊天助手,请简短回答。"),
("human", "{question}"),
]
)
chain = prompt | llm
first = chain.invoke({"question": "我最喜欢的水果是苹果,请记住。"})
print("第一次回答:", first.content)
# 这里没有把第一次对话传回模型,所以模型不知道“我”喜欢什么。
second = chain.invoke({"question": "我最喜欢的水果是什么?"})
print("第二次回答:", second.content)
02_in_memory_chatbot.py
# 这个文件演示如何用 RunnableWithMessageHistory 保存内存中的多轮对话。
# 消息只存在当前 Python 进程中,脚本结束后就会消失。
import httpx
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed",
model="default_model",
temperature=0,
max_tokens=96,
http_client=httpx.Client(trust_env=False),
)
# history 会在运行时被替换成当前会话的历史消息列表。
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个中文聊天助手,请根据历史消息简短回答。"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
)
chain = prompt | llm
# 字典中的每个 session_id 对应一份独立的内存历史。
store: dict[str, InMemoryChatMessageHistory] = {}
def get_session_history(session_id: str) -> InMemoryChatMessageHistory:
"""取得指定会话的消息历史,没有时就创建一份。"""
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
chatbot = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
xiaoming_config = {"configurable": {"session_id": "xiaoming"}}
other_config = {"configurable": {"session_id": "other-user"}}
answer1 = chatbot.invoke({"input": "我叫小明。"}, config=xiaoming_config)
print("小明第 1 轮:", answer1.content)
answer2 = chatbot.invoke({"input": "我叫什么名字?只回答名字。"}, config=xiaoming_config)
print("小明第 2 轮:", answer2.content)
# 换一个 session_id 后,不会读取小明的历史消息。
answer3 = chatbot.invoke({"input": "我叫什么名字?"}, config=other_config)
print("另一个会话:", answer3.content)
03_sqlite_chatbot.py
# 这个文件演示如何把聊天历史保存到 SQLite。
# 即使重新创建聊天链,只要 session_id 相同,就能继续读取原来的消息。
from pathlib import Path
import httpx
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
# 数据库统一放在 llm_learning/data 目录。
project_root = Path(__file__).resolve().parents[2]
database_path = project_root / "data" / "chat_history.db"
database_path.parent.mkdir(parents=True, exist_ok=True)
connection = f"sqlite:///{database_path}"
llm = ChatOpenAI(
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed",
model="default_model",
temperature=0,
max_tokens=96,
http_client=httpx.Client(trust_env=False),
)
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个中文聊天助手,请根据历史消息简短回答。"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
)
def get_session_history(session_id: str) -> SQLChatMessageHistory:
"""从 SQLite 中取得指定 session_id 的历史消息。"""
return SQLChatMessageHistory(session_id=session_id, connection=connection)
def create_chatbot() -> RunnableWithMessageHistory:
"""创建一个会自动读取和写入 SQLite 历史的聊天链。"""
return RunnableWithMessageHistory(
prompt | llm,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
session_id = "sqlite-demo"
config = {"configurable": {"session_id": session_id}}
# 清理同名演示会话,让脚本每次运行都得到清晰结果。
get_session_history(session_id).clear()
chatbot = create_chatbot()
first = chatbot.invoke({"input": "我家养了一只叫豆豆的猫。"}, config=config)
print("第 1 轮:", first.content)
# 模拟程序重新创建聊天链,数据库中的历史仍然存在。
chatbot = create_chatbot()
second = chatbot.invoke({"input": "我的猫叫什么名字?只回答名字。"}, config=config)
print("重新创建聊天链后的回答:", second.content)
history = get_session_history(session_id)
print("数据库文件:", database_path)
print("数据库消息数量:", len(history.messages))
04_chatbot_with_summary.py
# 这个文件演示如何在历史较长时生成摘要。
# SQLite 始终保留完整历史,只有发送给模型的上下文会被压缩。
from pathlib import Path
import httpx
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_core.messages import BaseMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableLambda
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
project_root = Path(__file__).resolve().parents[2]
database_path = project_root / "data" / "chat_history.db"
database_path.parent.mkdir(parents=True, exist_ok=True)
connection = f"sqlite:///{database_path}"
llm = ChatOpenAI(
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed",
model="default_model",
temperature=0,
max_tokens=128,
http_client=httpx.Client(trust_env=False),
)
# 旧消息超过限制后,只保留最近 4 条原始消息。
recent_message_count = 4
summary_prompt = ChatPromptTemplate.from_messages(
[
("system", "请把下面的聊天历史压缩成一段简短事实摘要,不要遗漏姓名、喜好和地点。"),
("human", "{history_text}"),
]
)
summary_chain = summary_prompt | llm | StrOutputParser()
answer_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是中文聊天助手。历史摘要:{summary}"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
)
def messages_to_text(messages: list[BaseMessage]) -> str:
"""把消息对象转换成便于摘要模型阅读的文本。"""
lines = []
for message in messages:
role = "用户" if message.type == "human" else "助手"
lines.append(f"{role}:{message.content}")
return "\n".join(lines)
def prepare_context(values: dict) -> dict:
"""生成旧消息摘要,并保留最近几条原始消息。"""
history = values.get("history", [])
if len(history) <= recent_message_count:
return {"input": values["input"], "summary": "暂无。", "history": history}
old_messages = history[:-recent_message_count]
recent_messages = history[-recent_message_count:]
summary = summary_chain.invoke({"history_text": messages_to_text(old_messages)})
print("生成的历史摘要:", summary)
return {"input": values["input"], "summary": summary, "history": recent_messages}
def get_session_history(session_id: str) -> SQLChatMessageHistory:
"""从 SQLite 中取得完整历史。"""
return SQLChatMessageHistory(session_id=session_id, connection=connection)
# RunnableWithMessageHistory 先注入完整历史,再由 prepare_context 压缩模型输入。
answer_chain = RunnableLambda(prepare_context) | answer_prompt | llm
chatbot = RunnableWithMessageHistory(
answer_chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
session_id = "summary-demo"
config = {"configurable": {"session_id": session_id}}
get_session_history(session_id).clear()
questions = [
"我叫小明。",
"我最喜欢的水果是苹果。",
"我现在住在杭州。",
"请说出我的名字、喜欢的水果和居住城市。",
]
for number, question in enumerate(questions, start=1):
answer = chatbot.invoke({"input": question}, config=config)
print(f"第 {number} 轮用户:{question}")
print(f"第 {number} 轮助手:{answer.content}")
full_history = get_session_history(session_id)
print("SQLite 中保留的完整消息数量:", len(full_history.messages))
05_inspect_chat_history.py
# 这个文件用于查看 SQLite 中保存的完整聊天历史。
# 默认查看 04_chatbot_with_summary.py 创建的 summary-demo 会话。
import argparse
from pathlib import Path
from langchain_community.chat_message_histories import SQLChatMessageHistory
parser = argparse.ArgumentParser(description="查看指定 session_id 的聊天历史")
parser.add_argument("session_id", nargs="?", default="summary-demo")
args = parser.parse_args()
project_root = Path(__file__).resolve().parents[2]
database_path = project_root / "data" / "chat_history.db"
connection = f"sqlite:///{database_path}"
history = SQLChatMessageHistory(session_id=args.session_id, connection=connection)
print("会话 ID:", args.session_id)
print("消息数量:", len(history.messages))
for number, message in enumerate(history.messages, start=1):
print(f"{number}. {type(message).__name__}:{message.content}")
9. 常见问题
9.1 模型仍然记不住上一轮
依次检查:
- 提示词中是否包含 MessagesPlaceholder(“history”)。
- history_messages_key 是否也是 history。
- 两次调用是否使用相同 session_id。
- 是否通过 RunnableWithMessageHistory 调用,而不是直接调用原始 chain。
9.2 不同用户看到了相同历史
通常是所有请求都使用了同一个固定 session_id。实际页面应为每个用户或浏览器会话生成独立值,下一篇 Gradio 文章会处理这个问题。
9.3 重启后历史消失
InMemoryChatMessageHistory 只存在内存中。需要跨进程保存时,应改用 SQLChatMessageHistory 或其他持久化历史实现。
9.4 摘要后数据库消息减少
不要为了写入摘要而调用 clear() 清空正式会话,也不要用一条摘要消息覆盖原始消息。正确做法是完整历史继续留在数据库中,只在构造本次模型输入时使用摘要。
9.5 本地接口请求出现 502
/v1/models 可以访问,只能证明 HTTP 服务已经启动并能返回模型列表。如果聊天请求返回 502,应先检查 MLX-LM Server 终端中的模型加载或推理错误,再使用最小 /v1/chat/completions 请求确认聊天接口是否正常。
只有直接请求聊天接口成功、Python 调用仍然失败时,才继续检查客户端是否读取了系统代理。本章使用:
http_client=httpx.Client(trust_env=False)
它可以防止本地请求被系统代理或 VPN 转发,但不能修复模型服务端的加载或推理异常。
10. 小结
这一章把前面学习的内容第一次组合成了完整功能:
- ChatOpenAI 连接本地 Qwen3。
- ChatPromptTemplate 组织系统消息、历史消息和当前问题。
- MessagesPlaceholder 为历史消息预留位置。
- RunnableWithMessageHistory 自动读取和写入历史。
- session_id 隔离不同会话。
- SQLChatMessageHistory 将消息持久化到 SQLite。
- StrOutputParser 提取历史摘要文本。
- RunnableLambda 在回答前整理模型上下文。
| 知识点 | 作用 | 本文返回或保存的内容 |
|---|---|---|
| InMemoryChatMessageHistory | 保存当前进程中的消息 | BaseMessage 列表 |
| SQLChatMessageHistory | 将消息持久化到 SQLite | 完整用户消息和模型消息 |
| MessagesPlaceholder | 在提示词中插入历史 | 多条消息对象 |
| RunnableWithMessageHistory | 自动读取、注入和写入历史 | 包装后的 Runnable |
| session_id | 区分不同会话 | 字符串会话标识 |
| StrOutputParser | 提取摘要字符串 | str |
| 历史摘要 | 压缩发送给模型的旧上下文 | 摘要 + 最近消息 |