戰(zhàn):從工具編排到標(biāo)準(zhǔn)化技能庫(kù)的Agent穩(wěn)定性指南)
這兩年只要聊到Agent落地幾乎繞不開(kāi)一個(gè)問(wèn)題模型能力確實(shí)在漲但Agent干活的穩(wěn)定性和復(fù)雜度上限卡在技能的組織方式上。工具注冊(cè)頁(yè)幾十個(gè)函數(shù)堆在那里Prompt里塞滿各種能力說(shuō)明結(jié)果模型還是經(jīng)常選錯(cuò)、漏用、甚至把兩個(gè)工具的調(diào)用順序搞反。我折騰過(guò)幾輪方案之后把思路收斂到了一個(gè)名字上agent-skills。簡(jiǎn)單說(shuō)就是不再把能力當(dāng)成零散工具扔給模型而是把每個(gè)能穩(wěn)定執(zhí)行的任務(wù)閉環(huán)定義成一個(gè)標(biāo)準(zhǔn)化的“技能”再交給Agent按需調(diào)度。這篇文章不是概念科普而是把我自己做技能庫(kù)設(shè)計(jì)、注冊(cè)、調(diào)度、排錯(cuò)的全過(guò)程拆開(kāi)講一遍。如果你正在做Agent功能開(kāi)發(fā)或者剛被多工具編排的穩(wěn)定性折磨到頭疼這篇內(nèi)容可以直接拿來(lái)當(dāng)參考骨架。從技能的定義標(biāo)準(zhǔn)到匹配打分公式再到一次真實(shí)任務(wù)的執(zhí)行日志我會(huì)盡量把每一步為什么這么做講清楚。1. 先想清楚Agent的“技能”到底是什么1.1 技能、工具、插件的邊界很多項(xiàng)目里工具、插件、技能這三個(gè)詞是混著用的。前端按鈕叫工具后端接口叫工具第三方服務(wù)接入叫插件然后模型函數(shù)調(diào)用也叫工具?;斓阶詈笥懻撜{(diào)度邏輯的時(shí)候根本說(shuō)不清在調(diào)度什么。我自己的做法是先做嚴(yán)格區(qū)分。工具是最小可復(fù)用操作單元。它可以是一次API調(diào)用、一段固定算法、一個(gè)數(shù)據(jù)庫(kù)查詢。特征是輸入輸出明確不需要模型做復(fù)雜推理就能執(zhí)行。插件本質(zhì)上是適配外部系統(tǒng)能力的代理把外部系統(tǒng)暴露能力包裝成工具。技能則是面向一個(gè)具體業(yè)務(wù)目標(biāo)編排出來(lái)的能力閉環(huán)它內(nèi)部可以調(diào)用多個(gè)工具也可以依賴其他技能并且包含校驗(yàn)、回退、狀態(tài)處理。舉個(gè)例子發(fā)送HTTP請(qǐng)求是工具接入某個(gè)數(shù)據(jù)平臺(tái)是插件而“生成一份某項(xiàng)目的周報(bào)”就是技能。周報(bào)技能內(nèi)部可能拉取項(xiàng)目進(jìn)展數(shù)據(jù)調(diào)用模板渲染檢查數(shù)據(jù)完整性最后產(chǎn)出Markdown。如果只把“拉取數(shù)據(jù)”“渲染模板”作為兩個(gè)工具暴露給模型模型很大概率會(huì)在拉數(shù)據(jù)之前就開(kāi)始寫結(jié)論或者漏掉某個(gè)項(xiàng)目模塊。把整個(gè)過(guò)程封裝成技能以后模型只需要決定一件事當(dāng)前請(qǐng)求該不該調(diào)用“生成周報(bào)”技能。我見(jiàn)過(guò)一個(gè)項(xiàng)目最初用了22個(gè)底層工具模型的工具命中率只有六成左右。后來(lái)把22個(gè)工具重新編排成了7個(gè)技能命中率提高到九成以上而且調(diào)用路徑也穩(wěn)定了。核心原因不是模型變強(qiáng)了而是決策維度從二十二選一變成了七選一判斷難度大幅下降。1.2 為什么需要一個(gè)獨(dú)立的技能層有人會(huì)問(wèn)能力都封成技能是不是就把模型的靈活性限制住了這個(gè)擔(dān)心有一定道理但實(shí)際中遇到的更多問(wèn)題是反向的靈活性太高導(dǎo)致行為不可控。沒(méi)有技能層時(shí)系統(tǒng)一般長(zhǎng)這樣一個(gè)超大System Prompt里面把所有能做的事、所有工具說(shuō)明、所有規(guī)則全部鋪給模型。模型每次請(qǐng)求都重新理解一遍導(dǎo)致幾類問(wèn)題反復(fù)出現(xiàn)。第一Prompt過(guò)長(zhǎng)關(guān)鍵信息被稀釋模型對(duì)低頻工具的感知會(huì)退化。第二工具間沒(méi)有約束關(guān)系模型可能調(diào)用一個(gè)應(yīng)該先預(yù)處理數(shù)據(jù)的工具。第三完全沒(méi)有評(píng)估單元你只知道模型返回了文本不知道它有沒(méi)有真的完成“收集數(shù)據(jù)、分析趨勢(shì)、生成結(jié)論”這一整套動(dòng)作。第四無(wú)法復(fù)用換一個(gè)業(yè)務(wù)場(chǎng)景Prompt里的工具段落就得重新寫。技能層的核心作用是把不穩(wěn)定的大模型決策拆成兩層。上層是意圖路由模型只需要判斷“用戶想要什么”。下層是技能執(zhí)行對(duì)應(yīng)一系列定義好的確定性邏輯。這樣既保留了大模型的語(yǔ)義理解能力又把關(guān)鍵動(dòng)作固定成可控流程。你可以把它理解成給一個(gè)比較有想法的新人配了一批標(biāo)準(zhǔn)作業(yè)程序新人負(fù)責(zé)判斷接哪個(gè)活具體每個(gè)活怎么做按程序來(lái)。同時(shí)技能層還帶來(lái)一個(gè)隱藏價(jià)值可測(cè)試性。普通工具函數(shù)可以寫單測(cè)但Agent行為很難寫單測(cè)。技能定義一旦完成就能對(duì)執(zhí)行結(jié)果做結(jié)構(gòu)化校驗(yàn)。輸入Schema、輸出Schema、依賴列表、回退策略全部是機(jī)器可讀的這為后面的自動(dòng)評(píng)估和質(zhì)量追蹤提供了基礎(chǔ)。2. 搭建技能庫(kù)從定義到落地的完整方案2.1 技能標(biāo)準(zhǔn)化定義技能標(biāo)準(zhǔn)化聽(tīng)起來(lái)像是一個(gè)偏文檔的工作實(shí)際上它直接決定調(diào)度系統(tǒng)的檢索和匹配效果。技能定義不是給人看的手冊(cè)是給模型和路由模塊共同消費(fèi)的數(shù)據(jù)。我最終收斂出的最小信息結(jié)構(gòu)包含四層身份層、語(yǔ)義層、協(xié)議層、執(zhí)行層。身份層很簡(jiǎn)單技能ID、版本、所屬領(lǐng)域、優(yōu)先級(jí)。這是為了管理用的后續(xù)的統(tǒng)計(jì)、灰度、回滾都靠它。語(yǔ)義層是整個(gè)定義里最容易被忽視但最關(guān)鍵的部分。它包含技能名稱、描述、觸發(fā)條件、適用場(chǎng)景、反例。描述不能寫得像功能說(shuō)明書(shū)要寫成“意圖說(shuō)明書(shū)”。比如描述一個(gè)日?qǐng)?bào)生成技能差的寫法是“生成日?qǐng)?bào)”好的寫法是“當(dāng)用戶需要匯總當(dāng)日工作內(nèi)容、產(chǎn)出結(jié)果和明日的計(jì)劃時(shí)生成結(jié)構(gòu)化日?qǐng)?bào)支持按項(xiàng)目、按時(shí)間段過(guò)濾”。這里的關(guān)鍵是回答兩個(gè)問(wèn)題這個(gè)技能解決什么意圖什么情況下不應(yīng)該調(diào)用它。協(xié)議層用JSON Schema描述輸入和輸出。輸入字段必須有類型、必填性、約束條件輸出字段必須定義成功和失敗形態(tài)。這不僅是給模型看的也是執(zhí)行引擎用來(lái)做校驗(yàn)的。協(xié)議定義得越嚴(yán)格下游做緩存、審計(jì)、重試就越簡(jiǎn)單。執(zhí)行層是技能真正干活的部分。它定義技能內(nèi)部如何調(diào)用工具、依賴哪些其他技能、是否允許并行、失敗時(shí)怎么做。執(zhí)行層可以是代碼、工作流描述文件也可以是給模型的一段指令。我給一個(gè)實(shí)際的YAML示例這是我在項(xiàng)目里用到的技能定義結(jié)構(gòu)id: weekly_report name: 周報(bào)生成 version: 1.2.0 domain: report priority: high description: 當(dāng)用戶需要按周匯總項(xiàng)目進(jìn)展、分析風(fēng)險(xiǎn)并生成匯報(bào)文檔時(shí)使用。 適用于周維度的工作回顧不適用于日?qǐng)?bào)或?qū)崟r(shí)狀態(tài)查詢。 triggers: - 用戶提到本周工作匯總 - 需要周報(bào)格式的報(bào)告 - 項(xiàng)目周例會(huì)材料準(zhǔn)備 exclude: - 用戶只要日?qǐng)?bào) - 用戶只想查某個(gè)單條數(shù)據(jù) input_schema: type: object properties: project_names: type: array items: type: string description: 需要匯總的項(xiàng)目列表默認(rèn)全部活躍項(xiàng)目 week_offset: type: integer default: 0 description: 0表示本周-1表示上周 required: - project_names output_schema: type: object properties: report_content: type: string description: 生成的Markdown格式周報(bào) risk_items: type: array items: type: string generated_at: type: string format: date-time required: - report_content execute: engine: workflow steps: - collect_data: tool: project_data_sync params: projects: $input.project_names - analyze_risk: tool: risk_detector params: data: $steps.collect_data.result - render_report: tool: markdown_renderer params: analysis: $steps.analyze_risk.result fallback: - collect_data 失敗時(shí)嘗試使用 cache_reader 技能 - analyze_risk 失敗時(shí)跳過(guò)風(fēng)險(xiǎn)分析并標(biāo)記 warning這個(gè)結(jié)構(gòu)用下來(lái)還算干凈。每個(gè)字段都指向一個(gè)明確用途沒(méi)有冗余。2.2 技能注冊(cè)與發(fā)現(xiàn)機(jī)制定義好技能之后要解決的就是注冊(cè)和發(fā)現(xiàn)。我在一開(kāi)始犯過(guò)一個(gè)錯(cuò)誤把技能放在一個(gè)大目錄里每次調(diào)度都去全量掃描。技能少的時(shí)候問(wèn)題不大但是技能數(shù)量上了50個(gè)之后全量匹配的性能和準(zhǔn)確性都開(kāi)始出問(wèn)題。后來(lái)我把注冊(cè)和發(fā)現(xiàn)拆成了兩條鏈路。第一條是元數(shù)據(jù)注冊(cè)中心負(fù)責(zé)保存技能定義、版本、狀態(tài)、健康記錄。技能上線之前要在這里做Schema校驗(yàn)、依賴校驗(yàn)、試運(yùn)行。第二條是檢索鏈路負(fù)責(zé)在調(diào)度時(shí)快速找到候選技能。檢索鏈路會(huì)為每個(gè)技能建立語(yǔ)義索引、關(guān)鍵詞索引和標(biāo)簽索引。語(yǔ)義索引用模型為技能描述生成向量匹配時(shí)把用戶請(qǐng)求也向量化用余弦相似度做初篩。關(guān)鍵詞索引覆蓋技能名稱、觸發(fā)詞、領(lǐng)域標(biāo)簽用來(lái)做精確召回。標(biāo)簽索引是人工維護(hù)的粗粒度分類比如“報(bào)告生成”“數(shù)據(jù)查詢”“任務(wù)調(diào)度”用來(lái)做領(lǐng)域過(guò)濾和權(quán)限控制。注冊(cè)中心提供統(tǒng)一的上報(bào)入口技能開(kāi)發(fā)完成后調(diào)用注冊(cè)接口即可完成上線。我在項(xiàng)目里用的是這樣一個(gè)簡(jiǎn)化接口內(nèi)部會(huì)完成健康檢查和索引刷新POST /skills/registry/register Content-Type: application/json { skill_file: skill_definition.yaml, source: team_a, environment: prod }注冊(cè)成功后系統(tǒng)會(huì)給技能分配一個(gè)全局唯一ID同時(shí)觸發(fā)索引構(gòu)建。索引構(gòu)建完成之前技能不會(huì)參與調(diào)度這樣避免上線半截技能被模型選到。圍繞發(fā)現(xiàn)機(jī)制我還有一個(gè)經(jīng)驗(yàn)不要只依賴語(yǔ)義相似度做最終決策。語(yǔ)義相似度適合召回和排序但不適合做硬性過(guò)濾。原因是向量空間里的相近不代表業(yè)務(wù)意圖相同描述里有“日?qǐng)?bào)”和“周報(bào)”兩個(gè)詞向量可能非常接近但業(yè)務(wù)上絕對(duì)不能混淆。所以最終調(diào)度要結(jié)合規(guī)則約束。3. 技能調(diào)度的核心邏輯如何讓Agent選對(duì)技能3.1 技能匹配與路由技能庫(kù)建好之后調(diào)度器就是大腦。調(diào)度器的任務(wù)是把用戶請(qǐng)求映射到正確的技能并在多個(gè)技能都匹配時(shí)做取舍。我的調(diào)度流程分四步。第一步請(qǐng)求解析把用戶原始輸入抽成結(jié)構(gòu)化意圖提取關(guān)鍵實(shí)體。第二步候選召回從索引里拉出得分最高的前N個(gè)技能。第三步打分排序綜合語(yǔ)義相似度、上下文匹配度、歷史成功率、成本代價(jià)計(jì)算綜合分。第四步閾值決策超過(guò)閾值的技能進(jìn)入執(zhí)行隊(duì)列沒(méi)有超過(guò)則走兜底策略。打分公式我調(diào)過(guò)好幾版當(dāng)前用的是這個(gè)score 0.45 * semantic_sim 0.20 * context_match 0.25 * historical_success 0.10 * cost_factorsemantic_sim是用戶請(qǐng)求與技能描述的語(yǔ)義相似度范圍0到1。context_match表示當(dāng)前對(duì)話上下文與技能觸發(fā)條件的匹配程度觸發(fā)條件里有用戶剛提到的關(guān)鍵詞就給高分。historical_success是過(guò)去30天該技能的成功率用來(lái)抑制那些總執(zhí)行失敗但表面相似的技能。cost_factor則綜合計(jì)算調(diào)用該技能的預(yù)計(jì)Token消耗和延遲成本高的技能會(huì)被輕微降權(quán)。閾值我設(shè)的是0.65。低于0.65但高于0.4的請(qǐng)求進(jìn)入“模糊候選區(qū)”系統(tǒng)會(huì)讓模型二次確認(rèn)意圖或者把請(qǐng)求拆解后重新匹配。這個(gè)設(shè)計(jì)比直接拒絕體驗(yàn)好很多因?yàn)楹芏嗾?qǐng)求本身就是復(fù)合意圖拆解后反而能命中多個(gè)技能。有一點(diǎn)要注意技能打分不能只看當(dāng)前請(qǐng)求還要考慮會(huì)話歷史。用戶可能在前面幾輪里已經(jīng)提到了項(xiàng)目范圍后面只說(shuō)“那生成周報(bào)吧”這時(shí)候單純看當(dāng)前這句話語(yǔ)義匹配分不會(huì)很高。我處理的辦法是維護(hù)一個(gè)會(huì)話上下文向量每輪對(duì)話結(jié)束后把抽取到的實(shí)體和意圖合并進(jìn)上下文再參與匹配打分。3.2 技能組合與編排很多任務(wù)不是單技能能搞定的。比如“把最近一周的線上事故梳理成報(bào)告并發(fā)給負(fù)責(zé)人”這里面至少包含“查詢事故記錄”“生成報(bào)告”“發(fā)送消息”三個(gè)技能。調(diào)度器需要支持技能組合。我把組合模式分成四類順序執(zhí)行、條件執(zhí)行、并行執(zhí)行、循環(huán)執(zhí)行。順序執(zhí)行最簡(jiǎn)單前一個(gè)技能的輸出作為后一個(gè)技能的輸入。條件執(zhí)行是看前一個(gè)技能的執(zhí)行結(jié)果決定分支走向。并行執(zhí)行要求技能之間沒(méi)有數(shù)據(jù)依賴且累計(jì)成本可控。循環(huán)執(zhí)行通常用于批處理場(chǎng)景比如遍歷多個(gè)項(xiàng)目逐個(gè)生成周報(bào)。在實(shí)現(xiàn)層面技能定義里的execute字段會(huì)聲明依賴關(guān)系和數(shù)據(jù)類型。調(diào)度器執(zhí)行前會(huì)先做依賴拓?fù)渑判虼_保每個(gè)技能的輸入都已經(jīng)被上游技能準(zhǔn)備好。如果出現(xiàn)循環(huán)依賴注冊(cè)中心直接拒絕上線。組合執(zhí)行時(shí)最頭疼的是失敗處理。我見(jiàn)過(guò)太多工作流一條鏈路中間斷了整個(gè)任務(wù)就失敗了。實(shí)際上很多中間步驟是可以降級(jí)處理的。比如剛才說(shuō)的“事故報(bào)告并發(fā)送”如果“發(fā)送消息”技能失敗報(bào)告本身已經(jīng)生成了完全可以降級(jí)成返回報(bào)告內(nèi)容提示用戶手動(dòng)發(fā)送。這就是我在技能定義里增加fallback字段的原因。編排層面的另一個(gè)問(wèn)題是Token消耗控制。技能一組合上下文容易變得很長(zhǎng)。我不想為了讓模型理解流程就把所有中間結(jié)果全部塞進(jìn)模型上下文。我的做法是執(zhí)行引擎自己做數(shù)據(jù)流轉(zhuǎn)只有最終呈現(xiàn)給用戶的輸出才進(jìn)入模型上下文中間數(shù)據(jù)放在執(zhí)行狀態(tài)的內(nèi)部存儲(chǔ)里。這一步能把單次復(fù)雜任務(wù)的Token消耗降下來(lái)不少。4. 實(shí)戰(zhàn)用 agent-skills 跑通一個(gè)完整任務(wù)4.1 環(huán)境準(zhǔn)備與初始化前面講的都是框架這節(jié)把實(shí)際操作過(guò)一次的完整流程拉出來(lái)。假設(shè)我要跑一個(gè)“站點(diǎn)健康巡檢”的任務(wù)核心是讓Agent每天自動(dòng)檢查幾個(gè)數(shù)據(jù)源的健康狀態(tài)并生成一份巡檢報(bào)告。環(huán)境準(zhǔn)備需要幾樣?xùn)|西一個(gè)調(diào)度服務(wù)運(yùn)行環(huán)境我用的是Python項(xiàng)目需要裝一些基礎(chǔ)依賴。一個(gè)向量索引服務(wù)用來(lái)做技能描述檢索。還有一個(gè)KV存儲(chǔ)用來(lái)保存會(huì)話上下文和技能執(zhí)行狀態(tài)。我給一個(gè)安裝依賴的參考命令pip install skills-sdk requests pydantic openai初始化目錄如下技能定義、執(zhí)行腳本、日志分開(kāi)放后面排查問(wèn)題會(huì)輕松很多。agent-skills-project/ skills/ health_check.yaml report_builder.yaml alert_dispatcher.yaml executors/ health_check_runner.py report_builder_runner.py alert_runner.py registry/ local_registry.json logs/ execution.log初始化的最后一步是啟動(dòng)索引同步讓注冊(cè)中心感知到技能目錄下的所有YAML文件。啟動(dòng)后會(huì)看到一行日志輸出顯示同步了3個(gè)技能定義并生成對(duì)應(yīng)向量索引。4.2 技能編寫與注冊(cè)先寫“站點(diǎn)健康巡檢”這個(gè)技能。這個(gè)技能做的事情是接收站點(diǎn)列表逐個(gè)發(fā)起探測(cè)請(qǐng)求記錄返回碼和響應(yīng)時(shí)間然后把結(jié)果整理成表格。技能定義如下id: site_health_check name: 站點(diǎn)健康巡檢 version: 1.0.0 domain: ops priority: high description: 當(dāng)用戶需要檢查多個(gè)站點(diǎn)的可用性、響應(yīng)速度和錯(cuò)誤狀態(tài)時(shí)使用。 適用于主動(dòng)巡檢和定期任務(wù)不適用于分析歷史監(jiān)控?cái)?shù)據(jù)。 triggers: - 巡檢 - 健康檢查 - 站點(diǎn)探測(cè) exclude: - 用戶要分析監(jiān)控趨勢(shì) input_schema: type: object properties: urls: type: array items: type: string format: uri description: 待檢查的站點(diǎn)地址 timeout: type: integer default: 5 description: 單站探測(cè)超時(shí)秒數(shù) required: - urls output_schema: type: object properties: results: type: array items: type: object properties: url: type: string status: type: integer response_ms: type: integer healthy: type: boolean summary: type: string required: - results - summary execute: engine: code ref: executors/health_check_runner.py fallback: - 單站點(diǎn)超時(shí)后跳過(guò)并標(biāo)記異常 - 全部站點(diǎn)異常時(shí)調(diào)用 alert_dispatcher 技能對(duì)應(yīng)的執(zhí)行腳本核心邏輯很簡(jiǎn)潔import asyncio import aiohttp async def run(input_data): urls input_data[urls] timeout input_data.get(timeout, 5) results [] async with aiohttp.ClientSession() as session: for url in urls: try: async with session.get(url, timeouttimeout) as resp: results.append({ url: url, status: resp.status, response_ms: round(resp.elapsed.total_seconds() * 1000, 2), healthy: resp.status 400, }) except Exception as exc: results.append({ url: url, status: 0, response_ms: None, healthy: False, }) healthy_count sum(1 for r in results if r[healthy]) summary f共檢查 {len(results)} 個(gè)站點(diǎn)健康 {healthy_count} 個(gè)異常 {len(results) - healthy_count} 個(gè) return {results: results, summary: summary}寫完定義和腳本之后調(diào)用注冊(cè)接口把技能推上去curl -X POST http://localhost:8000/skills/registry/register \ -H Content-Type: application/json \ -d {skill_file: skills/health_check.yaml, source: ops_team, environment: prod}注冊(cè)成功后會(huì)收到一條包含技能ID和索引狀態(tài)的響應(yīng)我看到的是229號(hào)技能上線索引構(gòu)建耗時(shí)大概1.2秒。這時(shí)候技能庫(kù)就準(zhǔn)備好接受調(diào)度了。4.3 任務(wù)執(zhí)行過(guò)程實(shí)錄接下來(lái)模擬一個(gè)真實(shí)請(qǐng)求。用戶發(fā)來(lái)一句話“幫我把首頁(yè)、訂單服務(wù)和支付網(wǎng)關(guān)都探測(cè)一下看看現(xiàn)在正常不?!闭{(diào)度器會(huì)先做意圖解析把這句話里的實(shí)體提取出來(lái)站點(diǎn)列表是首頁(yè)、訂單服務(wù)、支付網(wǎng)關(guān)意圖是站點(diǎn)健康巡檢。然后去技能索引里召回候選排在最前面的就是“站點(diǎn)健康巡檢”技能得分0.87超過(guò)閾值。執(zhí)行引擎接收技能定義里的輸入Schema自動(dòng)構(gòu)造如下輸入{ urls: [ https://home.internal.example, https://order.internal.example, https://pay.internal.example ], timeout: 5 }隨后引擎調(diào)用執(zhí)行腳本得到結(jié)果{ results: [ { url: https://home.internal.example, status: 200, response_ms: 120.4, healthy: true }, { url: https://order.internal.example, status: 503, response_ms: 800.1, healthy: false }, { url: https://pay.internal.example, status: 200, response_ms: 215.9, healthy: true } ], summary: 共檢查 3 個(gè)站點(diǎn)健康 2 個(gè)異常 1 個(gè) }按照f(shuō)allback定義異常數(shù)大于0會(huì)額外觸發(fā)“告警分發(fā)”技能把訂單異常的信息推送到告警渠道。調(diào)度日志里能看到兩個(gè)技能先后執(zhí)行全部成功。這個(gè)流程看起來(lái)很順暢但我是踩了不少坑才跑通的。第一次跑的時(shí)候技能描述里的觸發(fā)詞寫得太具體只寫了“健康檢查”“巡檢”用戶換成“看看網(wǎng)站通不通”就匹配不到得分只有0.3直接走了兜底流程。后來(lái)我把描述改成了意圖導(dǎo)向加上了“可用性”“響應(yīng)速度”“正常不”這些口語(yǔ)化表達(dá)匹配分才上來(lái)。這個(gè)調(diào)整給我一個(gè)很深的印象技能描述是給語(yǔ)義模型看的要覆蓋用戶說(shuō)話的正常變化而不是只寫內(nèi)部術(shù)語(yǔ)。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 技能命中率低匹配不到正確技能這是最常見(jiàn)的現(xiàn)象。表現(xiàn)是用戶請(qǐng)求明明很明確但調(diào)度器給出的最終得分很低技能沒(méi)有被觸發(fā)或者觸發(fā)了錯(cuò)誤的技能。我一般按三個(gè)順序排查。第一個(gè)檢查點(diǎn)是技能描述。描述里如果用的全是抽象術(shù)語(yǔ)模型其實(shí)很難和用戶口語(yǔ)建立關(guān)聯(lián)。比如“獲取業(yè)務(wù)對(duì)象的關(guān)聯(lián)數(shù)據(jù)”這種描述在詞面上就和“我想看看這個(gè)訂單關(guān)聯(lián)的客戶信息”重疊度不高。解決辦法是重寫描述加入用戶視角的常見(jiàn)說(shuō)法。第二個(gè)檢查點(diǎn)是召回索引。索引沒(méi)刷新會(huì)直接導(dǎo)致新技能和最近調(diào)整過(guò)描述的技能無(wú)法被召回。排查方法是先直接查索引里有沒(méi)有該技能的向量再看調(diào)度日志里召回階段返回的候選列表。第三個(gè)檢查點(diǎn)是閾值。閾值設(shè)得太高會(huì)把正常匹配殺掉。我會(huì)用一段時(shí)間的調(diào)度數(shù)據(jù)來(lái)校正閾值統(tǒng)計(jì)觸發(fā)請(qǐng)求得分分布找一個(gè)誤殺率和錯(cuò)誤觸發(fā)率平衡的點(diǎn)。當(dāng)前我的經(jīng)驗(yàn)值是0.6到0.7之間比較穩(wěn)。還有一個(gè)很隱蔽的問(wèn)題上下文匹配分權(quán)重過(guò)高導(dǎo)致帶上下文偏置的錯(cuò)誤命中。比如用戶前面聊了幾輪“支付對(duì)賬”突然問(wèn)“有哪些工具可以用”調(diào)度器可能因?yàn)樯舷挛睦锓磸?fù)出現(xiàn)支付而把“支付工具列表”技能排到前面。這個(gè)場(chǎng)景需要給上下文匹配加時(shí)間衰減只參考最近幾輪避免長(zhǎng)期話題影響當(dāng)下路由。5.2 技能沖突與依賴管理技能多了以后描述相近的情況會(huì)明顯增加?!吧芍軋?bào)”和“生成項(xiàng)目周報(bào)”在語(yǔ)義上高度重合調(diào)度器無(wú)法精確區(qū)分時(shí)兩個(gè)都會(huì)被召回但靠前的可能不是用戶真正想要的。我的處理思路分三步。第一步盡量合并同類技能。如果兩個(gè)技能面向的核心意圖一致只是參數(shù)不同就合成一個(gè)通過(guò)輸入Schema和觸發(fā)條件細(xì)化來(lái)區(qū)分分支。第二步確實(shí)需要保留兩個(gè)技能的要在描述里寫出明確的排除條件讓語(yǔ)義模型能區(qū)分“日?qǐng)?bào)場(chǎng)景”和“周報(bào)場(chǎng)景”。第三步在調(diào)度規(guī)則里維護(hù)一份人工的沖突消解表當(dāng)兩個(gè)技能得分都超過(guò)閾值且差距小于0.05時(shí)優(yōu)先選擇優(yōu)先級(jí)高的那個(gè)。依賴管理的問(wèn)題主要集中在循環(huán)依賴和幽靈依賴上。循環(huán)依賴是好查的因?yàn)橥負(fù)渑判驎?huì)直接報(bào)錯(cuò)。幽靈依賴更隱蔽技能B實(shí)際依賴技能A的輸出但定義文件里沒(méi)有聲明結(jié)果調(diào)度器單獨(dú)執(zhí)行技能B時(shí)數(shù)據(jù)為空。后來(lái)我強(qiáng)制要求在技能定義里寫明上游依賴再在注冊(cè)時(shí)掃描執(zhí)行腳本里的數(shù)據(jù)引用用自動(dòng)化檢查替代人工記憶。5.3 性能與資源控制技能庫(kù)大了以后性能問(wèn)題就來(lái)了。調(diào)度環(huán)節(jié)最明顯的瓶頸是語(yǔ)義檢索每個(gè)請(qǐng)求都要把請(qǐng)求文本向量化再和幾百個(gè)技能向量做相似度計(jì)算。優(yōu)化方向有兩個(gè)一是給向量索引建ANN索引把檢索從全量計(jì)算變成近似最近鄰但前提是技能數(shù)量確實(shí)大到全量已經(jīng)撐不住了二是在語(yǔ)義檢索之前先用關(guān)鍵詞索引做初篩把候選集合縮到幾十個(gè)以內(nèi)再做精確匹配。執(zhí)行環(huán)節(jié)的性能問(wèn)題通常是上游工具響應(yīng)慢導(dǎo)致整個(gè)技能鏈路被拖住。我在前面介紹過(guò)給每個(gè)工具調(diào)用設(shè)置超時(shí)合理的超時(shí)值是上游P95響應(yīng)時(shí)間加一點(diǎn)緩沖而不是隨便設(shè)一個(gè)固定值。如果上游接口P95已經(jīng)到3秒超時(shí)設(shè)2秒就會(huì)頻繁觸發(fā)降級(jí)。資源控制還有一個(gè)容易被忽略的地方技能內(nèi)部如果維護(hù)了大量中間態(tài)的臨時(shí)數(shù)據(jù)比如大文件或者長(zhǎng)文本緩存鏈路結(jié)束后必須顯式清理。我給執(zhí)行引擎加了執(zhí)行上下文池每次任務(wù)結(jié)束后統(tǒng)一回收中間數(shù)據(jù)否則跑一整天任務(wù)之后內(nèi)存會(huì)緩慢攀升直到觸發(fā)告警。我把這輪調(diào)試中遇到較多的問(wèn)題整理成一張速查表供參考現(xiàn)象常見(jiàn)原因排查動(dòng)作技能始終無(wú)法命中描述與用戶意圖語(yǔ)義跨度大重寫描述覆蓋口語(yǔ)表達(dá)新技能不參與調(diào)度索引未同步觸發(fā)索引重建檢查注冊(cè)狀態(tài)命中兩個(gè)相似技能描述邊界不清補(bǔ)充exclude條件設(shè)置優(yōu)先級(jí)技能鏈路中間失敗依賴聲明缺失檢查依賴列表補(bǔ)全上游依賴執(zhí)行結(jié)果一直重試上游響應(yīng)超時(shí)調(diào)大超時(shí)或增加緩存降級(jí)內(nèi)存緩慢上漲中間態(tài)數(shù)據(jù)未清理增加執(zhí)行上下文回收機(jī)制6. 一套沉淀下來(lái)的技能設(shè)計(jì)原則技能庫(kù)從混亂到穩(wěn)定這個(gè)過(guò)程持續(xù)了不短的時(shí)間最后沉淀出幾條我在新項(xiàng)目里會(huì)直接照做的原則。第一條單一職責(zé)。一個(gè)技能只完成一項(xiàng)業(yè)務(wù)目標(biāo)不要試圖把“查數(shù)據(jù)寫報(bào)告發(fā)消息”塞進(jìn)一個(gè)技能里。組合是調(diào)度器的事不是技能定義的事。集中在單個(gè)技能里的邏輯越復(fù)雜可測(cè)試性和可復(fù)用性就越差。第二條描述面向用戶意圖而不是面向?qū)崿F(xiàn)。技能描述里應(yīng)該回答“用戶什么時(shí)候需要我”而不是“我能做什么”。每次寫完描述我都會(huì)拿幾類真實(shí)的用戶請(qǐng)求去試召回效果看那些換了個(gè)說(shuō)法的請(qǐng)求能不能被召回。第三條顯式聲明依賴。技能之間的數(shù)據(jù)依賴、順序依賴、沖突關(guān)系都要寫進(jìn)定義不要讓調(diào)度器猜。任何隱藏的依賴都會(huì)在未來(lái)的某次調(diào)整中變成事故。第四條給每個(gè)技能設(shè)計(jì)降級(jí)路徑。所有技能都必須定義fallback動(dòng)作哪怕只是返回錯(cuò)誤信息和提示語(yǔ)。沒(méi)有降級(jí)路徑的技能在網(wǎng)絡(luò)抖動(dòng)、服務(wù)異動(dòng)的時(shí)候會(huì)成為整個(gè)Agent的瓶頸?,F(xiàn)在再看agent-skills這個(gè)項(xiàng)目我覺(jué)得它的本質(zhì)不是技術(shù)框架的搭建而是把“模型能力”和“業(yè)務(wù)穩(wěn)定性”之間的邊界劃清楚了。模型做語(yǔ)義理解技能做行為約束兩者配合才能讓Agent既靈活又可靠。如果你也在重構(gòu)自己的Agent體系可以從定義第一批技能開(kāi)始不要急著堆工具先把你最常用的幾個(gè)任務(wù)閉環(huán)寫清楚再逐步擴(kuò)展。這個(gè)方向走通了以后后面接更多場(chǎng)景就不會(huì)慌。