Token不够用?这是每个Agent开发者都会遇到的墙
上周一个朋友找我吐槽:他开发的AI客服Agent在处理复杂售后问题时总是"断片"——聊到第三轮就开始忘记前面说了什么,甚至重复问用户已经回答过的问题。他检查了代码逻辑没问题,最后发现是上下文窗口爆了。
这就是AI Agent开发中最容易被忽视、也最容易踩坑的问题:上下文窗口管理。主流大模型的上下文窗口虽然越来越大(豆包Seed 2.0支持256K,GPT-4o支持128K),但Token不是免费的,而且超长上下文反而会降低模型推理质量。如何在有限Token内让Agent保持高效运作,是区分新手和老手的关键能力。
这篇文章我会从实际踩坑经验出发,系统总结一套可落地的上下文优化方案,从架构设计到代码实现,每一步都是我在生产环境中验证过的。
先搞清楚:上下文窗口里到底装了什么
很多人以为上下文窗口只是"对话历史",实际上每次API调用时,发送给大模型的Token包含以下几部分:
| 组成部分 | 典型占比 | 优化空间 |
|---|---|---|
| System Prompt(系统指令) | 5-15% | 中(精简措辞、模块化) |
| 对话历史(messages) | 50-70% | 大(摘要压缩、滑动窗口) |
| 工具定义(Function Calling) | 10-20% | 大(动态加载、精简描述) |
| 检索结果(RAG Context) | 10-25% | 极大(重排序、截断) |
| 用户输入 | 3-5% | 小(用户说的话不能砍) |
这张表是我在一个真实项目中的实测数据。可以看到,对话历史和工具定义是Token消耗的大头,也是优化的主战场。
一个常见的误区是:以为买了256K上下文的模型就可以随便用。实际上,256K Token大约对应20万汉字,听起来很多,但如果你的Agent每次调用带50个工具定义加上10轮对话历史,轻松就能吃掉一半。
策略一:System Prompt精简——把废话全部砍掉
System Prompt是每个Agent的"人格设定",但很多人写System Prompt的时候像写小说,动辄上千Token。我见过最离谱的一个:开发者直接把一整份产品需求文档塞进了System Prompt,导致每次调用都要发送2000+ Token的固定开销。
优化原则很简单:只保留对当前任务有直接指导意义的内容。
一个实战技巧是分层设计System Prompt:
// 核心层(必带,每次都发送,控制在200 Token以内)
const corePrompt = `你是一个技术客服助手。回答要简洁准确。
当无法解决问题时,主动建议转人工。禁止编造技术参数。`;
// 能力层(按需注入,只在相关任务时发送)
const capabilityPrompts = {
billing: "你了解以下计费规则:基础版29元/月,专业版99元/月...",
technical: "产品技术栈:前端React+TypeScript,后端Go+gRPC...",
refund: "退款政策:7天无理由,超过7天需工单审核..."
};
// 动态组装
function buildSystemPrompt(taskType) {
return corePrompt + "\n\n" + (capabilityPrompts[taskType] || "");
}
这种分层设计在OpenClaw的Skill系统中天然支持——每个Skill自带精简的SKILL.md作为System Prompt,按需加载,不会一次性塞入所有技能说明。
关于System Prompt的设计优化,可以参考OpenClaw技能开发实战中的SKILL.md编写规范。
策略二:对话历史压缩——从200轮对话中提炼5句话
对话历史是Token增长最快的地方。一个活跃的Agent一天可能处理上千轮对话,如果不做管理,上下文很快就爆炸。
我在生产环境用过三种压缩策略,效果都很好:
- 滑动窗口 + 摘要:保留最近N轮完整对话,更早的对话用AI生成摘要替代。这是最实用的方案。
- 语义过滤:用小模型给每条对话打标签(重要/普通/无效),只保留重要对话和最近对话。
- 结构化提取:把对话中的关键信息(用户意图、已提供的数据、已执行的步骤)提取成结构化JSON,丢弃原始对话。
滑动窗口+摘要是我的首选方案,实现起来也不复杂:
async function compressContext(messages, maxTokens = 4000) {
if (countTokens(messages) <= maxTokens) return messages;
// 保留最近5轮完整对话
const recentMessages = messages.slice(-10);
const olderMessages = messages.slice(0, -10);
if (olderMessages.length === 0) return recentMessages;
// 用小模型对旧对话生成摘要
const summary = await callSmallModel(
"请用200字以内总结以下对话的关键信息," +
"包括:用户的核心需求、已确认的事实、已采取的步骤、未解决的问题。\n" +
olderMessages.map(m => m.role + ": " + m.content).join("\n")
);
// 摘要作为一条system消息插入
return [
{ role: "system", content: "历史对话摘要:" + summary },
...recentMessages
];
}
这里有一个关键细节:摘要请求用小模型(如豆包lite),不要用主模型。因为摘要生成是一个高频操作,用小模型能节省80%以上的成本。关于豆包模型的选型策略,可以参考豆包大模型API调用教程中的模型对比章节。
策略三:工具定义动态加载——不用的工具不要带
Function Calling的工具定义是另一个Token黑洞。我见过一个极端案例:Agent注册了78个工具,每次API调用都要发送78个工具的JSON Schema,光工具定义就占了8000+ Token。而用户一次交互最多只会用到2-3个工具。
解决方案是动态工具加载:根据用户意图只注入相关工具。这需要加一层"工具路由":
// 工具注册表(带标签)
const toolRegistry = {
search_knowledge: {
description: "搜索企业知识库",
tags: ["问答", "查询", "文档"],
schema: { /* ... */ }
},
create_ticket: {
description: "创建工单",
tags: ["投诉", "问题", "转人工"],
schema: { /* ... */ }
},
query_order: {
description: "查询订单状态",
tags: ["订单", "物流", "查询"],
schema: { /* ... */ }
}
// ... 更多工具
};
// 第一轮:用小模型分析意图,匹配工具
async function selectTools(userMessage) {
const toolList = Object.entries(toolRegistry)
.map(([name, tool]) => name + ": " + tool.description + " [" + tool.tags.join(",") + "]")
.join("\n");
const intent = await callSmallModel(
"用户消息:" + userMessage + "\n" +
"可用工具列表:\n" + toolList + "\n" +
"请返回最可能用到的3个工具名称,用逗号分隔。只返回名称。"
);
return intent.split(",").map(t => t.trim()).filter(t => toolRegistry[t]);
}
// 第二轮:只带匹配的工具调用主模型
const tools = await selectTools(userMessage);
const response = await callMainModel(messages, { tools: tools.map(t => toolRegistry[t].schema) });
这个两阶段调用虽然多了一次API请求,但节省了大量Token。实测中,工具定义从8000 Token降到了800 Token左右,总成本反而降低了。
OpenClaw的Skill系统本质上就是这种动态加载机制的实现——OpenClaw Skill技能市场使用教程中有详细的按需加载机制说明。
策略四:RAG检索结果精炼——不要把整个文档喂给模型
RAG(检索增强生成)是Agent获取外部知识的主要方式,但很多人的RAG实现有个致命问题:检索回来5篇文档,直接全部塞进上下文。一篇技术文档可能就有上万Token,5篇就是5万Token。
优化RAG结果的关键是"召回+精炼"两步走:
- 第一步:多路召回——从向量数据库召回Top 10片段,再用BM25关键词匹配召回Top 10,取交集
- 第二步:Rerank重排序——用Cross-Encoder模型对候选片段重新打分,只取Top 3
- 第三步:截断控制——对每个片段限制最大长度(建议500-800 Token),超出部分用摘要替代
async function getRefinedContext(query, maxTokens = 3000) {
// 多路召回
const vectorResults = await vectorSearch(query, topK = 10);
const bm25Results = await bm25Search(query, topK = 10);
// 取交集并去重
const mergedResults = mergeAndDedupe(vectorResults, bm25Results);
// Rerank
const rerankedResults = await rerank(query, mergedResults, topK = 3);
// Token预算控制:按比例截断
const budgetPerChunk = Math.floor(maxTokens / rerankedResults.length);
return rerankedResults.map(chunk => ({
text: chunk.text.slice(0, budgetPerChunk * 4),
score: chunk.score,
source: chunk.metadata.source
}));
}
关于RAG系统的完整搭建方案,可以参考AI知识库搭建教程中的检索优化章节。
策略五:分层记忆架构——短期、长期、任务级三管齐下
前面四种策略都是"省Token"的技巧,但更根本的解决方案是分层记忆架构:不把所有信息都塞进上下文窗口,而是建立多级存储。
| 记忆层级 | 存储方式 | 生命周期 | 典型内容 |
|---|---|---|---|
| 工作记忆 | 上下文窗口内 | 单次对话 | 当前任务状态、刚说的话 |
| 任务记忆 | 结构化文件/数据库 | 任务周期 | 任务目标、中间结果、待办事项 |
| 长期记忆 | 向量数据库 | 永久 | 用户偏好、历史总结、经验教训 |
OpenClaw的MEMORY.md和memory/目录就是这种分层记忆的实现。AI Agent记忆系统搭建教程中详细介绍了基于向量数据库的长期记忆实现方案。
我自己的实践经验是:工作记忆控制在4K Token以内,任务记忆在需要时按需拉取,长期记忆通过向量检索按需注入。这样既保证了Agent的连贯性,又不会让上下文爆炸。
一份数据说话:优化前后的对比
我把这套优化方案用在一个真实的生产级客服Agent上,效果如下:
| 指标 | 优化前 | 优化后 | 改善幅度 |
|---|---|---|---|
| 单次调用平均Token | 12500 | 3200 | 降低74% |
| 月度API费用 | 约4200元 | 约1100元 | 节省74% |
| 回答质量(人工评估) | 7.2/10 | 8.5/10 | 提升18% |
| 上下文超限错误率 | 每周3-5次 | 0次 | 完全消除 |
| 响应速度(P95) | 3.8秒 | 1.2秒 | 提升68% |
最有意思的是回答质量反而提升了。这证实了AI圈的一个共识:更短的上下文不等于更差的效果。冗余信息会干扰模型的注意力,精炼的上下文反而让模型更聚焦。
踩坑清单:这些坑我帮你踩过了
- 摘要不要丢关键数字:对话摘要时务必保留具体数值(订单号、金额、日期),不要用"某个订单"这种模糊表述。我为此专门在摘要Prompt里加了"必须保留所有数字和ID"的指令
- 工具路由用小模型可能误判:小模型对工具匹配的准确率约85%,意味着有15%的情况会漏掉需要的工具。解决方案是设置一个兜底机制——如果主模型请求了未加载的工具,自动补充加载
- Token计算要留安全余量:不同分词器的计算结果可能差异5-10%,建议预留15%的缓冲空间,避免刚好卡在边界上
- 不要每轮都压缩:频繁调用摘要模型会增加延迟。我一般设置为每10轮对话或Token超过阈值时才触发压缩
- 测试要用真实数据:Token消耗在不同语言(中/英)、不同内容类型(代码/自然语言/表格)下差异很大,不要用假数据做测试
写在最后
上下文窗口优化不是一个"锦上添花"的技巧,而是把Agent从Demo推向生产的必经之路。Demo里聊5轮就够了,但生产环境的Agent需要处理成百上千轮对话、几十个工具、海量检索结果——没有好的Token管理策略,再强的模型也会变成"金鱼记忆"。
核心方法论可以总结为一句话:不重要的信息不要进上下文,进了上下文的信息要精炼,精炼不了的存到外部记忆中按需拉取。这套思路不仅适用于大模型对话,也适用于所有资源受限场景下的智能系统设计。
如果你正在搭建自己的AI Agent,推荐从OpenClaw办公自动化实战入门,它内置的Skill系统和记忆管理机制已经帮你处理了大部分上下文管理的工作。
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论