DAY 23 / PHASE 2 · 应用与系统

Personal AI Infra

LLM Gateway · 跨模型路由 · Cache 层 · Observability & Key 管理

2026-06-06 · BigCat

超级个体的护城河不在某个 prompt,在那条没人看见的私有 AI 管线。

// WHY THIS MATTERS

当你同时在用 Claude Code、Cursor、几个自写脚本、两三个 side project,每个都各自 import anthropic / import openai、各自硬编 API key、各自直连 provider——你已经拥有了一个没有中枢的分布式系统,而且是最差的那种:换模型要改 N 处、成本不可见、provider 抖一下全线挂、出了 bug 没有 trace。本期讲的不是某个工具,是把这堆散点收口成一条私有管线的四层架构:一个统一接入的 gateway、一层跨模型路由 + fallback、一层cache、一套observability + key 治理。这套东西在公司叫「平台团队的活」,但对超级个体,它就是一个周末能搭起来、之后每天省钱省心的基础设施。装一次,所有 app 受益。

// 01

LLM Gateway:所有调用收口到一个端点

论断:别让每个脚本各自直连 provider SDK——auth / 路由 / cache / 日志应该收口在一个 OpenAI 兼容端点后面。

背景与原理

Gateway 是整套 infra 的单一入口。所有 app 不再认识「Anthropic」「OpenAI」「Gemini」,只认识一个本地端点 http://localhost:4000/v1,统一说 OpenAI ChatCompletions 方言。Gateway 在背后负责真正的 provider 适配、鉴权、记账、缓存、日志。LiteLLM Proxy 是个体最省事的选择:一个 config.yaml 把 100+ provider 映射成统一模型名,跑起来就是一个 OpenAI 兼容服务器。

关键收益是解耦:换底层模型只改 gateway 一处配置,所有下游 app 零改动;想给某个 app 限额、换 key、加 fallback,全在中枢做。这正是下面这张图想表达的——四层都挂在 gateway 后面,app 侧只看见一个端点。

┌─ Apps ────────────┐ │ Cursor · 脚本A │ 都只认识一个 OpenAI 兼容端点 │ side-proj · cron │ http://localhost:4000/v1 └─────────┬──────────┘ │ (OpenAI 方言 + virtual key) ▼ ┌─────────────────── LLM GATEWAY (LiteLLM) ───────────────────┐ │ ① Auth virtual key → 限额 / 模型白名单 / 过期 │ │ ② Router 按任务/成本选模型 + provider fallback 链 │ │ ③ Cache prompt cache 透传 + 语义缓存命中直接返回 │ │ ④ Observ. 每次调用 → trace / token / cost / latency │ └───┬───────────────┬───────────────┬───────────────┬─────────┘ ▼ ▼ ▼ ▼ Anthropic OpenAI Gemini 本地 Ollama (真 key 只存在 gateway 这一层,app 永远拿不到)

实战示例

# config.yaml —— 统一模型名映射到真实 provider
model_list:
  - model_name: smart            # app 只说 "smart"
    litellm_params:
      model: anthropic/claude-opus-4-8
      api_key: os.environ/ANTHROPIC_API_KEY
  - model_name: cheap
    litellm_params:
      model: anthropic/claude-haiku-4-5
      api_key: os.environ/ANTHROPIC_API_KEY

# 启动:litellm --config config.yaml   →  localhost:4000
# app 侧零感知 provider:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:4000/v1", api_key="sk-my-virtual-key")
client.chat.completions.create(model="smart", messages=[...])  # 用 OpenAI SDK 调 Claude
失败模式:把 gateway 做成单点故障——所有 app 直连本地一个进程,gateway 一挂全线挂。个体方案:gateway 配置无状态化(config + 环境变量),用 supervisord / systemd 自动拉起;或干脆把不可中断的关键 app 保留一条直连 provider 的 fallback 路径,gateway 只承载「能容忍偶发抖动」的流量。
进阶资源 · LiteLLM Proxy 文档, docs.litellm.ai/docs/simple_proxy · LiteLLM 源码, github.com/BerriAI/litellm
// 02

跨模型路由与 Fallback:按任务选模型,按可用性兜底

论断:路由有两个正交维度——「这个任务该用哪档模型」(成本)和「这个 provider 挂了怎么办」(可用性)。别混在一起。

