重复的提示词是免费token的最大浪费。同一个问题问两次,就付了两次钱。缓存可以拦截这些重复。大多数LLM缓存实现需要外部依赖。我构建了一个只用标准库的版本。它基于SimHash。它能在API调用前识别相似请求。
目标环境是MonkeyCode的免费层。Disclosure: This article was prepared as part of MonkeyCode's product outreach. 免费额度是1000万token。配额会变化。上线前请查看仪表盘。
重复请求比你想的多
真实工作负载充满了近似重复。同一个错误信息被发送给LLM两次。同一个代码片段被请求解释三次。每次措辞略有不同。精确匹配缓存抓不到这些。你需要一个语义缓存。
语义缓存的核心问题:如何快速判断两个提示词相似?完整比较所有历史记录太慢。嵌入向量需要外部API。SimHash提供了一个折中。它把文本压缩成一个固定长度的指纹。相似文本产生相似指纹。汉明距离衡量相似度。
SimHash如何工作
SimHash把每个token映射到一个哈希值。每个哈希值投票给一个位向量。多数票决定最终指纹。两个相似文本的指纹差异很小。汉明距离小于阈值就判定为相似。
import hashlib
import re
HASH_BITS = 64
def tokenize(text):
return re.findall(r'\w+', text.lower())
def simhash(text):
vector = [0] * HASH_BITS
for token in tokenize(text):
digest = int(hashlib.md5(token.encode()).hexdigest(), 16)
for i in range(HASH_BITS):
vector[i] += 1 if (digest >> i) & 1 else -1
return sum((1 << i) for i in range(HASH_BITS) if vector[i] > 0)
def hamming(a, b):
return bin(a ^ b).count('1')
这个实现只用标准库。没有numpy。没有外部嵌入服务。它足够快,可以在每个API调用前运行。
实现:一个零依赖的缓存层
缓存层用SQLite存储指纹和响应。lookup方法计算输入指纹。它扫描所有已存储的指纹。汉明距离小于阈值就返回缓存响应。
import sqlite3
import time
class SimHashCache:
def __init__(self, db_path="simhash_cache.db", threshold=8):
self.conn = sqlite3.connect(db_path)
self.threshold = threshold
self._init_db()
def _init_db(self):
self.conn.execute("""
CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hash INTEGER UNIQUE,
prompt TEXT,
response TEXT,
hits INTEGER DEFAULT 0,
created_at REAL
)
""")
self.conn.commit()
def lookup(self, prompt):
h = simhash(prompt)
rows = self.conn.execute("SELECT hash, response FROM entries").fetchall()
for stored_hash, response in rows:
if hamming(h, stored_hash) <= self.threshold:
self.conn.execute(
"UPDATE entries SET hits = hits + 1 WHERE hash = ?",
(stored_hash,),
)
self.conn.commit()
return response
return None
def store(self, prompt, response):
h = simhash(prompt)
self.conn.execute(
"INSERT OR REPLACE INTO entries (hash, prompt, response, created_at) VALUES (?, ?, ?, ?)",
(h, prompt, response, time.time()),
)
self.conn.commit()
接入API调用只需要几行代码:
import httpx
cache = SimHashCache()
ENDPOINT = "https://YOUR_ENDPOINT/v1/chat/completions"
API_KEY = "YOUR_KEY"
MODEL = "free-model-id"
def cached_completion(prompt):
cached = cache.lookup(prompt)
if cached:
return cached, "cache"
payload = {
"model": MODEL,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0,
}
r = httpx.post(
ENDPOINT,
json=payload,
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=60,
)
r.raise_for_status()
data = r.json()
response = data["choices"][0]["message"]["content"]
cache.store(prompt, response)
return response, "api"
模拟:命中率有多高
我用一个模拟工作负载测试缓存。它包含精确重复和近似重复。这不是生产测量。它展示了缓存在混合负载下的行为。
workload = [
"Explain the CAP theorem in one paragraph",
"Explain the CAP theorem in one paragraph",
"Explain the CAP theorem briefly",
"Explain the CAP theorem",
"Write a Python function to reverse a string",
"Write a Python function to reverse a string",
"How do I reverse a string in Python?",
"What is the time complexity of quicksort?",
]
def simulate(workload):
cache = SimHashCache(":memory:")
api_calls = 0
cache_hits = 0
for prompt in workload:
cached = cache.lookup(prompt)
if cached:
cache_hits += 1
else:
api_calls += 1
cache.store(prompt, f"response for: {prompt[:30]}")
print(f"API calls: {api_calls}")
print(f"Cache hits: {cache_hits}")
print(f"Hit rate: {cache_hits / len(workload):.0%}")
simulate(workload)
在这个模拟中,8个请求只有3个到达API。命中率是62%。精确重复和近似重复都被拦截。真实命中率取决于你的工作负载。日志分析、错误分类、代码解释这类任务命中率高。一次性创意写作命中率接近零。
限制:缓存不适用的时候
SimHash缓存有三个限制。第一,它只缓存确定性输出。temperature=0是必要条件。任何随机性都会破坏缓存。第二,它不感知上下文。同一个提示词在不同会话中可能需要不同答案。第三,它存储明文响应。敏感数据需要加密或跳过缓存。
阈值也需要调优。阈值太小会漏掉相似请求。阈值太大会返回错误响应。8是一个合理的起点。用你自己的数据校准。
结论
免费token是有限的。重复请求在悄悄消耗它们。一个SimHash缓存层可以拦截大部分重复。实现只有几十行代码。它不需要外部依赖。它可以在任何免费层前面工作。MonkeyCode的免费层是一个合适的起点。1000万token的额度足够运行这个缓存并观察命中率。先测量你的工作负载。再决定缓存是否值得。
MonkeyCode provides free models that can run this workflow. A free server option is enough to reproduce the setup.
Top comments (0)