当前位置: 首页 > news >正文

多轮对话管理:你的上下文窗口正在被「蚕食」,每轮都在亏钱

🦞 一只用 AI Agent 搭副业产线的程序员


回忆一下我们在第三篇文章里说的:API 不会帮你「记住」任何东西。每一轮对话,你都得把整个历史重新发过去。

第 1 轮:10 条消息,500 token
第 10 轮:100 条消息,5000 token
第 50 轮:500 条消息,25000 token

你的 Agent 不是在做事情——是在烧钱,而且烧得越来越快。

这篇文章给你两套解决方案:滑动窗口和摘要压缩。带上代码,带上实测数据。


先看清楚问题有多大

我跑了一个模拟——Agent 跟 AI 进行 20 轮对话,记录每轮的 token 消耗:

轮次 累计消息数 输入 token 本轮花费(DeepSeek Pro) 第 1 轮 2 320 ¥0.0003 第 5 轮 10 1680 ¥0.0017 第 10 轮 20 3560 ¥0.0036 第 15 轮 30 5420 ¥0.0054 第 20 轮 40 7310 ¥0.0073 20 轮总花费:¥0.087 看起来不贵?

换成 GPT-4o:

20 轮总花费:¥0.87

换成 Claude Opus 4:

20 轮总花费:¥2.61

一个 Agent 任务跑 20 轮很正常。如果你在用 Claude 跑一个每天 10 次任务的 Agent——一个月 ¥780?

更要命的是,这只是「聊天」。如果每一轮你还塞入了 RAG 检索的文档(5000 token/次),那数字要乘 5-10 倍。


方案一:滑动窗口——最简单粗暴的办法

思路:只保留最近 N 条消息,旧的全部丢掉。