背景与原理

第一维度(成本路由):不是所有调用都值得 frontier 模型。分类、抽取、格式化、路由判断本身——这些用 Haiku 档够了,价格差一个数量级。把「贵模型默认」改成「便宜模型默认、贵模型按需」,是个体 token 账单立刻砍半的最快动作。

第二维度(可用性 fallback):provider 会限流、会 5xx、会偶发拒答。Fallback 是一条有序模型链:主模型报错就自动试下一个。OpenRouter 把这个做成了一个 models 数组参数——按优先级排,前一个出错自动落到下一个,最终按真正用上的那个模型计费。LiteLLM 也有等价的 router fallback。

关键纪律:两个维度不要纠缠。成本路由是「我主动选便宜的」,fallback 是「被动兜底别让我挂」。把降级模型误当成本优化,会在 provider 抖动时悄悄把质量也降了还不自知。

实战示例

# 成本路由:先用一句话分类,再决定派哪档模型
tier = classify(task)                       # 用 cheap 模型判断难度
model = "smart" if tier == "hard" else "cheap"

# 可用性 fallback:OpenRouter models 数组,按序兜底
client.chat.completions.create(
    model="anthropic/claude-opus-4-8",
    extra_body={"models": [          # 主模型出错自动顺延
        "anthropic/claude-sonnet-4-6",
        "openai/gpt-latest",
    ]},
    messages=[...],
)
# 响应里 model 字段会告诉你最终实际用了哪个 → 一定要 log 它
失败模式:(1)跨 provider fallback 时假设 prompt 通用——同一份 system prompt 在 Claude 上调好的,落到 GPT 上行为可能漂移;fallback 链里跨家时要么接受质量波动,要么各家备一份 prompt。(2)路由分类器本身用了贵模型,省的钱还不够分类的钱。分类必须用最便宜的档,甚至规则/正则先筛。
进阶资源 · OpenRouter Model Fallbacks, openrouter.ai/docs/.../model-fallbacks · LiteLLM Router & Fallbacks, docs.litellm.ai/docs/simple_proxy
// 03

Cache 两层:Provider 端 Prompt Cache + 语义缓存

论断:「缓存」在 LLM 里是两个完全不同的东西——别把 provider 端的 prefix 复用和应用层的语义命中混为一谈。

背景与原理

第一层 · Provider prompt caching:缓存的是prefix(前缀)。把稳定不变的部分(system prompt、工具定义、长文档)标上 cache_control,provider 端缓存这段 KV 计算,后续命中只按极低费率收 read。Anthropic 文档明确:cache read 约为基础 input 价的 0.1×,5 分钟 cache write 约 1.25×、1 小时约 2×。这一层是同一个长 prefix 反复调时的省钱利器(agent loop、多轮对话尤其明显),但它只复用计算、不跳过推理——每次还是真调了模型。

第二层 · 语义缓存(GPTCache 这类):缓存的是整个问答对。新 query 先做 embedding,和历史 query 比相似度,足够像就直接返回旧答案,完全不调模型——延迟和成本都→0。代价是「相似」的判定有风险:阈值松了会返回不该返回的旧答案。

两层正交、可叠加:prefix cache 降每次调用的成本,语义 cache 降调用次数。前者几乎零风险,后者省得多但要管阈值。

实战示例

# 第一层:Anthropic prompt caching —— 给稳定 prefix 打标
client.messages.create(
  model="claude-opus-4-8",
  system=[{
    "type": "text",
    "text": LONG_STABLE_INSTRUCTIONS,          # 长且每次都一样
    "cache_control": {"type": "ephemeral"}  # ← 这段被缓存,read≈0.1×
  }],
  messages=[{"role":"user","content": today_query}],  # 变化部分放后面
)

# 第二层:语义缓存 —— 命中就不调模型(GPTCache 思路)
emb = embed(query)
hit = vec_store.search(emb, threshold=0.95)   # 阈值是关键旋钮
if hit: return hit.cached_answer            # 0 token, 0 延迟
ans = call_llm(query); vec_store.add(emb, ans)

纪律一条:把易变内容放 prefix 末尾。prompt cache 命中的前提是前缀逐字节一致,一个动态时间戳塞在 system 开头就能让整段 cache 全部失效。

