在一把 key 上把多轮会话状态存到 API 之外:按后端裁剪与回灌历史
本文介绍如何将多轮会话状态保存在自有存储中,每次调用 API 前重建消息数组,从而在切换后端或触及 token 上限时摆脱对厂商侧 thread 对象的依赖,实现更灵活、可控的会话管理。
为什么要把会话状态存在自己手里
多数 LLM API 提供了便捷的 thread 或会话对象,让开发者无需手动管理历史消息。但这种便利也带来耦合:会话状态绑定在特定厂商、特定模型上,换后端或模型时历史难以迁移;厂商对 thread 的存储策略、长度限制也由平台决定,触及 token 上限时无法精细裁剪。
更可控的做法是:每次调用前,从自己的存储中读取历史,重建完整的消息数组。这样,会话状态完全由你掌控,跨后端切换只是换一个 API 端点,历史裁剪策略也能按需定制。
核心思路:无状态调用 + 历史重建
LLM API 本质上是无状态的:每次请求都需要传入完整的消息列表。所谓“会话”,只是客户端把历史消息拼接起来再发送。因此,你可以:
- 将会话历史以你喜欢的形式(如数据库记录、JSON 文件)存储在自己的系统中。
- 每次调用前,按时间顺序读取历史,构造
messages数组。 - 将新用户输入追加到数组末尾,发送给 API。
- 收到回复后,将用户消息和助手回复一并写回存储。
这样,API 本身不需要维护任何会话状态,换用不同后端时只需调整请求格式(如字段名、角色定义),历史数据无需迁移。
按后端裁剪历史
不同模型对上下文窗口的限制不同,同一个会话在换后端时可能超出新模型的 token 上限。由于历史在你手中,你可以实现灵活的裁剪策略:
- 保留最近 N 轮:简单有效,适合对话场景。
- 按 token 数动态裁剪:估算每条消息的 token 数,从最早的消息开始丢弃,直到总 token 数低于阈值。
- 保留系统提示和关键消息:始终保留系统指令和最近几条消息,中间部分可压缩或摘要。
裁剪时需注意保持消息的完整性,避免将用户消息和助手回复拆散导致角色交替混乱。
实现要点
- 存储结构:每条消息至少包含角色(system/user/assistant)和内容。可附加时间戳、消息 ID 便于排序。
- 重建消息数组:按时间顺序读取,必要时插入系统提示。注意不同后端对系统消息的位置要求可能不同。
- 裁剪逻辑:在重建后、发送前执行。可根据目标后端的上下文限制动态调整。
- 写入时机:在收到 API 响应后,将本轮用户消息和助手回复原子性地写入存储,避免并发问题。
换后端与回灌历史
当切换后端时,只需:
- 从存储中读取历史。
- 按新后端的格式要求转换消息(如角色名称、系统提示位置)。
- 应用新后端的裁剪策略。
- 发送请求。
由于历史是独立存储的,你可以在不同模型间自由切换,甚至在同一会话中混合使用多个后端(例如用便宜模型处理简单轮次,用强模型处理复杂轮次)。
一把 key 调多模型时的注意事项
使用聚合 API 服务时,一个 key 可调用多个模型。此时更需注意:
- 不同模型对消息格式的支持可能略有差异(如是否支持 system 角色、是否要求消息角色交替)。
- 裁剪策略应按模型配置,而非全局统一。
- 记录每条消息使用的模型,便于调试和成本分析。
通过将状态外置,你获得的是对会话的完全控制权,以及在不同后端间平滑迁移的能力。