把 AI 账单砍掉 88%:独立开发者的多模型路由实战
适用场景: 有真实付费用户、AI 成本开始咬毛利的独立产品 前置条件: 你的产品已经在调某个 LLM API,手上能改到调用代码;本文示例用 Python +
anthropicSDK
目标
8 月 8 日有个被广泛转发的案例:一位做 AI 写作工具的独立开发者,120 个付费用户、月收入 960 美元,把 AI 成本从 514 美元砍到 62 美元——降幅 88%,毛利率从勉强打平升到 94%,月活还涨到了 1500。他没换供应商、没砍功能,用的是三板斧:任务分级、模型路由、结果缓存。
这篇不讲空道理。我们拿一个具体的小服务——用户反馈处理(输入一条用户反馈,输出情感分类 + 一段客服回复草稿)——从「两步都用最贵的模型」一路改造到「分级路由 + 缓存」,代码首尾连贯,最后能整段跑出改造前后的成本对比。照着做,把同一套手法搬到你自己的产品上。示例用 Claude 系模型,思路对任何多档位的 LLM 供应商都通用。
改造前:朴素版在哪烧钱
大多数产品最初都这么写——所有请求都发给最强的模型,省事:
pythonimport anthropic client = anthropic.Anthropic() def text_of(resp): return next((b.text for b in resp.content if b.type == "text"), "") # 客服话术规范:品牌语气、禁语、赔偿口径……真实场景通常几百到上千字 REPLY_SYSTEM = "你是资深客服。根据用户反馈写一段简短、诚恳的回复草稿。……" def process_feedback_naive(text): # 第一步:分类。一个"正面/负面/中性"的判断,却在按旗舰价付费 s = client.messages.create( model="claude-opus-5", max_tokens=16, system="你是分类器,只输出一个词:正面/负面/中性。", messages=[{"role": "user", "content": text}], ) # 第二步:写回复 r = client.messages.create( model="claude-opus-5", max_tokens=512, system=REPLY_SYSTEM, messages=[{"role": "user", "content": text}], ) return text_of(s).strip(), text_of(r)
问题一眼可见:「把这段话分个类」这种简单活,也在按 Opus 的价格付费。 而这类简单请求,往往占了产品调用量的大头——这就是 88% 降幅的来源。下面三板斧逐个上。
第一板斧:任务分级
把服务里的每个 LLM 调用点按难度分三档。判断标准是「错一点会怎样」,不是「重不重要」:
- 简单档:分类、打标签、抽取、格式化——输出短、规则清晰、错了重试即可;
- 主链路档:大多数生成和推理——正文写作、总结、多步问答;
- 高价值档:真正难、错了代价大的——复杂代码、深度分析、需要长链推理的。
我们这个服务:分类 = 简单档,回复 = 主链路档。 对照档位单价(Claude 系,每百万 token 输入/输出,以官方为准):
| 模型 | 输入 | 输出 | 本服务用在 |
|---|---|---|---|
| Haiku 4.5 | $1 | $5 | 分类步 |
| Sonnet 5 | $3 | $15 | 回复步 |
| Opus 5 | $5 | $25 | 本例用不到,留给更难的任务 |
同一个分类请求,走 Haiku 比走 Opus 便宜 5 倍。
第二板斧:模型路由
把「哪档走哪个模型」抽成一张表,两步各自落档——分类下沉到 Haiku,回复放在 Sonnet:
pythonMODEL_BY_TIER = { "simple": "claude-haiku-4-5", "main": "claude-sonnet-5", "hard": "claude-opus-5", } def classify(text): model = MODEL_BY_TIER["simple"] # 简单档 → Haiku resp = client.messages.create( model=model, max_tokens=16, system="你是分类器,只输出一个词:正面/负面/中性。", messages=[{"role": "user", "content": text}], ) return text_of(resp).strip() def draft_reply(text, sentiment): model = MODEL_BY_TIER["main"] # 主链路 → Sonnet resp = client.messages.create( model=model, max_tokens=512, system=REPLY_SYSTEM, messages=[{"role": "user", "content": f"用户反馈(情感:{sentiment}):{text}"}], ) return text_of(resp)
别把「省钱」凌驾到主链路质量上。用户直接看到的回复草稿留在 Sonnet,别为省钱降到 Haiku。降本降的是分类这种用户根本感知不到模型档位的步骤。
第三板斧:结果缓存
同样的输入不该付两次钱。两层缓存各管一段。
应用层缓存:完全相同的反馈直接命中本地缓存,连 API 都不发——用户反馈里重复内容很常见(「发货太慢」能收到几十条)。给分类步加一层:
pythonimport hashlib _class_cache = {} # 生产环境换成 Redis def classify(text): key = hashlib.sha256(text.encode()).hexdigest() if key in _class_cache: return _class_cache[key] # 命中,零成本 model = MODEL_BY_TIER["simple"] resp = client.messages.create( model=model, max_tokens=16, system="你是分类器,只输出一个词:正面/负面/中性。", messages=[{"role": "user", "content": text}], ) label = text_of(resp).strip() _class_cache[key] = label return label
供应商 prompt caching:回复步每次都带同一大段 REPLY_SYSTEM 前缀(客服规范),把它缓存下来,命中部分只按约 0.1 倍计费。给稳定前缀打断点,每次变的内容放在断点之后:
pythondef draft_reply(text, sentiment): model = MODEL_BY_TIER["main"] resp = client.messages.create( model=model, max_tokens=512, system=[{ "type": "text", "text": REPLY_SYSTEM, # 每次都一样的固定前缀 "cache_control": {"type": "ephemeral"}, }], messages=[{"role": "user", # 只有这里每次变 "content": f"用户反馈(情感:{sentiment}):{text}"}], ) return text_of(resp)
prompt caching 有最低可缓存长度(Sonnet 约 1024 token)。REPLY_SYSTEM 只有几十字是不会真缓存的——真实客服规范往往几百上千字,正好够格。前缀太短时 cache_read_input_tokens 会一直是 0,别以为是配置错了。
拼成可运行的完整版
三板斧到这里各就各位,组装起来就是最终的 process_feedback:
pythondef process_feedback(text): sentiment = classify(text) # 简单档 → Haiku + 应用层缓存 reply = draft_reply(text, sentiment) # 主链路 → Sonnet + prompt caching return sentiment, reply
classify、draft_reply 就是上面第二、三板斧写好的版本——所有片段最终都落在这一个函数里。这就是「代码用在哪」的答案:它们共同组成一条可运行的反馈处理管道。
验证:跑对照,把降本算出来
降本幅度不能靠感觉,跑一遍对照。加一个成本累加器,按 usage 折算实际花费:
pythonPRICE = { # 每百万 token(输入, 输出),美元 "claude-haiku-4-5": (1, 5), "claude-sonnet-5": (3, 15), "claude-opus-5": (5, 25), } total = 0.0 def add_cost(model, u): global total pin, pout = PRICE[model] read = u.cache_read_input_tokens or 0 # 缓存命中,约 0.1 倍 write = u.cache_creation_input_tokens or 0 # 缓存写入,约 1.25 倍 total += (u.input_tokens * pin + read * pin * 0.1 + write * pin * 1.25 + u.output_tokens * pout) / 1_000_000
在 classify(未命中缓存那条分支)和 draft_reply 的 create 之后各补一行 add_cost(model, resp.usage),朴素版的两个 create 之后同样补上。然后跑同一批反馈对照:
pythonfeedbacks = [ "东西不错就是发货慢", "客服态度差,再也不买了", "东西不错就是发货慢", # 故意重复:改造版这条分类会命中缓存 "物流很快,会回购", ] total = 0.0 for f in feedbacks: process_feedback_naive(f) print(f"朴素版(全 Opus): ${total:.4f}") total = 0.0 _class_cache.clear() for f in feedbacks: process_feedback(f) print(f"改造版(分级 + 缓存): ${total:.4f}")
你会看到两行数字,差距一目了然——第 3 条重复反馈在改造版里分类零成本,回复步则吃到 prompt caching 的折扣。案例里 88% 的降幅,主体就来自「简单请求下沉到 Haiku」这一步,缓存是叠加的第二层。把这套对照套到你自己产品调用量最大的那条链路上,先改那一条,账单立刻见效。
反复发相同前缀却发现 cache_read_input_tokens 一直是 0,多半是前缀里混进了「隐形失效元凶」——datetime.now()、随机 UUID、未排序的 JSON 塞进了 system。任何一个字节变化都会让整段缓存作废,把这些动态内容挪到断点之后。
常见问题
降档会不会掉质量? 简单档任务在 Haiku 和 Opus 上表现基本无差——分类、抽取本就不吃智力。吃智力的任务(本例的回复步)留在 Sonnet/Opus 即可,分级的意义就是别让两类请求一刀切用同一个模型。
缓存会不会返回过期结果? 应用层缓存要给会变的内容设过期时间(实时数据别缓存);供应商 prompt caching 有自己的 TTL(默认 5 分钟),只影响计费不影响正确性。
多供应商路由值得做吗? 单一供应商内部的三档路由已经能吃掉大部分收益,实现也简单。跨供应商路由收益递减、复杂度陡增,先把本篇三板斧做透再考虑。