痛点
AI Agent 上线后,Token 账单往往是最大的"惊喜"。一个中等规模的客服 Agent,日均处理 5000 次对话,每次对话平均消耗 4000 tokens,按 Claude/GPT-4 级别模型计算,月费轻松突破 $3000+。更扎心的是——70% 以上的请求存在高度相似性,重复为相同问题付费。
真实场景:某 SaaS 平台的运维 Agent,处理告警自动诊断。80% 的告警是同类问题(CPU 高、磁盘满、OOM),每次都从头推理,token 白白烧掉。
方案
三层优化策略,叠加使用可降低 50-70% 的 Token 开销:
- 语义缓存(Semantic Cache)— 相似问题命中缓存,直接返回历史结果
- Prompt Cache— 利用模型厂商的 Prompt Caching 特性,减少重复前缀计费
- 智能路由(Model Routing)— 简单任务走小模型,复杂任务走大模型
实操步骤
第 1 步:部署语义缓存层
用 Redis + 向量相似度实现语义缓存。核心逻辑:将用户 query 转为 embedding,在缓存中查找相似度 > 0.95 的历史结果。
import hashlib
import json
import numpy as np
import redis
from openai import OpenAI
client = OpenAI()
r = redis.Redis(host="localhost", port=6379, db=0)
SIMILARITY_THRESHOLD = 0.95
CACHE_TTL = 3600 # 1小时过期
def get_embedding(text: str) -> list[float]:
resp = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return resp.data[0].embedding
def cosine_similarity(a: list[float], b: list[float]) -> float:
a, b = np.array(a), np.array(b)
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
def semantic_cache_get(query: str) -> str | None:
query_emb = get_embedding(query)
# 扫描缓存中的 embedding keys
for key in r.scan_iter("cache:emb:*"):
cached_emb = json.loads(r.get(key))
if cosine_similarity(query_emb, cached_emb["embedding"]) > SIMILARITY_THRESHOLD:
return cached_emb["response"]
return None
def semantic_cache_set(query: str, response: str):
emb = get_embedding(query)
cache_key = f"cache:emb:{hashlib.md5(query.encode()).hexdigest()}"
r.setex(cache_key, CACHE_TTL, json.dumps({
"embedding": emb,
"response": response
}))
生产环境建议用 GPTCache 或 Redis 向量搜索模块(RediSearch) 替代暴力扫描,支持百万级缓存条目的毫秒级检索。
第 2 步:启用 Prompt Cache
Anthropic Claude 和 OpenAI 均支持 Prompt Caching——对请求中重复的 system prompt / 前缀部分,后续调用只收 10-25% 的费用。
from anthropic import Anthropic
anthropic = Anthropic()
# 将大段 system prompt 标记为可缓存
SYSTEM_PROMPT = """你是运维诊断 Agent,负责分析告警并给出修复方案...
(此处省略 2000 tokens 的规则和知识库内容)"""
def call_with_cache(user_query: str) -> str:
response = anthropic.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=[{
"type": "text",
"text": SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"} # 标记缓存
}],
messages=[{"role": "user", "content": user_query}]
)
return response.content[0].text
关键配置:cache_control: {"type": "ephemeral"} 标记后,相同前缀在 5 分钟内的后续请求,缓存命中部分按原价 10% 计费。对于 system prompt 占比 60%+ 的 Agent,这一项就能省 40-50%。
第 3 步:配置智能模型路由
用 LiteLLM 或自建路由层,根据任务复杂度分流:
# litellm_config.yaml
model_list:
- model_name: "agent-router"
litellm_params:
model: "claude-sonnet-4-20250514"
api_key: "sk-xxx"
model_info:
id: "complex"
- model_name: "agent-router"
litellm_params:
model: "claude-haiku-3"
api_key: "sk-xxx"
model_info:
id: "simple"
router_settings:
routing_strategy: "cost-based" # 成本优先路由
allowed_fails: 2
num_retries: 3
路由判定逻辑(在 Agent 框架层实现):
def classify_and_route(query: str) -> str:
"""简单分类器,决定走大模型还是小模型"""
simple_patterns = [
"磁盘使用率", "CPU 高", "内存不足", "服务重启",
"证书过期", "DNS 解析失败"
]
# 匹配常见模式走小模型
if any(p in query for p in simple_patterns):
return "claude-haiku-3" # 成本仅为 Sonnet 的 1/10
return "claude-sonnet-4-20250514"
生产环境推荐用一个微调过的小分类模型(如 distilbert)做意图分类,准确率可达 95%+。
避坑
-
语义缓存阈值不能设太低:相似度阈值低于 0.90 会导致误命中,返回不相关的历史答案。建议从 0.95 起步,观察一周后逐步调到 0.92-0.93。同时对缓存结果加一层"答案相关性校验"兜底。
-
Prompt Cache 有最小 token 要求:Anthropic 要求可缓存部分至少 1024 tokens(Claude Sonnet/Opus),OpenAI 要求 1024 tokens。如果你的 system prompt 太短,缓存不会生效。解决方案:将知识库、Few-shot 示例合并到 system prompt 中凑够长度。
-
模型路由的"灰色地带"问题:不是所有 query 都能清晰分为"简单/复杂"。对于分类不确定的请求,先走小模型,加一个置信度检查——如果小模型输出置信度低于阈值,自动 fallback 到大模型重试。这比一律走大模型省钱,比一律走小模型靠谱。
总结
| 策略 | 节省幅度 | 实施难度 | 适用场景 |
|---|---|---|---|
| 语义缓存 | 30-50% | 中 | 高重复率场景(客服/告警) |
| Prompt Cache | 20-40% | 低 | System Prompt 长的 Agent |
| 智能路由 | 40-60% | 中 | 任务复杂度差异大的场景 |
三者叠加,实测可将月度 Token 账单从 $3000 压到 $800-1200。优先落地 Prompt Cache(零改造成本),再上语义缓存(ROI 最高),最后精细化模型路由。成本优化的核心原则:不为重复问题重复付费,不为简单问题过度付费。