1. 为什么我花三周时间把所有生产服务从 OpenAI 切到了其他平台
去年底,我负责的两个 SaaS 产品同时遭遇了“OpenAI 黑天鹅”——不是模型崩了,而是账单崩了。一个面向教育机构的智能批改系统,单月 API 费用从 1.2 万突然跳到 4.7 万;另一个做法律文书辅助的工具,在周五下午三点整,所有请求开始返回429 Rate Limit Exceeded,而我们根本没动过配额设置。运维同事查了半小时日志,最后在 OpenAI 控制台发现:他们的“自动配额调整策略”悄无声息地把我们的免费额度砍掉了 80%。那一刻我坐在工位上,盯着屏幕上那行红色错误,心里只有一个念头:不能再把命脉交给单一供应商了。
这其实不是新鲜事。过去两年,我经手过 7 个不同行业的 AI 集成项目,从跨境电商的客服话术生成,到本地医院的病历结构化提取,再到制造业的设备故障描述转维修工单。凡是深度依赖 OpenAI API 的,无一例外都踩过至少三个坑:价格不可控、响应不可靠、数据不可见。所谓“5 行代码就能跑起来”,本质是把技术债打包进了一行pip install openai里。真正上线后,你才发现自己签的不是 API 合约,而是一张单方面可修改的空白支票。
我今天要聊的,不是“哪个替代品最像 ChatGPT”,而是在真实业务场景中,如何用最小代价完成 API 层的平滑迁移。不谈虚的“多模态能力”或“上下文长度”,只看四个硬指标:调用延迟是否稳定在 800ms 内、1000 次请求的失败率是否低于 0.3%、中文长文本处理是否丢字、以及——最关键的——当主服务挂掉时,备用通道能否在 3 秒内自动接管。后面你会看到,这五个平台里,有三个连第一条都做不到,但剩下两个,已经在我司生产环境扛住了连续 47 天、日均 230 万次请求的压力测试。
关键词里提到的 “Towards AI - Medium”,其实是这类内容最常见的传播路径:先在 Medium 上发一篇带流量密码的标题党文章,再被各种 AI 资讯号搬运洗稿。但真实世界里的技术选型,从来不是比谁家官网 demo 更炫,而是看凌晨两点服务器报警时,你敢不敢直接 SSH 进去改配置。所以接下来的内容,我会把每个平台的真实压测数据、迁移时踩过的具体坑、以及我们最终保留的三套 fallback 策略,全部摊开来讲。如果你正在评估替代方案,别急着抄代码,先看看我们这三周里删掉的 17 个错误配置和重写的 4 个重试模块。
2. 替代方案选型逻辑:为什么不是“谁便宜选谁”,而是“谁稳选谁”
2.1 我们定义的“可用性”标准,比官网白皮书严苛十倍
很多团队在做替代方案调研时,第一反应是打开各家定价页,拉出 GPT-4 Turbo 和 Claude 3 Haiku 的 token 单价对比表。这就像买车前只比油耗,却忽略刹车距离和转向精准度。我们在启动迁移前,和产品、法务、运维三方一起锁定了四条铁律,任何平台只要违反其中一条,直接出局:
铁律一:必须提供确定性 SLA,且违约赔偿可兑现
OpenAI 的 SLA 是“99.9% 可用性”,但条款里写着“因模型训练导致的服务中断不计入 SLA”。我们要求替代方必须明确写出:“因服务商自身基础设施故障导致的不可用,每低于承诺值 0.1%,赔偿当月费用的 5%”。最后只有两家满足——Anthropic 和 Google Vertex AI。
铁律二:中文长文本处理必须通过“三段式验证”
不是简单测 1000 字摘要,而是用真实业务数据跑三轮:① 输入 3200 字医疗报告(含大量专业缩写),输出必须保留所有诊断编码;② 输入 5800 字法院判决书,关键法条引用不能错位;③ 输入 12000 字设备维修日志,时间戳和部件编号必须零丢失。结果 5 家里 3 家在第二轮就出现“将‘GB/T 19001-2016’识别为‘GB/T 190012016’”这种致命错误。
铁律三:API 响应头必须携带可追踪的 request_id,且支持按此 ID 查原始日志
这是为了应对客户投诉。比如某教育机构反馈“AI 批改作文时把‘锲而不舍’判为错字”,我们必须能立刻定位到那次请求的完整输入、模型版本、token 分片记录。OpenAI 虽然返回 request_id,但控制台日志只保留 48 小时,且无法关联到具体 prompt。最终只有 Azure OpenAI 和 Together AI 提供了 30 天可审计日志。
铁律四:必须支持私有化部署选项,且 License 费用不与调用量挂钩
这是给未来留的活路。当业务增长到月调用量超 5 亿 token 时,按量付费模式会变成财务黑洞。我们要求合同里必须写明:“达到 X 亿 token/月后,可选择一次性买断 Y 年 license,费用固定为 Z 万元”。只有 Mistral 和 Anthropic 给出了明确阶梯报价。
这四条筛下来,最初列的 12 个候选平台只剩 5 个。但请注意:筛选不是为了找“最好”的,而是为了排除“绝对不能用”的。就像医生不会问“哪种抗生素最强”,而是先确认患者对青霉素不过敏。
2.2 价格陷阱:那些藏在定价页角落的“隐形成本”
很多人以为换平台就是换单价,实际远不止如此。我们核算过真实迁移成本,发现有三类费用在官网定价页上根本找不到:
第一类:Token 计算方式差异带来的“缩水税”
OpenAI 的gpt-4-turbo按输入+输出总 token 计费,但 Anthropic 的claude-3-haiku对输入 token 采用“分段计费”:前 1000 token 按标准价,之后每 1000 token 加收 15% 溢价。我们测试过一批 8000 字法律文书,OpenAI 计费 8200 token,Anthropic 却计为 9400 token——表面单价低 20%,实际贵了 7%。
第二类:重试机制引发的“雪崩税”
OpenAI 默认开启指数退避重试,但很多替代平台需要手动实现。我们曾用某国产平台替换客服系统,因未配置重试间隔,单次超时触发 5 次重试,导致实际请求数翻了 5 倍。更糟的是,该平台对重试请求同样计费。后来我们加了熔断器,但额外增加了 32 行监控代码和 2 个 Prometheus 指标。
第三类:合规适配产生的“翻译税”
所有平台都宣称“支持 GDPR”,但落地时差别巨大。OpenAI 要求用户自行签署 DPA(数据处理协议),而 Google Vertex AI 直接在控制台提供预签署模板,且支持按国家/地区单独勾选数据驻留区域。我们为满足欧盟客户要求,光法务审核就花了 11 个工作日,其中 7 天耗在反复修改 DPA 附件条款上。
所以当你看到“某平台价格比 OpenAI 低 40%”时,请务必追问三个问题:
- 这个价格是否包含 10% 的重试冗余?
- 是否已计入 token 计算差异导致的 5-15% 溢价?
- 法务团队确认 DPA 签署周期是否小于 5 个工作日?
2.3 架构设计原则:永远不要让 API 成为单点故障
我们最终采用的不是“全量切换”,而是“三层分流架构”:
用户请求 → API 网关 → [主通道:Anthropic] ↓(失败率 >0.5% 或延迟 >1.2s) [备通道:Google Vertex AI] ↓(同上条件) [兜底通道:本地微调 Mistral-7B]这个设计源于一次惨痛教训:去年 3 月,Anthropic 的东京节点因电力故障中断 22 分钟,我们所有依赖它的服务全部降级。复盘时发现,问题不在 Anthropic,而在我们没做任何降级预案——连最基础的“返回缓存结果”都没实现。
现在网关层强制要求:
- 主通道失败时,必须在 800ms 内完成备通道切换(实测平均 320ms);
- 备通道若也失败,立即启用本地模型,且返回头中携带
X-Fallback: mistral-7b标识; - 所有 fallback 请求自动进入独立队列,避免拖垮主通道资源。
这套机制让我们在最近一次 Anthropic 全球性延迟事件中,保持了 99.98% 的 P95 响应成功率。更重要的是,它把“API 不可用”这个技术问题,转化成了“用户体验降级”的产品问题——用户看到的是“正在使用优化版模型”,而不是“服务暂时不可用”。
3. 五大平台深度实测:参数、延迟、容错与真实代码片段
3.1 Anthropic Claude 3 系列:稳字当头的“企业级选择”
我们最终将 65% 的核心业务切到了 Claude 3 Opus,不是因为它最强,而是因为它最不像 AI 模型,更像一台精密仪器。Opus 在长文本推理上的稳定性,让我想起十年前用 IBM DB2 做金融交易系统的感觉——没有惊喜,但绝对可靠。
关键参数实测(基于 1000 次并发请求):
| 指标 | 数据 | 说明 |
|---|---|---|
| P95 延迟 | 780ms | 输入 4000 字中文,输出 1200 字摘要 |
| 错误率 | 0.17% | 全部为rate_limit_exceeded,无internal_error |
| 中文专有名词保留率 | 99.3% | 测试集含 237 个医学术语、189 个法律条文编号 |
| 上下文窗口利用率 | 92% | 在 200K token 上下文中,实际有效利用率达 184K |
迁移时必须改的三处代码:
- System Prompt 位置变更:OpenAI 放在
messages[0],Claude 要求放在system字段,且不能混入 messages 数组。我们写了兼容层:
def build_claude_payload(prompt, messages): # OpenAI 格式:[{"role":"system","content":...}, {"role":"user","content":...}] # Claude 格式:{"system": "...", "messages": [...]} system_content = "" user_messages = [] for msg in messages: if msg["role"] == "system": system_content = msg["content"] else: user_messages.append(msg) return { "system": system_content, "messages": user_messages, "max_tokens": 4096 }- Stop Sequence 逻辑反转:OpenAI 的
stop是“遇到即停”,Claude 的stop_sequences是“遇到即截断并返回”。我们曾因此导致法律文书摘要被截在“根据《中华人民共和国”处,后面法条全丢了。解决方案是主动在 prompt 末尾加标记:
请严格按以下格式输出: 【摘要开始】 {内容} 【摘要结束】然后用正则提取【摘要开始】(.*)【摘要结束】。
- Token 计数器必须重写:Claude 的 tokenizer 对中文标点处理不同,
。和.被视为不同 token。我们弃用了 HuggingFace 的tiktoken,改用 Anthropic 官方anthropic-tokenizer库,并做了缓存:
from anthropic import Anthropic client = Anthropic() # 缓存 token 计数,避免每次请求都计算 token_cache = LRUCache(maxsize=1000) def count_tokens(text): if text in token_cache: return token_cache[text] count = client.count_tokens(text) token_cache[text] = count return count实操心得:Claude 最大的优势是“确定性”。同样的 prompt,100 次请求的结果一致性达 99.8%,而 GPT-4 Turbo 只有 92.3%。这对需要审计的场景(如医疗报告生成)至关重要。但代价是创意性稍弱——让它写营销文案,不如 GPT-4 Turbo 出彩。
3.2 Google Vertex AI:被低估的“云原生集成王”
很多人忽略 Vertex AI,觉得它只是 Google Cloud 的配套服务。实际上,它是目前唯一能把大模型 API 和传统云服务无缝缝合的平台。我们用它重构了客服系统,把意图识别、知识库检索、话术生成全链路跑在一个服务里,省掉了 3 个中间件。
核心优势实测:
- 冷启动速度:首次请求延迟仅 410ms(OpenAI 平均 680ms),因为 Vertex AI 的模型实例常驻内存;
- 混合调用能力:可同时调用
gemini-pro和text-bison,并在同一请求中指定不同模型处理不同段落。我们用这特性实现了“法律条款用 gemini,日常对话用 bison”的动态路由; - 日志深度:每个请求自动生成 trace_id,可直接在 Cloud Logging 中关联到 Compute Engine 实例日志,排查问题时不用跨 4 个控制台。
必须注意的坑:
- 地域限制:
gemini-pro在亚太区只有东京和大阪节点,且不支持上海区域。我们曾因选错 region 导致请求全部 403; - 配额申请周期:新项目默认配额极低(每天 100 次),提额需人工审核,最快也要 3 个工作日;
- Python SDK 版本陷阱:
google-cloud-aiplatform==1.42.0开始强制要求 Python 3.9+,而我们旧系统还在用 3.8。最终降级到 1.41.2,但失去了 streaming 支持。
一段真实的流式响应处理代码:
from google.cloud import aiplatform from google.cloud.aiplatform.gapic.schema import predict def stream_gemini_response(prompt): # Vertex AI 的 streaming 必须用 protobuf 格式 instance = predict.instance.TextInstance( content=prompt, parameters={"temperature": 0.3, "max_output_tokens": 2048} ) response = client.predict( endpoint="projects/xxx/locations/asia-northeast1/endpoints/xxx", instances=[instance], parameters={"stream": True} # 关键!必须显式声明 ) for prediction in response.predictions: yield prediction["content"] # 注意:这里 yield 的是 dict,不是字符串,需二次解析避坑提示:Vertex AI 的最大价值不在模型本身,而在它和 BigQuery、Cloud Storage 的原生打通。我们把用户对话日志实时写入 BigQuery,用 SQL 直接分析“哪些问题触发了 fallback”,这功能 OpenAI 生态至今没有。
3.3 Mistral AI(via Together AI):开源模型的“性价比之王”
当我们需要处理大量非敏感数据(如电商商品描述生成)时,Mistral-7B 成了最优解。不是因为它多强,而是在 7B 参数量级上,它做到了商业模型 13B 的效果,且完全可控。
性能对比(同硬件环境):
| 模型 | 输入 2000 字耗时 | 输出 500 字耗时 | 显存占用 |
|---|---|---|---|
| Mistral-7B (4-bit) | 310ms | 280ms | 6.2GB |
| Llama-3-8B (4-bit) | 420ms | 390ms | 7.1GB |
| GPT-3.5-turbo | 580ms | 520ms | —— |
部署实录:
我们用 Ollama 在 2 台 A10 服务器上部署,配置如下:
# ollama run mistral:7b-instruct-q4_K_M # 但发现默认配置在高并发下崩溃,最终采用: ollama run --num_ctx 32768 --num_gpu 1 --num_thread 8 \ mistral:7b-instruct-q4_K_M关键参数解释:
--num_ctx 32768:强制提升上下文窗口,否则默认 4K 会频繁截断;--num_gpu 1:指定只用 1 块 GPU,避免多卡通信开销;--num_thread 8:CPU 线程数设为 8,过高反而因锁竞争降低吞吐。
API 层适配技巧:
Mistral 的 REST API 返回格式和 OpenAI 不兼容,我们写了轻量转换层:
# 将 Mistral 的 { "response": "xxx" } 转为 OpenAI 格式 def mistral_to_openai_format(raw_response): return { "id": f"chatcmpl-{uuid.uuid4().hex}", "object": "chat.completion", "created": int(time.time()), "model": "mistral-7b", "choices": [{ "index": 0, "message": { "role": "assistant", "content": raw_response.get("response", "") }, "finish_reason": "stop" }], "usage": { "prompt_tokens": count_tokens(raw_response.get("prompt", "")), "completion_tokens": count_tokens(raw_response.get("response", "")), "total_tokens": 0 } }血泪教训:千万别信“Mistral-7B 支持 32K 上下文”的宣传。实测中,当输入超过 24K token 时,首 token 延迟飙升至 2.3s。我们最终在应用层做了分块处理:超过 20K 的文档,自动按语义切分为 3 段并行处理,再合并结果。
3.4 Groq(LPU 推理引擎):为“快”而生的异构计算
Groq 的本质不是模型提供商,而是用 LPU(Language Processing Unit)重构了推理流水线。它不卖模型,只卖算力,但效果惊人:我们用它跑 Llama-3-70B,P95 延迟仅 420ms,而同等配置的 A100 需要 1.8s。
适用场景极其明确:
✅ 适合:需要亚秒级响应的实时场景(如游戏 NPC 对话、编程助手实时补全)
❌ 不适合:长文档摘要、多步推理、需要高精度数学计算的任务
真实压测数据:
| 任务类型 | Groq (Llama-3-70B) | A100 (同模型) |
|---|---|---|
| 生成 200 字代码注释 | 380ms | 1.62s |
| 5000 字技术文档摘要 | 超时(>30s) | 8.4s |
| 多轮对话状态保持 | 支持 12 轮 | 支持 24 轮 |
接入要点:
Groq 的 API 设计反直觉——它没有max_tokens参数,而是用temperature控制输出长度(值越低,输出越短)。我们摸索出经验公式:
# 当需要约 N 字符输出时,设 temperature = max(0.1, 1.0 - N/5000) # 例如要 300 字,temperature = 1.0 - 300/5000 = 0.94必须配置的 Header:
X-Groq-Request-Timeout: 30000 # 强制 30s 超时,避免长任务阻塞 X-Groq-Response-Format: "json" # 否则默认返回 text/plain个人体会:Groq 是把“快”做到极致的偏科生。它让我想起当年第一次用 SSD 替换机械硬盘——不是功能更多,而是彻底改变了交互节奏。但千万别把它当通用模型用,否则会付出 3 倍成本换回 1.2 倍性能。
3.5 Perplexity API:被严重低估的“研究型工作流”
Perplexity 的 API 文档极少被提及,但它解决了 OpenAI 最难搞的痛点:事实核查与来源追溯。我们用它重构了学术写作助手,用户提问“请总结量子退火最新进展”,它返回的不仅是摘要,还有 7 个权威来源链接及对应段落高亮。
核心能力拆解:
- 自动溯源:每个生成句子末尾带
[1],点击跳转至原始论文 PDF 的具体页码; - 多源融合:可同时搜索 arXiv、PubMed、IEEE Xplore,且自动去重;
- 可信度评分:返回字段
confidence_score(0-1),低于 0.6 的内容自动标灰。
实测局限:
- 中文支持弱:搜索中文文献时,召回率仅 63%,远低于英文的 94%;
- 无法定制知识库:不能像 RAG 那样注入私有文档;
- 免费额度少:每月仅 100 次,商用需联系销售。
我们改造的 workflow:
graph LR A[用户提问] --> B{是否含“最新”“2024”等时效词?} B -->|是| C[调用 Perplexity API] B -->|否| D[调用 Claude 3] C --> E[提取来源链接] E --> F[用 Selenium 自动下载 PDF] F --> G[用 PyMuPDF 提取对应页码文本] G --> H[喂给 Claude 3 做深度解读]这个组合拳让我们学术产品的引用准确率从 72% 提升到 98.6%。
关键认知:Perplexity 不是替代 OpenAI,而是补足它的盲区。就像显微镜和望远镜的关系——一个看细节,一个看全局。
4. 迁移全流程:从环境准备到线上灰度的 12 个关键动作
4.1 环境准备阶段:别让证书和网络毁掉三天努力
很多团队卡在第一步:连不通 API。不是代码问题,而是环境配置。我们整理出高频故障点:
SSL 证书问题(占失败案例的 43%):
OpenAI 的证书由 DigiCert 签发,而某些国产平台用 Let's Encrypt。当服务器 OpenSSL 版本 <1.1.1 时,Let's Encrypt 的 ISRG Root X1 证书不被信任。解决方案不是升级 OpenSSL(可能影响其他服务),而是:
# 下载 ISRG Root X1 证书并追加到系统证书库 curl -o /tmp/isrgrootx1.pem https://letsencrypt.org/certs/isrgrootx1.pem sudo cp /tmp/isrgrootx1.pem /usr/local/share/ca-certificates/ sudo update-ca-certificatesDNS 解析超时(占失败案例的 28%):
某些平台(如 Together AI)的域名解析依赖 Cloudflare,而国内部分云厂商 DNS 会污染 CF 的 NS 记录。我们最终在/etc/resolv.conf中强制指定:
nameserver 1.1.1.1 # Cloudflare nameserver 8.8.8.8 # Google # 注释掉所有云厂商默认 nameserver代理配置陷阱:
如果公司网络走代理,OpenAI 的https://api.openai.com可能被放行,但https://api.together.xyz却被拦截。我们写了自动检测脚本:
import requests def check_proxy_access(url): try: # 绕过系统代理,直连测试 response = requests.get(url, timeout=5, proxies={}) return response.status_code == 200 except: return False # 若直连失败,再试代理4.2 代码迁移阶段:五处必须重写的“胶水代码”
1. 重试逻辑重构
OpenAI SDK 内置指数退避,但其他平台需手动实现。我们统一用tenacity库:
from tenacity import retry, stop_after_attempt, wait_exponential @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10), reraise=True ) def call_anthropic_api(payload): # 实际调用逻辑 pass关键点:max=10限制最长等待 10 秒,避免雪崩。
2. Token 计数标准化
不同平台 tokenizer 不同,我们建立统一计数器:
class TokenCounter: def __init__(self): self._counters = { "openai": tiktoken.encoding_for_model("gpt-4"), "anthropic": Anthropic().get_tokenizer(), "mistral": AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2") } def count(self, text, platform): if platform in self._counters: return len(self._counters[platform].encode(text)) return len(text.split()) * 1.3 # 保守估算3. 错误码映射表
各平台错误码不统一,我们做了标准化:
| 原始错误 | 统一错误码 | 处理建议 |
|---|---|---|
rate_limit_exceeded(Anthropic) | ERR_RATE_LIMIT | 降级到备通道 |
RESOURCE_EXHAUSTED(Vertex) | ERR_RATE_LIMIT | 同上 |
context_length_exceeded(Mistral) | ERR_CONTEXT_OVERFLOW | 截断输入并告警 |
4. 日志埋点增强
在每个 API 调用前后加结构化日志:
logger.info("api_call_start", extra={ "provider": "anthropic", "model": "claude-3-opus", "input_tokens": 3200, "request_id": request_id }) # ...调用... logger.info("api_call_end", extra={ "provider": "anthropic", "latency_ms": 780, "output_tokens": 1200, "fallback_used": False })5. 熔断器配置
用circuitbreaker库防止级联故障:
from circuitbreaker import circuit @circuit(failure_threshold=5, recovery_timeout=60) def call_vertex_api(payload): # 调用逻辑 pass # 连续 5 次失败后,60 秒内直接返回 fallback4.3 灰度发布阶段:用数据说话,而非拍脑袋
我们拒绝“一刀切”切换,而是设计了四级灰度:
Level 1:内部员工(100% 流量)
- 所有员工登录后自动进入灰度,但界面无感知;
- 监控重点:错误率、P95 延迟、fallback 触发次数;
- 阈值:错误率 >0.5% 或延迟 >1.2s,自动回滚。
Level 2:白名单客户(5% 流量)
- 仅对签署 NDA 的客户开放;
- 增加人工审核环节:所有生成内容需运营确认后才展示;
- 目的:收集真实业务反馈,而非技术指标。
Level 3:随机用户(30% 流量)
- 按用户 ID 哈希分流;
- 强制开启 A/B 测试:同一用户始终走同一通道;
- 对比维度:用户停留时长、任务完成率、客服咨询量。
Level 4:全量发布(100% 流量)
- 仅当 Level 3 数据持续 72 小时优于 OpenAI 时启动;
- 发布后 24 小时内,保留 10% 流量回切 OpenAI 作为对照组。
关键监控看板:
我们搭建了 Grafana 看板,核心指标包括:
api_fallback_rate{provider="anthropic"}:备通道调用占比;api_latency_p95_ms{model="claude-3-opus"}:P95 延迟;token_efficiency_ratio:(输出 token / 输入 token)比率,反映模型“废话率”。
实操心得:灰度不是技术动作,而是产品决策。我们曾因 Level 2 客户反馈“法律文书生成少了‘综上所述’这个固定开头”,临时加了 post-process 规则——这种细节,永远无法在测试环境发现。
5. 常见问题与实战排查指南:那些文档里不会写的真相
5.1 “为什么我的请求总是 429,但配额明明没用完?”
这是最高频问题。根本原因有三个:
原因一:突发流量触发“瞬时配额”限制
OpenAI 的配额分两层:日配额 + 每分钟配额。某客户在促销时 1 分钟内发起 1200 次请求,虽日配额只用了 3%,但触发了每分钟 1000 次的硬限制。解决方案:
- 在客户端加令牌桶限流:
bucket = TokenBucket(1000, 60); - 服务端用 Redis 记录每分钟请求数,超限时返回
Retry-After: 30。
原因二:IP 共享导致“连坐处罚”
某些云厂商(如 AWS EC2)的公网 IP 是共享池。你邻居的爬虫触发了风控,整个 IP 段被限流。我们用curl -v https://api.openai.com/v1/models测试,发现返回头中有:
x-ratelimit-limit-requests: 10000 x-ratelimit-remaining-requests: 0 x-ratelimit-reset-requests: 1719234000但你的账号明明没调用过。此时必须更换 IP 或联系云厂商。
原因三:User-Agent 携带敏感信息
我们曾用requests库,默认 User-Agent 是python-requests/2.28.1,被某平台识别为“非浏览器流量”而限流。解决方案:
headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" }5.2 “中文输出乱码/丢字,但英文正常,怎么排查?”
这不是模型问题,而是编码问题。我们总结出排查路径:
Step 1:确认传输层编码
检查 HTTP 响应头:
Content-Type: application/json; charset=utf-8若缺失charset=utf-8,强制指定:
response = requests.post(url, json=payload) response.encoding = 'utf-8' # 关键! data = response.json()Step 2:检查模型 tokenizer 配置
Mistral 默认 tokenizer 对中文支持不佳。我们改用mistralai/Mistral-7B-Instruct-v0.2的tokenizer_config.json,将add_prefix_space设为false。
Step 3:验证 JSON 解析
某些平台返回的 JSON 中文是\u4f60\u597d形式,但 Pythonjson.loads()默认不转义。解决方案:
import json data = json.loads(response.text, ensure_ascii=False) # 关键参数5.3 “Fallback 切换后,用户看到重复内容,怎么解决?”
这是网关层经典问题。根源在于:主通道超时后,请求已发往备通道,但主通道后续又返回了慢响应,导致两次渲染。
终极解决方案:请求去重 ID(Request Deduplication ID)
- 客户端生成唯一
x-request-id(UUID v4); - 网关层用 Redis 记录
x-request-id → status,有效期 5 分钟; - 当收到重复 ID 时,直接返回
409 Conflict并附带首次响应; - 前端捕获 409,从 localStorage 读取缓存结果。
# 网关伪代码 def handle_request(request): req_id = request.headers.get("x-request-id") if not req_id: req_id = str(uuid.uuid4()) cache_key = f"req:{req_id}" cached_status = redis.get(cache_key) if cached_status == "processing": return Response(status=409, headers={"X-Request-ID": req_id}) redis.set