失败模式:语义缓存阈值过松,把「2023 年财报」的旧答案返回给「2024 年财报」的新问——两句话 embedding 很近,但答案完全错。高事实性、强时效的 query 不要上语义缓存;它适合 FAQ、客服、稳定知识这类「问法多变、答案稳定」的场景。
进阶资源 · Anthropic Prompt Caching, platform.claude.com/.../prompt-caching · GPTCache, github.com/zilliztech/GPTCache
// 04

Observability 与 Key 治理:看得见花了多少,管得住谁在花

论断:没有 trace 的 AI 管线是在盲飞——你不知道钱花哪了、哪个 app 在烧、key 泄露了多久。

背景与原理

Observability:LLM 调用的可观测和普通 API 不同,你要看的是 prompt/completion 对、token 用量、每次调用成本、延迟、以及(agent 场景)整条调用链。Langfuse 是个体首选的开源方案:可自托管,原生理解 LLM 概念(token、cost、trace、嵌套 span),接 OpenTelemetry 和各家 SDK。装上之后,「这个月哪个 side project 偷偷烧了 $40」从猜测变成一张图。

Key 治理——三条铁律:(1) 真 key 永远不进代码、不进 git,只存环境变量或 secret manager,且只存在 gateway 这一层。(2) 给每个 app 发 virtual key 而非共用真 key:LiteLLM 的 virtual key 支持 per-key 预算、限流、模型白名单、过期时间——某个 app 失控烧钱,只伤它自己的额度,且能单独吊销不影响别人。(3) 预算从小开始($5–50),按真实用量上调,每把 key 设硬上限——这是个体防「凌晨死循环烧爆账单」的唯一兜底。

实战示例

# Langfuse:装饰器自动 trace,零侵入
from langfuse.openai import OpenAI   # drop-in 替换,自动记 token/cost/latency
client = OpenAI(base_url="http://localhost:4000/v1", api_key="sk-my-virtual-key")

# LiteLLM virtual key:per-app 限额 + 模型白名单 + 过期
# POST /key/generate
{
  "key_alias": "side-project-blog",
  "max_budget": 20,            # 这把 key 这辈子最多花 $20
  "budget_duration": "30d",     # 每 30 天重置
  "models": ["cheap"],          # 只许用便宜档,防误用 opus
  "duration": "90d"             # key 本身 90 天后失效
}
失败模式:(1)只看「总花费」不看「分 app / 分 key 花费」——总账单涨了却定位不到哪个项目,等于没监控。trace 一定要带 app/key 维度的 tag。(2)所有 app 共用一把真 key 还硬编进多个 repo:一处泄露,全线裸奔,且因为没分 key 你根本不知道是哪个 app 漏的、影响面多大。
进阶资源 · Langfuse 文档, langfuse.com/docs · Langfuse 源码, github.com/langfuse/langfuse · LiteLLM Virtual Keys, docs.litellm.ai/docs/proxy/virtual_keys

// 综合实战 · 一个周末搭起你的私有 AI 管线

四层串成一个能跑的最小 infra,之后所有 app 共享:

  1. 起 gateway:LiteLLM 一个 config.yaml,映射 smart/cheap 两档模型,真 key 走环境变量。litellm --config config.yaml 跑起来,端点 localhost:4000
  2. 挂路由 + fallbacksmart 配一条跨档 fallback 链(opus→sonnet→gpt);新写的脚本默认调 cheap,确认需要推理深度再升 smart
  3. 开 cache:稳定 system prompt 全部打 cache_control;对 FAQ 型 app 接一层语义缓存,阈值从 0.95 起调。
  4. 接 Langfuse + 发 virtual key:每个 app 一把 virtual key,带 max_budget 和模型白名单;Langfuse 自托管,所有调用带 app tag。
  5. 验收:把你现有的 2-3 个脚本的 base_url 全改成 localhost:4000,跑一周。周末打开 Langfuse——你第一次一眼看清每个 app 花了多少、命中率多少、哪个 provider 抖过。这张图就是你私有管线的回报。

搭完后,「换模型」从改 N 个 repo 变成改一行 config;「这个月 AI 花了多少」从信用卡账单倒查变成一张实时仪表盘。这就是从「会用 AI 工具」到「拥有 AI 基础设施」的分水岭。

// ENGLISH GLOSSARY

