踐:從用例設(shè)計(jì)到CI流水線集成)
把LLM技能當(dāng)成普通函數(shù)來做自動化測試這個(gè)想法我第一次聽的時(shí)候覺得有點(diǎn)“想多了”——模型輸出本來就是概率性的同一句話問兩次答案都能不一樣怎么測但后來真正開始在Agent項(xiàng)目里維護(hù)二十多個(gè)技能模塊每次改一句提示詞都要把十幾個(gè)場景手工點(diǎn)一遍我才意識到不把這件事自動化后面只會越來越痛苦。這篇就是用skillprobe這套工具把LLM技能測試落地的全記錄從測試用例設(shè)計(jì)到指標(biāo)判定再到接進(jìn)CI流水線踩過的坑和驗(yàn)證過的思路都會寫到。如果你正在做Agent類應(yīng)用或者給LLM封裝了各種“技能”RAG檢索、工具調(diào)用、結(jié)構(gòu)化抽取、內(nèi)容生成你大概率也會遇到同一個(gè)問題技能看著能用但改一版提示詞之后到底有沒有改壞別的地方基本靠運(yùn)氣。skillprobe就是拿來解決這個(gè)問題的它的定位很像pytest之于Python——只不過被測對象是LLM技能而不是普通函數(shù)。下面直接進(jìn)入正題。1. 為什么必須給LLM技能上自動化測試1.1 LLM技能不是代碼但它也需要“單元測試”先對齊一下概念。這里說的“LLM技能”指的是你在Agent或應(yīng)用里給大模型封裝好的能力模塊。舉個(gè)例子一個(gè)給客服機(jī)器人用的“訂單查詢技能”它的輸入可能是用戶原話和用戶ID內(nèi)部邏輯是把用戶原話做意圖識別、抽取出訂單號再去調(diào)用訂單系統(tǒng)API最后把結(jié)果整理成自然語言回答。整個(gè)鏈路是“模型提示詞外部工具調(diào)用”的組合而不是一段普通的函數(shù)代碼。普通函數(shù)測試很簡單輸入A斷言輸出是B。但LLM技能不一樣它的輸出是概率性的同一個(gè)輸入配上稍微不一樣的采樣參數(shù)結(jié)果就會有波動。而且它的“正確”也不只是數(shù)值對錯(cuò)還有語義是否正確、格式是否規(guī)范、引用是否能對上原文。所以傳統(tǒng)的單元測試框架直接用不了需要一個(gè)能適配“非確定性輸出”的測試方案。skillprobe做的事就是把LLM技能測試拆成幾層技能注冊和加載是否正常、輸入輸出schema是否匹配、端到端行為是否正確、多次執(zhí)行是否穩(wěn)定。每一層都有對應(yīng)的判定邏輯而不是簡單拿一個(gè)字符串去比對。我自己的體會是LLM技能的維護(hù)成本其實(shí)比普通代碼高得多。普通代碼出問題報(bào)錯(cuò)堆棧一看就明白了。LLM技能出問題往往是“回答得挺流暢但內(nèi)容里有小錯(cuò)”或者“這個(gè)場景回答得很好換一個(gè)說法就不行了”。這些問題單靠開發(fā)時(shí)手動測兩條路徑根本發(fā)現(xiàn)不了。自動化測試不是錦上添花而是把質(zhì)量底線兜住。1.2 手工驗(yàn)證的三個(gè)致命問題先說說我最早是怎么干的。每個(gè)技能寫完之后打開一個(gè)測試面板把樣例輸入粘貼進(jìn)去點(diǎn)一次看輸出是否合理。流程看起來沒毛病但實(shí)際用下來有三個(gè)繞不開的問題。第一個(gè)問題是覆蓋度太低。手工測試天然偏向happy path你會下意識地測那些“自己知道應(yīng)該有結(jié)果”的輸入。但真實(shí)用戶不會按測試用例說話他們可能一句話里帶口音、可能只給一半信息、可能上來就問一個(gè)完全超出技能范圍的問題。這些邊界情況手工測試基本覆蓋不到。第二個(gè)問題是回歸很難發(fā)現(xiàn)。LLM技能里面的提示詞是脆弱的你為了修A問題改了一句話很可能B問題就跑偏了。手工測試的記錄都在腦子里時(shí)間一長你根本想不起來上一版在某個(gè)輸入上的輸出長什么樣。等你發(fā)現(xiàn)B問題掛了往往是在線上被用戶投訴之后。第三個(gè)問題是對“狀態(tài)”幾乎無法驗(yàn)證。很多技能是有上下文的比如多輪對話場景里的“追問訂單號”技能需要在多輪里累積信息。手工測試時(shí)狀態(tài)在你腦子里你記得上一輪說過什么。但自動化測試要驗(yàn)證的是“技能自己在多輪之間對不對”這需要把對話歷史當(dāng)輸入?yún)?shù)顯式管理起來手工方式很難穩(wěn)定復(fù)現(xiàn)。這三個(gè)問題疊加起來結(jié)論很清楚LLM技能的測試必須自動化而且必須是可重復(fù)執(zhí)行的自動化否則你沒法在版本迭代里建立信心。2. skillprobe能測什么從技能注冊到端到端行為2.1 技能核心要素拆解在用skillprobe之前我先把一個(gè)LLM技能拆成了四個(gè)要素這樣才知道該測什么。第一個(gè)要素是技能聲明。每個(gè)技能都有一個(gè)描述文件里面寫清楚技能名稱、一句話描述、輸入?yún)?shù)的定義、輸出結(jié)果的定義。這個(gè)文件的作用是讓Agent的主模型知道“什么情況下該調(diào)用這個(gè)技能”同時(shí)讓外部系統(tǒng)知道“該傳什么進(jìn)來、能拿什么回去”。技能聲明要是寫錯(cuò)了模型根本不會在合適的時(shí)機(jī)調(diào)用這個(gè)技能。第二個(gè)要素是實(shí)現(xiàn)邏輯。包括提示詞模板、內(nèi)部的工具調(diào)用鏈、后處理邏輯。這是整個(gè)技能的核心也是最容易改壞的部分。提示詞模板改了輸出風(fēng)格可能變工具調(diào)用順序變了結(jié)果可能延遲或者出錯(cuò)后處理邏輯改了格式可能不符合schema。第三個(gè)要素是運(yùn)行配置。模型名稱、溫度、最大token數(shù)、超時(shí)時(shí)間、是否啟用流式輸出等。這類配置直接影響輸出的穩(wěn)定性和性能。溫度太高同一輸入每次輸出都不同超時(shí)太短長文本生成直接失敗。第四個(gè)要素是外部依賴。RAG技能依賴向量庫和檢索器工具調(diào)用技能依賴下游API。這些依賴的狀態(tài)變化也會影響技能行為比如向量庫里新增了一批文檔技能的引用來源就會變。測試時(shí)需要考慮這些依賴是否可用、是否可控。skillprobe的測試設(shè)計(jì)基本就圍繞這四個(gè)要素展開每個(gè)要素對應(yīng)一類測試項(xiàng)而不是只盯著“輸出內(nèi)容像不像”。2.2 skillprobe的四個(gè)核心能力具體來說skillprobe做了下面四類事情。第一類是技能注冊健康檢查。它會驗(yàn)證技能的描述文件是否能被正常解析輸入輸出的schema定義是否合法技能能否被模擬的Agent運(yùn)行時(shí)加載。這一步很像傳統(tǒng)的冒煙測試——跑不通就不往下走了省得后面所有用例都因?yàn)榧寄芗虞d不了而失敗。第二類是輸入輸出schema校驗(yàn)。執(zhí)行技能之后先不看內(nèi)容質(zhì)量先看返回的結(jié)果結(jié)構(gòu)和定義是否一致。比如你定義輸出是一個(gè)JSON對象包含“訂單號”和“狀態(tài)”兩個(gè)字段那么返回結(jié)果就必須是一個(gè)合法的JSON而且這兩個(gè)字段必須存在類型必須正確。這層校驗(yàn)?zāi)茏コ鲆淮箢悺澳P蜎]按格式輸出”的臟數(shù)據(jù)問題。第三類是端到端行為斷言。這是核心部分。給定一組輸入執(zhí)行整個(gè)技能鏈路然后對輸出做語義和結(jié)構(gòu)層面的判定。判定方式可以是結(jié)構(gòu)化斷言、語義相似度、LLM評分等后面會細(xì)講。第四類是穩(wěn)定性與性能采集。同一個(gè)用例執(zhí)行多次收集輸出的波動情況、耗時(shí)分布、token消耗。LLM技能上線前最好知道它在“大概率情況下”表現(xiàn)如何而不是只測一次看個(gè)結(jié)果。把這幾類能力組合起來skillprobe對技能的覆蓋就比較完整了。實(shí)踐下來技能注冊和schema校驗(yàn)抓的問題最多因?yàn)樘崾驹~一改輸出格式經(jīng)常悄悄變了端到端行為斷言的價(jià)值在回歸測試?yán)镒蠲黠@。3. 如何設(shè)計(jì)一份能抓Bug的測試用例3.1 測試用例的最小單元樣例輸入加參考輸出加容忍度測試用例設(shè)計(jì)是整個(gè)方案里最需要花心思的部分。skillprobe里一個(gè)用例的最小單元由三部分組成樣例輸入、參考輸出、容忍度設(shè)置。樣例輸入要好理解就是一個(gè)能被技能執(zhí)行的輸入。對于訂單查詢技能樣例輸入可能是“幫我查一下單號SD20240513的物流進(jìn)度”。對于內(nèi)容生成技能樣例輸入可能是一段產(chǎn)品需求和目標(biāo)用戶描述。樣例輸入的覆蓋策略要兼顧正常場景、邊界場景和異常場景。參考輸出是“這個(gè)輸入在理想情況下應(yīng)該返回什么”。這里有個(gè)關(guān)鍵點(diǎn)LLM技能的參考輸出不應(yīng)該是一個(gè)字都不能差的固定文本而應(yīng)該是一個(gè)帶語義空間的結(jié)果。你可以定義參考輸出的核心要素比如“必須包含訂單狀態(tài)字段”“必須提及最新的物流節(jié)點(diǎn)”“語氣必須禮貌”而不是規(guī)定整個(gè)句子長什么樣。容忍度設(shè)置就是告訴skillprobe“偏差多少算可以接受”。語義相似度閾值設(shè)0.8還是0.9結(jié)構(gòu)化字段允許哪些字段缺失調(diào)用外部API的次數(shù)允許幾次都在這里配置。一個(gè)完整的用例設(shè)計(jì)好了之后相當(dāng)于把“這個(gè)技能在這個(gè)輸入上應(yīng)該表現(xiàn)成什么樣”這個(gè)模糊的判斷變成了可以被機(jī)器執(zhí)行和復(fù)現(xiàn)的規(guī)則。3.2 參考輸出怎么生成人工種子加LLM輔助構(gòu)造參考輸出是新手最容易卡住的地方。一開始我天真地想讓我一個(gè)一個(gè)手工寫參考輸出寫出來應(yīng)該很精準(zhǔn)吧。結(jié)果寫了十幾個(gè)用例之后發(fā)現(xiàn)速度太慢而且我寫參考輸出的語氣和技能真實(shí)輸出的語氣總有偏差導(dǎo)致語義相似度算出來很低誤報(bào)率很高。后來我換了個(gè)策略三步走。第一步人工標(biāo)注種子集。每個(gè)技能先挑5到10個(gè)典型輸入手工寫出參考輸出。不需要長關(guān)鍵是包含該有的信息要素和正確的格式。第二步用LLM輔助擴(kuò)充參考輸出。把種子集喂給一個(gè)輔助模型讓它按照樣例的風(fēng)格和結(jié)構(gòu)給一批新的輸入生成參考輸出。這一步能快速擴(kuò)充用例庫但生成出來的東西只能當(dāng)候選不能直接用。第三步人工審閱。所有輔助生成的參考輸出都要過一遍眼確認(rèn)信息要素正確、格式符合預(yù)期、沒有幻覺。這一步很重要因?yàn)閰⒖驾敵霰旧砣绻绣e(cuò)后面所有判定就失去了基準(zhǔn)。這里有個(gè)容易忽視的坑生成參考輸出時(shí)用的模型和技能運(yùn)行時(shí)用的模型最好不要是同一次調(diào)用、同一個(gè)溫度配置。否則“測試參考輸出”和“被測輸出”之間可能會存在隱性的同源偏見導(dǎo)致在生成模型上測試結(jié)果虛高換一個(gè)模型就崩了。3.3 負(fù)面用例和對抗性樣本必須單獨(dú)建一批很多人做LLM技能測試時(shí)因?yàn)閰⒖驾敵霾恢涝趺炊ň椭苯臃艞壛素?fù)面用例。這是不對的。負(fù)面用例反而不需要復(fù)雜的參考輸出只需要一個(gè)預(yù)期行為要么拒絕要么觸發(fā)澄清要么明確返回“無法處理”。我維護(hù)的用例集里負(fù)面樣本大概占三成。常見的有這么幾類空輸入、超長輸入、格式錯(cuò)誤的輸入、明顯超出技能范圍的問題、含有誘導(dǎo)內(nèi)容的輸入比如讓模型忽略系統(tǒng)指令、輸出Prompt原文等。負(fù)面用例最典型的驗(yàn)證方式是執(zhí)行技能后斷言輸出里是否包含“無法回答”之類的拒絕語義或者斷言技能是否正確返回了某個(gè)錯(cuò)誤標(biāo)識。你不需要它答得漂亮只需要它不要“硬答”。很多線上事故就是負(fù)面輸入觸發(fā)了模型的胡編亂造而手工測試又從來不會拿這些輸入去點(diǎn)一遍。對抗性樣本這東西一開始沒那么復(fù)雜。先把項(xiàng)目里用戶反饋過的問題話術(shù)沉淀成用例再周期性跑一遍比專門去構(gòu)造隨機(jī)攻擊句子實(shí)用得多。3.4 測試用例也要做版本管理測試用例不是一次性寫完了就完事。技能版本迭代用例也要跟著更新。skillprobe的用例集建議直接用Git管理和技能代碼放在同一個(gè)倉庫里。這樣每次改動技能代碼的時(shí)候能直接看到用例集有沒有同步更新。用例文件本身建議用可讀性強(qiáng)的格式組織每個(gè)用例有個(gè)唯一ID有標(biāo)簽標(biāo)明場景類型有創(chuàng)建時(shí)間有最后更新人。我在項(xiàng)目里遇到過一次很尷尬的情況一個(gè)用例從創(chuàng)建起就沒人維護(hù)技能行為已經(jīng)合法變更了兩次用例的參考輸出卻還在描述最原始的版本每次跑測試必掛。最后排查了很久才發(fā)現(xiàn)不是技能的問題是用例過期了。所以用例也要有一個(gè)生命周期管理定期Review、清理和更新。我會在每次技能版本發(fā)布前跑一遍全量用例同時(shí)看一眼有沒有用例被跳過或者被標(biāo)記為“已知過時(shí)”。4. 指標(biāo)與判定邏輯別讓測試自己變成玄學(xué)4.1 先做結(jié)構(gòu)化斷言再做語義判斷判定邏輯是skillprobe的核心設(shè)計(jì)。我把它分成三層優(yōu)先做結(jié)構(gòu)化斷言再做語義判斷最后才用LLM評分。結(jié)構(gòu)化斷言是最硬的一層。它校驗(yàn)輸出是否滿足明確的規(guī)則比如輸出是否是合法JSON關(guān)鍵字段是否存在、類型是否正確、數(shù)值是否在一定范圍內(nèi)、枚舉值是否合法。這層判斷不需要模型參與純代碼就可以做速度極快準(zhǔn)確率百分百。我在項(xiàng)目里見過太多“模型一本正經(jīng)地輸出了一坨格式錯(cuò)誤的內(nèi)容”的案例。尤其是讓模型輸出JSON的場景經(jīng)常出現(xiàn)末尾多一個(gè)逗號、字段名被引號包錯(cuò)、嵌套結(jié)構(gòu)錯(cuò)亂這類問題。結(jié)構(gòu)化斷言能把這些全部攔下來。結(jié)構(gòu)化斷言通過之后再看語義。語義判斷通常用向量相似度把模型輸出和參考輸出分別轉(zhuǎn)換成向量計(jì)算余弦相似度。但這里要有心理準(zhǔn)備語義相似度是一個(gè)很粗糙的指標(biāo)它適合“生成型任務(wù)”的粗篩不適合“抽取型任務(wù)”的細(xì)判。抽取型任務(wù)建議直接用結(jié)構(gòu)化斷言加信息要素比對比如檢查輸出里是否包含指定實(shí)體。給不同任務(wù)設(shè)定合適的閾值很重要。生成型任務(wù)比如郵件撰寫、內(nèi)容總結(jié)語義相似度閾值設(shè)在0.75到0.85之間比較合理因?yàn)橥痪湓捒梢杂卸喾N合法表達(dá)方式。抽取型任務(wù)比如訂單信息抽取、實(shí)體識別閾值要往高了設(shè)或者干脆不用語義相似度改用要素級別的匹配。提示如果發(fā)現(xiàn)測試結(jié)果頻繁在“通過”和“失敗”之間橫跳先別急著懷疑模型先看一下閾值是不是設(shè)得太緊。閾值不是越大越好要結(jié)合技能的真實(shí)輸出分布來定。4.2 LLM-as-Judge的引入與偏見規(guī)避有些技能沒有標(biāo)準(zhǔn)答案比如“幫用戶把一段投訴內(nèi)容改寫得更委婉”。這種任務(wù)用語義相似度和結(jié)構(gòu)化斷言都很難覆蓋全這時(shí)候就需要引入LLM-as-Judge——用另外一個(gè)模型來當(dāng)評審給輸出打分。用LLM當(dāng)評審是有用的但它有自己的偏見這個(gè)必須得防。常見偏見有三個(gè)一是立場偏好評分模型容易給“和自己生成風(fēng)格相似的輸出”打高分二是長度偏好越長越容易得高分哪怕內(nèi)容冗余三是位置偏好在多條候選輸出對比時(shí)排在前面或后面的結(jié)果會影響評分。規(guī)避這些偏見skillprobe的做法是評分Prompt里明確給出評分維度比如“信息完整性”“格式規(guī)范性”“語氣恰當(dāng)性”每個(gè)維度單獨(dú)打分最后再看總分。同時(shí)把參考輸出也作為提示的一部分給評分模型讓它做的是“對照參考標(biāo)準(zhǔn)來打分”而不是“憑感覺給個(gè)分”。另外一個(gè)細(xì)節(jié)是評分模型最好和被測技能用的模型不是一個(gè)。雖然成本會高一點(diǎn)但至少能避免“自己夸自己”的嫌疑。我自己常用一個(gè)相對便宜但指令跟隨能力強(qiáng)的模型做評分效果是夠用的。4.3 可復(fù)現(xiàn)性才是自動化的命根子LLM技能測試最讓人頭大的就是今天通過了明天掛了而且找不到原因。要根治這個(gè)問題必須把可復(fù)現(xiàn)性當(dāng)成一等公民??蓮?fù)現(xiàn)性主要靠兩件事。第一件是固定采樣配置。skillprobe執(zhí)行用例時(shí)會把模型名稱、溫度、top_p、seed等參數(shù)全部記錄在測試報(bào)告里。如果你的模型接口支持固定seed就一定要固定下來如果不支持那就把溫度設(shè)成0降低隨機(jī)性。我在實(shí)踐里發(fā)現(xiàn)溫度設(shè)0也不意味著完全確定因?yàn)橛行┠P头?wù)端還有其他隨機(jī)因素這時(shí)候就要靠多次執(zhí)行取結(jié)果分布。第二件是固定依賴環(huán)境。RAG類的技能要固定檢索器和知識庫版本工具調(diào)用類的技能要mock掉外部API或者記錄下真實(shí)API在測試時(shí)刻的返回結(jié)果。如果不固定這些外部依賴那你的測試結(jié)果根本沒法定位問題——到底是技能本身壞了還是檢索器數(shù)據(jù)變了還是外部API抽風(fēng)了完全說不清。skillprobe支持給用例配置一個(gè)“依賴環(huán)境指紋”跑測試的時(shí)候自動記錄當(dāng)前檢索器版本、API mock版本、模型版本。這樣出了問題先看報(bào)告里環(huán)境指紋有沒有變化能節(jié)省大量排查時(shí)間。5. 把skillprobe接進(jìn)CI流水線5.1 流水線的最小閉環(huán)設(shè)計(jì)測試用例設(shè)計(jì)好了指標(biāo)判定邏輯也清晰了下一步就是讓它跑起來。我建議的最小閉環(huán)是觸發(fā)事件、執(zhí)行測試、生成報(bào)告、結(jié)果判定。觸發(fā)事件可以根據(jù)團(tuán)隊(duì)情況選。最常見的是PR合并前跑也就是代碼變更時(shí)觸發(fā)另外還可以加一個(gè)定時(shí)觸發(fā)比如每天夜里跑一次全量用例防止模型側(cè)或者依賴側(cè)悄悄變化導(dǎo)致技能行為漂移。執(zhí)行測試這一步skillprobe會按照配置去加載技能代碼、讀取測試用例集、逐個(gè)執(zhí)行。整個(gè)執(zhí)行過程必須是無狀態(tài)的每次跑之前從Git拉取指定版本跑完之后清理臨時(shí)文件。這樣才能保證測試結(jié)果可復(fù)現(xiàn)。生成報(bào)告是很多人容易忽略的環(huán)節(jié)。報(bào)告里除了“通過率”還必須有失敗用例的完整上下文輸入是什么、輸出是什么、參考輸出是什么、哪一項(xiàng)斷言沒過、耗時(shí)多少、當(dāng)時(shí)的環(huán)境指紋是什么。沒有這些信息測試掛了你也只能干瞪眼。結(jié)果判定要嚴(yán)格但也要留出人工確認(rèn)的通道。我的做法是通過率達(dá)到設(shè)定閾值就放行比如95%但如果低于閾值CI就直接失敗不允許合并代碼。同時(shí)配置了一個(gè)白名單機(jī)制允許維護(hù)者手動標(biāo)記某些用例為“已知問題”避免一個(gè)長期存在的小問題卡住整個(gè)團(tuán)隊(duì)的發(fā)布流程。5.2 一個(gè)可以直接抄的配置文件示例skillprobe的配置用YAML格式組織整體結(jié)構(gòu)比較直觀。下面這個(gè)是我在一個(gè)類似訂單查詢技能的項(xiàng)目里實(shí)際用的配置骨架project: name: order-query-skill version: 1.4.2 model: base: gpt-4-turbo # 技能運(yùn)行時(shí)使用的模型 temperature: 0 # 低溫度優(yōu)先保證可復(fù)現(xiàn) max_tokens: 1024 timeout_seconds: 30 tests: dir: ./tests/cases pass_threshold: 0.95 report: output_dir: ./reports format: [json, html] judge: mode: hybrid # 混合模式結(jié)構(gòu)化斷言 語義相似度 LLM評分 semantic_similarity: 0.80 llm_judge: model: gpt-4o-mini dimensions: - information_completeness - format_compliance - tone_appropriateness dependency_environment: retriever_version: 2024.06.01 mock_api: true幾個(gè)關(guān)鍵參數(shù)說一下。pass_threshold: 0.95的意思是全量用例集里至少九成五的用例要通過否則CI失敗。這個(gè)值一開始不要定太高建議先跑一周看基線再逐步收緊。semantic_similarity: 0.80是語義相似度的全局默認(rèn)閾值個(gè)別用例可以在用例文件里覆蓋。5.3 CI流水線腳本的關(guān)鍵步驟流水線腳本本身不復(fù)雜核心邏輯就四步裝環(huán)境、拉代碼、跑測試、上傳報(bào)告。# 裝依賴 pip install skillprobe # 拉技能代碼和用例集 git checkout commit_id git pull --recurse-submodules # 執(zhí)行測試指定配置文件和輸出目錄 skillprobe run --config ./config.yaml \ --report ./reports/ci_${BUILD_ID}.json # 檢查退出碼0表示通過非0表示未達(dá)閾值 exit_code$? # 把報(bào)告上傳到團(tuán)隊(duì)的可視化平臺 skillprobe upload --report ./reports/ci_${BUILD_ID}.json \ --project order-query-skill exit $exit_code用skillprobe CLI的時(shí)候有一點(diǎn)要特別提醒它支持兩種執(zhí)行模式。一種是快速模式只跑結(jié)構(gòu)化斷言和schema校驗(yàn)速度快適合PR階段快速反饋另一種是全量模式會帶上語義判定和LLM評分耗時(shí)較長適合夜間定時(shí)任務(wù)跑。我在實(shí)際項(xiàng)目里就是兩檔并行PR階段跑快速模式保證格式類問題不會流進(jìn)去每天晚上跑全量模式把語義和穩(wěn)定性問題撈一遍。這樣既能快速迭代又不會把質(zhì)量底線放掉。6. 常見問題與排查技巧實(shí)錄6.1 測試總是飄紅先查三個(gè)東西如果你剛開始用skillprobe跑出來的測試結(jié)果今天紅一片、明天綠一片別急著去調(diào)模型提示詞先按順序查三件事。第一查用例集的參考輸出是不是過期了。這是最常見的原因。技能行為已經(jīng)迭代了用例參考輸出還停留在老版本那今天通過了反而奇怪。把參考輸出同步一下問題大概率就解決了。第二查模型配置是不是變了??赡苁羌寄艽a里默認(rèn)溫度改了可能是模型服務(wù)端把版本升級了也可能是你從GPT-4換到了GPT-4-Turbo但沒有同步更新配置。這些都算環(huán)境漂移報(bào)告里的環(huán)境指紋一看便知。第三查外部依賴的狀態(tài)。RAG技能要去看檢索器是否加了新數(shù)據(jù)工具類技能要看下游API是否改了返回格式。我遇到過一個(gè)問題下游API在某個(gè)字段里多返回了一個(gè)“customer_note”結(jié)果技能原本的字段補(bǔ)全邏輯沒跟上輸出里把備注拼到訂單號里去了。這種問題如果不固定依賴環(huán)境排查起來會非常痛苦。6.2 提示詞改一句二十個(gè)用例掛了十八個(gè)這種大面積回歸是最嚇人的但也是自動化測試價(jià)值最大的時(shí)刻。我碰到過一次不過不是技能提示詞的問題而是基礎(chǔ)模型的版本被服務(wù)商悄悄升級了輸出風(fēng)格大變導(dǎo)致所有依賴“固定措辭”的用例全部掛掉。遇到這種大面積失敗第一件事是冷靜看報(bào)告不要直接改提示詞。先把失敗用例集中起來看它們共同的特征是什么。如果全部和“語氣”相關(guān)那大概率是模型風(fēng)格漂移如果全部和“字段缺失”相關(guān)那可能是輸出格式變了如果零散的掛才需要去懷疑具體用例。大面積回歸往往意味著要調(diào)整的是“默認(rèn)閾值”或者“參考輸出”這層而不是逐條修用例。skillprobe支持批量更新參考輸出但一定要走人工審閱尤其是生成型任務(wù)不要讓測試數(shù)據(jù)自己修改自己。注意提示詞里加任何內(nèi)容之前先想一想這個(gè)改動會不會影響輸出格式或者風(fēng)格。LLM技能的脆弱就在這里加一句“你是一個(gè)親切的客服”可能就會讓JSON輸出多一句客套話直接破壞結(jié)構(gòu)化斷言。6.3 并發(fā)執(zhí)行時(shí)API配額被瞬間打爆用例上百個(gè)之后串行執(zhí)行肯定等不起于是你開并發(fā)。然后問題來了LLM服務(wù)的API配額被瞬間打爆測試跑一半開始大量報(bào)超時(shí)整個(gè)流水線紅掉。這是我自己踩過的坑。解決方案是加限流而不是無腦提高并發(fā)數(shù)。skillprobe里可以配置最大并發(fā)數(shù)和每秒請求數(shù)。建議先把并發(fā)數(shù)設(shè)成4~6每秒請求數(shù)設(shè)成一個(gè)保守值跑完一輪看總耗時(shí)再逐步往上調(diào)。還有一點(diǎn)PR階段的快速模式尤其適合開高并發(fā)因?yàn)榻Y(jié)構(gòu)化斷言不需要調(diào)用大模型幾乎不占配額全量模式里的語義相似度和LLM評分是重頭戲并發(fā)要控制得更嚴(yán)格。如果你用的是按token計(jì)費(fèi)的服務(wù)建議給評分模型單獨(dú)開一個(gè)便宜一點(diǎn)的模型成本能省不少。6.4 歷史用例逐漸失真測試集需要體檢用例集不是資產(chǎn)用得不好就是負(fù)債。跑了一段時(shí)間后你會發(fā)現(xiàn)一些舊用例的輸入已經(jīng)和當(dāng)前業(yè)務(wù)不太匹配了或者參考輸出描述的場景已經(jīng)下線了。這類用例就會變成“永久紅色”第一周你還會點(diǎn)開看看第二周開始就變成“已知問題”了。我的建議是每個(gè)迭代都做一次用例體檢??催@么幾類長時(shí)間重復(fù)失敗的用例、執(zhí)行耗時(shí)異常的用例、輸入場景已經(jīng)被業(yè)務(wù)廢棄的用例。該更新的更新該標(biāo)記的標(biāo)記該刪的刪。寧可用例數(shù)量少而精也不要讓一坨僵尸用例在測試集里發(fā)臭。7. 實(shí)操心得與后續(xù)擴(kuò)展最后聊一點(diǎn)我個(gè)人在項(xiàng)目里摸爬滾打總結(jié)的東西。第一個(gè)建議是別一開始就想把全部技能都納入自動化測試。挑一個(gè)你正在頻繁迭代的技能先用skillprobe把基線跑起來。所謂基線就是你有把握它當(dāng)前行為是對的然后把當(dāng)前測試結(jié)果保存為基線版本。以后任何改動拿新結(jié)果和基線對比多出來的紅點(diǎn)就是改進(jìn)方向。第二個(gè)建議是LLM技能的自動化測試本質(zhì)上是在建立一種“行為契約”。你給技能設(shè)定了輸入和輸出的預(yù)期契約越清晰技能就越像一個(gè)可以被維護(hù)的系統(tǒng)。我見過很多同事在調(diào)提示詞的時(shí)候完全靠感覺跑一遍發(fā)現(xiàn)輸出還不錯(cuò)就提交了。但感覺不能成為質(zhì)量保障的手段尤其是在Agent應(yīng)用開始承載真實(shí)業(yè)務(wù)數(shù)據(jù)之后。第三個(gè)建議是關(guān)于擴(kuò)展的。skillprobe目前已經(jīng)能支撐單技能的端到端測試和CI集成但它還能往幾個(gè)方向延伸。一個(gè)是多模型對比測試同一個(gè)技能用例集在多個(gè)模型上跑看哪個(gè)模型表現(xiàn)得最穩(wěn)定另一個(gè)是技能版本A/B測試新老兩版技能在同一個(gè)用例集上對比量化評估改動帶來的收益或者損失。這兩個(gè)方向?qū)ψ鯝gent框架選型和技能優(yōu)化都特別有價(jià)值。如果你正在維護(hù)任何形式的LLM技能我真的建議花一個(gè)下午把skillprobe的閉環(huán)搭起來。它能解決的問題非常明確讓技能迭代從“靠感覺”變成“看數(shù)據(jù)”。這個(gè)轉(zhuǎn)變越早發(fā)生越好。