上周用 OpenClaw 接了个微信小助手的需求,功能不复杂——日程管理加信息摘要。跑了三天,Token 消耗直接飙到预算的 4 倍。看着后台用量曲线,整个人都不好了。

OpenClaw 能力确实强,阿里算力加持下推理速度也快,但不做 Token 优化,成本会把你吃穷。

核心思路就三个:压缩上下文、缓存高频请求、动态切换模型。实测组合使用可以把 Token 开销降 50%-70%,下面一个个拆。

先说结论

优化策略 实施难度 Token 节省比例 适用场景
Prompt 压缩 + 上下文裁剪 ⭐⭐ 30%-40% 所有场景
语义缓存层 ⭐⭐⭐ 20%-50% 高频重复查询
动态模型路由 ⭐⭐ 30%-60% 多任务混合场景

三个方案可以叠加,我最终那个微信小助手项目综合下来 Token 消耗降了约 63%。

环境准备

我的开发环境:

  • Python 3.11+
  • openai SDK(OpenClaw 兼容 OpenAI 协议)
  • redis(做缓存层用)
  • tiktoken(Token 计数)
pip install openai tiktoken redis

OpenClaw 走标准 OpenAI 兼容协议,用 openai SDK 直接调。我这边统一走聚合接口,方便后面做模型路由切换:

from openai import OpenAI

client = OpenAI(
 api_key="your-key",
 base_url="https://api.ofox.ai/v1" # 聚合接口,一个 Key 调 OpenClaw 和其他模型
)

方案一:Prompt 压缩 + 上下文裁剪

最简单也是回报最高的优化。很多人写 Prompt 跟写作文似的,废话一大堆,Token 就是这么烧掉的。

1.1 System Prompt 瘦身

我原来的 System Prompt 长这样(别笑,很多人都这么写):

# ❌ 反面教材:178 tokens
system_prompt_bad = """
你是一个非常专业的日程管理助手。你需要帮助用户管理他们的日程安排,
包括但不限于创建新的日程、修改已有的日程、删除日程、查询某个时间段的日程安排。
你应该用友好、专业的语气回复用户。当用户的请求不明确时,你应该主动询问更多细节。
你需要以 JSON 格式返回结构化的日程数据,包含 title、start_time、end_time、description 等字段。
请注意,所有时间都应该使用 ISO 8601 格式。
"""

优化后:

# ✅ 优化后:62 tokens
system_prompt_good = """日程助手。操作:创建/修改/删除/查询。
输出JSON:{title,start_time,end_time,desc},时间用ISO8601。
不明确时追问。"""

效果一样,Token 少了 65%。模型不需要你用「非常专业的」「包括但不限于」这种修饰语,它理解意图的能力比你想象的强。

1.2 对话历史滑动窗口

这才是 Token 消耗的大头。微信聊天场景下用户可能连续聊几十轮,每次把完整历史全塞进去,Token 会指数级增长。

import tiktoken

def count_tokens(messages, model="gpt-4o"):
 """计算消息列表的 token 数"""
 enc = tiktoken.encoding_for_model(model)
 total = 0
 for msg in messages:
 total += len(enc.encode(msg["content"])) + 4 # 每条消息的 overhead
 return total

def trim_conversation(messages, max_tokens=2000):
 """
 滑动窗口裁剪:保留 system prompt + 最近 N 轮对话
 超出 max_tokens 时,从最早的用户消息开始砍
 """
 system_msgs = [m for m in messages if m["role"] == "system"]
 chat_msgs = [m for m in messages if m["role"] != "system"]
 
 # 从最新的消息往前保留,直到塞不下
 trimmed = []
 current_tokens = count_tokens(system_msgs)
 
 for msg in reversed(chat_msgs):
 msg_tokens = count_tokens([msg])
 if current_tokens + msg_tokens > max_tokens:
 break
 trimmed.insert(0, msg)
 current_tokens += msg_tokens
 
 return system_msgs + trimmed


# 实际使用
conversation = [
 {"role": "system", "content": system_prompt_good},
 # ... 可能有 30 轮对话历史
]

