黄仁勋喊出"下一个ChatGPT"后,我用OpenClaw新版ContextEngine给公司Agent系统省了40%的Token费

0 阅读5分钟

黄仁勋喊出"下一个ChatGPT"后,我用OpenClaw新版ContextEngine给公司Agent系统省了40%的Token费

背景

昨天OpenClaw封神——黄仁勋GTC定性"下一个ChatGPT",腾讯QClaw升级微信小程序,官方发布v2026.3.7-beta。

前两条是资本市场的事,第三条跟我直接相关。

我们公司之前基于OpenClaw架构搭了一套内部Agent系统(之前那篇文章写过,替代了3个外包岗),每月API成本约¥8000。新版的可插拔ContextEngine让我看到了一个明确的优化方向——自定义上下文组装策略,砍掉无效Token消耗。

花了一天时间写了个自定义Engine,实测效果:Token消耗降低42%,月度API成本从¥8000降到¥4600,输出质量基本无感知下降。

这篇文章记录具体怎么做的。


问题分析:Token都花在哪了

在做优化之前,先搞清楚Token浪费在哪。我加了一周的埋点,统计每次模型调用的上下文构成:

平均每次调用的Token分布(旧版):
├── 系统提示词:  2100 tokens (18%)
├── 用户消息:    180 tokens  (2%)
├── 对话历史:   4200 tokens (36%)  ← 最大头
├── 记忆检索:   2800 tokens (24%)  ← 第二大
├── Skill上下文:  900 tokens  (8%)
├── 工具定义:   1400 tokens (12%)
└── 总计:      11580 tokens

两个明显的浪费点:

对话历史占36%,但大量是无用的。  我们的客服Agent平均一个工单对话10轮,但80%的有效信息集中在前2轮(客户描述问题)和最后2轮(正在处理的内容)。中间的确认、追问、等待回复,纯粹浪费Token。

记忆检索占24%,命中率只有45%。  默认的向量检索返回5条最相似的记忆,但其中近一半跟当前任务无关。这些不相关的记忆不仅浪费Token,还可能干扰模型的判断。


解法:自定义ContextEngine

核心思路

写一个SmartBudgetEngine,做三件事:

  1. 1. 对话历史智能压缩:只保留首轮+末2轮,中间的用摘要替代
  2. 2. 记忆检索提高阈值:相似度门槛从0.7提到0.82,只注入高相关记忆
  3. 3. 工具动态裁剪:根据用户消息内容,只注入可能用到的工具

实现

// smart-budget-engine/index.ts

interface ContextEngine {
  namestring;
  assemble(inputAssembleInput): Promise<AssembleOutput>;
  allocateBudget(maxTokensnumberctxBudgetContext): TokenBudget;
  retrieveMemory(querystringoptsRetrievalOptions): Promise<MemoryEntry[]>;
  filterTools(toolsTool[], contextToolFilterContext): Tool[];
  compressHistory(messagesMessage[], maxTokensnumber): Promise<Message[]>;
}

class SmartBudgetEngine implements ContextEngine {
  name = 'smart-budget';

  allocateBudget(maxTokensnumberctxBudgetContext): TokenBudget {
    const reserved = 2500;
    const available = maxTokens - reserved;
    
    // 根据任务类型动态调整比例
    const taskType = this.detectTaskType(ctx.userMessage);
    
    const profilesRecord<stringTokenBudget> = {
      // 客服场景:记忆优先
      'customer_service': {
        systemPrompt1800userMessage700,
        historyMath.floor(available * 0.2),
        memoryMath.floor(available * 0.45),
        skillsMath.floor(available * 0.1),
        toolsMath.floor(available * 0.25),
      },
      // 数据处理:工具优先
      'data_processing': {
        systemPrompt1500userMessage1000,
        historyMath.floor(available * 0.15),
        memoryMath.floor(available * 0.15),
        skillsMath.floor(available * 0.2),
        toolsMath.floor(available * 0.5),
      },
      // 默认:均衡
      'default': {
        systemPrompt1800userMessage700,
        historyMath.floor(available * 0.25),
        memoryMath.floor(available * 0.3),
        skillsMath.floor(available * 0.15),
        toolsMath.floor(available * 0.3),
      }
    };

    return profiles[taskType] || profiles['default'];
  }

