用架構(gòu):大模型API統(tǒng)一接入與治理實(shí)踐)
這兩年做了幾個(gè)AI應(yīng)用后我最深的體會(huì)是真正拉開項(xiàng)目復(fù)雜度的不是某個(gè)模型效果好不好而是能不能把一個(gè)多模型混合調(diào)用架構(gòu)搭好把多個(gè)大模型API統(tǒng)一管理起來。模型越來越多各家能力差異又大如果業(yè)務(wù)代碼直接散裝接入前期確實(shí)“爽”等要切換、降級(jí)、算成本的時(shí)候整個(gè)團(tuán)隊(duì)都會(huì)被拖進(jìn)泥潭。這篇文章就把我在某個(gè)內(nèi)部AI應(yīng)用項(xiàng)目里沉淀下來的統(tǒng)一接入方案寫出來包括API抽象層、路由策略、容錯(cuò)機(jī)制、成本觀測(cè)和幾個(gè)踩過的坑適合正在做Agent平臺(tái)、智能客服、內(nèi)容生成服務(wù)的開發(fā)者參考。1. 當(dāng)項(xiàng)目里同時(shí)接入三五個(gè)大模型API之后混亂是怎么滋生出來的1.1 每個(gè)模型一套接入方式代碼里到處是分支我最早接手的那套系統(tǒng)邏輯非常簡(jiǎn)單業(yè)務(wù)方指定一個(gè)模型我們調(diào)一個(gè)接口把結(jié)果返回給前端。后來需求多了有的場(chǎng)景要便宜快速有的場(chǎng)景要強(qiáng)推理有的場(chǎng)景要視覺理解于是陸陸續(xù)續(xù)接了好幾個(gè)不同廠商的API。最開始大家沒當(dāng)回事每個(gè)新模型都按SDK文檔在業(yè)務(wù)代碼里單獨(dú)寫一個(gè)方法沒多久代碼里就出現(xiàn)了大量if provider xxx的分支。更難受的是各家API差異比想象中大得多。請(qǐng)求參數(shù)有的叫temperature有的叫temp最大值還不一樣流式返回有的用SSE有的用自研事件格式錯(cuò)誤碼更是五花八門有的返回HTTP 429表示限流有的返回200但里層error_code是限流。每次聯(lián)調(diào)一個(gè)新模型光參數(shù)轉(zhuǎn)換和錯(cuò)誤解析就要折騰兩三天等接入的模型數(shù)量超過三個(gè)這套代碼基本沒人敢動(dòng)了。1.2 統(tǒng)一管理不只是“開發(fā)方便”更是線上穩(wěn)定性問題當(dāng)時(shí)很多人覺得多接幾個(gè)模型不就是為了買到更便宜的服務(wù)嗎開發(fā)麻煩一點(diǎn)忍忍就過去了。真正讓我下決心做統(tǒng)一架構(gòu)的是一次線上事故某個(gè)主力模型服務(wù)商發(fā)公告說當(dāng)天要升級(jí)我們想切到備用的另一個(gè)模型結(jié)果發(fā)現(xiàn)備用模型雖然早早就接好了但SDK版本不一致、請(qǐng)求參數(shù)沒適配生產(chǎn)環(huán)境一壓測(cè)就報(bào)錯(cuò)。最后只能頂著風(fēng)險(xiǎn)繼續(xù)用老服務(wù)整晚沒睡踏實(shí)。從那以后我想明白了分管多個(gè)大模型API的核心價(jià)值不在于“集成了多少家”而在于三個(gè)能力透明替換切換模型不應(yīng)該是改代碼、重新上線而是配置層面的動(dòng)作。策略路由不同請(qǐng)求能自動(dòng)選擇合適模型比如簡(jiǎn)單的摘要走低價(jià)模型復(fù)雜的推理走強(qiáng)模型。穩(wěn)定兜底某個(gè)模型故障時(shí)流量能自動(dòng)轉(zhuǎn)移到健康模型而不是讓用戶看到超時(shí)。1.3 我最終確定的架構(gòu)目標(biāo)統(tǒng)一管理多個(gè)大模型API本質(zhì)上是給業(yè)務(wù)層和模型服務(wù)商之間加一層“中間人”。這層中間人要做的事情不復(fù)雜但邊界要清晰對(duì)業(yè)務(wù)層暴露一套穩(wěn)定、中立的統(tǒng)一接口業(yè)務(wù)方不需要關(guān)心后端到底是哪家模型。對(duì)模型服務(wù)商通過適配器做隔離新增一個(gè)模型只需要實(shí)現(xiàn)一個(gè)適配器不改業(yè)務(wù)代碼。對(duì)運(yùn)維側(cè)提供路由、熔斷、降級(jí)、計(jì)費(fèi)的統(tǒng)一配置入口。架構(gòu)上就分成了四塊統(tǒng)一抽象層、適配器層、路由調(diào)度層、可觀測(cè)與成本層。下面我逐個(gè)講清楚設(shè)計(jì)和實(shí)現(xiàn)思路。2. 先把API抽象拉齊一個(gè)Request/Response模型吃下所有差異2.1 為什么必須先統(tǒng)一協(xié)議而不是直接做網(wǎng)關(guān)很多人一聽“統(tǒng)一管理多個(gè)大模型API”第一反應(yīng)是搭一個(gè)API網(wǎng)關(guān)把各家請(qǐng)求轉(zhuǎn)發(fā)一下。我覺得這是本末倒置。如果網(wǎng)關(guān)的轉(zhuǎn)發(fā)邏輯還是基于各家私有協(xié)議的每接一個(gè)模型就要在網(wǎng)關(guān)里寫一層透?jìng)鲄?shù)那網(wǎng)關(guān)反而成了又一個(gè)維護(hù)地獄。正確的做法是先定義一套中立的請(qǐng)求/響應(yīng)模型把所有模型的差異全部收斂在適配器里。業(yè)務(wù)方只認(rèn)這一套模型網(wǎng)關(guān)和路由也只認(rèn)這一套模型。只要協(xié)議層統(tǒng)一了后面做路由、做緩存、做成本統(tǒng)計(jì)才有共同的抓手。我定義統(tǒng)一請(qǐng)求時(shí)堅(jiān)持幾個(gè)原則字段用中性語義不偏向任何一家。比如對(duì)話歷史統(tǒng)一叫messages模型名統(tǒng)一叫model隨機(jī)性統(tǒng)一叫temperature。能力按最小可用集設(shè)計(jì)先覆蓋對(duì)話、流式、工具調(diào)用、JSON模式這四個(gè)高頻能力其他特殊能力走擴(kuò)展字段。參數(shù)范圍做歸一比如有的廠商溫度支持0到2有的只支持0到1統(tǒng)一層直接約束為0到1適配器內(nèi)部做縮放。這樣看起來“犧牲”了一些廠家特性但換來的是業(yè)務(wù)邏輯的高度一致。我在實(shí)踐里最大的體會(huì)是協(xié)議統(tǒng)一得越徹底后面的路由和成本代碼就越省力。2.2 供應(yīng)商適配器的接口拆分統(tǒng)一協(xié)議定義好之后每個(gè)模型服務(wù)商對(duì)應(yīng)一個(gè)適配器。適配器的核心接口非常收斂class ModelAdapter(Protocol): def get_model_info(self) - UnifiedModelInfo: ... def chat(self, request: UnifiedRequest) - UnifiedResponse: ... def stream_chat(self, request: UnifiedRequest) - Iterator[UnifiedEvent]: ...get_model_info返回模型元數(shù)據(jù)包括模型ID、支持哪些能力、上下文長(zhǎng)度、計(jì)費(fèi)單價(jià)等。chat走非流式stream_chat走流式。業(yè)務(wù)方永遠(yuǎn)只面向這兩個(gè)方法編程具體HTTP調(diào)用、鑒權(quán)、SDK封裝全部在適配器內(nèi)部。每個(gè)模型的元信息長(zhǎng)這樣class UnifiedModelInfo(BaseModel): model_id: str supports_tools: bool False supports_json_mode: bool False supports_vision: bool False max_context_tokens: int 0 prompt_price_per_1k: float 0.0 completion_price_per_1k: float 0.0這里有個(gè)很容易忽略的細(xì)節(jié)模型元信息不是寫死的常量而是每個(gè)適配器必須返回的真實(shí)值。因?yàn)楹竺媛酚梢鶕?jù)supports_tools、max_context_tokens、prompt_price_per_1k做篩選和排序。如果元信息不準(zhǔn)確路由算出來的結(jié)果全是錯(cuò)的。2.3 模型能力差異的歸一化處理統(tǒng)一協(xié)議不難真正花時(shí)間的是處理“大家都說自己支持工具調(diào)用但格式完全不一樣”這類問題。比如工具調(diào)用某個(gè)服務(wù)商返回的是結(jié)構(gòu)化的tool_calls數(shù)組另一個(gè)服務(wù)商要求把工具定義寫成一段特殊格式的字符串還有一個(gè)服務(wù)商把工具調(diào)用結(jié)果嵌在Markdown代碼塊里。統(tǒng)一層的做法是定義自己的UnifiedToolCall結(jié)構(gòu)class UnifiedToolCall(BaseModel): call_id: str function_name: str arguments: dict適配器負(fù)責(zé)把各家返回格式轉(zhuǎn)換成這個(gè)結(jié)構(gòu)反向時(shí)再把業(yè)務(wù)方的工具結(jié)果轉(zhuǎn)換成廠商要求的格式。這樣業(yè)務(wù)層永遠(yuǎn)不會(huì)看到廠商相關(guān)的字段。JSON mode也是重災(zāi)區(qū)。有的用response_format{type: json_object}有的叫structured_output有的干脆不支持只能靠提示詞約束。歸一化之后統(tǒng)一層只認(rèn)一個(gè)response_formatjson的開關(guān)適配器內(nèi)部實(shí)現(xiàn)“真支持就用參數(shù)不支持就用提示詞拼一段強(qiáng)約束文本”。我當(dāng)時(shí)的一個(gè)判斷是能靠適配器抹平的差異就不要讓業(yè)務(wù)方感知只有“支持還是不支持”這種硬能力差異才需要放到元信息里交給路由層決策。比如視覺請(qǐng)求只能路由到支持視覺的模型工具調(diào)用請(qǐng)求只能路由到支持工具的模型。3. 路由與調(diào)度按成本、延遲、質(zhì)量、配額做動(dòng)態(tài)決策3.1 四種路由策略的適用場(chǎng)景模型多了以后真正讓業(yè)務(wù)跑得又穩(wěn)又省的是路由策略。我沉淀下來最常用的四種如下表策略核心邏輯典型場(chǎng)景需要注意的問題固定主備優(yōu)先打主模型失敗才切備模型對(duì)穩(wěn)定性要求極高的核心鏈路主模型故障切換時(shí)可能有秒級(jí)抖動(dòng)權(quán)重比例按配置比例分配流量同時(shí)用多個(gè)模型分?jǐn)偝杀緹o法感知實(shí)時(shí)質(zhì)量和可用性延遲優(yōu)先從健康節(jié)點(diǎn)里選延遲最低的模型客服、實(shí)時(shí)助手等對(duì)時(shí)延敏感場(chǎng)景低成本慢模型容易被餓死成本優(yōu)先估算成本后選最便宜的合格模型批處理、摘要、分類等海量低頻請(qǐng)求需要先定義“合格”的底線這里最關(guān)鍵的是明確一點(diǎn)任何單一策略都不可能通吃。更合理的做法是“先過濾后打分”。過濾階段用硬性條件把不滿足的模型剔除比如上下文不夠、不支持視覺、配額已耗盡打分階段再用業(yè)務(wù)關(guān)心的維度排序比如成本權(quán)重0.4、延遲權(quán)重0.3、質(zhì)量權(quán)重0.3。3.2 路由權(quán)重與自動(dòng)降級(jí)的聯(lián)動(dòng)只做靜態(tài)權(quán)重是不夠的因?yàn)檎鎸?shí)線上環(huán)境隨時(shí)在變化。某個(gè)模型突然限流了如果還按原比例把流量打進(jìn)去那批請(qǐng)求就會(huì)大量失敗。我后來做了一個(gè)聯(lián)動(dòng)邏輯路由系統(tǒng)實(shí)時(shí)統(tǒng)計(jì)每個(gè)模型的滑動(dòng)窗口錯(cuò)誤率當(dāng)錯(cuò)誤率超過閾值時(shí)自動(dòng)把該模型標(biāo)記為“不健康”并把原本分配給它的流量按剩余模型的比例重新分配。這就是路由和容錯(cuò)的結(jié)合點(diǎn)。一個(gè)看起來簡(jiǎn)單的改動(dòng)但效果非常明顯。之前某個(gè)模型故障時(shí)業(yè)務(wù)方經(jīng)常反饋“部分請(qǐng)求超時(shí)”做了動(dòng)態(tài)降級(jí)之后故障期間的成功率能穩(wěn)定維持在99%以上只是響應(yīng)速度略有波動(dòng)。聯(lián)動(dòng)邏輯還要考慮“恢復(fù)”不能一健康就立刻把流量全部切回來那會(huì)造成抖動(dòng)。我采取的做法是“緩慢回升”恢復(fù)后的模型先接收一小部分流量觀察錯(cuò)誤率穩(wěn)定后才逐步恢復(fù)原權(quán)重。3.3 落到代碼的簡(jiǎn)易路由引擎我把路由引擎的骨架簡(jiǎn)化后基本長(zhǎng)這樣class ModelRouter: def __init__(self, registry: ModelRegistry, metrics: MetricsCollector): self.registry registry self.metrics metrics def route(self, req: UnifiedRequest) - ModelAdapter: candidates self.registry.filter_by_capabilities(req) candidates self.filter_healthy(candidates) scored [self.score_model(m, req) for m in candidates] scored.sort(keylambda x: x.score, reverseTrue) return scored[0].adapter打分函數(shù)里會(huì)把元信息和實(shí)時(shí)指標(biāo)結(jié)合起來比如成本分?jǐn)?shù)來自元信息里的價(jià)格字段延遲分?jǐn)?shù)來自最近5分鐘的P50延遲健康分來自滑動(dòng)窗口錯(cuò)誤率。分?jǐn)?shù)權(quán)重通過配置文件調(diào)整改配置就能調(diào)整路由傾向不用動(dòng)代碼。這樣我后面做實(shí)驗(yàn)就非常方便今天想讓請(qǐng)求多走便宜模型改一下權(quán)重重啟即可。4. 容錯(cuò)與降級(jí)重試、熔斷、超時(shí)一個(gè)都不能少4.1 不同失敗類型的處理優(yōu)先級(jí)很多團(tuán)隊(duì)的容錯(cuò)就是“失敗就重試三次”這在大模型API場(chǎng)景下會(huì)出大問題。大模型請(qǐng)求不像普通RPC那樣無狀態(tài)重試可能產(chǎn)生重復(fù)計(jì)費(fèi)而且很多生成任務(wù)不是冪等的。我按錯(cuò)誤類型把處理策略分成了幾檔4xx參數(shù)錯(cuò)誤不重試。這是代碼或配置問題重試只會(huì)浪費(fèi)額度應(yīng)該直接報(bào)錯(cuò)并告警。429限流可以先小退避重試一次但不宜反復(fù)重試。如果連續(xù)多次遇到429說明路由層應(yīng)該把流量切走了。5xx服務(wù)端錯(cuò)誤可以重試但前提是確認(rèn)請(qǐng)求沒有副作用。如果業(yè)務(wù)側(cè)已經(jīng)接收了用戶輸入重試前最好跟業(yè)務(wù)約定是否允許重復(fù)生成。連接超時(shí)和讀超時(shí)必須設(shè)置閾值。我見過有請(qǐng)求掛在某個(gè)模型上整整90秒沒返回用戶早就跑了。4.2 實(shí)現(xiàn)了熔斷器之后的真實(shí)效果統(tǒng)一接入層最適合做的事就是熔斷。我給每個(gè)模型節(jié)點(diǎn)都配了一個(gè)熔斷器狀態(tài)機(jī)遵循經(jīng)典的關(guān)閉、打開、半開三態(tài)。當(dāng)某個(gè)模型的連續(xù)失敗率超過閾值熔斷器打開后續(xù)請(qǐng)求直接拒絕進(jìn)入該模型快速失敗經(jīng)過冷卻時(shí)間后進(jìn)入半開狀態(tài)放少量試探請(qǐng)求成功率達(dá)到要求再關(guān)閉。我最初犯過一個(gè)錯(cuò)只按服務(wù)商維度做熔斷沒有把模型節(jié)點(diǎn)拆開。結(jié)果有一次某個(gè)具體模型因?yàn)榕漕~問題連續(xù)報(bào)錯(cuò)同廠商的其他模型是好的但整個(gè)服務(wù)商的流量都被切走了白白損失了可用的算力。后來改成“按模型節(jié)點(diǎn)熔斷”每個(gè)模型的熔斷狀態(tài)獨(dú)立維護(hù)問題就解決了。熔斷器對(duì)用戶體感的提升也很明顯。以前某個(gè)模型掛了用戶會(huì)一直轉(zhuǎn)圈等超時(shí)現(xiàn)在熔斷后立刻返回錯(cuò)誤前端可以馬上提示重新選擇服務(wù)或者走兜底方案用戶端的感知變成“稍后再試”而不是“服務(wù)卡死”。4.3 兜底方案設(shè)計(jì)兜底方案不是隨便寫一段“服務(wù)繁忙”就完事了。我在實(shí)踐中把兜底分成三級(jí)模型級(jí)降級(jí)優(yōu)先路由到同能力的備選模型比如強(qiáng)推理掛了切到中檔模型。結(jié)果級(jí)緩存如果是同質(zhì)化請(qǐng)求之前有高質(zhì)量結(jié)果可以直接把緩存結(jié)果返回。這個(gè)對(duì)摘要、分類這類任務(wù)尤其有效做法是按文本向量相似度或哈希做緩存命中率能做到20%到30%。提示級(jí)兜底以上都不可用時(shí)返回預(yù)設(shè)的降級(jí)文案并記錄一條告警。這樣設(shè)計(jì)之后線上很少有用戶真正看到“AI服務(wù)不可用”的裸奔狀態(tài)絕大部分異常都在前兩級(jí)被消化掉了。5. 上線后必須盯的指標(biāo)與成本分?jǐn)?.1 Token計(jì)價(jià)口徑天生不統(tǒng)一怎么拉齊說到成本第一道坎就是“各家對(duì)token的統(tǒng)計(jì)口徑不一樣”。有的按token計(jì)費(fèi)有的按字符計(jì)費(fèi)有的把緩存命中按折扣計(jì)費(fèi)有的直接不區(qū)分有的token數(shù)在返回體里明明白白有的要自己去估算。如果按廠商返回的數(shù)字直接加總算成本最后對(duì)賬一定對(duì)不上。我的做法是在統(tǒng)一層自己維護(hù)一套成本口徑適配器返回統(tǒng)一響應(yīng)時(shí)除了業(yè)務(wù)字段必須附帶input_tokens和output_tokens。這個(gè)數(shù)字不一定要跟廠商完全一致但要保證系統(tǒng)內(nèi)部所有模型用的是同一套統(tǒng)計(jì)規(guī)則。比如可以用一個(gè)通用分詞器統(tǒng)一估算也可以直接采信廠商但必須明確記下“本數(shù)字來自廠商估算可能存在10%以內(nèi)偏差”。然后成本就很簡(jiǎn)單了cost req_input_tokens / 1000 * model_info.prompt_price_per_1k resp_output_tokens / 1000 * model_info.completion_price_per_1k統(tǒng)一層會(huì)在每個(gè)請(qǐng)求日志里寫清楚用了哪個(gè)模型、消耗了多少token、估算成本是多少。這樣財(cái)務(wù)同事要對(duì)賬時(shí)能直接把日志拉出來不用再問研發(fā)“這兩個(gè)模型價(jià)格是怎么算的”。5.2 觀測(cè)面板上的關(guān)鍵指標(biāo)模型多了以后只看“整體成功率”完全不夠。我上線后長(zhǎng)期盯的是四個(gè)維度維度關(guān)鍵指標(biāo)踩坑提示模型狀態(tài)調(diào)用量、成功率、錯(cuò)誤碼分布錯(cuò)誤碼分布要區(qū)分服務(wù)端錯(cuò)誤和限流延遲P50、P95、流式首Token延遲流式請(qǐng)求必須單獨(dú)統(tǒng)計(jì)首Token耗時(shí)路由各策略命中比例、降級(jí)次數(shù)降級(jí)次數(shù)突增說明上游模型有波動(dòng)成本每請(qǐng)求成本、日成本、場(chǎng)景成本按場(chǎng)景分?jǐn)倓e只看總體大盤這里最容易被忽視的是“流式首Token延遲”。非流式接口的延遲很好理解但流式場(chǎng)景下用戶感知到的等待時(shí)間其實(shí)是首Token之前的時(shí)間。如果不單獨(dú)統(tǒng)計(jì)你會(huì)發(fā)現(xiàn)延遲指標(biāo)很漂亮但用戶還是覺得慢因?yàn)槟惆颜麄€(gè)流式拉滿的時(shí)間也算進(jìn)去了。5.3 成本預(yù)算控制在什么粒度比較合理預(yù)算控制我建議按“場(chǎng)景”切而不是按模型切。比如客服問答、內(nèi)容生成、文檔總結(jié)各給各的預(yù)算每個(gè)場(chǎng)景有獨(dú)立的日預(yù)算和月預(yù)算。這樣做有兩個(gè)好處一是某個(gè)場(chǎng)景的流量突然爆炸時(shí)能快速定位到是哪個(gè)功能在燒錢二是預(yù)算限制可以精細(xì)控制該場(chǎng)景的請(qǐng)求如果預(yù)估成本超標(biāo)直接降級(jí)到更便宜的模型。我在統(tǒng)一層實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的預(yù)算預(yù)檢每次路由前根據(jù)請(qǐng)求類型和模型價(jià)格估算成本如果該場(chǎng)景當(dāng)日預(yù)算剩余不足就強(qiáng)制走低成本模型甚至走緩存。這套機(jī)制上線后整體模型成本下降了大約30%因?yàn)闆]有錢花在“不需要高質(zhì)量模型”的請(qǐng)求上。6. 從這套架構(gòu)里沉淀出的幾條實(shí)戰(zhàn)經(jīng)驗(yàn)6.1 先跑通最小閉環(huán)再考慮完美抽象我見過不少團(tuán)隊(duì)一上來就畫很大的架構(gòu)圖要做模型市場(chǎng)、要做自動(dòng)調(diào)優(yōu)、要做全鏈路追蹤結(jié)果做了三個(gè)月還沒接完第一個(gè)模型。我的建議是反著來先選兩個(gè)差異最大的模型一個(gè)閉源強(qiáng)模型、一個(gè)開源低成本模型做一個(gè)最簡(jiǎn)單的適配器加網(wǎng)關(guān)把“代碼不感知廠商”這個(gè)核心鏈路跑通。第一版哪怕只支持chat和stream_chat都行工具調(diào)用后面再補(bǔ)。因?yàn)閰f(xié)議統(tǒng)一這件事不做永遠(yuǎn)不知道自己的接口設(shè)計(jì)哪里有問題只有真實(shí)接了兩個(gè)模型才會(huì)發(fā)現(xiàn)溫度范圍、錯(cuò)誤碼、流式格式這些差異比你想象得要多。最小閉環(huán)跑通后再逐步補(bǔ)充能力元信息、路由策略、預(yù)算控制。6.2 別忽略流式接口對(duì)統(tǒng)一層的沖擊如果一開始只支持非流式后面補(bǔ)流式支持會(huì)是一場(chǎng)災(zāi)難。因?yàn)榱魇讲皇恰岸喾祷貛讉€(gè)chunk”那么簡(jiǎn)單它牽扯到SSE連接管理、心跳、客戶端斷連取消、事件解析、工具調(diào)用增量拼接、最終計(jì)費(fèi)統(tǒng)計(jì)等一大堆邏輯。等業(yè)務(wù)量大了再改兼容成本極高。我最早設(shè)計(jì)Protocol時(shí)就同時(shí)要求實(shí)現(xiàn)chat和stream_chat哪怕第一版只有少數(shù)模型支持流式。后來新接入的廠商幾乎沒有額外改動(dòng)因?yàn)榱魇讲町惾吭谶m配器里被吸收掉了。這個(gè)決定后來被證明是最省時(shí)間的一個(gè)選擇。6.3 模型版本切換要留審計(jì)記錄模型API不只是模型本身在變提示詞、參數(shù)、路由策略也經(jīng)常調(diào)。以前排查問題時(shí)常碰到這種對(duì)話業(yè)務(wù)方說“我讓模型做了個(gè)總結(jié)結(jié)果輸出格式不對(duì)”研發(fā)說“是不是提示詞改了”兩邊扯皮半天。后來我在統(tǒng)一層里給每個(gè)請(qǐng)求都加了版本號(hào)和切換記錄寫到日志里。這里說的版本號(hào)不是一個(gè)數(shù)字而是一套“鏈路快照”請(qǐng)求命中了哪條路由策略、最終選了哪個(gè)模型、用什么版本的提示詞模板、適配器做了什么參數(shù)轉(zhuǎn)換。只要有了這套審計(jì)數(shù)據(jù)線上任何一次異常輸出都能回溯到具體的推理鏈路而不是猜。實(shí)際排查問題時(shí)這個(gè)能力比多寫十個(gè)監(jiān)控指標(biāo)都管用。這套多模型混合調(diào)用架構(gòu)做下來我最大的體會(huì)就是“統(tǒng)一層不是中轉(zhuǎn)站而是業(yè)務(wù)和模型之間的契約”。契約定得清晰后面加模型、換模型、控成本都只是適配器層面的工作量契約定得含糊所有復(fù)雜性都會(huì)反向滲透到業(yè)務(wù)代碼里越往后越難收拾。如果你正在處理多個(gè)大模型API的接入問題可以從一個(gè)統(tǒng)一協(xié)議和兩個(gè)適配器開始先把鏈路跑通再一步步把路由、熔斷、預(yù)算這些能力填進(jìn)去。