三级裁剪:零 LLM 成本的旧 Tool 结果瘦身术 三级裁剪零 LLM 成本的旧 Tool 结果瘦身术专栏信息《从零到一构建跨平台 AI 助手WeClaw 实战指南》专栏本文是模块八第 11 篇讲解压缩前的预处理裁剪流水线。作者与项目作者简介翁勇刚 WENG YONGGANG新概念龙虾-WeClaw 开发团队负责人一群专注于跨平台 AI 应用的实践者理念“再复杂的技术就能用代码讲清楚”项目地址https://github.com/wyg5208/weclaw.git官网地址https://weclaw.link作者 CSDNhttps://blog.csdn.net/yweng18摘要本文结构概览本文讲解在调用 LLM 生成摘要之前如何通过三级无损预处理裁剪旧的工具结果大幅减少 LLM 的输入 token降低压缩成本。三级分别为 MD5 去重、信息性摘要替换、大参数截断。背景上下文压缩需要调用 LLM 生成摘要。但旧消息中可能包含大量冗余的工具输出重复的文件内容、相似的搜索结果直接交给 LLM 摘要既浪费 token 又增加成本。核心问题如何在不调用 LLM 的前提下预先减少 40-60% 的旧消息 token解决方案三级裁剪流水线——Pass 1 MD5 去重 Pass 2 信息摘要替换 Pass 3 参数截断关键成果LLM 摘要的输入 token 减少 40-60%零额外 LLM 调用成本近期消息token-budget 保护区域内不受影响适合读者LLM Agent 开发者关注成本控制和大窗口模型优化阅读时长约 10 分钟关键词预处理裁剪、MD5去重、信息摘要、Token优化、成本控制一、为什么要在压缩前先裁剪1.1 数据说话在一次典型的长对话中旧消息的 token 分布可能是这样的消息类型Token 数占比信息价值文件读取结果45,00045%低已被模型分析过搜索结果15,00015%低重复/过期信息代码执行结果10,00010%中对话内容20,00020%高工具调用参数10,00010%低旧参数无需保留60% 的 token 给了工具结果而这些结果的原始细节在压缩后只需要一个摘要就够了。1.2 成本计算不裁剪: 100K tokens 输入 LLM 生成摘要 → 费用 $0.50 裁剪后: 50K tokens 输入 LLM 生成摘要 → 费用 $0.25 节省: 50%对于高频使用的应用这个节省非常可观。二、三级裁剪流水线[图片: 三级裁剪流水线图 | 生成方式: 文生图 PROMPT: “A three-stage pipeline diagram for tool result pruning: Stage 1 Deduplication with filter icon showing duplicate messages being removed, Stage 2 Summary Replacement with compress icon showing long text being replaced by one-line summaries, Stage 3 Argument Truncation with scissors icon showing long parameters being shortened, with before/after token counts shown at each stage, clean technical pipeline style, light background”]原始消息 (100K tokens) ↓ [Pass 1: MD5 去重] → 移除重复的工具结果 ↓ (90K tokens) [Pass 2: 信息摘要替换] → 用一行摘要替换旧的工具结果 ↓ (55K tokens) [Pass 3: 大参数截断] → 截断超过 500 字符的 tool_call 参数 ↓ (50K tokens) ↓ LLM 摘要 (输入 50K tokens比不裁剪节省 50%)2.1 Token-budget 保护区域重要裁剪只针对旧消息近期消息受 token-budget 保护不参与裁剪。PROTECTED_RATIO0.30# 最近 30% token 预算内的消息不裁剪defget_prunable_range(messages,token_limit):获取可裁剪的消息范围protected_budgetint(token_limit*PROTECTED_RATIO)accumulated0protected_startlen(messages)# 从后向前计算保护区域foriinrange(len(messages)-1,-1,-1):accumulatedestimate_tokens(messages[i])ifaccumulatedprotected_budget:protected_starti1breakreturnrange(0,protected_start)# 只有这个范围内的消息参与裁剪三、Pass 1MD5 去重3.1 场景同一个文件被读取多次Step 1: read_file(/src/main.py) → 返回 8000 tokens 的文件内容 Step 3: read_file(/src/main.py) → 又返回 8000 tokens内容相同 Step 7: read_file(/src/main.py) → 再次返回 8000 tokens同一个文件在长对话中被多次读取比如每次修改后重新读取验证产生了 24000 tokens 的冗余。3.2 实现importhashlibdefpass1_dedup(messages,prunable_range):Pass 1: MD5 去重——相同内容只保留最后一条seen_hashes{}# hash → 最后出现的位置to_removeset()MIN_DEDUP_LENGTH200# 只去重超过 200 字符的消息# 从后向前遍历保留最新的foriinreversed(range(prunable_range.start,prunable_range.stop)):msgmessages[i]ifmsg.get(role)!tool:continuecontentmsg.get(content,)iflen(content)MIN_DEDUP_LENGTH:continue# 计算 MD5content_hashhashlib.md5(content.encode()).hexdigest()ifcontent_hashinseen_hashes:# 重复标记为删除to_remove.add(i)else:seen_hashes[content_hash]i# 移除重复消息return[mfori,minenumerate(messages)ifinotinto_remove]3.3 去重效果去重前: 3 次 read_file(/src/main.py) 24000 tokens 去重后: 1 次 read_file(/src/main.py) 8000 tokens 节省: 16000 tokens (67%)四、Pass 2信息性摘要替换4.1 设计思路对于不同类型的工具用专门的一行摘要替换完整的工具结果[图片: 10 个高频工具摘要示例表 | 生成方式: Markdown 表格]工具原始结果一行摘要search_web5 条搜索结果2000 字符搜索 python async: 返回 5 条结果read_file文件内容8000 字符读取 /src/main.py: 245 行 Python 文件shell_exec命令输出3000 字符执行 pytest: 37 passed, 0 failedwrite_file写入确认200 字符写入 /src/utils.py: 成功stock_query行情数据1500 字符查询 AAPL: $185.23 (1.2%)web_fetch网页内容5000 字符获取 example.com: 标题 Example Pageanalyze_pdfPDF 解析结果10000 字符解析 report.pdf: 42 页, 提取 15 段文本list_dir目录列表500 字符列出 /src/: 12 个文件, 3 个子目录create_chart图表数据1000 字符生成图表: 折线图, 5 个数据点translate翻译结果800 字符翻译 EN→ZH: 200 字符文本4.2 实现# 工具摘要生成器映射TOOL_SUMMARIZERS{search_web:_summarize_search,read_file:_summarize_read_file,shell_exec:_summarize_shell,write_file:_summarize_write,stock_query:_summarize_stock,# ... 其他工具}defpass2_summary_replace(messages,prunable_range):Pass 2: 信息性摘要替换resultlist(messages)foriinrange(prunable_range.start,min(prunable_range.stop,len(result))):msgresult[i]ifmsg.get(role)!tool:continue# 查找对应的 tool_call 获取工具名tool_name_find_tool_name(messages,msg.get(tool_call_id,))ifnottool_name:continue# 使用专门的摘要器summarizerTOOL_SUMMARIZERS.get(tool_name)ifsummarizer:summarysummarizer(msg.get(content,),tool_name)result[i]{**msg,content:summary}returnresultdef_summarize_read_file(content,tool_name):read_file 的专门摘要linescontent.count(\n)1# 提取文件路径通常在结果开头path_matchre.search(r[/\\][\w/\\.-]\.\w,content[:200])pathpath_match.group(0)ifpath_matchelseunknown# 推断文件类型extpath.rsplit(.,1)[-1]if.inpathelseunknownreturnf[read_file] 读取{path}:{lines}行{ext}文件五、Pass 3大参数截断5.1 场景tool_call 的 arguments 过长{role:assistant,tool_calls:[{id:call_001,function:{name:write_file,arguments:{\path\: \/src/main.py\, \content\: \import os\\nimport sys\\n...[5000字符的代码]...\}}}]}tool_call 的arguments字段包含了完整的文件内容5000 字符但在压缩时这个细节已不需要——write_file的 tool_result 已经确认写入成功。5.2 实现MAX_ARGUMENT_LENGTH500# 超过 500 字符的参数截断defpass3_truncate_args(messages,prunable_range):Pass 3: 大参数截断resultlist(messages)foriinrange(prunable_range.start,min(prunable_range.stop,len(result))):msgresult[i]ifmsg.get(role)!assistant:continueiftool_callsnotinmsg:continuefortcinmsg[tool_calls]:fntc.get(function,{})argsfn.get(arguments,)iflen(args)MAX_ARGUMENT_LENGTH:fn[arguments]args[:MAX_ARGUMENT_LENGTH]...[truncated]returnresult六、完整流水线defprune_tool_results_advanced(messages,token_limit):三级裁剪的完整流水线 Args: messages: 消息列表 token_limit: 总 token 阈值 Returns: 裁剪后的消息列表不影响原始列表 prunable_rangeget_prunable_range(messages,token_limit)resultlist(messages)# Pass 1: MD5 去重resultpass1_dedup(result,prunable_range)# 注意去重可能改变索引需要重新计算范围prunable_rangeget_prunable_range(result,token_limit)# Pass 2: 信息摘要替换resultpass2_summary_replace(result,prunable_range)# Pass 3: 大参数截断resultpass3_truncate_args(result,prunable_range)returnresult6.1 实测效果指标裁剪前Pass 1 后Pass 2 后Pass 3 后总计节省Token 数100K85K55K50K50%消息数12010510510512.5%LLM 摘要成本$0.50$0.43$0.28$0.2550%七、总结与展望7.1 核心要点回顾预处理裁剪是免费午餐零 LLM 成本减少 50% 的摘要输入 token三级流水线层层递进去重 → 摘要替换 → 参数截断保护区域不裁剪最近 30% token 预算内的消息完整保留工具专门分支提升摘要质量不同工具用不同的摘要格式下期预告《实战踩坑录上下文管理的 10 个反直觉 Bug》从阈值失效到正则陷阱从占位累积到 API 不兼容一份完整的工程踩坑清单敬请期待版权声明本文为 CSDN 博主「翁勇刚」的原创文章遵循 CC 4.0 BY-SA 版权协议转载请附上原文出处链接及本声明。