# 裁剪到 2000 tokens 以内
trimmed = trim_conversation(conversation, max_tokens=2000)
response = client.chat.completions.create(
 model="openclaw", # 通过聚合接口调用
 messages=trimmed,
 max_tokens=500
)

实测 30 轮对话的场景,不裁剪每次请求大概 4000-6000 tokens,裁剪后稳定在 1500-2000 tokens。光这一步就砍掉了 60% 的输入 Token。

1.3 摘要压缩(进阶)

滑动窗口有个问题——早期上下文丢了。用户第 3 轮说了个重要信息,到第 20 轮已经被裁掉了。

解决方案:对被裁掉的历史做摘要,塞进 system prompt。

def summarize_old_context(old_messages):
 """用便宜的小模型给旧对话做摘要"""
 summary_response = client.chat.completions.create(
 model="deepseek-v3", # 用便宜模型做摘要
 messages=[
 {"role": "system", "content": "用50字内总结以下对话的关键信息,只保留事实。"},
 {"role": "user", "content": str(old_messages)}
 ],
 max_tokens=100
 )
 return summary_response.choices[0].message.content


def smart_trim(messages, max_tokens=2000):
 """智能裁剪:旧消息摘要 + 新消息保留"""
 system_msgs = [m for m in messages if m["role"] == "system"]
 chat_msgs = [m for m in messages if m["role"] != "system"]
 
 if count_tokens(messages) <= max_tokens:
 return messages
 
 # 找到需要裁掉的部分
 keep_msgs = []
 cut_msgs = []
 current_tokens = count_tokens(system_msgs) + 200 # 预留摘要空间
 
 for msg in reversed(chat_msgs):
 msg_tokens = count_tokens([msg])
 if current_tokens + msg_tokens > max_tokens:
 cut_msgs = chat_msgs[:chat_msgs.index(msg) + 1]
 break
 keep_msgs.insert(0, msg)
 current_tokens += msg_tokens
 
 if cut_msgs:
 summary = summarize_old_context(cut_msgs)
 system_msgs[0]["content"] += f"\n[历史摘要]{summary}"
 
 return system_msgs + keep_msgs

摘要用 DeepSeek V3 这种便宜模型跑,成本几乎可以忽略,但保住了关键上下文。

方案二:语义缓存层

微信小助手上线后发现一个规律:40% 的请求在语义上是重复的。比如不同用户都在问「明天天气怎么样」「帮我看看明天天气」「明天天气」,意思一模一样,每次都调 API 太浪费了。

import redis
import hashlib
import json

r = redis.Redis(host='localhost', port=6379, db=0)

def get_cache_key(messages):
 """
 生成缓存 key
 简单方案:直接对最后一条用户消息做 hash
 进阶方案:可以用 embedding 做语义相似度匹配
 """
 last_user_msg = ""
 for msg in reversed(messages):
 if msg["role"] == "user":
 last_user_msg = msg["content"]
 break
 # 归一化:去空格、转小写
 normalized = last_user_msg.strip().lower()
 return f"llm_cache:{hashlib.md5(normalized.encode()).hexdigest()}"


def cached_completion(messages, model="openclaw", ttl=3600):
 """带缓存的 API 调用"""
 cache_key = get_cache_key(messages)
 
 # 查缓存
 cached = r.get(cache_key)
 if cached:
 print("[Cache HIT] 省了一次 API 调用")
 return json.loads(cached)
 
 # 缓存未命中,调 API
 response = client.chat.completions.create(
 model=model,
 messages=messages,
 max_tokens=500
 )
 
 result = {
 "content": response.choices[0].message.content,
 "usage": {
 "prompt_tokens": response.usage.prompt_tokens,
 "completion_tokens": response.usage.completion_tokens
 }
 }
 
 # 写缓存,TTL 1 小时
 r.setex(cache_key, ttl, json.dumps(result, ensure_ascii=False))
 print(f"[Cache MISS] 消耗 tokens: {response.usage.total_tokens}")
 
 return result

