AI 应用的成本失控往往不是因为「模型太贵」,而是因为设计上没做任何节约:每次都全文重发、不用缓存、不控制输出长度、不看 trace。这篇给一套可落地的降本方法,按投入产出排。
一、先搞清楚钱花在哪
# 每次调用的成本构成
1. 输入 token × 输入单价 + 输出 token × 输出单价
1. 注意:输出单价通常是输入的 2-4 倍
1. 实例:某次 RAG 问答
1. 系统提示词 300 + 检索片段 1500 + 用户问题 20 = 1820 输入
1. 生成回答 400 输出
1. 假设每百万 token 输入 ¥5、输出 ¥15
1. 成本 = 1820/1e6 * 5 + 400/1e6 * 15 ≈ ¥0.015 / 次
1. 1 万次/天 → ¥150/天 → ¥4500/月
第一个关键认知:输入是主要成本,不是输出。RAG 场景里检索片段常占输入的 80% 以上。
二、降本手段排序(按投入产出)
| 手段 | 降本幅度 | 实现难度 | 优先级 |
|---|---|---|---|
| 缓存相同请求 | 视重复率,10-50% | 低 | 最高 |
| 压缩检索片段数量 | 20-40% | 低 | 高 |
| 限制输出长度 | 10-30% | 低 | 高 |
| 精简系统提示词 | 5-15% | 低 | 高 |
| 按任务路由到便宜模型 | 50-80% | 中 | 高 |
| 批量合并请求 | 10-20% | 中 | 中 |
| 本地模型承接简单任务 | 视分流比例 | 高 | 中 |
三、缓存:最大的一块免费收益
import hashlib, json
import redis.asyncio as redis
rdb = redis.from_url("redis://localhost:6379")
async def cached_chat(messages: list, model: str, **kw) -> dict:
"""只对「确定性」请求做缓存(temperature=0 或很低)。"""
key = "llm:" + hashlib.sha256(
json.dumps({"m": messages, "model": model, "kw": kw},
sort_keys=True, ensure_ascii=False).encode()
).hexdigest()[:32]
if (hit := await rdb.get(key)):
return json.loads(hit)
resp = await llm.chat(messages=messages, model=model, **kw)
await rdb.setex(key, 3600, json.dumps(resp)) # 缓存 1 小时
return resp
- 只缓存 temperature≈0 的请求:创意类任务缓存会让用户拿到重复内容
- 缓存键要包含完整上下文:model、messages、参数全都要,否则会串味
- RAG 场景分两层缓存:检索结果单独缓存(更稳定),生成结果按「问题+片段哈希」缓存
- TTL 要短:1-6 小时足够,太长会导致知识过期
四、压缩检索片段:少给但给准
import re
def lexical_overlap(sent: str, query: str) -> float:
"""句子与问题的字符 bigram 重叠率:中文不需要分词依赖即可用。"""
def bigrams(t: str) -> set:
t = re.sub(r'\s', '', t)
return {t[i:i+2] for i in range(len(t) - 1)}
a, b = bigrams(sent), bigrams(query)
return len(a & b) / len(b) if b else 0.0
def compress_chunk(chunk: str, query: str, max_sentences: int = 3):
"""从块里挑与问题最相关的几句。"""
sents = [s for s in re.split(r'(?<=[。!?])', chunk) if s.strip()]
scored = sorted(sents, key=lambda s: lexical_overlap(s, query), reverse=True)
return ''.join(scored[:max_sentences])
1. 效果:4000 字 → 约 1200 字,输入成本降 70%
1. 风险:压缩可能切掉上下文,要配合重排一起用
1. 另注:第三节的响应缓存不适用流式请求——流式返回的是迭代器,
1. 没法 json.dumps 落库,流式接口要单独处理(按增量输出完再缓存或干脆不缓存)
五、对账:4500 是怎么降到 900 的
标题的承诺要能对账。按第一节的基线(1 万次/天、单次 ¥0.015 ≈ ¥4500/月),把各手段的假设代入:
| 手段(假设) | 计算 | 每月节省 |
|---|---|---|
| 缓存相同请求(重复率约 25%,命中零成本) | 4500 × 25% | ¥1125 |
| 压缩检索片段 + 精简提示词(输入 1820 → 820 token:片段 1500→500、提示词 300→200) | 单次 ¥0.015 → 约 ¥0.010(输入 0.0041 + 输出 0.006),省 ¥0.005/次 × 22.5 万次/月 | ¥1125 |
| 限制输出(400 → 200 token)+ 简单任务路由便宜模型(50% 流量、单价约 1/5) | 输出省 ¥0.003/次;路由再省约 ¥0.0028/次,合计 ¥0.0058/次 × 22.5 万次/月 | ¥1305 |
三项合计省约 ¥3555,最终约 ¥945/月——即标题说的「900 左右」。剩余成本来自未命中缓存的复杂请求(输入长、输出长、还要走贵模型)。假设一变账就变:重复率更低或简单任务占比更少,终值都会上移,这张表的口径比第二节的百分比区间更保守。