💾 OpenClaw 工具结果缓存
同一个问题问了三遍,API费用翻了三倍。我看着账单上的数字,突然明白了一个道理:AI不会偷懒,但我们可以教它。
为什么需要缓存
场景:你的Agent每天要回答"OpenClaw怎么安装"这个问题100次。
- 不缓存:100次API调用,100份费用
- 缓存:1次API调用 + 99次缓存命中,费用降低99%
缓存不只是省钱,还有:
- 响应更快 - 缓存命中,毫秒级返回
- 负载更低 - 减少上游API压力
- 稳定性更高 - 上游挂了,缓存还能撑
缓存类型
1. 精确缓存(Exact Cache)
输入完全相同,返回缓存结果。最简单,命中率可能不高。
// 精确缓存
const cache = new Map();
async function cachedToolCall(toolName, args) {
const key = `${toolName}:${JSON.stringify(args)}`;
if (cache.has(key)) {
console.log('缓存命中!');
return cache.get(key);
}
const result = await executeTool(toolName, args);
cache.set(key, result);
return result;
}
2. 语义缓存(Semantic Cache)
输入语义相似,返回缓存结果。比如"今天天气"和"现在天气怎样"视为相同。
// 语义缓存 - 使用embedding相似度
import { getEmbedding } from './embedding';
const semanticCache = [];
async function semanticCachedCall(query, threshold = 0.95) {
const queryEmbedding = await getEmbedding(query);
// 查找相似查询
for (const cached of semanticCache) {
const similarity = cosineSimilarity(queryEmbedding, cached.embedding);
if (similarity > threshold) {
console.log(`语义缓存命中!相似度: ${similarity}`);
return cached.result;
}
}
// 未命中,执行并缓存
const result = await executeQuery(query);
semanticCache.push({ embedding: queryEmbedding, result });
return result;
}
3. TTL缓存(Time-based Cache)
缓存有时效性。天气数据缓存1小时,股票数据缓存1分钟。
// TTL缓存
class TTLCache {
constructor(defaultTTL = 3600000) { // 默认1小时
this.cache = new Map();
this.defaultTTL = defaultTTL;
}
set(key, value, ttl = this.defaultTTL) {
this.cache.set(key, {
value,
expiresAt: Date.now() + ttl
});
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expiresAt) {
this.cache.delete(key);
return null; // 已过期
}
return item.value;
}
}
OpenClaw 缓存配置
全局缓存配置
# openclaw.yaml
cache:
enabled: true
backend: redis # memory | redis | file
default_ttl: 3600 # 秒
redis:
host: localhost
port: 6379
prefix: 'openclaw:cache:'
rules:
# 工具特定缓存规则
- tool: 'web_search'
ttl: 1800 # 30分钟
key_pattern: '${args.query}'
- tool: 'weather_api'
ttl: 3600 # 1小时
key_pattern: '${args.location}:${args.date}'
- tool: 'stock_price'
ttl: 60 # 1分钟
key_pattern: '${args.symbol}'
Skill级缓存
# skills/weather.yaml
name: weather
description: 获取天气信息
cache:
enabled: true
ttl: 3600
key: '${location}:${date}' # 缓存键模板
invalidate_on:
- time: '06:00' # 每天6点失效
- event: 'location_change'
缓存策略
LRU(最近最少使用)
缓存满了,踢掉最久没用的。适合内存有限的场景。
// LRU缓存实现
class LRUCache {
constructor(maxSize = 1000) {
this.maxSize = maxSize;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return null;
// 访问时移到末尾(最近使用)
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.maxSize) {
// 删除最久未使用的(第一个)
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}
写穿透(Write-Through)
更新数据时同时更新缓存。保证缓存和源数据一致。
写回(Write-Back)
先更新缓存,异步更新源数据。更快,但有数据丢失风险。
缓存失效
主动失效
// 事件驱动的缓存失效
eventBus.on('data_updated', (dataType) => {
cache.invalidatePattern(`${dataType}:*`);
});
// 示例:用户更新了配置
eventBus.emit('data_updated', 'user_config');
被动失效
// TTL + 惰性删除
async function getWithInvalidation(key) {
const cached = await cache.get(key);
if (cached && isStale(cached)) {
// 后台异步刷新
refreshCache(key).catch(console.error);
// 先返回旧数据,不阻塞
return cached.value;
}
return cached?.value || fetchFresh(key);
}
缓存监控
// 缓存统计
const cacheStats = {
hits: 0,
misses: 0,
evictions: 0
};
function getCacheStats() {
const hitRate = cacheStats.hits / (cacheStats.hits + cacheStats.misses);
return {
...cacheStats,
hitRate: `${(hitRate * 100).toFixed(2)}%`
};
}
// 输出:{ hits: 850, misses: 150, evictions: 20, hitRate: '85.00%' }
最佳实践
- 识别可缓存内容 - 只缓存幂等、确定性的结果
- 合理设置TTL - 静态数据长TTL,动态数据短TTL
- 监控命中率 - 命中率低说明缓存策略有问题
- 防止缓存穿透 - 不存在的数据也缓存(空结果)
- 防止缓存雪崩 - TTL加随机偏移,避免同时失效
- 分布式缓存一致性 - 用Redis Pub/Sub同步失效
费用节省案例
妙趣AI每日查询统计:
| 查询类型 | 每日次数 | 单次成本 | 无缓存成本 | 有缓存成本 | 节省 |
|---|---|---|---|---|---|
| 天气查询 | 500 | $0.001 | $0.50 | $0.02 | 96% |
| 翻译 | 1000 | $0.002 | $2.00 | $0.20 | 90% |
| 知识问答 | 2000 | $0.005 | $10.00 | $1.00 | 90% |
相关链接
最后更新:2026-04-29 | 作者:妙趣AI