  async compressHistory(
    messagesMessage[], maxTokensnumber
  ): Promise<Message[]> {
    if (messages.length <= 6return messages;

    // 保留首轮(问题描述)+ 最后2轮(当前进展)
    const first = messages.slice(02);
    const recent = messages.slice(-4);
    const middle = messages.slice(2, -4);

    // 中间部分用一句摘要替代
    const middleSummary = await this.summarizeMessages(middle);
    
    return [
      ...first,
      { role'system'content`[${middle.length}条中间对话摘要] ${middleSummary}` },
      ...recent,
    ];
  }

  async retrieveMemory(
    querystringoptsRetrievalOptions
  ): Promise<MemoryEntry[]> {
    const results = await opts.defaultRetrieval(query, opts.limit * 2);
    
    // 提高相似度阈值:0.7 → 0.82
    const highRelevance = results.filter(m => m.similarity > 0.82);
    
    // 如果高相关结果不足2条,放宽到0.75保底
    if (highRelevance.length < 2) {
      return results.filter(m => m.similarity > 0.75).slice(03);
    }
    
    return highRelevance.slice(0, opts.limit);
  }

  filterTools(toolsTool[], contextToolFilterContext): Tool[] {
    const msg = context.userMessage.toLowerCase();
    
    // 关键词匹配:只保留跟用户消息相关的工具
    const scored = tools.map(tool => ({
      tool,
      relevancethis.toolRelevanceScore(tool, msg)
    }));

    // 保留相关度>0的工具,至少保留5个基础工具
    const relevant = scored.filter(s => s.relevance > 0);
    if (relevant.length >= 5) {
      return relevant.sort((a, b) => b.relevance - a.relevance)
                      .slice(010)
                      .map(s => s.tool);
    }
    
    // 不足5个时,补充通用工具
    return scored.sort((a, b) => b.relevance - a.relevance)
                 .slice(05)
                 .map(s => s.tool);
  }

  private detectTaskType(messagestring): string {
    const keywords = {
      customer_service: ['工单''客户''投诉''退款''咨询'],
      data_processing: ['数据''报表''统计''导出''excel'],
    };
    
    for (const [type, words] of Object.entries(keywords)) {
      if (words.some(w => message.includes(w))) return type;
    }
    return 'default';
  }

  private toolRelevanceScore(toolToolmessagestring): number {
    const toolWords = (tool.name + ' ' + tool.description).toLowerCase().split(/\s+/);
    const msgWords = message.split(/\s+/);
    const overlap = toolWords.filter(w => msgWords.some(m => m.includes(w) || w.includes(m)));
    return overlap.length / toolWords.length;
  }

  private async summarizeMessages(messagesMessage[]): Promise<string> {
    // 用小模型快速生成摘要,控制成本
    const content = messages.map(m => `${m.role}${m.content}`).join('\n');
    // 实际项目中调用一个低成本模型做摘要
    return `对话中讨论了${messages.length / 2}个来回,主要涉及问题确认和方案讨论`;
  }
}

module.exports = SmartBudgetEngine;

效果数据

部署一周后的对比数据:

指标旧版(默认Engine)新版(SmartBudget)变化
平均输入Token/次11,5806,720-42%
记忆检索命中率45%78%+33pp
对话历史Token占比36%18%-18pp
月度API成本¥8,000¥4,600-42.5%
工单分类准确率93.7%92.4%-1.3pp
周报生成质量(人工评分)4.2/54.0/5-0.2

Token消耗降了42%,输出质量只降了1-2个百分点。对于内部工具级别的应用,这个trade-off完全可以接受。


关键经验

1. 先埋点再优化。  不看数据就优化是盲人摸象。花一周时间统计Token分布,优化方向自然就清楚了。

2. 对话历史是最大的浪费源。  Agent的多轮对话里,大量内容是重复确认和等待回复。"首轮+末轮+中间摘要"的策略性价比最高。

3. 记忆检索的相似度阈值默认太低。  0.7的门槛会放进来很多噪音。根据场景调到0.8-0.85之间,命中率和Token效率都能提升。

4. 工具列表不需要全塞进去。  20个工具定义占4000 tokens,但大多数对话只会用到2-3个工具。按关键词动态裁剪,省Token又减少模型"选择困难"。

ContextEngine是v2026.3.7最值得投入研究的新能力。如果你的Agent系统月API成本超过5000块,花一天时间写个自定义Engine,大概率能回本。

有问题评论区聊。