这个方案虽然简单粗暴(纯文本 hash),在我的场景下缓存命中率达到了 35%。想做得更精细可以用 embedding 算语义相似度,命中率能到 50% 以上,但要多一次向量计算的开销,小项目可能不值得。

方案三:动态模型路由

这是我觉得最"聪明"的优化——不是所有请求都需要用最贵的模型。

思路很简单:简单问题用便宜模型,复杂问题才上 OpenClaw 或 Claude Opus 4.6。

def classify_complexity(user_message):
 """
 简单的复杂度分类器
 实际项目中可以用关键词匹配 + 小模型判断
 """
 simple_patterns = ["你好", "谢谢", "查询", "几点", "天气", "帮我看看"]
 complex_patterns = ["分析", "对比", "总结", "规划", "建议", "方案"]
 
 msg = user_message.lower()
 
 for p in complex_patterns:
 if p in msg:
 return "complex"
 for p in simple_patterns:
 if p in msg:
 return "simple"
 
 # 默认中等
 return "medium"


def route_model(user_message):
 """根据复杂度选择模型"""
 complexity = classify_complexity(user_message)
 
 model_map = {
 "simple": "deepseek-v3", # 简单问题,成本约 0.001 元/次
 "medium": "openclaw", # 中等问题
 "complex": "claude-opus-4.6", # 复杂推理
 }
 
 model = model_map[complexity]
 print(f"[Router] 复杂度={complexity}, 使用模型={model}")
 return model


# 实际调用
def smart_chat(messages):
 last_msg = ""
 for m in reversed(messages):
 if m["role"] == "user":
 last_msg = m["content"]
 break
 
 model = route_model(last_msg)
 
 response = client.chat.completions.create(
 model=model,
 messages=messages,
 max_tokens=500
 )
 return response

跑了一周数据,请求分布大概是:简单 45%、中等 35%、复杂 20%。算下来整体成本比全部用 OpenClaw 低了约 55%。

整体架构长这样:

命中

未命中

简单

中等

复杂

用户消息 - 微信

上下文裁剪/摘要压缩

语义缓存命中?

直接返回缓存结果

复杂度分类器

DeepSeek V3

OpenClaw

Claude Opus 4.6

写入缓存

返回用户

DeepSeek V3、OpenClaw、Claude Opus 4.6 都通过 ofox.ai 的聚合接口调,一个 API Key 切换不同模型,不用分别管各家的鉴权和 SDK。ofox.ai 是一个 AI 模型聚合平台,兼容 OpenAI 协议,低延迟直连,支持支付宝/微信付款,做模型路由只需要换 model 参数,代码完全不用改。

踩坑记录

坑 1:tiktoken 不支持 OpenClaw 的 tokenizer

tiktoken 目前没有 OpenClaw 的编码器,我用 gpt-4o 的编码器近似计算,实测误差在 5%-10% 左右。做预算控制够用了,但精确计费还是以 API 返回的 usage 为准。

坑 2:缓存 key 的归一化不够

一开始只做了去空格+转小写,结果「查日程」和「帮我查一下日程」就对不上。后来加了停用词过滤,命中率从 20% 提到了 35%。上 embedding 方案能更高,但要多一次向量计算的开销,小项目可能不值得。

坑 3:模型路由的分类器别用大模型做

我一开始想用 LLM 来判断复杂度,结果判断本身就要消耗 Token——脱裤子放屁。后来改成关键词匹配加消息长度启发式规则,零成本搞定。

小结

三招总结:

  1. Prompt 压缩 + 滑动窗口:最简单,立刻能做,输入 Token 砍 30-40%
  2. 语义缓存:适合有重复查询模式的场景,省 20-50% 的 API 调用
  3. 动态模型路由:把便宜模型用在简单任务上,整体成本降 30-60%

三个叠加起来,微信小助手的日均 Token 消耗从 280 万降到了 103 万,成本从每天大概 40 块降到 15 块左右。对个人项目来说,这个差距就是"能跑下去"和"跑不起"的区别。

OpenClaw 本身能力没问题,关键是学会该省省、该花花。有问题评论区聊。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