面对复杂的多步推理任务,如何动态分配上下文中各个模块(System prompt, RAG片段, 历史记录, 思维链)的Token配额?
面对复杂的多步推理任务,Token的分配就像是在管理一台计算机的“内存”和“CPU算力”。如果上下文(输入)塞得太满,模型就会缺乏足够的“思考空间”(输出CoT)来完成推理,甚至引发“中间迷失”(Lost in the middle)现象。
要实现真正的动态Token分配,需要建立一套优先级机制和路由算法。以下是构建这套动态分配系统的核心策略与工程实践:
一、 建立Token优先级的“马斯洛需求层次”
在动态计算分配时,必须遵循严格的优先级兜底机制:
- 绝对优先(不可妥协): System Prompt(系统指令) + 当前 User Query(用户问题)
- 推理红线(强依赖): 预留给 CoT (思维链) 的生成空间(Output Tokens)
- 弹性空间A(知识支撑): RAG 检索片段
- 弹性空间B(语境支撑): 历史记录 (Chat History)
二、 各模块的动态管理策略
1. 思维链 (CoT) - 预留“思考空间”
复杂多步推理最怕的是“话没说完Token耗尽”。
- 动态策略: 引入一个轻量级意图分类器(可以使用小模型如Qwen-1.5B或基于规则),预判当前任务的推理步数。
- 配额分配:
- 简单问答:预留 500 tokens
- 复杂多步分析/代码生成:强制预留
总预算的 40% - 50%(例如 8k 窗口预留 3k-4k tokens)。
2. RAG 片段 - 从“静态Top-K”到“动态阈值”
传统RAG固定取Top-5,这在多步推理中极其浪费Token。
- 动态策略:
- 分数阈值截断(Threshold Cutoff): 经过重排序(Reranker)后,只保留相关性得分大于
0.75的片段。如果没有高分片段,宁可只给1个片段,也不凑数。 - Token填充法(Token-Filling): 根据剩余Token预算,按相关性分数从高到低依次填入,直到触达RAG分配上限。
- 上下文压缩: 使用
LLMLingua等Prompt压缩技术,将冗长的RAG文本提炼成高信息密度的片段。
- 分数阈值截断(Threshold Cutoff): 经过重排序(Reranker)后,只保留相关性得分大于
3. 历史记录 (History) - 动态衰减与折叠
多步推理任务往往需要回顾之前的推理路径,但不能带入所有闲聊。
- 动态策略:
- 滑动窗口 + 动态摘要: 最近的 2-3 轮对话保留原文(约占历史预算的70%),更早的对话交给后台的小模型生成100字的全局摘要(占30%)。
- 相关性召回(Vectorized History): 把历史记录也当成RAG来做。只把与当前多步推理相关的历史轮次作为Context带入。
4. System Prompt - 模块化按需组装
不要把所有的规则(格式要求、约束、人设)都写死在System中。
- 动态策略: 将System Prompt组件化。如果当前任务是“数学推理”,则动态注入数学相关的System指令;如果是“总结”,则注入排版指令。保持System极简(建议控制在总体Token的 5-10%)。
三、 动态分配算法工作流(工程实现)
你可以通过一个 Orchestrator(编排器,如LangChain/LlamaIndex中的自定义逻辑)来实现以下动态分配公式:
设定已知:
- = 模型的最大上下文窗口 (例如 8192)
- = 预留的安全缓冲Token (例如 200)
计算步骤:
- 计算可用总预算:
- 锁定核心与输出:
- 计算当前 和 的真实Token消耗。
- 动态预测所需的 预留量(基于复杂度)。
- 剩余可变预算 () =
- 动态切分剩余预算 ():
- 引入一个动态调节因子 ()。
- 如果当前Query依赖外部知识, 设为 0.8(RAG拿80%剩余预算,History拿20%)。
- 如果当前Query是多轮追问(如“根据你刚才说的第二点继续推导”), 设为 0.2(History拿80%,RAG拿20%)。
- 填充与截断: 根据切分后的配额,分别对 RAG 和 History 执行“贪婪填充+截断”。
四、 进阶:超越单次Prompt的 Agentic 分配
面对极其复杂的多步推理,最好的Token分配策略就是“不放在一次Prompt里完成”。
ReAct / Plan-and-Solve 架构:
将一个需要 10k token上下文和 5k token CoT 的巨型任务,拆解为 5 个小任务。- Step 1 (Plan): 给模型极少的RAG,让它输出推理计划。
- Step 2-4 (Execute): 每次只带入当前步骤需要的一个RAG片段和上一步的结论(浓缩History)进行单步推理。
- 这种方法将“空间上的Token分配”转化为了“时间上的Token分配”,极大降低了单次调用的上下文压力。
自适应缓存 (Context Caching):
如果你使用的是支持 Prompt Caching 的模型(如 Claude 3.5 Sonnet 或 Gemini 1.5 Pro),可以将固定的 System Prompt 和超长的全量 RAG 知识库预先 Cache。这样你只需动态调整 History 和 CoT 的分配,不再受制于传统的Token限制焦虑。
总结
有效的动态分配不是写死几个 max_tokens 参数,而是构建一个“输入感知 -> 预算计算 -> 动态填充 -> 截断/压缩”的闭环。对于多步推理,“保CoT输出、压RAG质量、缩历史记录”是最核心的实战原则。