LLM Gateway / Proxy
统一接入层,把多家 provider 收口成一个 OpenAI 兼容端点。代表:LiteLLM Proxy。
OpenAI-compatible
用 OpenAI ChatCompletions 方言作为通用协议,任何 OpenAI SDK 客户端零改动接入。
Model Routing
按任务难度/成本主动选择不同档模型(如 cheap vs smart)。
Fallback Chain
有序模型链,主模型出错自动顺延到下一个,抗 provider 抖动。
Prompt Caching
provider 端缓存稳定 prefix 的 KV 计算,cache read 约基础价 0.1×。
cache_control
Anthropic API 中标记缓存边界的字段,{"type":"ephemeral"}
Semantic Cache
按 query embedding 相似度命中历史答案,命中则完全不调模型。代表:GPTCache。
Observability
对 LLM 调用的 trace/token/cost/latency 全链路可观测。代表:Langfuse。
Virtual Key
gateway 下发的代理 key,可带 per-key 预算、限流、模型白名单、过期。
Budget Window
virtual key 的周期性花费上限(如每 30 天 $20),防失控烧钱。

// 深入思考

Gateway 把所有调用收口,本身就成了单点故障 + 延迟新增一跳。对个体,这个集中化的收益真的盖过代价吗?
对个体(流量小、app 多)收益压倒性。新增一跳的延迟是本地进程级(毫秒),相对 LLM 推理几秒可忽略;单点故障可用 systemd 自动拉起 + 关键 app 保留直连兜底解决。换来的是:换模型一处改、成本一眼见、key 一处管。代价随流量规模上升而上升(企业级要考虑 HA、横向扩展),但个体远没到那个拐点——这里集中化几乎是纯赚。
Prompt cache 省的是「同一 prefix 反复调」的钱,语义 cache 省的是「相似 query」的钱。什么样的工作负载两者都帮不上?
「每次 prefix 都不同 + 每次 query 都新」的负载:比如一次性的大文档分析、每次喂不同代码库的 review、探索式研究。prefix 每次变 → 没有可复用前缀;query 每次新 → 语义命中率≈0。这类负载只能靠模型路由(用对档位)和 batch API(非实时打折)省钱。识别它的信号:cache 命中率长期低于 10% 还硬开 1h cache,write 溢价反而亏钱。
跨 provider fallback 听起来很稳,但它隐含一个危险假设。是什么?怎么防?
隐含假设:「换个模型,输出质量等价」。实际 prompt 是针对特定模型调优的,跨家 fallback 时行为会漂移——格式、语气、工具调用风格都可能变,而你浑然不觉因为「至少没报错」。防法:(1) 在 trace 里强制 log 实际命中的模型,定期看 fallback 触发率;(2) 对质量敏感的链路只在同家内 fallback(opus→sonnet),跨家 fallback 只用于「有总比没有强」的非关键路径;(3) 给跨家降级的输出打标,下游能识别。
Virtual key 的 per-key 预算是「硬上限」。但 LLM 调用的成本要等响应回来才知道——预算检查到底发生在调用前还是调用后?这造成什么边界问题?
预算是基于已记录的累计 spend 检查,发生在调用前;但单次成本要响应后才结算。所以存在「超额尾巴」:当累计逼近上限时,并发的几个请求可能都通过了预检、最后一起把账户冲过上限。对个体单线程影响很小,但 agent 并发或定时任务批量触发时会暴露。务实做法:把预算设在你真正能承受的数字之下留缓冲,并配合 provider 侧的硬性 spend limit 做二道防线——别把 gateway 预算当成绝对不可逾越的墙。
这套 infra 是「会用 AI」到「拥有 AI infra」的分水岭。但它会不会反而成为锁死你的复杂度负债?什么时候该拆掉?
会,如果过度工程。判据是痛点驱动:你有 ≥3 个 app、月账单要分摊、换过 ≥2 次模型——这三个信号出现才值得搭,否则一个 config.py 集中 key 就够。拆/简化的信号:某层半年没动过配置(说明没在解决真问题)、维护 gateway 的时间超过它省的时间。infra 的价值在「装一次所有 app 受益」,一旦 app 收敛到 1 个或全迁到托管 IDE,这套自建管线就该退化成一个轻量配置文件,别为了架构而架构。

// 延伸阅读