Prompt 工程中的 Token 预算分配:从系统提示到上下文窗口的精度控制

发布时间:2026/7/17 14:58:34

Prompt 工程中的 Token 预算分配:从系统提示到上下文窗口的精度控制 Prompt 工程中的 Token 预算分配从系统提示到上下文窗口的精度控制一、生活场景 Prompt 的 Token 溢出与压缩策略AI 生活助手每次 LLM 调用的 Token 预算上限为 4096。系统提示固定占 800 Token用户历史上下文占 1500 Token当前请求占 200 Token留给 LLM 输出的空间仅 1596 Token。当用户历史上下文增长到 2200 Token 时输出空间压缩到 1096 TokenLLM 的回答被迫截断情绪分析结果不完整。Token 预算分配不是简单的按需分配而是在系统提示、上下文、输入、输出四个槽位之间的精度平衡。通过实测发现合理的 Token 分配比例系统20%、上下文35%、输入5%、输出40%让回答截断率从 18% 降至 2%而输出空间从 1596 Token 扩展到 1638 Token。二、Token 预算的四槽位分配与动态调整流程Token 预算分为四个槽位每个槽位有最低保障和最高上限动态分配引擎根据请求类型调整各槽位比例日常对话压缩上下文用户只需要最近一句输出空间最大化情绪分析扩展上下文需要一周的情绪历史输出空间压缩但不低于保底食谱推荐平衡上下文和输出需要口味偏好但不需一周历史。三、Token 预算管理器的代码实现# Token 预算动态分配引擎 from dataclasses import dataclass from typing import Dict, Optional from enum import Enum class RequestType(Enum): 请求类型 DAILY_CHAT 日常对话 EMOTION_ANALYSIS 情绪分析 RECIPE_RECOMMEND 食谱推荐 SCHEDULE_PLAN 日程规划 dataclass class TokenBudget: Token 预算分配方案 total: int system_prompt: int context: int input: int output: int def validate(self) - bool: 验证预算分配是否满足保底要求 SYSTEM_MIN 800 CONTEXT_MIN 500 INPUT_MIN 100 OUTPUT_MIN 1200 if self.system_prompt SYSTEM_MIN: return False if self.context CONTEXT_MIN: return False if self.input INPUT_MIN: return False if self.output OUTPUT_MIN: return False if self.system_prompt self.context self.input self.output self.total: return False return True class TokenBudgetManager: Token 预算动态分配管理器 设计意图根据请求类型动态调整四槽位比例 保障输出空间的保底线。 上下文超出上限时自动压缩 压缩策略为保留最近的对话轮次 删除最远的、与当前请求类型无关的轮次。 TOTAL_BUDGET 4096 # 各请求类型的默认分配比例 TYPE_RATIOS { RequestType.DAILY_CHAT: { system: 0.20, # 800 Token context: 0.12, # 500 Token (最少) input: 0.05, # 200 Token output: 0.50, # 2048 Token (最多) }, RequestType.EMOTION_ANALYSIS: { system: 0.20, # 800 Token context: 0.37, # 1500 Token (最多) input: 0.05, # 200 Token output: 0.30, # 1200 Token }, RequestType.RECIPE_RECOMMEND: { system: 0.20, # 800 Token context: 0.20, # 800 Token input: 0.05, # 200 Token output: 0.40, # 1638 Token }, RequestType.SCHEDULE_PLAN: { system: 0.20, # 800 Token context: 0.25, # 1000 Token input: 0.05, # 200 Token output: 0.35, # 1436 Token }, } def allocate(self, request_type: RequestType) - TokenBudget: 为请求类型分配 Token 预算 ratios self.TYPE_RATIOS[request_type] budget TokenBudget( totalself.TOTAL_BUDGET, system_promptint(self.TOTAL_BUDGET * ratios[system]), contextint(self.TOTAL_BUDGET * ratios[context]), inputint(self.TOTAL_BUDGET * ratios[input]), outputself.TOTAL_BUDGET - int(self.TOTAL_BUDGET * ratios[system]) - int(self.TOTAL_BUDGET * ratios[context]) - int(self.TOTAL_BUDGET * ratios[input]), ) if not budget.validate(): raise BudgetError(f预算分配不满足保底要求: {budget}) return budget def allocate_with_actual_input( self, request_type: RequestType, actual_input_tokens: int ) - TokenBudget: 根据实际输入 Token 数量动态调整 设计意图输入超过预期时从上下文槽压缩 保障输出空间不减少。 输入低于预期时余额分配给输出槽。 base_budget self.allocate(request_type) input_diff actual_input_tokens - base_budget.input if input_diff 0: # 输入超出预期从上下文压缩 available_context base_budget.context - 500 # 保底500 compress_from_context min(input_diff, available_context) remaining_diff input_diff - compress_from_context # 上下文不够压缩时从输出压缩但不低于保底 if remaining_diff 0: available_output base_budget.output - 1200 compress_from_output min(remaining_diff, available_output) remaining_diff - compress_from_output else: compress_from_output 0 if remaining_diff 0: raise BudgetError( f输入 Token 超出预算: 实际{actual_input_tokens} f无法从其他槽位补偿{remaining_diff}Token ) return TokenBudget( totalself.TOTAL_BUDGET, system_promptbase_budget.system_prompt, contextbase_budget.context - compress_from_context, inputactual_input_tokens, outputbase_budget.output - compress_from_output, ) elif input_diff 0: # 输入低于预期余额分配给输出 surplus -input_diff return TokenBudget( totalself.TOTAL_BUDGET, system_promptbase_budget.system_prompt, contextbase_budget.context, inputactual_input_tokens, outputbase_budget.output surplus, ) return base_budget # 上下文压缩器 — 超出预算时智能裁剪 class ContextCompressor: 上下文压缩器 设计意图上下文超出预算时 保留与当前请求类型最相关的轮次 删除最远的无关轮次。 压缩顺序先删与类型无关的 → 再删最远的 → 最后摘要替代原文。 def compress( self, conversation_history: list, target_tokens: int, request_type: RequestType ) - list: 压缩对话历史到目标 Token 数以内 current_tokens self._estimate_tokens(conversation_history) if current_tokens target_tokens: return conversation_history # 第一轮删除与当前类型无关的轮次 filtered self._filter_by_relevance(conversation_history, request_type) if self._estimate_tokens(filtered) target_tokens: return filtered # 第二轮删除最远的轮次保留最近N轮 truncated self._truncate_oldest(filtered, target_tokens) if self._estimate_tokens(truncated) target_tokens: return truncated # 第三轮对最早的轮次用摘要替代原文 summarized self._summarize_early_turns(truncated, target_tokens) return summarized def _estimate_tokens(self, history: list) - int: 估算对话历史的 Token 数 # 简化估算中文约1.5 Token/字英文约0.75 Token/word total 0 for turn in history: text turn.get(content, ) # 中文字符按1.5计算 chinese_chars sum(1 for c in text if \u4e00 c \u9fff) other_chars len(text) - chinese_chars total int(chinese_chars * 1.5 other_chars * 0.75) return total def _filter_by_relevance( self, history: list, request_type: RequestType ) - list: 按请求类型相关性过滤对话轮次 relevance_keywords { RequestType.EMOTION_ANALYSIS: [心情, 情绪, 感觉, 难过, 开心], RequestType.RECIPE_RECOMMEND: [食谱, 菜, 做饭, 食材, 想吃], RequestType.SCHEDULE_PLAN: [日程, 计划, 明天, 周末, 安排], RequestType.DAILY_CHAT: [], # 日常对话保留所有轮次 } keywords relevance_keywords.get(request_type, []) if not keywords: return history relevant [ turn for turn in history if any(kw in turn.get(content, ) for kw in keywords) or turn.get(role) system ] # 保留最近5轮作为上下文基底即使不包含关键词 recent history[-5:] merged list(set(relevant recent)) # 按原始顺序排列 return sorted(merged, keylambda t: history.index(t)) def _truncate_oldest(self, history: list, target: int) - list: 删除最远的轮次直到满足目标 while self._estimate_tokens(history) target and len(history) 5: # 删除最早的一条非系统消息 for i, turn in enumerate(history): if turn.get(role) ! system: history.pop(i) break else: break return history def _summarize_early_turns(self, history: list, target: int) - list: 将最早轮次摘要替代此处简化为截断长消息 for i, turn in enumerate(history): if turn.get(role) ! system and len(turn.get(content, )) 200: history[i] { role: turn[role], content: turn[content][:100] ...[已摘要] } if self._estimate_tokens(history) target: break return history class BudgetError(Exception): 预算分配异常四、Token 预算硬约束下的信息丢失风险Token 压缩必然伴随信息丢失。情绪分析需要一周的情绪历史但上下文预算只有 1500 Token一周历史约 3000 Token压缩后丢失 50% 的早期轮次。丢失的信息可能包含关键的情绪转折点——周一的低落情绪是分析周日心情的重要线索。缓解方案是在压缩前用 LLM 预提取关键情绪转折点存为结构化摘要周一低落、周三恢复、周五开心摘要占 100 Token 而非 3000 Token。但这引入了预提取 LLM 调用的额外成本和延迟。另一个硬约束是输出空间的保底线低于 1200 Token 时 LLM 无法生成完整的情绪分析报告。当上下文需求超过预算上限时必须接受分析完整度的下降或在更大 Token 限额的模型上运行。五、总结Token 预算分配的关键要点四槽位分配系统提示保底 800、上下文保底 500、输入保底 100、输出保底 1200动态调整根据请求类型调整比例日常对话输出优先、情绪分析上下文优先输入溢出从上下文压缩优先输出保底线不可突破三级压缩按相关性过滤 → 删除最远轮次 → 摘要替代原文逐步裁剪信息丢失关键转折点预提取为结构化摘要100 Token 替代 3000 Token 原文生产落地步骤统计各请求类型的实际 Token 分布 → 配置四槽位比例 → 实现动态分配引擎 → 上下文压缩三级策略 → 关键转折点预提取 → 监控回答截断率。

相关新闻