packagecontexttypeSlidingWindowstruct{MaxMessagesint// 最多保留多少条消息}funcNewSlidingWindow(maxMessagesint)*SlidingWindow{return&SlidingWindow{MaxMessages:maxMessages}}func(sw*SlidingWindow)Trim(messages[]Message)[]Message{// 永远保留 System Promptiflen(messages)>0&&messages[0].Role=="system"{iflen(messages)<=sw.MaxMessages+1{returnmessages}// System Prompt + 最近 N 条start:=len(messages)-sw.MaxMessages result:=make([]Message,0,sw.MaxMessages+1)result=append(result,messages[0])// System Promptresult=append(result,messages[start:]...)returnresult}// 没有 System Prompt,直接截断iflen(messages)<=sw.MaxMessages{returnmessages}returnmessages[len(messages)-sw.MaxMessages:]}

使用:

window:=NewSlidingWindow(10)// 只保留最近 10 条forround:=0;round<50;round++{// 构造本轮消息messages=append(messages,Message{Role:"user",Content:taskInput})result:=callLLM(messages,0.1,500)messages=append(messages,Message{Role:"assistant",Content:result})// 裁剪messages=window.Trim(messages)fmt.Printf("第 %d 轮: 消息数=%d, token 估算=%d\n",round,len(messages),estimateTokens(messages))}

优点:简单,token 消耗恒定
缺点:旧信息永久丢失。如果用户在第 1 轮说了「我叫小王」,第 12 轮问「我叫什么?」——AI 已经忘了。

适合:短期交互(客服、代码生成辅助)、不需要长期记忆的场景


方案二:摘要压缩——保留信息密度

思路:当对话历史超过一定长度时,把旧的消息「压缩」成一段摘要。

typeSummaryCompressorstruct{MaxMessagesint// 超过这个数就压缩SummaryPromptstring// 摘要的 PromptllmClient*llm.Client}funcNewSummaryCompressor(maxMessagesint,client*llm.Client)*SummaryCompressor{return&SummaryCompressor{MaxMessages:maxMessages,llmClient:client,}}func(sc*SummaryCompressor)Compress(messages[]Message)([]Message,error){iflen(messages)<=sc.MaxMessages{returnmessages,nil}// 找出需要压缩的部分(中间部分,保留头尾)systemMsg:=messages[0]// System Prompt,不动recentMsgs:=messages[len(messages)-5:]// 最近 5 条,不动oldMsgs:=messages[1:len(messages)-5]// 中间部分,需要压缩// 把旧消息拼成文本varhistory strings.Builderfor_,msg:=rangeoldMsgs{history.WriteString(fmt.Sprintf("[%s]: %s\n",msg.Role,msg.Content))}// 让 AI 压缩成摘要summaryPrompt:=fmt.Sprintf(`将以下对话历史压缩为一段简洁的摘要(不超过 200 字)。 保留关键信息:人名、决策、承诺、数据、待办事项。 对话历史: %s 摘要:`,history.String())summary,err:=sc.llmClient.Chat([]Message{{Role:"user",Content:summaryPrompt},},0.1,200)iferr!=nil{returnnil,err}// 重建消息列表result:=[]Message{systemMsg}result=append(result,Message{Role:"system",Content:fmt.Sprintf("[对话历史摘要] %s",summary),})result=append(result,recentMsgs...)returnresult,nil}

效果实测:

压缩前:35 条消息,约 12000 token 压缩后:1 条 System + 1 条摘要 + 5 条最近 = 7 条消息,约 800 token 压缩率:93% 信息损失: - 用户在第 2 轮说的名字 → 保留在摘要中 ✅ - 用户在第 3 轮提到的技术栈 → 保留在摘要中 ✅ - 用户在第 8 轮随口说的一句话 → 消失了 ❌ - 用户在第 12 轮说的具体数据 → 消失了 ❌(因为离得远且没有压进摘要)

两种策略的对比实测

同一个任务—代码审查 Agent 跑 50 轮对话:

指标无管理滑动窗口(10)摘要压缩
50 轮总 Token187,00024,00031,000
每次成本(DeepSeek Pro)¥0.19¥0.024¥0.031
信息保留率100%~20%~70%
关键信息丢失0 次2 次0 次
代码复杂度10 行40 行

无管理的最贵但信息最完整。滑动窗口最便宜但丢了 2 次关键信息(用户前面提过的需求,被滑动窗口裁剪掉了)。摘要压缩中间价位,关键信息都保留了。


混合策略:生产环境的选择

最佳实践是组合使用:

typeHybridManagerstruct{window*SlidingWindow compressor*SummaryCompressor thresholdint// 超过多少条消息触发压缩}func(hm*HybridManager)Manage(messages[]Message)([]Message,error){iflen(messages)<=hm.threshold{returnmessages,nil// 消息还不多,不做任何处理}iflen(messages)<=hm.threshold*2{returnhm.window.Trim(messages),nil// 消息较多,滑动窗口}// 消息很多,先压缩再滑动窗口compressed,err:=hm.compressor.Compress(messages)iferr!=nil{// 压缩失败,降级为滑动窗口returnhm.window.Trim(messages),nil}returnhm.window.Trim(compressed),nil}

流程:

消息数 < 20 → 全部保留(成本可控,信息完整度优先) 消息数 20-40 → 滑动窗口,保留最近 20 条 消息数 > 40 → 压缩中间的旧消息 + 滑动窗口

上下文预算意识

现在你对上下文管理的成本有了直观感受,我们来建立「预算意识」:

组件占用 Token 预算要不要省
System Prompt100-500尽量精简
Few-shot 示例200-500只在必要时加
RAG 文档2000-8000按相关性截断
对话历史随轮次增长用滑动窗口/压缩
AI 输出200-1000设 MaxTokens 上限

你的上下文窗口不是无限的。每一段文字都在吃预算、都在花钱。写 Prompt 的时候,像写嵌入式程序一样精打细算。


一句话总结

多轮对话管理不是高级技巧——是做 Agent 的基本功。不管理上下文,你的 Agent 成本是 O(n²) 增长。用滑动窗口变成 O(1),用摘要压缩变成 O(log n)。

下一篇——模块二的收官之作——我把前面所有的 Prompt 工程知识打包成10 个拿来即用的模板。代码生成、代码审查、Bug 分析、日报生成……每个模板标注了适用场景和期望输出。

关注我,别错过。


🦞 一只用 AI Agent 搭副业产线的程序员

全平台同名:虾哥不加班
需要定制 AI 工具?来聊聊 → lob_ai

源码:GitHub - lobster-bujiaban

http://www.cnnetsun.cn/news/2748508.html

相关文章:

  • 无人机光伏板识别 中国地区太阳能电池板语义分割数据集 无人机航拍光伏 太阳能电池板分割图像数据集
  • 近红外光谱分析避坑指南:这8种数据预处理方法,你用对顺序了吗?
  • OBS本地AI语音识别字幕解决方案:LocalVocal完整指南
  • 老设备电池改造:用外部电源适配器为Pleo RB机器人实现无限续航
  • 从零自制Arduino开发板:ATmega328P核心电路设计与PCB实战
  • 警惕GPT-5.5等虚构模型名称:识别AI领域常见技术谣言
  • Cricut切割机改造鸡蛋盒:从乙烯基贴纸到个性化厨房收纳
  • 用Makey Makey和Scratch打造互动音乐识谱系统:STEAM教育实践
  • CCHP系统运行策略优化MATLAB工具包:基于MOPSO的经济-环保-能效协同寻优
  • LeetCode 746:使用最小花费爬楼梯 —— 题解笔记
  • 基于ESP8266与Blynk的智能家居系统:从硬件设计到物联网应用实战
  • ROS2 数据不在现场也能看:Ubuntu 22.04 用 Foxglove Bridge + cpolar 远程看话题和图像流
  • 电路设计入门:从原理图到PCB,手把手制作可调光LED台灯
  • 别再只怪固态硬盘!从TRIM和垃圾回收机制,看懂格式化后数据恢复的真相
  • 告别996?用AI重构工作流后,效率暴涨
  • 从ChatGPT到离职预警中台:AI工具整合失败的5个致命断点,90%的CTO在第3步就已失控
  • 基于ESP8266的WiFi同步OLED复古时钟:物联网开发实战指南
  • 微信好友关系终极检测:5分钟快速识别单向好友的完整指南
  • MATLAB实现的D-S证据融合工具集:含主融合函数与全套DST辅助计算模块
  • 从控制理论到射频电路:一个视频讲透奈奎斯特判据在ADS中的应用
  • Kafka拷打!!!
  • ICode竞赛Python一级通关秘籍:手把手教你搞定路线规划题(附20关代码详解)
  • 从零开始电路设计:智能感温杯垫实战与电子制作全流程解析
  • 基于免疫机制增强的MATLAB物流路径求解工具包(含真实数据与动态可视化)
  • 本科生可用的坐姿监测系统源码:带训练模型、语音提醒和图形界面
  • NAS跑大模型实战:GLM-5在家庭服务器上的部署与优化
  • AI工具链如何重塑CISSP/CEH认证路径:5大不可逆趋势与3步迁移方案
  • MCA Selector:让你的Minecraft世界重获新生的智能管家
  • MATLAB遗传算法实战:手把手教你为外卖站点或前置仓做智能选址排线
  • 单北斗GNSS在桥梁与大坝变形监测中的应用与发展分析