)
1. 為什么單次模型請求值得單獨拎出來聊很多人一上來就搞多輪對話、工具調用、記憶管理結果連一次干凈的模型請求都發(fā)不明白。我見過太多項目Agent 框架搭得花里胡哨最后排查問題發(fā)現是底層單次請求的參數傳錯了或者流式和非流式的處理邏輯混在一起導致整個鏈路雪崩。所以這次我想把“單次模型請求”這件事單獨拆開講透因為它是所有 AI Agent 的地基地基不平上面蓋什么都會歪。所謂單次模型請求說白了就是你的程序向大模型服務端發(fā)一次調用拿回一次結果中間不涉及多輪上下文拼接、不涉及工具回調、不涉及狀態(tài)保持。聽起來簡單但實際落地時有三種主流方式每種方式的適用場景、坑點和性能特征完全不一樣。選錯了方式輕則代碼冗余難維護重則線上超時率飆升、費用翻倍。這篇文章適合誰看如果你正在寫 Agent 的底層調用層或者你已經在用某些框架但想搞清楚它底層到底怎么發(fā)請求的再或者你單純想優(yōu)化一下自己項目里模型調用的穩(wěn)定性和成本那這篇內容應該能幫你省下不少試錯時間。我會把三種方式的原理、代碼實現、參數計算、排查技巧全部攤開講盡量做到你看完就能直接抄作業(yè)。2. 三種主流請求方式的全景拆解2.1 先搞清楚“單次請求”到底包含哪些環(huán)節(jié)在展開三種方式之前得先把一次完整的模型請求拆解清楚。不管用什么方式一次請求本質上都包含這幾個階段構造請求體、建立連接、發(fā)送數據、等待響應、解析響應、異常處理。區(qū)別在于不同方式對這些階段的控制粒度不一樣。構造請求體這一步核心是消息列表的組裝。單次請求通常只包含一條 system 消息和一條 user 消息但即使是這么簡單的結構也有講究。比如 system 消息放什么內容、user 消息要不要做預處理、需不需要加 few-shot 示例這些都會直接影響返回質量。我個人的習慣是單次請求的 system 消息盡量精簡把角色定義和輸出格式約束寫清楚就行不要把多輪對話才需要的東西塞進來。連接階段涉及的是網絡層面的東西。同步請求會阻塞當前線程直到拿到完整響應異步請求則把控制權交還出去流式請求會在連接建立后持續(xù)接收數據塊。這三種模式對應到具體的請求方式上就衍生出了下面要講的三種主流方案。2.2 三種方式的定義與核心差異第一種是原生 HTTP 同步請求。你直接用 HTTP 客戶端庫向模型服務的接口地址發(fā)一個 POST 請求body 里帶上模型名稱、消息列表、溫度等參數然后等服務器返回完整的 JSON 響應。這種方式最原始但也最透明沒有任何框架封裝出問題了你能精確知道是哪一層的事。第二種是 SDK 異步請求。主流模型服務商都會提供官方 SDK里面封裝了異步客戶端。你用 async/await 語法發(fā)起調用底層走的是異步 IO不會阻塞事件循環(huán)。這種方式在 Python 的 asyncio 生態(tài)或者 Node.js 的 Promise 體系里非常常見適合需要并發(fā)發(fā)起多個單次請求的場景。第三種是流式請求。同樣是發(fā)一次請求但服務端會以 Server-Sent Events 或者 chunked transfer 的方式逐塊返回內容。你在客戶端逐塊接收、逐塊處理用戶能看到文字一個個蹦出來。這種方式嚴格來說也是單次請求只不過響應的接收方式從“一次性拿完”變成了“邊拿邊處理”。這三種方式沒有絕對的優(yōu)劣關鍵看你的場景。下面我逐個展開把每種方式的實現細節(jié)和踩坑經驗都講清楚。3. 原生 HTTP 同步請求最笨但最可控的方式3.1 適用場景與選型理由原生 HTTP 同步請求最適合什么場景我的經驗是腳本類任務、離線批處理、調試階段、以及對第三方依賴極度敏感的生產環(huán)境。為什么因為這種方式不依賴任何廠商 SDK你只需要一個通用的 HTTP 客戶端庫就行。哪怕服務商的 SDK 更新了、出 bug 了、或者你用的語言根本沒有官方 SDK你照樣能發(fā)請求。舉個例子我之前做過一個數據標注清洗的小工具需要把幾千條文本逐條發(fā)給模型做分類。這種場景下我不需要流式輸出不需要并發(fā)因為要控制速率也不需要復雜的錯誤重試框架。直接用 HTTP 同步請求寫個循環(huán)每條請求之間加個 sleep簡單粗暴但極其穩(wěn)定。跑了一晚上一條沒丟。另一個典型場景是跨語言調用。比如你的主業(yè)務是 Java 寫的但模型服務商的 SDK 只有 Python 和 Node.js 版本。這時候用 Java 的 HTTP 客戶端直接發(fā)請求反而是最省事的方案。你只需要照著 API 文檔拼 JSON 就行不需要為了調個模型再引入一個 Python 微服務。3.2 請求構造的關鍵參數與計算過程構造一個同步請求核心參數其實就那么幾個但每個都值得細說。我拿一個典型的請求體來舉例{ model: 某模型名稱, messages: [ {role: system, content: 你是一個文本分類助手只輸出類別標簽。}, {role: user, content: 幫我判斷這條評論的情感傾向這個產品用起來還不錯。} ], temperature: 0.1, max_tokens: 64, top_p: 0.9 }temperature 設 0.1 是因為分類任務需要確定性輸出溫度越低模型越傾向于選概率最高的詞。max_tokens 設 64 是因為分類結果只需要輸出一個標簽給太多 token 純屬浪費。top_p 設 0.9 是配合低溫度做一點微調避免完全死板。這里有個計算過程值得展開max_tokens 到底設多少合適我的經驗公式是預期輸出長度的 1.5 倍再向上取整到 16 的倍數。比如你預期輸出 20 個字那 20 乘以 1.5 等于 30向上取到 32。為什么要留 1.5 倍余量因為模型有時候會輸出一些額外的解釋性文字雖然你在 prompt 里約束了只輸出標簽但它偶爾還是會多嘴。留點余量避免被截斷截斷了的響應處理起來更麻煩。還有一個容易被忽略的參數是超時時間。同步請求必須設超時而且不能設太長。我一般設 30 秒超過 30 秒沒返回就直接斷開重試。為什么不設 60 秒因為模型服務端的排隊時間加上推理時間正常情況下不會超過 30 秒。如果超過了說明服務端可能出問題了你等再久也沒用不如早點斷開走重試邏輯。3.3 完整代碼實現與逐行說明下面是一個用 Python 的 requests 庫實現的同步請求示例我逐段解釋import requests import json import time def single_request_sync(prompt, system_msg你是一個助手。, max_retries3): url https://api.example.com/v1/chat/completions headers { Content-Type: application/json, Authorization: Bearer YOUR_API_KEY } payload { model: 某模型名稱, messages: [ {role: system, content: system_msg}, {role: user, content: prompt} ], temperature: 0.1, max_tokens: 128 } for attempt in range(max_retries): try: resp requests.post(url, headersheaders, jsonpayload, timeout30) if resp.status_code 200: data resp.json() return data[choices][0][message][content] elif resp.status_code 429: wait 2 ** attempt time.sleep(wait) continue else: print(f請求失敗狀態(tài)碼{resp.status_code}響應{resp.text}) return None except requests.exceptions.Timeout: print(f第 {attempt 1} 次請求超時) continue except requests.exceptions.RequestException as e: print(f網絡異常{e}) continue return None這段代碼有幾個細節(jié)值得說。第一重試邏輯里對 429 狀態(tài)碼做了指數退避第一次等 1 秒第二次等 2 秒第三次等 4 秒。這是應對速率限制的標準做法比固定間隔重試要優(yōu)雅得多。第二超時異常和網絡異常分開捕獲因為它們的處理策略可能不同。超時通常重試就行網絡異??赡苄枰獧z查代理或者 DNS 配置。第三返回 None 而不是拋異常是因為調用方通常只需要知道“這次沒拿到結果”不需要處理復雜的異常鏈。注意API Key 千萬不要硬編碼在代碼里。我見過太多項目把 Key 寫在源碼里然后提交到了代碼倉庫結果被人掃到之后一夜之間跑掉幾千塊。正確做法是用環(huán)境變量或者密鑰管理服務。3.4 實操心得與常見坑用同步請求最大的坑就是阻塞。如果你在一個 Web 服務的主線程里發(fā)同步請求那這個請求沒返回之前整個線程就被卡住了。并發(fā)量一上來線程池瞬間打滿服務直接不可用。所以同步請求只適合腳本、離線任務、或者你明確知道并發(fā)量很低的場景。第二個坑是連接復用。requests 庫默認每次請求都新建連接如果你在循環(huán)里發(fā)幾千次請求每次都要做 TCP 三次握手和 TLS 握手開銷很大。解決辦法是用 requests.Session()它會在底層維護一個連接池復用已經建立的連接。實測下來用 Session 之后同樣數量的請求總耗時能降低 30% 到 40%。第三個坑是響應解析的健壯性。不要假設返回的 JSON 結構永遠符合預期。我遇到過服務端在高峰期返回了一個缺少 choices 字段的響應結果代碼直接 KeyError 崩了。后來我加了一層防御性解析先檢查 choices 是否存在、是否為空、message 里是否有 content全部確認之后再取值。多寫這幾行判斷能省掉很多半夜被叫起來修 bug 的時間。4. SDK 異步請求高并發(fā)場景下的效率利器4.1 異步請求的核心原理與優(yōu)勢異步請求的本質是把“等待網絡響應”這段時間利用起來。同步模式下線程發(fā)出請求后就傻等什么也干不了。異步模式下事件循環(huán)在等待響應的同時可以去處理其他任務等響應回來了再切回來繼續(xù)處理。對于單次模型請求這種 IO 密集型操作異步帶來的吞吐量提升是非常明顯的。我做過一個對比測試同樣是用 100 個單次請求去處理 100 條文本同步方式串行執(zhí)行耗時大約 180 秒異步方式并發(fā)執(zhí)行耗時不到 8 秒。差了 20 多倍。當然這是在服務端不限制并發(fā)的前提下實際使用中還要考慮速率限制但異步的收益依然巨大。SDK 異步請求和原生 HTTP 異步請求的區(qū)別在于SDK 幫你封裝了連接池管理、重試策略、參數校驗、響應解析這些臟活累活。你只需要關注業(yè)務邏輯不用操心底層細節(jié)。代價是你得依賴這個 SDK如果 SDK 有 bug 或者更新不及時你就得等或者自己 fork 一份改。4.2 異步客戶端的初始化與參數配置以 Python 生態(tài)中常見的異步 SDK 用法為例初始化通常長這樣import asyncio from some_sdk import AsyncClient client AsyncClient( api_keyYOUR_API_KEY, base_urlhttps://api.example.com/v1, timeout30.0, max_retries3, connection_pool_size20 )這里有幾個參數需要根據實際情況調整。connection_pool_size 是連接池大小設太小了并發(fā)上不去設太大了浪費資源。我的經驗值是預期并發(fā)數乘以 1.5。比如你預計同時最多發(fā) 10 個請求那連接池設 15 就夠了。timeout 和 max_retries 的含義和同步請求一樣但異步環(huán)境下超時的處理更復雜一些因為可能涉及多個協(xié)程同時超時。還有一個容易忽略的參數是并發(fā)限制。很多 SDK 不直接提供并發(fā)限制參數你需要自己用信號量來控制。比如semaphore asyncio.Semaphore(10) async def limited_request(prompt): async with semaphore: return await client.chat.completions.create( model某模型名稱, messages[{role: user, content: prompt}], temperature0.1, max_tokens128 )這個信號量保證了同時最多只有 10 個請求在飛超出的請求會排隊等待。為什么要做這個限制因為服務端通常有并發(fā)上限你一次性發(fā) 100 個請求過去大部分會被拒絕或者排隊反而降低了整體效率。用信號量控制在一個合理值讓請求平穩(wěn)地流過去總吞吐量反而更高。4.3 批量請求的編排與結果收集異步請求最典型的用法就是批量處理。假設你有 50 條文本需要分類可以這樣編排async def batch_process(texts): tasks [limited_request(f分類這條文本{t}) for t in texts] results await asyncio.gather(*tasks, return_exceptionsTrue) final [] for i, r in enumerate(results): if isinstance(r, Exception): print(f第 {i} 條處理失敗{r}) final.append(None) else: final.append(r.choices[0].message.content) return final這里用 gather 來并發(fā)執(zhí)行所有任務return_exceptionsTrue 讓單個任務的異常不會導致整個批次失敗。結果收集的時候逐條判斷是正常結果還是異常異常的就記 None后續(xù)可以單獨重試。這種模式在處理大批量數據時非常實用不會因為一條失敗就全軍覆沒。提示asyncio.gather 默認會等待所有任務完成如果你希望某個任務失敗后立即取消其他任務可以用 asyncio.wait 配合 FIRST_EXCEPTION 模式。但大多數批量處理場景下我們更希望“能跑多少跑多少”所以 gather 加 return_exceptions 是更常見的選擇。4.4 異步請求的常見問題與排查異步請求最常見的問題是“事件循環(huán)被阻塞”。什么意思就是你在異步函數里調用了同步的阻塞代碼比如 time.sleep()、同步的數據庫查詢、同步的文件讀寫。這些操作會把整個事件循環(huán)卡住導致其他協(xié)程全部無法執(zhí)行。解決辦法是用 asyncio.sleep() 替代 time.sleep()用異步數據庫驅動替代同步驅動用 aiofiles 替代普通文件操作。第二個問題是“協(xié)程泄漏”。你創(chuàng)建了協(xié)程但沒有 await 它或者創(chuàng)建了任務但沒有保存引用導致協(xié)程在后臺悄悄運行然后報錯而你完全不知道。排查方法是開啟 asyncio 的調試模式它會在協(xié)程被垃圾回收時打印警告。具體做法是在程序入口加一行asyncio.get_event_loop().set_debug(True)。第三個問題是“連接池耗盡”。當并發(fā)量超過連接池大小時新的請求會等待可用連接。如果等待時間超過了超時設置就會拋異常。排查方法是監(jiān)控連接池的使用率如果長期處于滿載狀態(tài)就需要調大連接池或者降低并發(fā)量。我一般會在日志里記錄每次請求的等待時間如果發(fā)現等待時間明顯增長就是連接池不夠用的信號。5. 流式請求用戶體驗優(yōu)先的交互方式5.1 流式請求的工作機制流式請求和前面兩種的本質區(qū)別在于響應的接收方式。同步和異步都是“發(fā)一次請求等一個完整響應”流式是“發(fā)一次請求持續(xù)接收數據塊”。服務端每生成一個 token 或者一小段文本就立即推送給客戶端客戶端收到后可以立刻展示給用戶。這種機制在交互式場景下體驗極好。用戶問一個問題模型開始回答文字一個個蹦出來用戶感覺系統(tǒng)反應很快。如果等完整響應再展示用戶可能要盯著 loading 轉圈好幾秒體驗差很多。所以但凡是對用戶體驗有要求的場景流式幾乎是必選項。但流式也有代價。首先你不能像同步請求那樣直接拿到完整的 JSON 做解析得自己處理數據塊的拼接。其次流式請求的錯誤處理更復雜因為可能在傳輸中途斷開。再次流式請求不太適合批量處理因為它的優(yōu)勢在于“邊生成邊展示”批量場景下沒有展示需求用流式反而增加了處理復雜度。5.2 流式數據的接收與拼接實現流式請求的響應通常是一系列以特定格式分隔的數據塊。以常見的 SSE 格式為例每個數據塊長這樣data: {choices:[{delta:{content:你}}]} data: {choices:[{delta:{content:好}}]} data: [DONE]你需要逐行讀取遇到data:開頭就解析 JSON取出 delta 里的 content 拼接到結果字符串上遇到[DONE]就結束。下面是一個簡化的實現import requests import json def stream_request(prompt): url https://api.example.com/v1/chat/completions headers { Content-Type: application/json, Authorization: Bearer YOUR_API_KEY } payload { model: 某模型名稱, messages: [{role: user, content: prompt}], stream: True, temperature: 0.7, max_tokens: 512 } full_content with requests.post(url, headersheaders, jsonpayload, streamTrue, timeout60) as resp: for line in resp.iter_lines(): if not line: continue line_str line.decode(utf-8) if line_str.startswith(data: ): data_str line_str[6:] if data_str.strip() [DONE]: break try: chunk json.loads(data_str) delta chunk[choices][0][delta] if content in delta: content_piece delta[content] full_content content_piece print(content_piece, end, flushTrue) except (json.JSONDecodeError, KeyError, IndexError): continue return full_content這段代碼的關鍵點在于streamTrue 告訴 requests 不要一次性讀取全部響應而是以流的方式逐行讀取。iter_lines 會按行迭代每次拿到一行數據。解析的時候要處理各種異常情況因為流式數據塊偶爾會出現格式不完整的情況比如網絡抖動導致一行被截斷。用 try-except 包住解析邏輯遇到壞數據就跳過不要讓它中斷整個流。5.3 流式場景下的超時與中斷處理流式請求的超時設置和同步請求不一樣。同步請求的超時是“從發(fā)出請求到收到完整響應”的總時間流式請求的超時通常分為兩部分連接超時和讀取超時。連接超時是指建立連接的時間讀取超時是指兩次數據塊之間的最大間隔。我一般把連接超時設 10 秒讀取超時設 30 秒。讀取超時不能設太短因為模型在生成某些復雜內容時兩個 token 之間的間隔可能會比較長。如果設太短正常生成過程中就被斷開了用戶看到一半內容沒了體驗很差。中斷處理也是流式場景必須考慮的。用戶可能在生成過程中點擊“停止”按鈕這時候你需要主動關閉連接。在 Python 里可以用 resp.close() 來關閉流式響應。關閉之后已經接收到的內容要保留不能丟掉。我見過一些實現用戶點停止之后已經生成的內容也消失了這就是沒有做好中斷時的狀態(tài)保存。還有一個坑是流式請求的重試。同步請求重試很簡單重新發(fā)一次就行。流式請求如果中途斷了重試的話是從頭開始還是從斷點繼續(xù)大多數模型服務不支持斷點續(xù)傳所以只能從頭開始。但從頭開始意味著用戶已經看到的內容會重復體驗不好。我的做法是如果流式請求在生成早期就斷了比如前 10 個 token 內直接重試如果已經生成了一大半才斷就不重試了把已生成的內容展示給用戶并提示“生成中斷”。5.4 流式與非流式的性能對比實測我做過一組實測對比流式和非流式在相同任務下的表現。任務是用模型生成一段 300 字左右的文案分別用流式和非流式各跑 20 次記錄首字節(jié)時間和總完成時間。指標非流式流式首字節(jié)時間2.8 秒0.6 秒總完成時間3.2 秒3.4 秒用戶感知等待3.2 秒0.6 秒網絡傳輸量略少略多從數據可以看出流式的總完成時間其實比非流式略長因為數據分塊傳輸有額外的協(xié)議開銷。但首字節(jié)時間從 2.8 秒降到了 0.6 秒用戶感知的等待時間大幅縮短。這就是流式的核心價值不是讓生成更快而是讓用戶感覺更快。注意流式請求的 max_tokens 設置要比非流式更謹慎。因為流式是邊生成邊展示如果 max_tokens 設得太大用戶可能要等很久才能看到完整內容。我一般會根據預期輸出長度來設留 20% 余量就夠了不要像非流式那樣留 50% 余量。6. 三種方式的選型決策與混合使用策略6.1 選型決策表與判斷邏輯到底該用哪種方式我整理了一個決策表你可以對照自己的場景來選場景特征推薦方式理由腳本任務、離線批處理原生 HTTP 同步簡單可控不依賴 SDK高并發(fā)批量處理SDK 異步吞吐量高SDK 封裝完善交互式對話、實時展示流式首字節(jié)時間短用戶體驗好跨語言調用原生 HTTP 同步不受 SDK 語言限制需要精細控制請求細節(jié)原生 HTTP完全透明無框架黑盒快速原型開發(fā)SDK 異步代碼量少開發(fā)效率高判斷邏輯其實就三條第一有沒有實時展示需求有就選流式。第二需不需要高并發(fā)需要就選異步。第三對第三方依賴敏不敏感敏感就選原生 HTTP。三條都不滿足那就隨便選哪個順手用哪個。6.2 混合使用的最佳實踐實際項目中這三種方式往往不是互斥的而是混合使用的。我拿一個典型的 Agent 系統(tǒng)舉例用戶在前端輸入問題后端需要先做意圖識別單次請求用同步或異步都行然后根據意圖決定是直接回答還是調用工具。如果是直接回答用流式返回給用戶如果是調用工具工具調用本身可能是異步批量請求等工具結果回來后再用流式生成最終回答。這種混合模式下關鍵是要統(tǒng)一請求層的接口。我的做法是定義一個統(tǒng)一的請求函數內部根據參數決定走同步、異步還是流式對外暴露一致的調用方式。這樣上層業(yè)務邏輯不需要關心底層用的是哪種方式切換起來也方便。還有一個實踐是“降級策略”。比如正常情況下用流式但如果流式連接建立失敗自動降級到非流式同步請求。雖然用戶體驗差一點但至少能拿到結果。這種降級邏輯在線上環(huán)境非常有必要因為流式請求對網絡穩(wěn)定性的要求比非流式更高。6.3 成本與性能的平衡計算選型的時候不能只看技術還得算成本。三種方式在 token 消耗上其實是一樣的因為計費是按輸入輸出 token 數來的和傳輸方式無關。但在重試場景下流式的成本可能更高因為流式中斷后重試是從頭開始已經生成的 token 白費了。非流式中斷后重試之前那次請求可能根本沒產生輸出不消耗輸出 token。我算過一筆賬假設流式中斷率是 5%每次中斷平均浪費 200 個輸出 token一天 10000 次請求那每天浪費的 token 是 10000 乘以 5% 乘以 200 等于 100000 個 token。按某模型的價格算一天多花幾塊錢一個月多花一百多。不算多但如果你的中斷率更高或者請求量更大這個數字就會很可觀。所以我的建議是流式請求一定要做好中斷檢測和快速失敗。如果連接建立后 5 秒內沒有收到任何數據塊直接斷開重試不要傻等。這樣能把浪費的 token 降到最低。7. 常見問題排查與避坑經驗實錄7.1 請求超時與連接失敗的排查思路超時和連接失敗是最常見的問題排查思路可以按這個順序來先確認網絡連通性用 curl 或者 telnet 測試接口地址是否可達再確認 API Key 是否有效有時候 Key 過期了返回的錯誤信息不明顯然后檢查請求體格式特別是 JSON 的字段名和數據類型最后看服務端狀態(tài)如果服務端在維護或者過載你這邊怎么調都沒用。我遇到過一個很隱蔽的問題請求偶爾超時但大部分時候正常。排查了半天發(fā)現是 DNS 解析不穩(wěn)定有時候解析到了慢的節(jié)點。解決辦法是在 HTTP 客戶端里配置 DNS 緩存或者直接用 IP 地址加 Host 頭的方式繞過 DNS。這個問題在容器化環(huán)境里特別常見因為容器的 DNS 配置有時候不太靠譜。7.2 返回結果異常的調試方法返回結果異常包括返回空內容、返回亂碼、返回格式不符合預期、返回內容被截斷??諆热萃ǔJ?max_tokens 設太小模型還沒來得及輸出就達到了上限。亂碼一般是編碼問題確認請求和響應都用 UTF-8。格式不符合預期可能是 prompt 約束不夠強或者 temperature 太高導致模型自由發(fā)揮。截斷就是 max_tokens 不夠加大就行。調試的時候我習慣把完整的請求體和響應體都打到日志里但要注意脫敏別把 API Key 和用戶隱私數據打進去。日志級別用 DEBUG只在排查問題時開啟平時關掉避免日志膨脹。7.3 速率限制與重試策略的實戰(zhàn)配置速率限制是模型服務商保護自己系統(tǒng)的機制你沒法繞過只能適應。常見的限制維度有每分鐘請求數、每分鐘 token 數、并發(fā)連接數。應對策略就是指數退避加隊列。我的標準配置是最大重試 3 次第一次等待 1 秒第二次 2 秒第三次 4 秒。如果三次都失敗就把請求放入延遲隊列過 30 秒再試。延遲隊列里的請求不再重試只嘗試一次失敗就記錄到死信隊列人工介入處理。提示不要用固定間隔重試比如每次都等 1 秒。因為如果服務端是因為過載才限流你固定間隔重試只會加重它的負擔。指數退避給服務端喘息的時間成功率更高。7.4 常見問題速查表問題現象可能原因排查方法解決方案請求超時網絡不通、服務端過載、超時設太短用 curl 測試連通性查看服務端狀態(tài)頁調整超時時間增加重試返回 401API Key 無效或過期檢查 Key 是否正確、是否過期更換有效 Key返回 429觸發(fā)速率限制查看響應頭中的限制信息降低并發(fā)增加退避等待返回空內容max_tokens 太小、prompt 有問題檢查 max_tokens 設置簡化 prompt增大 max_tokens優(yōu)化 prompt流式中斷網絡抖動、服務端主動斷開查看中斷時的錯誤信息實現斷線重連或降級到非流式響應亂碼編碼不一致檢查請求和響應的 Content-Type統(tǒng)一使用 UTF-8連接池耗盡并發(fā)量超過連接池大小監(jiān)控連接池使用率調大連接池或降低并發(fā)這張表里的每一行都是我實際踩過的坑。特別是流式中斷那個我一開始沒做降級處理結果網絡一抖動用戶就看到半截內容投訴了好幾次。后來加了降級邏輯流式失敗自動切非流式雖然慢一點但至少完整。7.5 我個人的避坑清單最后分享幾個我總結的避坑要點都是血淚教訓第一永遠不要相信網絡是穩(wěn)定的。任何一次請求都可能失敗所以重試邏輯是必須的不是可選的。第二永遠不要假設響應格式是固定的。服務端可能升級、可能出 bug、可能返回意料之外的字段。防御性解析能幫你省掉很多深夜修 bug 的時間。第三永遠不要在循環(huán)里發(fā)同步請求而不加延遲。你以為你在快速處理實際上你在快速觸發(fā)速率限制。第四流式請求一定要設讀取超時。我見過有人沒設讀取超時結果服務端掛了但連接沒斷客戶端傻等了一個小時。第五日志要打全但要注意脫敏。請求 ID、耗時、狀態(tài)碼、token 用量這些都要記但 API Key、用戶輸入內容這些敏感信息不要記。第六上線前一定要做壓力測試。你以為你的代碼能扛住 100 并發(fā)實際可能 10 并發(fā)就崩了。提前測出來比線上出問題強。這些經驗看起來簡單但每一條背后都有真實的事故。希望你看完之后能少走點彎路。單次模型請求雖然只是 Agent 系統(tǒng)里的一小環(huán)但這一環(huán)的穩(wěn)定性直接決定了整個系統(tǒng)的可用性。把這一環(huán)做扎實了上面的多輪對話、工具調用、記憶管理才有意義。