定方案:自建網(wǎng)關(guān)與多供應(yīng)商降級實戰(zhàn))
做服務(wù)端這塊以來最讓我頭疼的采購單不是服務(wù)器而是大模型 API 的賬單。官方接口質(zhì)量確實有保證可價格明明白白放在那里按 Token 計費量一上來就肉疼直接連官方接口又經(jīng)常遇上限流和超時尤其業(yè)務(wù)高峰期429 和 504 交替出現(xiàn)用戶那邊直接看到白屏。這個問題說白了就是官方 API 太貴、直連又不穩(wěn)而我最后是用“自建接入網(wǎng)關(guān) 多供應(yīng)商降級梯隊 用量治理”這套組合解決的。這篇文章不繞彎子直接講我踩過的坑、算過的賬以及現(xiàn)在線上跑的方案適合預(yù)算有限的個人開發(fā)者和三五十人團(tuán)隊參考。1. 先摸清癥結(jié)官方 API 貴在哪里直連不穩(wěn)又卡在哪里1.1 Token 計費里的三個隱形漏斗很多人只看模型單價覺得“價格表這么低怎么可能超預(yù)算”。真正的賬單爆炸通常來自三個不容易察覺的環(huán)節(jié)。第一個是上下文膨脹。我接手過一個客服機器人最初 Prompt 只有兩百字后來運營同學(xué)不斷在系統(tǒng)提示里補充規(guī)則不知不覺膨脹到了四千字。用戶每次對話都要攜帶這段超長前綴而輸入 Token 是按人頭重新計費的。假設(shè)一天一萬次請求兩百字變四千字輸入成本直接翻二十倍。這個成本增長不體現(xiàn)在單次體驗上只體現(xiàn)在月底賬單里。第二個是重試重復(fù)計費。官方 API 偶發(fā)超時很正常最初客戶端邏輯是“超時就重新請求”但事前沒有排查上一個請求是否已經(jīng)被服務(wù)端接收。結(jié)果高峰期經(jīng)常出現(xiàn)同一個請求被重復(fù)處理用戶只看到一次回答賬單上卻計了兩次費。這個坑在高并發(fā)場景下尤其明顯因為超時集中在流量高峰一次雪崩可能帶來成倍的重試。第三個是無腦重復(fù)調(diào)用。同一個問題在不同會話里問了很多遍每次都走完整推理鏈路完全沒考慮緩存。哪怕系統(tǒng)提示詞優(yōu)化得再好高頻重復(fù)問題依然在燒錢。后來我加了語義緩存之后類似“怎么退款”“人工客服幾點上班”這類重復(fù)問題大概有三分之一被直接命中攔截沒有進(jìn)模型。為了看清“貴”到底貴在哪我算過一筆簡單的賬。假設(shè)某主流模型輸入價格是 0.5 元/百萬 Token輸出價格是 2 元/百萬 Token每天一萬次調(diào)用平均每次輸入 2000 Token、輸出 500 Token輸入日成本 10000 × 2000 × 0.5 / 1000000 10 元輸出日成本 10000 × 500 × 2 / 1000000 10 元月成本大約 600 元。如果上下文膨脹到 8000 Token輸入日成本會變成 40 元月成本直接變成一千五以上。再加上重試重復(fù)計費實際可能到兩千元。所以說“貴”不完全是因為單價更多是用量習(xí)慣和接入方式造成的浪費。1.2 直連不穩(wěn)定缺的其實不是網(wǎng)絡(luò)而是治理層先定義一下這里說的“直連”客戶端把請求直接發(fā)到官方 API Endpoint不經(jīng)過任何中間層。這種方式在開發(fā)階段完全沒問題但放到生產(chǎn)環(huán)境就暴露出四個缺口。第一是并發(fā)與限流。官方網(wǎng)關(guān)對單個 Key 的每分鐘請求數(shù)、每分鐘 Token 數(shù)都有硬限制。業(yè)務(wù)秒級并發(fā)一上來429 就滿天飛。429 一旦出現(xiàn)如果客戶端沒有做指數(shù)退避只會加劇限流陷入“越急越失敗越失敗越重試”的惡性循環(huán)。第二是連接復(fù)用。官方接口走 HTTPS每次新建連接都要完成 TCP 握手和 TLS 握手。連接池配置不當(dāng)或者長連接被服務(wù)端斷開后沒有及時重連P99 延遲會被拉得很高。很多團(tuán)隊只盯著模型推理時間卻忽略了握手開銷占了總耗時的大半。第三是超時與重試規(guī)范。超時值設(shè)得太短正常的慢請求被誤殺設(shè)得太長故障時用戶要等很久。更麻煩的是重試沒有冪等標(biāo)識失敗后重新發(fā)送服務(wù)端可能已經(jīng)處理過了于是用戶得到一次回復(fù)賬單卻記了兩次費用。第四是鏈路波動。不同地區(qū)訪問同一 APIDNS 解析可能落到不同的集群節(jié)點時延方差巨大。業(yè)務(wù)高峰期官方網(wǎng)關(guān)本身也會出現(xiàn)負(fù)載不均表現(xiàn)為時好時壞。這些問題都不是改一個“超時時間”能解決的它們屬于接入層治理的范疇。想解決“直連不穩(wěn)”不能只盯著網(wǎng)絡(luò)而是要在應(yīng)用和上游之間補一層統(tǒng)一處理限流、超時、重試、緩存和故障切換。這正好引出后面要展開的方案。2. 方案權(quán)衡官方企業(yè)版、第三方 API 服務(wù)商、自建網(wǎng)關(guān)到底怎么選2.1 先算成本賬再談選型在動手接第三方之前我建議所有人都先做一次成本測算。因為很多時候不是平臺貴而是你選錯了購買方式。官方公開價適合開發(fā)調(diào)試、低頻率生產(chǎn)調(diào)用。優(yōu)點是沒有中間商缺點是沒有折扣也沒有流量治理能力。官方企業(yè)版適合月度調(diào)用量穩(wěn)定且高的團(tuán)隊。銷售可以談?wù)劭弁ǔ1裙_價低 10% 到 40%。缺點是要求一定消費量流程偏重對個人開發(fā)者基本談不下來。第三方 API 服務(wù)商這些廠商向上游批量采購再把能力以兼容 OpenAI SDK 的方式開放給開發(fā)者。對散戶來說它解決的是兩件事用批發(fā)價買到更多調(diào)用量以及獲得多線路容災(zāi)能力。缺點是需要在第三方平臺充值存在平臺穩(wěn)定性和數(shù)據(jù)合規(guī)風(fēng)險。自建網(wǎng)關(guān)它不替代供應(yīng)商而是在你的應(yīng)用和多個上游之間加一層統(tǒng)一做緩存、重試、熔斷和計費。自建網(wǎng)關(guān)解決的是以后每次調(diào)整供應(yīng)商不需要動業(yè)務(wù)代碼只需要改配置。我把四個方案做了對比方案準(zhǔn)入門檻成本水平穩(wěn)定性適合場景官方公開價低基準(zhǔn)價一般取決于鏈路開發(fā)測試、低頻生產(chǎn)官方企業(yè)折扣高基準(zhǔn)價 70%-90%較高用量大的正規(guī)業(yè)務(wù)第三方 API 服務(wù)商低基準(zhǔn)價 50%-85%中高個人開發(fā)者、小團(tuán)隊自建網(wǎng)關(guān)中增加少量開發(fā)成本高需要長期精細(xì)治理的業(yè)務(wù)實際選型時不一定只選一個。我最后是把“官方 Key 第三方服務(wù)商”都作為上游外面再包一層自建網(wǎng)關(guān)做治理組合起來用。2.2 第三方 API 服務(wù)商為什么能“便宜又穩(wěn)”很多人第一次接觸第三方 API 服務(wù)商會懷疑它是不是藏著什么貓膩為什么上游官方價格那么貴它卻能便宜 20% 甚至 50%原因主要有三個。第一是折扣差。他們和上游簽的是預(yù)付包量協(xié)議或者享受階梯折扣采購價低于公開價。平臺再把能力轉(zhuǎn)售給大量開發(fā)者從中賺取合理差價。只要用量足夠大差價空間是真實存在的。第二是網(wǎng)絡(luò)復(fù)用。這些平臺有自己的機房和網(wǎng)絡(luò)資源能把官方接口放到更優(yōu)鏈路上并提供統(tǒng)一的接入入口。對個人開發(fā)者來說省去了自己優(yōu)化網(wǎng)絡(luò)的成本。第三是多上游容災(zāi)。成熟的平臺通常同時接多個上游供應(yīng)商某一個源出了問題能自動切換。這是散戶自己很難做到的。但選擇平臺時一定要謹(jǐn)慎。我自己的篩選標(biāo)準(zhǔn)是支持 OpenAI 兼容格式、計價透明、有請求級賬單、提供小額測試額度、明確說明數(shù)據(jù)不留存、工單響應(yīng)及時。決定長期使用前先充一個最低檔用三天小流量灰度重點看 P99 耗時、429 限流頻率和錯誤返回結(jié)構(gòu)。這里必須提醒一句遇到那些聲稱“無限調(diào)用”“無視限流”“永久免費”的渠道盡量遠(yuǎn)離。大概率有違規(guī)風(fēng)險數(shù)據(jù)安全也完全沒有保障。2.3 自建網(wǎng)關(guān)把主控權(quán)拿回自己手里自建網(wǎng)關(guān)聽起來工程量大其實對一個十人左右的技術(shù)團(tuán)隊來說用 FastAPI Redis 就能實現(xiàn)一個最小可用版本。它不需要很強的造輪子能力核心是這五個模塊配置中心用來維護(hù)模型列表、供應(yīng)商優(yōu)先級、成本權(quán)重路由層按業(yè)務(wù)場景把請求分到不同上游緩存層攔截高頻重復(fù)問題可靠性層負(fù)責(zé)超時、重試、熔斷和退避可觀測層記錄每一次調(diào)用的 Token、費用、延遲和狀態(tài)碼。網(wǎng)關(guān)的另一層價值在于它可以承載“灰度切換”。以前想從一個供應(yīng)商切到另一個要改代碼發(fā)版有了網(wǎng)關(guān)只需要在配置中心調(diào)整分流比例比如官方 20%、第三方 A 平臺 70%、第三方 B 平臺 10%改完立即生效。供應(yīng)商故障時可以把對應(yīng)權(quán)重直接改成 0其他流量自動走剩余通道。第三章我會給出一個最小可用的實現(xiàn)它不需要復(fù)雜框架一個 Python 封裝類就夠了。3. 實操從零搭起一套省錢又不飄的 API 接入層3.1 第一步盤點業(yè)務(wù)調(diào)用畫像任何改動之前先回答幾個問題日均調(diào)用量是多少高峰期 QPS 是多少平均輸入 Token、輸出 Token 分別是多少上下文有沒有過度膨脹P99 延遲、429 和 5xx 錯誤率是多少每月實際出賬費用是多少哪些功能模塊消耗最大我當(dāng)時把所有調(diào)用場景列了個表客服、摘要、分類、寫作。每個場景標(biāo)注“高價值/低成本”。比如用戶直接操作的對話屬于高價值后臺批量摘要屬于低成本。這個標(biāo)記決定了后續(xù)的路由策略高價值場景優(yōu)先保證質(zhì)量低成本場景優(yōu)先用廉價供應(yīng)商。同時還要統(tǒng)計上下文長度分布。有些場景明明只是分類任務(wù)卻把整篇長文都塞進(jìn)去輸入成本被拉得很高。后來我加了前置截斷和關(guān)鍵信息抽取平均輸入 Token 從五千降到了一千二成本直接降了一截。3.2 第二步配置上游供應(yīng)商與降級梯隊網(wǎng)關(guān)的第一步是定義供應(yīng)商。我用 Python 字典維護(hù)了一個全局配置示例結(jié)構(gòu)如下import os PROVIDERS { official: { api_key: os.environ[OFFICIAL_API_KEY], base_url: https://api.official.com/v1, models: [main-model], priority: 1, max_retries: 2, }, third_party: { api_key: os.environ[THIRD_PARTY_API_KEY], base_url: https://api.third-party.com/v1, models: [cheap-model], priority: 2, max_retries: 3, }, third_party_b: { api_key: os.environ[THIRD_PARTY_B_API_KEY], base_url: https://api.third-party-b.com/v1, models: [backup-model], priority: 3, max_retries: 3, }, }優(yōu)先級的含義是正常情況下優(yōu)先嘗試 priority 小的供應(yīng)商失敗后依次向后降級。但這里有一個很關(guān)鍵的經(jīng)驗不要把所有流量都壓在同一個優(yōu)先級上尤其是不要把便宜供應(yīng)商設(shè)為唯一首選。因為第三方平臺便宜往往伴隨更嚴(yán)格的并發(fā)限制一旦 QPS 起來限流會遠(yuǎn)比官方更猛。我的做法是核心實時請求先走官方或質(zhì)量最高的供應(yīng)商非核心批量任務(wù)走便宜供應(yīng)商這樣能拉平整體成本同時保住核心體驗。還有一點Key 一定通過環(huán)境變量注入不要寫死在代碼里。后面避坑環(huán)節(jié)我會詳細(xì)講原因。3.3 第三步實現(xiàn)帶重試、退避和降級的調(diào)用封裝網(wǎng)關(guān)最核心的代碼是一個調(diào)用封裝。先用 OpenAI SDK 創(chuàng)建多個客戶端再按優(yōu)先級逐個嘗試配合指數(shù)退避和隨機抖動。示例實現(xiàn)如下import os import random import time from openai import OpenAI class LLMGateway: def __init__(self, providers): self.clients {} self.providers providers for name, cfg in providers.items(): self.clients[name] OpenAI( api_keycfg[api_key], base_urlcfg[base_url] ) def chat(self, messages, temperature0.7, max_attempts5): attempts 0 while attempts max_attempts: for name, cfg in sorted( self.providers.items(), keylambda item: item[1][priority] ): try: resp self.clients[name].chat.completions.create( modelcfg[models][0], messagesmessages, temperaturetemperature, timeout30, ) self._record(name, resp) return resp except Exception as e: self._log_error(name, e) # 指數(shù)退避 隨機抖動 sleep_time 0.5 * (2 ** attempts) random.uniform(0, 0.5) time.sleep(sleep_time) attempts 1 raise RuntimeError(all providers failed)這段代碼有兩個細(xì)節(jié)值得說。第一重試不能立刻重試。立刻重試往往還是失敗而且會加重上游壓力。指數(shù)退避加隨機抖動是為了避免多個請求在同一時刻集體重試形成二次壓垮。第二每次失敗后都要記錄供應(yīng)商名和異常類型否則到時候只知道“調(diào)不通”不知道是哪條鏈路先掛的。如果業(yè)務(wù)對冪等有要求我建議在請求參數(shù)里帶上自定義 request_id傳給上游并在日志里記錄。這樣即使發(fā)生重復(fù)請求對賬時也能查出來避免重復(fù)計費說不清。3.4 第四步語義緩存把高頻重復(fù)問題攔在網(wǎng)關(guān)外成本優(yōu)化的另一個大頭是緩存。最簡單的是精確匹配把用戶輸入做哈希查 Redis命中直接返回上次結(jié)果。import hashlib import redis cache redis.Redis(hostlocalhost, port6379, decode_responsesTrue) def chat_with_cache(gateway, messages): text .join(msg[content] for msg in messages) key fllm_cache:{hashlib.sha256(text.encode()).hexdigest()} cached cache.get(key) if cached: return cached resp gateway.chat(messages) content resp.choices[0].message.content cache.setex(key, 600, content) # 10 分鐘過期 return content精確匹配能解決“一模一樣的重復(fù)問題”但解決不了“說法不同但意圖相同”的問題比如“怎么退款”和“退款流程是什么”。想攔這部分就要上語義緩存先用 embedding 接口把輸入向量化再和緩存中的歷史向量算相似度閾值高于 0.92 就認(rèn)為語義重復(fù)直接返回緩存結(jié)果。不過語義緩存建議謹(jǐn)慎放開原因有兩點一是相似不等于相同可能誤導(dǎo)用戶二是隱私敏感內(nèi)容不該被緩存。我的經(jīng)驗是先只對客服場景開放精確匹配語義匹配在小流量下驗證效果之后再逐步放大。緩存命中時我會在響應(yīng)頭加一個 X-Cache: HIT 標(biāo)記方便后續(xù)和日志對應(yīng)排查。4. 把“省下來的錢”變成可觀測的數(shù)字4.1 設(shè)計費用日志與日結(jié)報表很多團(tuán)隊省錢靠月底看賬單問題賬單出來已經(jīng)晚了根本不知道是哪天的哪個功能燒掉了預(yù)算。我在網(wǎng)關(guān)里加了結(jié)構(gòu)化日志要求每次調(diào)用都至少記錄以下字段時間戳、trace_id、業(yè)務(wù)場景標(biāo)簽、供應(yīng)商名、模型名、輸入 Token、輸出 Token、估算費用、延遲、HTTP 狀態(tài)碼、是否緩存命中。費用估算可以簡單用一個函數(shù)def estimate_cost(input_tokens, output_tokens, price_map): return ( input_tokens * price_map[input] output_tokens * price_map[output] )price_map 來自統(tǒng)一配置每個供應(yīng)商每種模型單獨維護(hù)。日志落盤后每天跑一次聚合腳本按場景和供應(yīng)商兩個維度做統(tǒng)計。這樣我能立刻回答三個問題今天花了多少錢哪個場景花得最多哪個供應(yīng)商實際最便宜這套報表上線第一周就發(fā)現(xiàn)了問題某個后臺批處理任務(wù)占了 40% 的費用而它的業(yè)務(wù)價值很低只是因為重試邏輯寫得不合理每次失敗都把整批任務(wù)重新跑一遍。后來把重試粒度從批改成單條費用直接腰斬。4.2 設(shè)置成本與穩(wěn)定性告警有了數(shù)據(jù)之后接下來就是設(shè)閾值。我目前的告警規(guī)則如下日成本環(huán)比增長超過 50% 時告警單個供應(yīng)商 5 分鐘錯誤率超過 5% 時告警P99 延遲超過 5 秒時告警賬戶余額低于預(yù)期三天用量時告警。告警渠道直接用企業(yè)微信或釘釘機器人本質(zhì)就是往 Webhook 推一條 JSON。重點不是怎么推而是閾值怎么定。第一次我把錯誤率閾值設(shè)成了 0結(jié)果一天收到幾百條告警徹底疲勞。后來改成“連續(xù)兩個統(tǒng)計周期超過閾值才告警”準(zhǔn)確率高了很多。穩(wěn)定性告警的下一步是自動切換。當(dāng)?shù)谌焦?yīng)商連續(xù)錯誤率升高時網(wǎng)關(guān)可以把它的權(quán)重臨時降為 0把流量切到官方 Key 或其他備用通道。同時保留人工確認(rèn)機制避免自動切換引發(fā)的誤判。4.3 每周復(fù)盤動態(tài)調(diào)整分流比例我固定每周五做一次成本復(fù)盤數(shù)據(jù)表格很簡單各供應(yīng)商的實際到賬費用、成功率、P99 延遲、重試率。如果某個平臺聲稱便宜 10%但限流導(dǎo)致重試率高達(dá) 25%實際算下來比官方還貴這種表面便宜就沒有意義。調(diào)整方式很直接在配置中心改分流比例。比如這周官方折扣力度大核心請求比例可以從 20% 提到 40%下周某個第三方平臺服務(wù)不穩(wěn)定權(quán)重直接降到 0。整個過程不需要重新發(fā)版網(wǎng)關(guān)讀到新配置后自動生效。這里有一個值得強調(diào)的思路成本優(yōu)化不是一次性動作而是一個持續(xù)調(diào)參的過程。模型價格會變、供應(yīng)商政策會變、業(yè)務(wù)流量也會變只有通過數(shù)據(jù)驅(qū)動才能長期維持合理的成本水平。5. 避坑實錄這些問題我花了真金白銀才學(xué)會5.1 API Key 硬編碼是全劇最蠢的錯誤我見過不少項目為了圖省事把 API Key 直接寫在代碼里甚至寫在前端的靜態(tài)資源里。這種操作一旦被爬走后果就是被刷量扣費。我自己早期就吃過一次虧因為急著上線把 Key 放到了前端配置里當(dāng)天夜里被刷掉一千多塊。第三方平臺的 Key 泄露比官方 Key 更肉疼因為很多第三方按余額實時扣費濫用速度極快?,F(xiàn)在我的所有 Key 一律從環(huán)境變量讀取配置文件中只保留鍵名不留值。另外每個環(huán)境用獨立的 Key方便出問題時單獨輪換不用全量重發(fā)。5.2 廉價平臺偷偷混用小模型有一段時間我發(fā)現(xiàn)輸出質(zhì)量時好時壞同一個問題有時候回答很深入有時候明顯淺薄。查日志發(fā)現(xiàn)請求日志里 model 字段完全一致但返回內(nèi)容的 Token 分布和推理風(fēng)格差異很大。后來我懷疑平臺在低峰期用小模型頂替了大模型來控制成本。為了驗證我準(zhǔn)備了一組“金絲雀問題”專門問那些只有高版本模型才答得出來、低版本模型大概率會答錯的知識點。每隔一小時跑一次結(jié)果果然抓到了問題。這樣的平臺我直接拉黑因為它破壞的是用戶信任不是錢的問題。5.3 沒有熔斷被上游故障拖垮某次第三方平臺整體故障我們這邊所有請求都在超時重試線程池很快被打滿連健康檢查都開始失敗。問題根本不在于那一次上游抖動而在于我們沒有做熔斷。如果一個請求連續(xù)失敗 20 次網(wǎng)關(guān)應(yīng)該主動打開熔斷開關(guān)直接拒絕新請求 10 秒讓積壓流量快速消化。熔斷恢復(fù)也要講究不能時間一到就立刻全量放流量而應(yīng)該先用小流量試探等成功率回升后再逐步放開。這一套邏輯雖然簡單但關(guān)鍵時候能保住整套服務(wù)的命。5.4 長上下文請求最容易預(yù)算超標(biāo)有些模型單價并不高但如果業(yè)務(wù)不控制上下文長度一次請求塞幾十萬 Token照樣能把預(yù)算燒穿。我現(xiàn)在對用戶輸入長度做前置限制超過閾值就截斷再配合關(guān)鍵信息抽取。對于長文檔場景先做分段摘要再把摘要喂給模型而不是讓模型直接閱讀全文。同樣重要的還有系統(tǒng)提示詞。不要覺得提示詞寫得越長模型越聰明它每多一個 Token 都會進(jìn)入每次調(diào)用的計費基數(shù)。我現(xiàn)在的建議是提示詞只保留必要規(guī)則把不影響正確性的歷史包袱全部清掉。5.5 免費額度只適合測試不適合生產(chǎn)不少平臺會送免費額度用來跑通流程沒有問題。但生產(chǎn)鏈路對可靠性、售后、并發(fā)能力的要求完全不同。如果只是因為免費省下了費用卻扛不住限流得不償失。我見過有人把免費額度用進(jìn)生產(chǎn)結(jié)果平臺因為風(fēng)控收緊直接封號臨時找不到替代供應(yīng)商業(yè)務(wù)停了半天。我現(xiàn)在的策略是免費額度只用來做模型效果測評和連通性測試正式生產(chǎn)至少準(zhǔn)備一個付費供應(yīng)商和一個官方兜底通道。穩(wěn)定優(yōu)先省錢在后。這套組合方案線上跑了一段時間之后效果確實符合預(yù)期。月度 API 成本從原來的兩千四降到了一千出頭接口的 P99 延遲從動輒七八秒降到兩秒以內(nèi)更重要的是上游故障時基本做到了無感切換。整個過程中最大的體會是省錢不能靠單一技巧而是要把用量治理嵌入到開發(fā)的日常流程里。如果你正準(zhǔn)備接大模型 API我的建議是先花一天時間優(yōu)化提示詞和加緩存再選一兩家正規(guī)服務(wù)商做小流量灰度同時保留官方 Key 做兜底。很多細(xì)節(jié)不親自踩一遍看再多文章也體會不到。