
最近折腾了一个多智能体LLM(大型语言模型)SaaS应用Socra,在Railway上跑生产环境,前两周其实一直有点问题,直到真实用户开始用才发现。这篇说说我是怎么搭的三供应商降级链路(Anthropic → Google → Groq),中间踩了哪些坑,以及现在生产环境实际在跑的代码。
刚部署的时候LLM路由很简单:一个供应商、一个模型、一个API密钥。本地开发没问题。
然后真实用户开始用了。
Groq的免费额度是每分钟6000 tokens。Socra一次主流程——5个专业AI角色并行运行,每个约1500输入tokens——一次并发大约消耗9500 tokens。算一下:任何有点流量的会话,有3/5的智能体都在返回429错误。
应用界面上给用户展示的是智能体卡片,有些显示琥珀色的"Error"文字。我以为是竞态条件,结果不是——是我天真地以为一个免费层API能扛住多智能体并发。
解决方案不是优化,而是加冗余。
最终生产环境的路由顺序:
1. Anthropic Claude Haiku — 如果设置了 ANTHROPIC_API_KEY
2. Google Gemini 2.0 Flash — 如果设置了 GOOGLE_API_KEY ← 生产环境默认
3. Groq LLaMA 3.1 8B — 如果设置了 GROQ_API_KEY ← 降级方案
4. Stub 模式 — 演示场景,无需API密钥
为什么是这个顺序?成本和速率限制,不是模型质量:
| 供应商 | 模型 | 输入 $/MTok | 输出 $/MTok | 免费层 TPM |
|---|---|---|---|---|
| Anthropic | claude-haiku-4-5 | $0.80 | $4.00 | 无 |
| gemini-2.0-flash | $0.075 | $0.30 | 1,000,000 | |
| Groq | llama-3.1-8b-instant | $0.06 | $0.06 | 6,000 |
Google的免费额度比Groq多150倍——而Socra是5个LLM调用同时触发。对于一个学生做的SaaS,LLM成本需要接近零来测试,这差异不是一点点——这是应用能跑和不能跑的区别。
系统中每个LLM调用都走两个入口之一:`_call_llm`(非流式,用于结构化JSON)和`_stream_llm_tokens`(流式,用于对话文本)。两者用同样的路由逻辑:
# backend/llm_client.py
async def _call_llm(system: str, messages: list[dict], max_tokens: int, json_mode: bool = False) -> str: if settings.anthropic_api_key: return await _call_anthropic(system, messages, max_tokens, json_mode) elif settings.google_api_key: return await _call_google(system, messages, max_tokens, json_mode) elif settings.groq_api_key: return await _call_groq(system, messages, max_tokens, json_mode) else: return _stub_response(messages)
很简单。路由逻辑就是:哪个key设置了?匹配第一个。
Google AI Studio暴露了一个OpenAI兼容的端点。这意味着你不需要Google的SDK——只需要把OpenAI SDK指向不同的base URL:
async def _call_google(system: str, messages: list[dict], max_tokens: int, json_mode: bool = False) -> str: from openai import AsyncOpenAI client = AsyncOpenAI( api_key=settings.google_api_key, base_url="https://generativelanguage.googleapis.com/v1beta/openai/", ) kwargs = { "model": "gemini-2.0-flash", "max_tokens": max_tokens, "messages": [{"role": "system", "content": system}, *messages], } if json_mode: kwargs["response_format"] = {"type": "json_object"} response = await client.chat.completions.create(**kwargs) return response.choices[0].message.content or ""
流式调用同样适用——用`stream=True`然后`async for chunk in stream`迭代。
这个模式值得记住:Groq、Azure OpenAI和Google AI Studio都支持OpenAI兼容的端点格式。如果你用可配置的`base_url`和`api_key`写OpenAI SDK,就能以几乎零额外代码获得多供应商支持。
这里开始乱了。多智能体管道运行生成主流程后,Socra需要从LLM返回结构化JSON——评估分数、假设追踪、快速回复选项。最初的做法是在流里加分隔符:
Stream: "Here are my questions... ###JSON###{"eval_delta": {...}, "choices": [...]}"
用Anthropic没问题(Claude很听话,会按格式要求输出)。用小模型就完全炸了。
8B的Groq模型有时包含分隔符,有时不,有时放在句子中间。解析静默失败,`choices`返回空——用户收到第一条消息后看不到任何快速回复选项。
修复方案:两次独立调用。
# 调用1:流式输出纯文本,无格式要求
async for token in _stream_llm_tokens(system, messages): yield token full_message += token # 调用2:流式结束后,单独获取结构化数据
eval_data = await _call_llm( system=eval_system_prompt, messages=messages + [{"role": "assistant", "content": full_message}], json_mode=True
)
Anthropic路径仍然用分隔符(那里可靠,而且省一次API调用)。Groq和Google路径用两次调用。稍微多点延迟,但零解析失败。
这个浪费了我45分钟。
部署到Railway后,所有LLM调用都报错`Illegal header value`。API密钥是对的——我从Groq控制台直接复制的。但其实不是。我粘贴到Railway的Variables标签页时,末尾多了个看不见的`\n`。
修复两件事:
class Settings(BaseSettings): groq_api_key: str = "" anthropic_api_key: str = "" google_api_key: str = "" @validator('groq_api_key', 'anthropic_api_key', 'google_api_key', pre=True) def strip_keys(cls, v): return v.strip() if v else v
现在应用对复制粘贴错误有防御了。`.strip()`几乎没成本,却能杜绝一类真正难调试的错误。
添加Google作为第二个供应商后,我推到Railway然后查看日志。日志说:
Using Groq LLaMA for LLM calls
但我明明设置了`GOOGLE_API_KEY`。整整两天我以为Google没生效。实际上生效了。是启动日志错了。
`main.py`里的lifespan检查有个bug:
# 之前——完全跳过Google
if settings.anthropic_api_key: logger.info("Using Anthropic Claude")
elif settings.groq_api_key: # ← 在Google之前检查Groq logger.info("Using Groq LLaMA")
`_call_llm`里的实际路由是对的(Google第二个检查,在Groq之前)。但日志检查顺序不同——所以如果Groq也设置了(确实设置了),它就日志"Using Groq"而实际上每个调用都去了Google。
修复:让启动日志完全镜像路由逻辑。
5个并行专业智能体怼Groq的6k TPM免费额度:数学上根本不可能,我还在装作可以。
每个智能体约1500输入tokens + 生成约400输出tokens = 每次调用约1900 tokens。5个并行调用 = 同时发起9500 tokens。Groq的速率限制器在同一个分钟窗口里看到全部9500 tokens,超出部分直接拒绝。
试了三种方案,顺序是:
方案1:带退避的重试。对429错误加3次重试,指数退避4s/8s。稍微有帮助。没解决根本的数学问题。
方案2:带延迟的顺序执行。从`asyncio.gather()`改成顺序调用,每个智能体间隔1.5s。这把token突发分散到多个速率限制窗口。Groq上有效,但主流程多了约7.5s——明显能感觉到。
方案3:切到Google。Google免费额度是1,000,000 TPM。问题完全消失。现在Groq是降级方案,不是主方案。
真正的教训:按降级供应商的速率限制来设计,而不只是主供应商。Groq快且便宜,但不适合免费层上的并行多智能体工作负载。
切换到Google作为生产默认后,我做了完整的token和成本拆解:
| 阶段 | 输入tokens | 输出tokens |
|---|---|---|
| 对话(平均7轮) | ~16,700 | ~3,500 |
| 5个专业智能体 | ~24,000 | ~3,500 |
| 综合 | ~12,700 | ~2,500 |
| 魔鬼代言人 | ~2,800 | ~600 |
| 每次会话总计 | ~56,200 | ~10,100 |
按Google Gemini Flash定价($0.075输入 / $0.30输出 每百万tokens):
输入成本: 56,200 / 1,000,000 × $0.075 = $0.0042
输出成本: 10,100 / 1,000,000 × $0.30 = $0.0030
总计: ~$0.007 每次会话
Socra一次完整主流程会话收费₹499(约$6)。LLM成本每次会话:$0.007。LLM成本 alone毛利率99.8%。
Railway托管约$30/月。盈亏平衡约每月6个付费会话。
这笔账能算通全靠供应商选择。用Anthropic Haiku做同样会话成本约$0.085——贵12倍,对应毛利率约98.6%。也还行,但重点是:供应商选择是产品决策,不只是技术决策。
1. 从第一天就设计多供应商。降级链路是在第三阶段生产炸了之后才加的。本来应该从架构开始就包含。路由抽象(`_call_llm`加供应商检测)简单到30分钟就能加——没有理由从单供应商开始。
2. 部署并行调用前测试速率限制数学。5个并行智能体 × 1900 tokens = 一次并发9500 tokens。Groq免费额度是6000 TPM。这是小学算术,我直到用户开始报错才去做。
3. 在配置层strip API密钥。settings类里加`.strip()`是5分钟的改动,能消除一整类部署bug。
4. 让启动日志完全镜像路由逻辑。日志说"Using Groq"而实际用着Google,比没有日志更糟糕——它会误导调试。
Socra的技术栈:FastAPI + React + PostgreSQL + Railway + LangGraph(多智能体管道)+ Langfuse v4(每次调用的LLM可观测性)+ Clerk(认证)+ Razorpay(支付)。这里描述的LLM降级链路处理整个系统的所有LLM调用——对话、智能体、综合、PPT生成和评审评分。
实际运行的应用在上。上面描述的方案——OpenAI兼容端点、两次调用的结构化输出、配置层的供应商检测——今天都在生产环境跑着。