:從大Prompt困局到技能編排架構(gòu))
1. Agent Skills是什么先從一個翻車的項目說起去年年中我接手了一個企業(yè)內(nèi)部知識庫助手需求聽起來不復雜員工在飛書里問一句上季度的報銷流程是什么機器人就得給出準確答復順便把相關(guān)表單鏈接甩出來。最開始我走的是當時最流行的路子——把所有業(yè)務規(guī)則、文檔索引、問答邏輯全部塞進一個巨大的System Prompt里再配上幾個function calling接口。初版跑起來確實像那么回事demo演示的時候管理層還點頭了。但上線第三周就出事了。隨著業(yè)務文檔從二十篇漲到兩百篇Prompt從三千字膨脹到八千字模型開始頻繁出現(xiàn)幻覺把去年的報銷標準答成前年的甚至會把休假申請和調(diào)休規(guī)則混著答。我試著繼續(xù)往Prompt里加約束結(jié)果就像往沙子里摻水越多越散。后來我徹底放棄了增量修Prompt的方案把架構(gòu)重構(gòu)成基于Agent Skills的技能編排模式——三個星期后準確率從76%拉到了94%維護成本還降了一半。今天這篇就聊聊一個合格的Agent Skills體系應該怎么搭哪些坑我是真金白銀踩出來的。這篇文章適合誰看如果你手上正在做AI客服、Copilot工具、知識庫機器人或者任何一個大模型要執(zhí)行多步任務的項目并且已經(jīng)感受到大Prompt方案越來越難維護那這篇文章正好解決你的問題。我會先講清楚Skill的底層架構(gòu)邏輯再給出一套能落地的實現(xiàn)方案最后把我排查過的幾個真實翻車現(xiàn)場攤開給你看。2. 為什么大Prompt硬調(diào)這條路必然走死2.1 上下文窗口再大也裝不下整個業(yè)務系統(tǒng)很多人的第一反應是模型上下文窗口不是有兩百萬token了嗎把所有東西都塞進去不就行了這個想法我在項目里也試過但實踐下來有三個繞不開的硬傷。第一個硬傷是注意力稀釋。模型對Prompt頭部和尾部的內(nèi)容記憶最強中間部分最容易丟失。當一份業(yè)務手冊被塞進上下文中間位置時模型對它的關(guān)注度會顯著下降表現(xiàn)出來就是——規(guī)則明明寫在Prompt里模型卻像沒看見一樣自由發(fā)揮。第二個硬傷是更新成本。業(yè)務規(guī)則每個月都在變每次修改都是對Prompt的一次大手術(shù)改一行字可能引起系統(tǒng)其他部分行為異常而且無從排查。第三個硬傷最致命——你沒法測試。Prompt越長輸出越不穩(wěn)定同樣的輸入今天能答對明天就答錯回歸測試根本沒法做。2.2 推理與執(zhí)行混在一起必然互相污染我在重構(gòu)前好好想了想為什么大Prompt方案這么脆弱根子上是因為它把**推理該做什么和執(zhí)行怎么做**攪在了一起。舉個例子。知識庫助手的任務是查報銷規(guī)則并舉一反三給出注意事項。在舊架構(gòu)里模型要先理解這個任務屬于哪個業(yè)務域然后在八百行Prompt里定位相關(guān)規(guī)則再決定要不要調(diào)用接口拿最新數(shù)據(jù)最后還要組織語言答給用戶。這四層思考全都發(fā)生在一次推理里任何一個環(huán)節(jié)狀態(tài)不好整條鏈路就歪了。Agent Skills的思路是把這四層拆開第一步模型只負責判斷這問題歸哪個Skill管第二步Skill內(nèi)部預先定義好了該怎么辦。判斷是模型的活執(zhí)行是代碼的活兩者不再互相干擾。用大白話說大Prompt模式是讓AI當全才實習生——每個任務都要現(xiàn)場想一遍流程Skill模式是讓AI當調(diào)度員——它只負責把工單派給合適的老師傅。2.3 可觀測性與可維護性的鴻溝還有一個經(jīng)常被忽略的問題是排查故障的效率。大Prompt方案里用戶問一句報銷比例多少模型答錯了你根本不知道它是在哪一步想錯的是沒定位到規(guī)則是規(guī)則太長讀漏了還是定位對了但輸出時表達錯了這不是玄學這就是架構(gòu)缺陷——你拿不到中間過程的日志能看到的只有輸入和輸出。Skill架構(gòu)天然具備日志切面。模型判斷這個問題屬于Query_LeavePolicy然后Skill執(zhí)行體去數(shù)據(jù)庫撈數(shù)據(jù)——每一步都有明確的日志節(jié)點。用戶投訴一條答錯的消息我打開鏈路追蹤一眼就能定位是模型選錯了Skill還是Skill里的數(shù)據(jù)查詢出了問題。這個差異在調(diào)試期還不明顯等業(yè)務量上來之后就是天壤之別。3. 一個可用的Agent Skill到底長什么樣3.1 解剖四個核心組成部分很多人理解的Skill就是給模型多一個工具這個認知太淺了。一個真正可用的Skill要包含四個層次的內(nèi)容缺一個都容易在生產(chǎn)環(huán)境出問題。第一層是元數(shù)據(jù)Skill Definition。這層負責向模型描述這個Skill叫什么、管什么用、什么情況下該用它、不該用它。這是模型做意圖路由的最關(guān)鍵依據(jù)也是整個體系里最需要打磨的部分。我見過太多團隊把description寫得太籠統(tǒng)比如處理請假相關(guān)查詢模型根本分不清我明天想休息一天和幫我看看我的年假余額該走哪個Skill。第二層是輸入Schema。也就是這個Skill接收哪些參數(shù)每個參數(shù)的類型、格式、約束條件是什么。這一層決定了模型猜你的接口參數(shù)時有多容易出錯。我實測下來的經(jīng)驗是參數(shù)越少越好命名越貼合自然語言越好必填項之外的都盡量給默認值。第三層是執(zhí)行邏輯。這是Skill真正干活的代碼體——可能是調(diào)用內(nèi)部API可能是拼裝查詢語句可能是觸發(fā)下游工作流也可能只是做一次模板化回答。要注意的是執(zhí)行邏輯里頭還要包含輸入校驗和結(jié)果檢查不能假設(shè)模型給的參數(shù)永遠是合法的。第四層是失敗處理。這是最容易被人忽略、但生產(chǎn)環(huán)境最要命的環(huán)節(jié)。Skill調(diào)用了外部API結(jié)果服務掛了這時候是返回兜底回答、重試三次、還是把錯誤反饋給模型讓它換個思路沒有這層設(shè)計Skill體系會在真實流量下被沖垮。3.2 一個可直接參考的Skill定義示例以我項目里的查假期余額Skill為例它的定義是這樣寫的{ name: query_leave_balance, description: 查詢員工當前可用的年假、調(diào)休、病假余額。當用戶詢問我還有多少天假年假剩幾天能不能休兩天調(diào)休等涉及具體假期余額數(shù)值的問題時必須使用本技能。注意本技能只查余額不處理請假申請流程。, parameters: { type: object, properties: { employee_id: { type: string, description: 員工的工號可以從對話上下文中提取若無法確定則默認為當前會話用戶 }, leave_type: { type: string, enum: [annual, compensatory, sick, all], description: 要查詢的假期類型若用戶未指明則設(shè)為all } }, required: [employee_id] } }注意這個Skill里我寫了注意本技能只查余額不處理請假申請流程這不是廢話這是在模型路由時給它一把尺子——防止用戶問我要請假兩天時模型誤調(diào)用這個只讀類Skill。在模型能力不夠強的階段這種正向反向約束非常管用。代碼運行效果如何需要加入你自己的真實項目場景來驗證。在我實際系統(tǒng)中Skill執(zhí)行業(yè)務流程圖如下用戶提問 -- [意圖路由模型] -- 匹配到query_leave_balance -- [參數(shù)抽取] -- employee_id10023, leave_typeall -- [執(zhí)行體] -- 調(diào)用HR系統(tǒng)API -- 獲取結(jié)果 -- [模板化組裝] -- 您的年假余額為7天調(diào)休余額為2天 -- [失敗重試兜底] -- 若API超時則回復系統(tǒng)繁忙請稍后再試3.3 路由層怎么選型模型判斷還是規(guī)則優(yōu)先Skill的執(zhí)行邏輯寫好了接下來要解決的問題是模型怎么知道該調(diào)用哪個Skill這里有兩個主流方案——純模型路由和規(guī)則兜底模型路由。純模型路由最省事讓大模型讀一遍所有Skill的description然后輸出自己該調(diào)用哪個。缺點是模型會有概率性抽風Skills一多還會互相搶生意。我目前的線上方案是混合式先用非常輕量的規(guī)則層做前置過濾比如會話中檢測到余額剩幾天這類強關(guān)鍵詞直接把候選Skill范圍縮小到兩三個再把縮小后的候選列表交給模型裁決。這么做的實測效果是路由準確率從88%提升到96%而且規(guī)則層邏輯簡單出了問題容易調(diào)。4. 從零構(gòu)建一個技能庫五步落地實操4.1 第一步盤點高頻任務劃清邊界不要一上來就寫代碼先用一個周末的時間把業(yè)務方的歷史提問記錄翻一遍。我當時的做法是把三個月內(nèi)所有用戶問句拉出來按主題聚類統(tǒng)計頻率然后給每個主題寫一句話定義。這里有個關(guān)鍵經(jīng)驗Skill的粒度寧可粗一點也不要切太碎。我剛開始把查年假查調(diào)休查病假拆成了三個Skill結(jié)果模型頻繁把問題路由錯。后來合并成query_leave_balance一個Skill、內(nèi)部用參數(shù)區(qū)分假期類型準確率立刻上來了。Skill粒度怎么判斷我給你一個標準如果你發(fā)現(xiàn)自己要寫超過三句話來解釋這個Skill的邊界那它大概率太碎了反過來如果這個Skill里要寫一排switch case分五六種邏輯那它太胖了應該拆。一鏟子下去邊界清清楚楚的才是好Skill。4.2 第二步把description寫成給陌生人看的產(chǎn)品說明這回是Skill工程里最考驗功力的環(huán)節(jié)。很多技術(shù)出身的朋友容易把description寫成技術(shù)文檔風格——調(diào)用此接口可獲取員工假期余額數(shù)據(jù)但關(guān)鍵問題是模型不是人它要用這段描述來判斷意圖描述寫得好不好直接決定路由質(zhì)量。我總結(jié)了一個description寫作公式觸發(fā)場景 具體例句 反向排除。觸發(fā)場景是當用戶詢問XX時具體例句是例如我還有多少天假年假剩幾天——例句寧可多寫因為模型對具體句子的理解力遠比對抽象規(guī)則的理解力強。反向排除是注意本技能只查余額不處理XX情況這個能顯著降低模型把相似意圖誤判到這個Skill的概率。4.3 第三步參數(shù)Schema設(shè)計要克制參數(shù)設(shè)計的核心思想就兩個字克制。我踩過的坑是給Skill設(shè)計了七八個參數(shù)期望模型一次性把信息全抽出來。結(jié)果模型在抽取時頻繁出錯缺這個漏那個。后來我把參數(shù)縮減到只留關(guān)鍵的兩三個其余信息交給執(zhí)行體內(nèi)部從上下文或數(shù)據(jù)庫補全。比如查假期余額這個Skill其實只有一個必填參數(shù)——employee_id。leave_type給了默認值all。這樣的話模型就算沒聽清用戶要查哪種假也不會調(diào)用失敗。記住每個必填參數(shù)都是一次潛在的意圖誤判點能省則省。4.4 第四步執(zhí)行體要帶防呆設(shè)計執(zhí)行體是Skill真正干活的代碼很多人都寫得很直給——拿到參數(shù)直接請求API拿到結(jié)果直接返回。這在真實生產(chǎn)環(huán)境是大忌。我在重構(gòu)后給所有Skill的執(zhí)行體加了三個標準件第一個是輸入預校驗。模型抽出來的employee_id可能帶空格、可能是中文數(shù)字甚至可能跟用戶提的不是同一個人的工號。我在校驗層做了格式清洗、白名單校驗不合法就進失敗分支。第二個是結(jié)果結(jié)構(gòu)校驗。外部API返回的數(shù)據(jù)未必是預期的格式特別是HTTP 200但body里是個錯誤碼這種情況需要顯式判斷。第三個是降級分支。API超時、限流、異常都應該有對應的溫和回復不能直接把堆棧拋給用戶。這里用Python寫一個執(zhí)行體骨架你可以直接參考def execute(employee_id: str, leave_type: str) - Result: # 輸入預校驗 employee_id validate_employee_id(employee_id) if not employee_id: return Result( statusfailed, reply抱歉我沒有識別到您的工號請確認是否已綁定賬戶。 ) # 核心調(diào)用 res hr_api_client.get_leave_balance(employee_id, leave_type) # 結(jié)果結(jié)構(gòu)校驗 if res.code ! 200 or res.data is None: log.warning(fHR API返回異常: {res}) if res.code 504: return Result(statusfailed, reply系統(tǒng)查詢超時了請稍后重試) return Result(statusfailed, reply暫時無法獲取假期余額請稍后再試) # 模板化組裝 balance res.data reply build_reply_message(balance, leave_type) return Result(statusok, replyreply)加的每一層看起來都多余但生產(chǎn)環(huán)境的數(shù)據(jù)辣不辣眼睛只有上線后才知道。沒有這層防呆一個API偶發(fā)超時就能讓整個Agent體系被用戶罵上熱搜。4.5 第五步建立Skill回歸測試集Skill體系的優(yōu)勢之一就是可以做回歸測試。我建了一個意圖金標集從歷史真實用戶問句里挑出200條人工標注好每條應該路由到哪個Skill。每次改完一個Skill的描述或路由規(guī)則就跑一遍這批測試數(shù)據(jù)看看路由準確率是升了還是降了。沒有這套機制你就永遠不知道改完一個Skill是不是把另一個Skill搞掛了。這里有個實操細節(jié)金標集需要每隔一兩周補充新問句。用戶總會用你意想不到的話術(shù)提問把帶回來的新case加入測試集路由準確率才會持續(xù)提升。維護這個測試集是我認為整個Skill體系里性價比最高的一件事。5. 實測中的翻車現(xiàn)場與排查鏈路5.1 事故一兩個Skill怎么也分不清上線第一周就出現(xiàn)了一個詭異現(xiàn)象用戶問報銷打完款了嗎系統(tǒng)一會兒走查報銷進度Skill一會兒走查報銷規(guī)則Skill。我在日志里追蹤發(fā)現(xiàn)路由模型在兩個Skill的description之間反復橫跳相同的問題今天走A明天走B。排查鏈路是這樣的首先打開鏈路日志確認問題確實出在路由環(huán)節(jié)而不是執(zhí)行環(huán)節(jié)——兩個Skill都執(zhí)行成功了但選錯的那個答案牛頭不對馬嘴。然后我把兩個description拿出來并排看問題立刻暴露了——查報銷進度的description里寫的是處理報銷相關(guān)查詢而查報銷規(guī)則寫的是回答報銷標準相關(guān)問題。模型會認為報銷打完款了嗎既跟進度有關(guān)又跟規(guī)則沾邊于是兩邊都在候選區(qū)。修復方案很簡單把description改成互斥的強烈觸發(fā)詞。查報銷進度只回答錢到哪了、款有沒有打、審批走到哪一步了查報銷規(guī)則只回答能報多少、范圍是什么、流程怎么走。改完之后相同問題的路由準確率從82%升到了99%。教訓描述Skill時痛痛快快地把這個Skill的場景邊界鎖死不要用模糊的領(lǐng)域詞匯。5.2 事故二模型抽參數(shù)時過擬合到了錯誤語義有一次用戶問我想用調(diào)休抵半天你看看我這周還能不能請假——這個問句其實包含兩個訴求查調(diào)休余額、順便確認能不能請假。我的路由模型正確選到了query_leave_balance但是參數(shù)抽取環(huán)節(jié)把leave_type抽成了sick因為請假兩個字觸發(fā)了病假聯(lián)想。結(jié)果系統(tǒng)一本正經(jīng)地回答您的病假余額為5天用戶當場無語。這個問題的根因在于參數(shù)抽取的提示詞里給的枚舉含義不夠清晰。我給enum值寫了annual年假、compensatory調(diào)休、sick病假模型確實沒有理由把請假理解成調(diào)休。修復辦法是在description里把每個枚舉值的語義都寫了跨場景例句——當用戶提到調(diào)休換休時使用compensatory——效果立竿見影。參數(shù)枚舉命名的偏差是Agent體系里最容易忽略但實際頻繁翻車的地方。5.3 事故三Skill內(nèi)部的循環(huán)請求燒穿預算還有一個讓我后怕的事故。某個Skill的執(zhí)行體在調(diào)用外部搜索API時如果第一次結(jié)果為空代碼里寫了一個for循環(huán)自動換關(guān)鍵詞重試三次。結(jié)果某個高峰期搜索API響應變慢一批請求全部觸發(fā)了循環(huán)重試單次用戶請求最多發(fā)了12次外部API直接導致合同額度超支。排查鏈路從成本監(jiān)控告警開始拉出計費日志定位到一批search_skill的調(diào)用鏈看到每個鏈路里都掛了七八次API記錄。修復方案是給所有外部重試加上總次數(shù)上限、單次超時和熔斷開關(guān)同時在Skill執(zhí)行體入口加了一層高峰期限流。這里要提醒一句給執(zhí)行體寫重試邏輯之前一定先做一次極端情況推演——如果外部系統(tǒng)延遲百分之五十我的重試策略會把流量放大幾倍5.4 一套通用的排查方法鏈路日志 回放測試踩了這么多坑之后我沉淀了一套自己的排查流程分享一下先看路由日志確認模型選的是不是對的Skill。不是的話問題在意圖路由層。再看參數(shù)日志確認抽出來的參數(shù)合不合理。參數(shù)錯了問題在抽取層??磮?zhí)行日志確認執(zhí)行體內(nèi)部的每一步是否按預期走。用金標集回放把出錯的用戶問句喂給同一個系統(tǒng)確認修復是否生效、有沒有副作用。這四步走一遍大部分問題都能精準定位。如果你發(fā)現(xiàn)始終定位不到問題那就要回頭檢查是不是Skill的description寫得太模糊導致模型行為本身不穩(wěn)定——這種情況改了日志也查不出花來。6. 從能用到好用技能調(diào)優(yōu)與體系演進6.1 多輪對話里的狀態(tài)與記憶是Skill體系的隱藏難點單個Skill調(diào)好了還不夠多輪對話場景才是真正的分水嶺。用戶說幫我查一下張三年假接著又補了一句順便看看他的調(diào)休——這句話在系統(tǒng)里怎么處理最自然的設(shè)計是讓上一輪的Skill狀態(tài)延續(xù)下來把第二輪的兩個參數(shù)合并進第一輪的查詢上下文里。我的實現(xiàn)思路是引入一個輕量級的會話狀態(tài)槽位第一輪Skill執(zhí)行完后我把參數(shù)和結(jié)果摘要寫回會話槽位第二輪路由模型先看槽位里有啥再決定是新建Skill調(diào)用還是基于槽位信息增量執(zhí)行。說實話這個模塊目前還沒有標準答案各家都有自己的實現(xiàn)方式。我的建議是第一階段別追求通用記憶機制先把業(yè)務強相關(guān)的狀態(tài)槽位做好就夠了——比如剛才那種查詢類場景一個skill_result_slot就能覆蓋絕大多數(shù)需求。6.2 Skill版本管理與團隊協(xié)作當你的技能庫超過二十個Skill并且是三四個人一起維護時版本問題就浮出水面了。我們今天上午剛改完A Skill的路由描述下午發(fā)現(xiàn)B Skill的準確率掉了——這種狗血劇情我經(jīng)歷了不止一次。落地方案非常樸素但好用每個Skill的變更必須走Pull Request路由金標集測試必須過90%才能合并。Skill的description變更看似是個小改動但影響的可能是所有路由的決策分布。把改了Skill描述必須附帶金標集通過率寫進代碼評審清單能擋掉至少一半的踩坑。另外執(zhí)行體代碼和Skill描述文件一定要放在同一個倉庫同一次發(fā)布里分開管理會出現(xiàn)描述已經(jīng)寫好了代碼還沒部署的中間態(tài)觸發(fā)各種離奇錯誤。6.3 跨Agent的技能共享——下一步的擴展方向目前我正在嘗試把Skill抽象成團隊級的共享資產(chǎn)。思路是建一個內(nèi)部的技能注冊中心每個Skill定義成一個帶契約的聲明式文件任何Agent實例都可以通過注冊中心拉取。這樣新開一個Agent項目不用從零維護一套技能庫直接注冊需要的現(xiàn)有Skill即可更多時間去沉淀新的業(yè)務技能。這里做一個小預告聲明式Skill定義如果能標準化未來的Agent協(xié)作會變得更像樂高拼裝——不同團隊維護不同的技能模塊上層Agent通過編排把它們組合成完整工作流。到這個階段Agent不再是單個系統(tǒng)的附屬品而是一個可以跨系統(tǒng)復用的業(yè)務能力層。這中間的技術(shù)選型比如用JSON Schema還是YAML做契約、要不要引入語義注冊表還在探索中有進展了我再寫一篇繼續(xù)分享。最后再補一句個人體會我在這個項目里最大的收獲不是技術(shù)方案的升級而是重新理解了架構(gòu)的意義——把需要猜的部分和不需要猜的部分干凈利落地分開永遠好過在大Prompt里用更多的文本去掩蓋不確定性。Agent Skills的本質(zhì)就是這條原則的工程化落地。