定輸出)
用 LLM 搭過處理管道的人基本都遇到過同一類問題同一個 prompt 跑兩次摘要細節(jié)不一樣批量任務跑到中間突然開始報錯重試一次又好了重新啟動進程再跑上次輸出的字段數(shù)量都變了。這類問題表面上是模型“抽風”本質上是把 LLM 當成了一個純函數(shù)卻忽略了它默認就不是純函數(shù)。開發(fā)更確定性的 LLM pipelines核心不是追求一個字都不差而是讓管道里的每一段都有可復現(xiàn)的輸入、可觀測的中間狀態(tài)、可校驗的輸出。下面這篇內容就是為了解決這個問題寫的適合做批量文本處理、知識庫入庫、RAG 抽取、Agent 任務編排的開發(fā)者。1. 先定位問題LLM 管道里的“不確定性”到底來自哪里1.1 模型輸出隨機性的第一來源是采樣大多數(shù) LLM 在生成下一個 token 時不是只選概率最高的那一個。它會從概率分布里抽樣。這個抽樣過程引入了隨機性所以即使輸入完全一樣輸出也可能不一樣。相關參數(shù)常見的有幾個參數(shù)作用對確定性的影響temperature控制概率分布平滑程度越小越接近貪心設為 0 時通常走 argmaxtop_p截斷概率累積區(qū)間越小搜索空間越小波動會降低top_k只保留概率最高的 k 個 token能限制候選范圍但不能保證完全一致seed給隨機數(shù)生成器一個初始值可以在部分框架和 API 中復現(xiàn)結果max_tokens限制輸出長度影響截斷進而影響結構化解析結果溫度調低是多數(shù)團隊做的第一件事。把 temperature 設置為 0代表盡量走最高概率路徑會顯著降低隨機性。但也要明白temperature0 不等于零風險。遇到 GPU 上的批量推理某些算子在并行歸約時存在浮點計算順序問題遇到云 API模型權重可能在你不知道的時候更新遇到帶隨機采樣但不允許傳入 seed 的接口你仍然無法從外部完全控制。1.2 “確定性管道”不等于“每次輸出都一樣”這是很容易被誤解的一個點。如果目標是讓機器產(chǎn)生穩(wěn)定可靠的數(shù)據(jù)那么更合理的定義是在相同的輸入、相同的代碼版本、相同的模型版本、相同的參數(shù)條件下管道輸出可復現(xiàn)在輸入有微小差異時輸出能保持結構一致在模型某一步抽風時管道不會把錯誤結果當成功結果直接寫進數(shù)據(jù)庫。這比“逐字一致”更貼近工程需要。比如我用一段文本抽取標題、作者和發(fā)布時間。第一次跑出來author: 張三第二次跑出author: 張 三。逐字對比會認為兩次結果不同但業(yè)務上可能都算正確。更有用的做法是先約定字段的 JSON schema再用校驗器檢查字段類型和格式最后對可歸一化字段做后處理。所以先不要把目標定成“每次一模一樣”而是定成“每次跑完都滿足統(tǒng)一 schema且同一輸入的可復現(xiàn)率足夠高”。1.3 并不是所有場景都值得追求確定性處理一批發(fā)票、合同、病例文本時字段抽取錯誤會導致后續(xù)流程混亂這時候確定性很重要。做聊天助手、寫廣告文案、頭腦風暴或者要求模型生成多個候選版本時過度固定參數(shù)反而會損失效用。你要的是多樣性而不是可復現(xiàn)性。判斷標準很簡單如果 LLM 的輸出會作為結構化數(shù)據(jù)進入下一個系統(tǒng)那就必須做控制如果只是給人看可以放寬。2. 從單次模型調用開始鎖定隨機源2.1 請求參數(shù)要完整保存而不是只保存輸出很多管道不穩(wěn)定不是模型本身的問題而是調用方根本沒記錄自己到底傳了什么參數(shù)。同一個 prompt上一次走的是gpt-4o-mini下一次代碼里改成了gpt-4o上一次傳了 system prompt下一次 system prompt 丟了上一次 temperature 是 0下一次因為配置中心更新變成了 0.7。這些變動都會讓結果看起來“隨機”。我建議對每一次模型請求都記錄如下信息模型名稱和版本標識system prompt 模板 IDuser prompt 模板 ID溫度、top_p、seed、max_tokens輸入內容 hash輸出內容返回的狀態(tài)碼、耗時、重試次數(shù)請求發(fā)起時間這些日志不一定都要進業(yè)務表可以單獨放在請求日志里。等到排查時它們能幫你快速判斷問題出在模型還是出在調用方。2.2 給每個 prompt 模板做版本號同一個 prompt 模板沒有被修改但代碼里拼接變量的方式變了也可能導致輸出不穩(wěn)定。最穩(wěn)的做法是給模板命名并打版本。例如document-extract-v3、summary-qa-v2。模板內容一旦修改就生成新版本不要原地覆蓋。這樣做的原因是當管道批量跑完 5000 條數(shù)據(jù)后如果你的模板悄悄改動了一個字那么從修改點之后的所有輸出都無法和之前的數(shù)據(jù)放在一起對比。模板版本號最好直接進入日志字段并且作為緩存 key 的一部分。2.3 結構化輸出在前自由文本在后如果你想盡可能穩(wěn)定就別讓模型自由發(fā)揮格式。最實用的手段是要求模型返回 JSON最好使用接口提供的 JSON Mode 或結構化輸出能力。一個典型的穩(wěn)定請求配置可以長這樣{ model: your-model-name, messages: [ { role: system, content: 你是文檔信息抽取助手。只輸出 JSON不要輸出解釋。 }, { role: user, content: 從下面的文檔中抽取字段。\n\n文檔\n{{input_text}} } ], response_format: { type: json_object }, temperature: 0, seed: 42 }這里要強調即使模型支持 JSON Mode也不能完全信任輸出。校驗器仍然要寫。JSON Mode 只能保證格式可解析不保證字段值正確更不保證模型沒有漏掉字段。3. 工程層面的確定性控制緩存、冪等、重試與校驗3.1 用緩存消除重復計算也消除重復波動當輸入內容幾乎不變時很多管道沒必要反復調用模型。比如一個文檔清洗任務每天跑一次但其中 80% 的文檔根本沒變過。如果每次都全量調用模型不僅浪費成本還會因為模型版本、采樣參數(shù)的變化把原本穩(wěn)定的結果改亂。更合理的做法是給每個任務算緩存 keyprompt 模板版本號模型名采樣參數(shù)輸入文本 hash附帶數(shù)據(jù) hash當緩存 key 完全一致時直接返回上次結果。這個策略能大幅提升管道穩(wěn)定性。但要注意一點緩存 key 不能只放文件路徑。同一份文檔改過內容但路徑?jīng)]變路徑就不適合做 key。用內容 hash 更可靠。3.2 讓每次請求具備冪等性冪等的意思是同一個任務執(zhí)行一次和執(zhí)行多次最終結果一致。在 LLM 管道里冪等不是天然成立的。因為模型調用可能有網(wǎng)絡超時、服務端 5xx、限流導致你不得不重發(fā)請求。重發(fā)時如果上下文里夾了時間戳或者隨機 ID輸出就可能變化。為了讓管道更可重試需要把業(yè)務唯一 ID 和模型請求解耦。請求內部不要依賴“當前時間”這類不穩(wěn)定變量。若必須記錄時間請把它放在日志層而不是塞進 prompt。重試時要做的第一件事不是機械地重發(fā)原請求而是記錄上一次失敗的狀態(tài)。比如錯誤類型是超時、限流還是 schema 校驗失敗。不同錯誤類型對應的處理策略不同。超時和限流可以稍等重試schema 校驗失敗則需要把錯誤信息反饋給模型或者切換為更保守的 prompt。3.3 三層校驗器格式校驗、字段校驗、業(yè)務規(guī)則校驗我見過的穩(wěn)定管道幾乎都有一個三層校驗機制。第一層是格式校驗。檢查模型輸出的 JSON 能否被解析字段類型是否符合預期。第二層是內容校驗。檢查關鍵字段是否為空、枚舉值是否合法、日期格式是否正確。第三層是業(yè)務規(guī)則校驗。例如金額不能為負數(shù)、文檔編號必須匹配正則、摘要不能超過指定字數(shù)。如果模型輸出沒有通過校驗不建議直接丟棄也不建議直接手動糾錯。更穩(wěn)的做法是保存原始模型輸出作為樣本記錄校驗錯誤原因基于錯誤信息構造一次修復請求修復失敗則進入人工隊列這樣即使模型在個別樣本上出問題管道整體仍然可控。4. 批量場景里最容易破壞確定性的三個隱患4.1 輸入規(guī)范化不足順序和格式互相干擾批量處理的第一個問題不是模型而是輸入文件。一批文檔可能是 UTF-8也可能是 GBK有的行尾是 LF有的是 CRLF有的文件開頭有 BOM有的沒有。這些差異都會讓同一段文本在進入模型前產(chǎn)生變化。為了穩(wěn)定建議先做輸入規(guī)范化統(tǒng)一轉碼為 UTF-8統(tǒng)一換行符移除或記錄 BOM去除不可見控制字符保留原始排版時至少保證分塊邏輯確定分塊也值得注意。如果按固定 token 數(shù)切分模型的 tokenizer 更新后切分位置會變。如果按段落切分又要先約定段落邊界。最好的辦法是既保存分塊結果也保存分塊 id。這樣即使重跑也能對比是哪一塊發(fā)生了變化。4.2 不要依賴“返回順序”來表示數(shù)據(jù)位置批量調用模型時很多管道會使用并發(fā)。并發(fā)返回順序經(jīng)常是亂的。第一份文檔可能 1 秒返回第二份文檔可能 5 秒返回。如果你直接把結果按返回順序寫入結果表那數(shù)據(jù)順序就和原始文檔順序對不上了。解決辦法是使用任務 ID 關聯(lián)結果。每條輸入在進入管道時拿到一個唯一 ID模型返回后把結果寫回task_id對應的記錄里。最后再按task_id聚合而不是按時間順序聚合。這一步看起來多余但能避免非常多奇怪的問題。尤其當你做長文檔摘要或知識庫切片入庫時只有順序可控后續(xù)檢索才能穩(wěn)定復現(xiàn)。4.3 Agent 或多工具鏈路里還要控制工具調用歷史當管道升級為 Agent也就是讓模型決定下一步調用哪個工具時不確定性會明顯增加。原因不完全是模型推理隨機而是工具本身會改變上下文。第一次調用工具返回了 10 條檢索結果第二次調用可能因為檢索接口排序變化返回了另外 10 條。模型后續(xù)決策自然就不同了。要讓 Agent 管道更確定至少要做到工具調用的入?yún)⒑头祷亟Y果都必須記錄檢索結果進入上下文前要排序并裁剪設定允許的最大調用輪數(shù)每一步都校驗工具調用格式而不是讓模型無限嘗試不要把所有工具都塞給模型只暴露當前子任務需要的工具如果你還在早期學習階段直接讀 LLM 官方文檔或觀察框架源碼先跑通單工具調用再增加第二個工具。不要一上來就讓模型自己決定復雜流程。5. 一個可落地的執(zhí)行順序從最小樣例到批量回歸5.1 先用 5 條樣本跑通全鏈路真正做起確定性控制時最忌諱直接開全量。我一般先用 5 到 10 條高代表性樣本跑一遍。樣本要覆蓋不同長度的文本、不同格式、特殊字符、空字段等邊界情況。然后檢查三個東西模型輸出是否都能被解析為預期結構校驗器有沒有誤報日志里能不能看到每條請求對應的模型版本、參數(shù)、輸入 hash如果這些都沒問題再擴大到 50 條。50 條能幫你發(fā)現(xiàn)并發(fā)、超時、限流這類單條跑時看不見的問題。5.2 固化執(zhí)行計劃并填寫記錄搭建管道時可以用一個簡單的執(zhí)行計劃來表示完整流程。例如1. 規(guī)范化輸入統(tǒng)一編碼、讀取文本、計算內容 hash 2. 查緩存如果內容 hash 和參數(shù)組合命中直接返回 3. 構造 prompt使用固定模板注入規(guī)范化文本 4. 調用模型溫度 0記錄完整請求日志 5. 解析輸出嘗試 JSON 解析 6. 校驗結果格式、字段、業(yè)務規(guī)則 7. 校驗失敗則進入修復子流程 8. 成功則寫入結果表并記錄輸出 content hash這套流程不依賴具體框架。你可以在 LangChain、自研代碼或任何 LLM 框架上實現(xiàn)但每一步都要獨立可觀測方便重跑時對比。5.3 把失敗樣本收集成回歸集穩(wěn)定管道不是一次寫出來的而是靠回歸集長期維護出來的。準備一個失敗樣本目錄里面放典型的解析失敗樣本字段缺失樣本校驗失敗樣本超時或限流樣本每次你修改 prompt 模板、調整切分邏輯、升級模型版本后都用這批樣本重新跑一遍對比修復率。如果原來失敗的樣本變得能通過說明改動方向正確。這個回歸集不要太大。50 到 100 條就足夠。太大了反而會拖慢開發(fā)迭代。5.4 模型升級前先做對比實驗無論你用的是本地模型還是云 API換版本之前都必須先跑對比實驗。先把舊版本的輸出保存下來再切到新版本在回歸集上跑一遍。逐條對比字段一致性。遇到差異較大的情況不要急著改代碼先把 prompt 模板和模型版本日志調出來確認差異源。真正跑生產(chǎn)時我更建議把模型名稱寫成環(huán)境變量或配置項而不是硬寫在代碼里。這樣切版本時可以隨時做 A/B 對比也方便回滾。6. 輸出不一致時按這個順序排查遇到管道結果不穩(wěn)定時很多人第一反應是調整 temperature或者懷疑模型能力。但實際踩坑后會發(fā)現(xiàn)很多問題出在管道本身。排查順序建議如下先看同一個輸入是不是真的“同一個輸入”。比較字節(jié)級 hash。再看 prompt 模板版本是否被改動或者變量注入順序是否發(fā)生變化。再看請求參數(shù)確認模型名、溫度、seed、max_tokens 沒有在運行期間被覆蓋。再看模型服務端側本地模型確認權重文件和推理框架版本云 API 確認模型是否被配置成自動升級。最后看重試邏輯確認上一次超時或失敗后重試是否帶上了額外上下文或新參數(shù)。這里可以配合一張快速判斷表現(xiàn)象優(yōu)先排查可能原因同一條輸入偶爾結果不同請求參數(shù)、服務端版本溫度非 0、seed 未生效、模型版本漂移批量跑到后期逐漸變慢或報錯上下文長度、并發(fā)數(shù)、限流輸入沒截斷、請求堆積JSON 解析偶發(fā)失敗輸出格式約束、校驗器模型返回了額外解釋文字換環(huán)境后結果大面積變化依賴版本、Python 或 Node 版本、GPU 驅動推理框架不一致結果文件里順序錯亂任務 ID 關聯(lián)、寫入邏輯并發(fā)返回順序被當作業(yè)務順序新增字段后舊數(shù)據(jù)和新數(shù)據(jù)對不上prompt 模板版本、schema 版本沒有做版本遷移如果你看到錯誤消息里有類似request timed out或schema相關的報錯先不要急著認為是模型能力不足。第一個動作是查請求日志看超時發(fā)生在哪個階段schema 校驗到底因為哪個字段失敗。很多時候這類報錯是輸入格式不符合預期或者重試時沒有帶上原始校驗錯誤。把錯誤信息喂回給模型是一種常見補救方法但喂回去之前一定要先做長度控制否則可能把模型上下文撐爆。7. 不必強求完全一致把確定性留給真正需要的地方7.1 需要高確定性的典型場景如果你正在做下面這些事確定性控制應該寫進需求而不是事后補救批量文檔字段抽取合同、票據(jù)、日志的結構化落庫自動化測試中的預期結果比對合規(guī)審計場景中的處理留痕知識庫入庫前的切分和元數(shù)據(jù)提取需要重復實驗的算法評測這些場景的共同點是模型輸出不是終點而是要進入數(shù)據(jù)庫、報表或下游系統(tǒng)。輸出不穩(wěn)定會造成臟數(shù)據(jù)而臟數(shù)據(jù)比慢數(shù)據(jù)更難處理。7.2 更適合保留多樣性的場景對應地這些場景不要過度固定聊天機器人回復風格營銷文案生成頭腦風暴和探索性問答推薦標題或摘要的候選生成教學場景中的多角度解釋如果做聊天助手時也把溫度強行調成 0用戶會發(fā)現(xiàn)每次問同一句話得到近乎相同的回答體驗反而生硬。這類系統(tǒng)應該把人放在校驗環(huán)節(jié)用產(chǎn)品邏輯規(guī)避質量波動而不是靠壓制隨機性。7.3 我的最終建議開發(fā)更確定性的 LLM pipelines不是一個開關能解決的問題。它是一套工程習慣輸入要規(guī)范化參數(shù)要記錄模板要版本化輸出要校驗失敗要可重試模型升級要對比。我更建議先把單條任務跑穩(wěn)再改造成批量任務先保證 100 條數(shù)據(jù)可復現(xiàn)再加入 Agent 或工具調用先做輸出 schema 約束再去追求緩存和調度優(yōu)化。踩過幾次之后會發(fā)現(xiàn)很多問題不是模型能力不夠而是管道設計沒有把“不確定性”當作頭等變量來對待。一旦你把模型當作管道里一個可能犯錯、需要約束的外部組件穩(wěn)定性就會慢慢長出來。