Token-Budget 尾部保护:别再按“轮次“保护上下文了 Token-Budget 尾部保护别再按轮次保护上下文了专栏信息《从零到一构建跨平台 AI 助手WeClaw 实战指南》专栏本文是模块八第 5 篇讲解从固定轮次到 token-budget 的精确保护策略。作者与项目作者简介翁勇刚 WENG YONGGANG新概念龙虾-WeClaw 开发团队负责人一群专注于跨平台 AI 应用的实践者理念“再复杂的技术也能用代码讲清楚”项目地址https://github.com/wyg5208/weclaw.git官网地址https://weclaw.link作者 CSDNhttps://blog.csdn.net/yweng18摘要本文结构概览本文从保护最近 12 轮这个看似合理的策略出发揭示它在实际场景中的粒度问题然后介绍 token-budget 尾部保护的设计与实现包括边界对齐、用户消息锚定和 soft ceiling 机制。背景上下文压缩时需要决定哪些消息保留原文哪些消息压缩为摘要。保留的部分称为尾部保留策略的质量直接决定压缩后对话的连贯性。核心问题固定轮次策略如保留最近 12 轮为什么不够好如何按 token 预算精确控制保留范围解决方案基于 token-budget 的尾部保护——按实际 token 消耗而非消息条数来划定保护边界并向前对齐到 user 消息边界。关键成果保护精度提升一条 50KB 文件内容和一条 100 字符消息不再被同等对待边界对齐永远不会切断 tool_call/tool_result 配对用户锚定最后一条 user 消息永远不被摘要吞掉适合读者LLM Agent 开发者关注上下文压缩质量优化阅读时长约 10 分钟关键词尾部保护、Token Budget、边界对齐、用户消息锚定、上下文压缩一、保护最近 12 轮有什么问题1.1 一个极端的场景消息 1 (user): 请帮我分析这个文件 消息 2 (tool): [read_file 返回了 50KB 的文件内容] 消息 3 (assistant): 文件分析如下... 消息 4 (user): 再搜索一下相关新闻 消息 5 (tool): [web_search 返回了 500 字符的摘要] 消息 6 (assistant): 相关新闻如下... ... 消息 23 (user): 好的总结一下如果保护最近 12 轮意味着保留最后 12 条消息消息 12-23那么消息 2 被压缩50KB 的文件内容变成摘要——合理因为文件内容本身可以被摘要保留但如果消息 2 在保护范围内50KB 的文件内容完整保留挤占了大量窗口空间导致更多早期消息被压缩1.2 核心矛盾消息Token 数12 轮策略实际价值文件读取结果~15000保护完整保留低可摘要用户最新请求~50保护完整保留高活跃任务搜索结果~500保护完整保留中问题固定轮次策略对所有消息一视同仁无法按实际 token 消耗和消息价值区分。二、Token-Budget 设计[图片: 两种保护策略对比 | 生成方式: 文生图 PROMPT: “Side-by-side comparison of two context protection strategies: Left shows fixed-round protection cutting through a tool call pair with a red dashed line, Right shows token-budget protection with clean boundary at user message highlighted in green, annotated with token counts at each message, technical diagram style, clean white background”]2.1 核心思想不是保留多少条消息而是保留多少 token 的消息。# Token-budget 尾部保护TAIL_BUDGET_RATIO0.15# 尾部预算 总阈值的 15%deflocate_preservation_boundary(messages,token_limit):从后向前累积 token找到保护边界 Args: messages: 消息列表 token_limit: 总 token 阈值 Returns: int: 保护边界的索引位置此位置之后的消息保留原文 tail_budgetint(token_limit*TAIL_BUDGET_RATIO)accumulated0boundarylen(messages)# 从后向前遍历foriinrange(len(messages)-1,-1,-1):msg_tokensestimate_tokens(messages[i])accumulatedmsg_tokensifaccumulatedtail_budget:boundaryi1breakreturnboundary2.2 实际效果对比以 100K token 阈值的对话为例旧方案固定 12 轮 保留最后 12 条消息 约 45000 tokens包含 30KB 文件内容 浪费30000 tokens 给了低价值的文件内容 新方案token-budget 15% tail_budget 100K * 0.15 15000 tokens 从后向前累积恰好在第 8 条消息处达到 15000 tokens 精确保护了最近 8 条消息释放了更多空间给摘要三、边界对齐不切在 tool_call 中间3.1 问题Token-budget 的边界可能恰好落在 tool_call 和 tool_result 之间消息 5 (assistant): tool_calls: [call_001] ← 边界在这里 消息 6 (tool): tool_call_id: call_001 ← 这条被截掉了结果tool_call 没有对应的 tool_result产生孤儿消息。3.2 向前对齐到 user 消息defalign_to_user_boundary(messages,raw_boundary):向前对齐到最近的 user 消息边界 确保保护范围从一条 user 消息开始 不会切断 assistant(tool_call) → tool(tool_result) 配对 boundaryraw_boundary# 向前查找最近的 user 消息whileboundary0andmessages[boundary].get(role)!user:boundary-1# 如果找不到 user 消息至少保证不从 tool 消息中间开始ifboundary0:boundaryraw_boundary# 保持原始边界returnboundary3.3 对齐效果对齐前 [msg5: assistanttool_calls] | [msg6: tool_result] [msg7: user] [msg8: assistant] ↑ 边界切断了配对 对齐后 [msg5: assistanttool_calls] [msg6: tool_result] | [msg7: user] [msg8: assistant] ↑ 边界从 user 消息开始四、用户消息锚定最后一条 user 消息永不被吞4.1 极端场景如果用户的最后一条消息很长比如粘贴了一大段代码token-budget 可能在到达这条消息之前就耗尽了消息 10 (user): 请分析这段代码 [10000 tokens 的代码] 消息 11 (assistant): [分析中...] 消息 12 (user): 继续 tail_budget 15000 tokens 从后向前消息 12 (5 tokens) 消息 11 (200 tokens) 消息 10 (10000 tokens) 10205 ... 但如果消息 10 是 20000 tokens就会超出 budget4.2 锚定机制defensure_last_user_preserved(messages,boundary):确保最后一条 user 消息在保护范围内 如果最后一条 user 消息被划入了待压缩区域 将边界前移到它之前 # 找到最后一条 user 消息的位置last_user_posNoneforiinrange(len(messages)-1,-1,-1):ifmessages[i].get(role)user:last_user_posibreakiflast_user_posisnotNoneandlast_user_posboundary:# 最后一条 user 消息在边界之前 → 需要调整boundarylast_user_posreturnboundary4.3 Soft Ceiling防止单条大消息独占如果最后一条 user 消息有 50000 tokens它会独占整个 tail_budgetSOFT_CEILING_RATIO1.5# soft ceiling budget * 1.5defapply_soft_ceiling(messages,boundary,tail_budget):如果保护区域过大限制单条消息的 token 贡献soft_ceilingint(tail_budget*SOFT_CEILING_RATIO)# 计算实际保护区域的 token 数protected_tokenssum(estimate_tokens(m)forminmessages[boundary:])ifprotected_tokenssoft_ceiling:logger.info(fSoft ceiling triggered:{protected_tokens}{soft_ceiling})# 标记超大消息需要截断但不删除# 后续由压缩引擎决定如何处理returnboundary五、完整流程从预算到边界defcompute_tail_protection(messages,token_limit):计算尾部保护的完整流程 Returns: int: 最终的保护边界索引 # Step 1: 计算 token budgettail_budgetint(token_limit*TAIL_BUDGET_RATIO)# 15%# Step 2: 从后向前累积找到原始边界accumulated0raw_boundarylen(messages)foriinrange(len(messages)-1,-1,-1):accumulatedestimate_tokens(messages[i])ifaccumulatedtail_budget:raw_boundaryi1break# Step 3: 向前对齐到 user 消息边界aligned_boundaryalign_to_user_boundary(messages,raw_boundary)# Step 4: 确保最后一条 user 消息被保护final_boundaryensure_last_user_preserved(messages,aligned_boundary)# Step 5: Soft ceiling 保护final_boundaryapply_soft_ceiling(messages,final_boundary,tail_budget)returnfinal_boundary5.1 可视化[图片: token-budget 累积过程 | 生成方式: Python matplotlib 脚本水平条形图展示消息列表从右向左着色累积(绿色保护, 红色待压缩), 标注 budget 线位置和对齐后的边界]# Python 绘图脚本importmatplotlib.pyplotaspltimportnumpyasnp messages[(user,200,请分析文件),(tool,15000,read_file 结果),(assistant,800,文件分析...),(user,100,搜索新闻),(tool,500,search 结果),(assistant,600,新闻摘要...),(user,50,总结一下),(assistant,400,综合分析...),]budget3000# 假设 tail_budget 3000 tokensroles[m[0]forminmessages]tokens[m[1]forminmessages]labels[m[2]forminmessages]# 从后向前累积accumulated[]total0fortinreversed(tokens):totalt accumulated.insert(0,total)fig,axplt.subplots(figsize(14,5))colors[greenifaccbudgetelseredforaccinaccumulated]barsax.barh(range(len(messages)),tokens,colorcolors,alpha0.7)# 标注fori,(bar,label)inenumerate(zip(bars,labels)):ax.text(bar.get_width()50,bar.get_y()bar.get_height()/2,f{label}({tokens[i]}tok),vacenter,fontsize9)ax.axvline(xbudget,colorblue,linestyle--,labelfBudget ({budget}tok))ax.set_yticks(range(len(messages)))ax.set_yticklabels([f{r}#{i}fori,rinenumerate(roles)])ax.set_xlabel(Tokens)ax.set_title(Token-Budget Tail Protection (right-to-left accumulation))ax.legend()plt.tight_layout()plt.savefig(tail_budget.png,dpi150)六、总结与展望6.1 核心要点回顾固定轮次是穷人版保护无法区分消息的 token 消耗和价值Token-budget 按实际需求保护15% 的预算足够覆盖近期对话边界对齐防止配对断裂永远不从 tool_call/tool_result 中间切割用户锚定保证连贯性最后一条 user 消息是锚点不能被吞掉6.2 一个设计决策“Token-budget 的 15% 比例是如何确定的”这个值来自 Hermes-Agent 的实践。太小如 5%在短对话中可能只保护 1-2 条消息太大如 30%会挤占摘要空间。15% 在大多数场景下能保护 5-10 条近期消息是一个不错的平衡点。下期预告《结构化摘要从 6 字段到 10 字段的信息保全术》为什么活跃任务字段是最重要的新增10 字段模板的设计思路双语约束前缀的作用与原理敬请期待版权声明本文为 CSDN 博主「翁勇刚」的原创文章遵循 CC 4.0 BY-SA 版权协议转载请附上原文出处链接及本声明。