一文看懂 Agent 对话历史压缩:用 TaoToken 统一 Key 打通摘要法上下文窗口治理
发布时间:2026/9/28 19:42:44来源:尧图网络
1. Agent 长对话为什么一定会撑爆上下文窗口先说结论Agent 的 messages 数组是只增不减的只要任务够复杂任何上下文窗口都会被填满。这不是模型能力问题是架构决定的。我拿一个真实任务举例让 Agent 去分析 nginx 日志提取 error统计近一天内每半小时的 error 数量、每个接口的请求量按数量降序排序最后写入 statistic.txt。这个任务听起来不复杂但 Agent 实际执行时会经历这些步骤先 ls 找日志文件再 grep 提取 error 行然后 awk 按时间窗口聚合再 sort 排序中间可能还要 cat 看文件格式、head 确认字段、wc 统计行数。每一轮工具调用messages 至少新增两条消息assistant 的 tool_call 和 tool 的返回结果而 grep 和 cat 的输出动辄几十上百行。算一笔账假设每轮工具输出平均 800 tokens10 轮就是 8000 tokens加上 system prompt、历史对话、工具定义轻松突破 15000 tokens。如果用的是本地部署的小模型context window 可能只有 4096 或 8192跑三四轮就满了。就算用云端大模型窗口有 128K一个复杂任务跑 50 轮循环照样能塞满。有人会说现在模型窗口越来越大了还需要压缩吗我的实测感受是窗口变大只是把爆炸时间往后推没有解决根本问题。三个原因第一token 费用是按输入量算的历史越长每轮请求越贵第二超长上下文会让模型注意力分散容易在无关信息里迷失重点第三响应速度会明显变慢因为模型要处理的输入变多了。所以上下文压缩不是可选项是 Agent 工程化的必修课。这篇就聚焦摘要法这一条主线把配置、阈值、提示词模板、验证动作全部拆开讲清楚同时演示怎么用 TaoToken 的统一 Key 把 LLM 通道接起来让压缩逻辑跑通。2. 用 TaoToken 统一 Key 接入 LLM 通道在写压缩代码之前得先把 LLM 调用通道搭好。Agent 的压缩逻辑需要频繁调用 LLM 来生成摘要如果每次都要切换不同的 API Key、不同的 base_url维护成本会很高。TaoToken 的思路是提供一个统一的 API 入口你只需要一个 Key就能在多个模型之间切换压缩用的摘要模型和主任务用的模型可以分开配置。TaoToken 是什么它是一个 LLM API 聚合通道提供统一的 OpenAI 兼容接口。你可以把它理解成一个API 路由器底层对接了多种模型上层暴露标准的 /v1/chat/completions 接口。对 Agent 开发者来说好处是不用为每个模型单独写适配代码换模型只改一个 model 字段。适合谁用正在做 Agent 长对话治理、需要频繁调用 LLM 做摘要压缩、或者想在多个模型之间做成本对比的开发者。如果你只是偶尔调一次 API可能感受不到统一 Key 的价值但如果你在跑 Agent 循环每轮都要调 LLM统一通道能省很多事。接入前你需要准备的东西一个 TaoToken 账号一个 API Key。Key 在控制台的 API Keys 页面生成地址是 https://taotoken.net/console/api-keys 。生成后保存好后面配置里要用。这里要区分两个地址官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 注意 API 地址后面不加 UTM 参数直接用于代码里的 base_url。如果你还没决定用哪个模型做摘要可以先去模型对话页面试一下不同模型的摘要效果地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。摘要任务对模型要求不高小模型也能胜任关键是提示词要写清楚。3. 可复制的 config.toml 与 settings.json 配置骨架配置分两块一块是 TaoToken 的接入配置一块是压缩策略的参数配置。我习惯把接入信息放在 config.toml把压缩阈值和提示词放在 settings.json这样调参的时候不用动代码。先看 config.toml# config.toml - TaoToken 接入配置 [llm] base_url https://taotoken.net/api api_key sk-your-taotoken-key-here main_model gpt-4o-mini # 主任务模型 summary_model gpt-4o-mini # 摘要专用模型可以用更便宜的 timeout 60 max_retries 3 [agent] max_iterations 30 # Agent 最大循环次数 compact_enabled true # 是否开启压缩再看 settings.json这里放压缩策略的核心参数{ compact: { threshold: 20, keep_recent: 6, summary_prompt: Summarize the following conversation history. Keep all important facts, file paths, command results, and decisions. Be concise but dont lose critical details., summary_prefix: [Previous conversation summary]: , ack_message: Understood. I have the context from our previous conversation. Let me continue. } }参数解释一下。threshold 是触发压缩的消息条数阈值超过 20 条就压缩。keep_recent 是保留最近多少条不压缩设为 6 意味着最近 3 轮对话每轮 userassistant 两条保持原样。summary_prompt 是发给 LLM 的摘要指令要求保留事实、文件路径、命令结果和决策。summary_prefix 是摘要插入 messages 时的前缀让模型知道这是历史摘要。ack_message 是压缩后插入的 assistant 确认消息保持对话角色交替的完整性。为什么 threshold 设 20、keep_recent 设 6这是我试过几组参数后的经验值。threshold 太小会导致压缩过于频繁每次压缩都要调一次 LLM反而增加开销太大则压缩效果不明显。20 条大约对应 10 轮工具调用是一个比较自然的压缩点。keep_recent 设 6 是因为最近几轮对话往往包含当前任务的上下文压缩掉会导致模型失忆保留 6 条能覆盖最近 3 轮的关键信息。如果你用的是本地小模型context window 只有几千 tokens建议把 threshold 降到 10keep_recent 降到 4。如果是云端大模型可以适当放宽到 30 和 8。4. 摘要法压缩的核心实现与提示词模板配置准备好之后核心逻辑就是一个 compact_messages 函数。它的职责很明确检查消息条数没超阈值就原样返回超了就压缩旧消息、保留最近消息、重新组装。import json import toml from openai import OpenAI # 加载配置 config toml.load(config.toml) settings json.load(open(settings.json)) client OpenAI( base_urlconfig[llm][base_url], api_keyconfig[llm][api_key] ) COMPACT_THRESHOLD settings[compact][threshold] KEEP_RECENT settings[compact][keep_recent] SUMMARY_PROMPT settings[compact][summary_prompt] SUMMARY_PREFIX settings[compact][summary_prefix] ACK_MESSAGE settings[compact][ack_message] SUMMARY_MODEL config[llm][summary_model] def compact_messages(messages): if len(messages) COMPACT_THRESHOLD: return messages system_msg messages[0] old_messages messages[1:-KEEP_RECENT] recent_messages messages[-KEEP_RECENT:] old_text for msg in old_messages: role msg.get(role, unknown) content msg.get(content, ) if content: old_text f[{role}]: {content}\n summary_response client.chat.completions.create( modelSUMMARY_MODEL, messages[ {role: system, content: SUMMARY_PROMPT}, {role: user, content: old_text} ] ) summary summary_response.choices[0].message.content return [ system_msg, {role: user, content: f{SUMMARY_PREFIX}{summary}}, {role: assistant, content: ACK_MESSAGE}, *recent_messages ]这段代码有几个关键点。第一system_msg 永远保留因为它是 Agent 的核心指令压缩进摘要会丢失基础设定。第二old_messages 是要被压缩的部分recent_messages 是保留原样的部分切分位置在 messages[1:-KEEP_RECENT]。第三摘要调用用的是 summary_model可以和主任务模型不同摘要任务对模型能力要求不高用便宜的小模型就行。摘要提示词模板是压缩效果的关键。我用的模板是Summarize the following conversation history. Keep all important facts, file paths, command results, and decisions. Be concise but dont lose critical details.这个模板强调了三件事保留事实facts、保留文件路径file paths、保留命令结果command results。为什么强调这些因为 Agent 后续决策依赖这些具体信息。比如前面 grep 出来的日志路径、awk 统计出的数字、用户指定的输出文件名这些如果丢了Agent 就得重新执行工具去获取压缩就白做了。如果你做的是结构化任务比如订票、查数据可以把提示词改成要求输出 JSON 格式{ summary_prompt: Summarize the conversation into a JSON object with keys: entities, state, intent. Keep all concrete values. }这样摘要结果本身就是结构化的后续解析更方便。压缩在 Agent 循环里的调用位置也很重要。我的做法是在每轮循环开始前检查一次def run_agent(user_message, max_iterations30): messages [ {role: system, content: You are a helpful agent...}, {role: user, content: user_message} ] for i in range(max_iterations): messages compact_messages(messages) # 每轮开始前检查 response client.chat.completions.create( modelconfig[llm][main_model], messagesmessages, toolstools ) # ... 处理工具调用追加消息到 messages这样 messages 的数量会像锯齿波一样涨到阈值 → 压缩回去 → 继续涨 → 再压缩。永远不会超过阈值太多Agent 可以持续工作下去。5. 验证压缩效果token 占用对比与请求测试代码写完了怎么验证压缩真的生效了我一般做两个动作一是打印压缩前后的消息条数和 token 估算二是实际发一次请求看返回是否正常。先加一段验证代码def estimate_tokens(text): # 粗略估算英文约 4 字符 1 token中文约 1.5 字符 1 token return len(text) // 3 def print_compact_stats(messages_before, messages_after): before_count len(messages_before) after_count len(messages_after) before_tokens sum(estimate_tokens(str(m.get(content, ))) for m in messages_before) after_tokens sum(estimate_tokens(str(m.get(content, ))) for m in messages_after) print(f压缩前: {before_count} 条消息, 约 {before_tokens} tokens) print(f压缩后: {after_count} 条消息, 约 {after_tokens} tokens) print(f压缩率: {(1 - after_tokens / before_tokens) * 100:.1f}%)跑一个模拟场景构造 25 条消息其中包含几段长文本模拟工具输出然后调用 compact_messages打印统计。预期结果是消息条数从 25 降到 91 条 system 1 条摘要 1 条确认 6 条最近消息token 占用减少 60% 到 80%。实测下来一个包含 10 轮工具调用的对话压缩前约 12000 tokens压缩后约 3000 tokens压缩率 75% 左右。摘要本身消耗约 500 tokens 的输入和 200 tokens 的输出但这是一次性开销后续每轮请求都省下了 9000 tokens 的输入。第二个验证动作是发一次真实请求确认压缩后的 messages 能被模型正常理解def test_compressed_context(): messages build_long_conversation() # 构造长对话 compressed compact_messages(messages) response client.chat.completions.create( modelconfig[llm][main_model], messagescompressed ) print(response.choices[0].message.content)如果模型能基于摘要继续回答说明压缩没有丢失关键上下文。如果模型回答我不知道之前发生了什么说明摘要提示词需要调整要更强调保留事实和决策。这里有个细节压缩后插入的 assistant 确认消息Understood. I have the context...很重要。它保持了对话角色的交替让模型知道摘要已经被接受了。如果省略这条messages 会变成 user摘要后面直接跟 user最近消息角色不交替有些模型会报错或行为异常。6. 压缩场景下的常见报错与排查压缩逻辑跑起来之后容易遇到几类问题我按出现频率排一下。第一类摘要调用超时或返回空。原因是 old_text 太长摘要模型处理不过来。排查方法是打印 old_text 的长度如果超过 10000 字符考虑分批摘要或者先截断。解决方式是在 config.toml 里把 summary_model 换成上下文窗口更大的模型或者把 threshold 调小让每次压缩的旧消息少一些。第二类压缩后模型失忆重复执行已完成的步骤。原因是摘要丢失了关键状态信息。排查方法是把摘要内容打印出来看是否包含任务进度、已完成步骤、待办事项。解决方式是修改 summary_prompt明确要求保留 task progress 和 completed steps。我常用的增强版提示词是Summarize the conversation. Keep: 1) all file paths and command results, 2) task progress and completed steps, 3) pending actions, 4) user preferences and constraints. Be concise.第三类messages 角色顺序错乱导致 API 报错。原因是压缩后组装的消息列表角色不交替。排查方法是打印压缩后每条消息的 role确认是 system → user → assistant → user → assistant 的交替顺序。解决方式是确保摘要消息用 user 角色确认消息用 assistant 角色最近消息保持原有角色。第四类压缩频率过高导致 token 费用不降反升。原因是 threshold 设得太小每几轮就压缩一次摘要调用的开销超过了节省的输入 token。排查方法是统计压缩次数和摘要调用总 token。解决方式是把 threshold 调大比如从 10 调到 20 或 30让每次压缩处理更多旧消息摊薄摘要成本。第五类工具调用消息在压缩后丢失 tool_call_id导致后续请求报错。原因是摘要只保留了 content没有保留 tool_calls 结构。排查方法是检查 old_messages 里是否有带 tool_calls 字段的消息。解决方式是在压缩时跳过带 tool_calls 的消息或者把它们的内容提取出来放进摘要文本但不要保留原始结构。更稳妥的做法是确保 keep_recent 覆盖到最近一次工具调用这样 tool_call_id 不会进入压缩区。如果你在接入 TaoToken 的过程中遇到 Key 无效或 401 报错先去 API Keys 页面确认 Key 是否正确生成、是否过期地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果确认 Key 没问题但还是报错检查 base_url 是否写成了 https://taotoken.net/api 注意末尾不要加斜杠也不要在 API 地址上加 UTM 参数。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有完整的接口说明和错误码对照。如果你打算长期跑 Agent 任务压缩逻辑会频繁调用 LLM可以考虑 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 适合需要稳定通道和批量调用的场景。最后说一个我踩过的坑压缩后的摘要消息不要用 system 角色。我一开始图省事把摘要塞进 system 消息里结果模型把摘要当成了系统指令行为变得很奇怪。正确做法是用 user 角色让模型把它当作用户提供的上下文信息来处理。这个细节在接入文档里没有明说但实测下来影响很大。
网站建设高端定制企业官网