:Function Calling與MCP的Schema設計與容錯策略)
1. 端側 Agent 工程化的核心命題1.1 從 Demo 到產品端側 Agent 的工程化鴻溝很多人在端側跑通第一個 Agent Demo 的時候都會有一種錯覺這東西已經成了。本地模型加載起來Function Calling 調通一兩個工具命令行里問一句“幫我查一下明天天氣”模型返回一個結構化的 JSON工具執(zhí)行完把結果塞回去模型再吐出一段自然語言——整個鏈路跑通了感覺離產品就差一個 UI。但真正把端側 Agent 往產品方向推的時候問題會成倍地冒出來。模型在 PC 上跑得好好的到了手機端內存直接爆掉Function Calling 在單輪對話里沒問題一旦進入多輪、多工具、帶狀態(tài)的場景模型開始胡編參數(shù)JSON Schema 寫得稍微復雜一點小模型的輸出就開始不穩(wěn)定該填字符串的地方給你填了個對象該用枚舉的地方給你編了一個不存在的值。這些問題在 Demo 階段都可以靠“換個 prompt”糊過去但在工程化階段每一個都是必須系統(tǒng)性解決的硬骨頭。端側 Agent 工程化要解決的核心矛盾其實就一句話在算力、內存、功耗都受限的端側環(huán)境里讓一個能力有限的小模型穩(wěn)定地完成復雜的工具調用任務。這個矛盾決定了端側 Agent 的工程化思路和云端 Agent 有本質區(qū)別。云端 Agent 可以堆模型參數(shù)、堆上下文長度、堆并發(fā)端側不行端側每一兆內存、每一次推理延遲都要精打細算。我自己的體會是端側 Agent 的工程化難點集中在三個層面協(xié)議層Function Calling 的格式定義與約束、調度層多工具、多輪次的狀態(tài)管理與編排、運行時層模型加載、內存管理、推理加速。這三個層面里協(xié)議層是地基調度層是骨架運行時層是血肉。這一篇主要聊協(xié)議層和調度層也就是 Function Calling 和 MCP 相關的工程化實踐運行時層的內容留到下一篇展開。1.2 為什么 Function Calling 是端側 Agent 的命門Function Calling 這個概念本身不復雜就是讓模型輸出一段結構化的內容告訴外部系統(tǒng)“我要調用哪個函數(shù)、傳什么參數(shù)”。但在端側環(huán)境里這件事的難度會被放大好幾倍。原因在于端側跑的多半是 1B 到 7B 級別的小模型這些模型的指令遵循能力和格式化輸出能力遠不如云端的大模型。你讓 GPT-4 輸出一個符合 JSON Schema 的對象它基本不會出錯但你讓一個 3B 的量化模型做同樣的事它可能會在 JSON 外面包一層解釋文字可能會把字段名拼錯可能會在數(shù)值字段里填一個字符串甚至可能直接編造一個 Schema 里不存在的字段。所以端側 Agent 的 Function Calling 工程化核心不是“怎么讓模型調用工具”而是“怎么讓模型在能力受限的情況下盡可能穩(wěn)定地輸出符合預期的結構化內容”。這里面涉及到 Schema 設計、Prompt 約束、輸出解析、錯誤恢復等一系列工程手段每一個環(huán)節(jié)都有很多細節(jié)可以摳。我見過不少團隊在這個環(huán)節(jié)踩坑最常見的就是直接把云端 Agent 的那套 Schema 搬到端側結果小模型根本扛不住那么復雜的結構輸出成功率慘不忍睹。正確的做法是反過來先摸清楚端側模型的能力邊界然后在這個邊界內設計盡可能簡單的 Schema再用工程手段去兜底。1.3 MCP 在端側 Agent 里的定位MCPModel Context Protocol這兩年被討論得很多它的核心價值是給模型和外部工具之間定義了一套標準化的通信協(xié)議。在云端 Agent 場景里MCP 解決的是“工具生態(tài)碎片化”的問題——不同的工具提供方各自定義接口Agent 開發(fā)者要一個個適配成本很高。有了 MCP工具提供方按協(xié)議暴露能力Agent 側按協(xié)議調用雙方解耦。但在端側 Agent 場景里MCP 的定位需要重新思考。端側 Agent 的工具集通常是固定的、有限的不像云端 Agent 那樣需要動態(tài)發(fā)現(xiàn)和接入大量第三方工具。所以端側引入 MCP更多是為了統(tǒng)一內部的工具調用抽象讓 Agent 的核心邏輯和具體工具實現(xiàn)解耦方便后續(xù)替換和擴展。舉個例子你在端側做了一個支持“查天氣、設鬧鐘、發(fā)消息”三個工具的 Agent如果不用 MCP你可能在代碼里硬編碼三個函數(shù)的調用邏輯如果用 MCP你可以把這三個工具都封裝成 MCP ServerAgent 側只負責按協(xié)議發(fā)請求具體工具怎么實現(xiàn)、用什么語言實現(xiàn)、跑在哪個進程里都不影響 Agent 的核心邏輯。這樣一來后續(xù)要加新工具、要換工具實現(xiàn)改動量會小很多。不過端側引入 MCP 也有代價主要是協(xié)議本身的開銷。MCP 的通信基于 JSON-RPC每次調用都有序列化和反序列化的成本在端側這種資源緊張的環(huán)境里這個開銷不能忽略。所以我的建議是端側 Agent 是否引入 MCP要看工具集的規(guī)模和變化頻率。如果工具就三五個且基本不變直接硬編碼可能更劃算如果工具有十幾個且經常增刪那 MCP 帶來的解耦收益就值得那點協(xié)議開銷。2. Function Calling 的 Schema 設計與約束策略2.1 JSON Schema 在端側的精簡原則JSON Schema 是 Function Calling 的基礎它定義了模型可以調用的函數(shù)長什么樣、參數(shù)是什么類型、哪些是必填的。在云端場景里Schema 可以寫得很詳細字段描述可以很長枚舉值可以很多因為大模型有足夠的上下文窗口和理解能力去消化這些信息。但端側不行。端側模型的上下文窗口通常只有 2K 到 8KSchema 本身就要占掉一部分端側模型的理解能力有限Schema 里的描述文字太長反而會干擾它的判斷。所以端側 Function Calling 的 Schema 設計核心原則就是精簡。具體怎么精簡我總結了幾個實操要點。第一字段描述能短則短不要寫“請?zhí)顚懹脩粝胍樵兊某鞘忻Q支持中文和英文”直接寫“城市名”就夠了。第二枚舉值不要太多超過五個枚舉值的字段考慮改成字符串讓模型自由填寫然后在外部做校驗和映射。第三嵌套結構能扁平就扁平端側模型處理嵌套對象的準確率明顯低于扁平結構。第四必填字段盡量少非必要字段都設成可選減少模型漏填導致的調用失敗。下面是一個對比示例左邊是云端風格的 Schema右邊是端側精簡后的 Schema// 云端風格字段描述詳細枚舉值多 { name: search_flight, description: 搜索符合條件的航班信息返回航班列表, parameters: { type: object, properties: { departure_city: { type: string, description: 出發(fā)城市名稱支持中文或英文例如北京、上海、Beijing }, arrival_city: { type: string, description: 到達城市名稱支持中文或英文 }, date: { type: string, description: 出發(fā)日期格式為YYYY-MM-DD }, cabin_class: { type: string, enum: [economy, premium_economy, business, first], description: 艙位等級 } }, required: [departure_city, arrival_city, date] } }// 端側精簡描述短枚舉少結構扁平 { name: search_flight, description: 搜索航班, parameters: { type: object, properties: { from: {type: string, description: 出發(fā)城市}, to: {type: string, description: 到達城市}, date: {type: string, description: 日期YYYY-MM-DD}, cabin: {type: string, description: 艙位:經濟/商務/頭等} }, required: [from, to, date] } }精簡后的 Schema 在端側模型上的調用成功率實測下來比云端風格的高出不少。原因很簡單模型要處理的信息少了出錯的概率自然就低了。2.2 參數(shù)類型的選擇與陷阱端側 Function Calling 里參數(shù)類型的選擇有很多講究選錯了類型會直接導致模型輸出不穩(wěn)定。字符串類型是最安全的端側模型對字符串的處理能力最強幾乎不會出錯。所以能用字符串的地方盡量用字符串哪怕這個參數(shù)本質上是數(shù)字或布爾值。比如“是否開啟某功能”這個參數(shù)用布爾類型的話模型可能會輸出true字符串而不是true布爾值導致解析失敗但如果用字符串類型約定yes和no模型輸出的穩(wěn)定性會高很多。數(shù)字類型要小心端側模型經常會把數(shù)字輸出成字符串或者在數(shù)字里混入單位。比如讓它填“溫度”參數(shù)它可能輸出25度而不是25。解決辦法是在 Schema 里明確說明“只填數(shù)字不要帶單位”同時在外部解析時做容錯處理用正則把數(shù)字提取出來。枚舉類型在端側要慎用尤其是枚舉值較多的時候。模型可能會輸出一個不在枚舉列表里的值或者輸出枚舉值的變體比如大小寫不一致、多了空格。如果一定要用枚舉建議枚舉值用簡單的英文單詞不要用中文或復雜字符串同時在外部做映射和兜底。數(shù)組類型在端側是最容易出問題的模型經常會把數(shù)組輸出成字符串或者數(shù)組元素類型不一致。如果確實需要數(shù)組參數(shù)建議限制數(shù)組長度并且在 Schema 里明確說明元素類型。比如“標簽列表”這個參數(shù)可以約定最多三個標簽每個標簽是字符串。2.3 多工具場景下的 Schema 組織端側 Agent 通常需要支持多個工具怎么把這些工具的 Schema 組織起來喂給模型也是一個工程化問題。最直接的做法是把所有工具的 Schema 拼成一個大 JSON 數(shù)組一次性塞進 system prompt 里。這種做法在工具數(shù)量少的時候沒問題但工具一多prompt 長度會迅速膨脹端側模型的上下文窗口根本扛不住。我的做法是分層組織。第一層是工具分類把功能相近的工具歸為一組比如“出行類”“通訊類”“設備控制類”。第二層是組內工具每個組內的工具 Schema 放在一起。在對話時先讓模型判斷用戶意圖屬于哪個分類然后只把該分類下的工具 Schema 加載進上下文。這樣每次推理時上下文里只有少量工具的 Schema長度可控。這個思路類似于“路由”用一個輕量的分類步驟換取上下文空間的節(jié)省。分類步驟本身也可以用一個很小的模型或者規(guī)則引擎來做不一定非要走大模型推理。還有一種做法是動態(tài)加載根據對話歷史判斷當前可能需要哪些工具只加載這些工具的 Schema。這種做法更靈活但實現(xiàn)復雜度也更高需要維護一個工具和意圖的映射關系。在端側資源緊張的環(huán)境里我傾向于用分層組織這種簡單可靠的方案。2.4 Prompt 約束與輸出格式控制Schema 定義好了接下來要解決的是怎么讓模型按 Schema 輸出。端側模型的指令遵循能力有限光靠 Schema 本身不夠還需要在 prompt 里加約束。約束的核心是明確輸出格式。我通常會在 system prompt 里寫清楚如果需要調用工具只輸出一個 JSON 對象不要輸出任何其他文字JSON 對象必須包含name和arguments兩個字段arguments必須符合對應工具的 Schema。這些約束要寫得直白、具體不要用抽象的描述。除了文字約束還可以用示例約束。在 prompt 里給一兩個輸入輸出的示例讓模型模仿。端側模型對示例的模仿能力比對文字描述的理解能力更強給示例往往比寫一堆規(guī)則更有效。還有一個技巧是輸出前綴。在 prompt 的末尾加上{name:這樣的前綴引導模型從這里開始續(xù)寫。這種做法能顯著提高 JSON 輸出的成功率因為模型不需要自己決定從哪里開始輸出 JSON只需要接著前綴往下寫就行。當然這個前綴要在解析時補回去。實測下來這幾種約束手段組合使用端側模型的 Function Calling 成功率能從百分之六七十提升到百分之九十以上。剩下的百分之十靠外部解析和重試來兜底。3. 工具調用鏈路的工程化實現(xiàn)3.1 從模型輸出到工具執(zhí)行的完整鏈路一個完整的端側 Function Calling 鏈路從模型輸出到工具執(zhí)行再到結果回傳中間有很多環(huán)節(jié)每個環(huán)節(jié)都可能出問題。鏈路的第一步是模型推理模型根據當前對話上下文和工具 Schema輸出一段文本。這段文本理論上應該是一個 JSON 對象但實際上可能是 JSON 外面包了文字、可能是多個 JSON、可能是格式錯誤的 JSON。第二步是輸出解析從模型輸出里提取出 JSON 對象。這一步要做容錯比如去掉 JSON 前后的多余文字、修復常見的格式錯誤比如單引號、尾逗號、處理模型輸出的多個 JSON取第一個或最后一個。第三步是參數(shù)校驗檢查解析出來的 JSON 是否符合 Schema。這一步要檢查必填字段是否都有、字段類型是否正確、枚舉值是否在范圍內。校驗不通過的話要么重試要么走兜底邏輯。第四步是工具執(zhí)行根據name找到對應的工具實現(xiàn)把arguments傳進去執(zhí)行。這一步要處理工具執(zhí)行失敗的情況比如網絡超時、參數(shù)不合法。第五步是結果回傳把工具執(zhí)行的結果塞回對話上下文讓模型基于結果生成最終回復。這一步要注意結果的格式最好是結構化的方便模型理解。這五步里第一步和第二步是最容易出問題的也是工程化投入最多的地方。第三步到第五步相對標準化但也不能掉以輕心。3.2 輸出解析的容錯策略輸出解析是端側 Function Calling 工程化里最臟最累的活因為你要面對模型各種千奇百怪的輸出格式。我總結了幾種常見的異常輸出和對應的處理策略。第一種是JSON 外面包了文字比如模型輸出“好的我來幫你查一下天氣 {name: get_weather, arguments: {city: 北京}}”。處理策略是用正則找到第一個{和最后一個}把中間的部分提取出來。第二種是JSON 格式錯誤比如用了單引號、多了尾逗號、少了引號。處理策略是先嘗試標準 JSON 解析失敗的話用寬松的解析器比如 Python 的json5或者自己寫一個簡單的修復邏輯。第三種是輸出多個 JSON比如模型先輸出一個工具調用然后又輸出了一段解釋解釋里又包含了一個 JSON。處理策略是只取第一個完整的 JSON 對象忽略后面的內容。第四種是字段名或類型錯誤比如把arguments寫成了args把字符串寫成了數(shù)字。處理策略是在解析后做字段映射和類型轉換盡量把模型的輸出往正確的格式上靠。第五種是完全無法解析模型輸出了一段自然語言根本沒有 JSON。處理策略是走重試邏輯把模型的輸出和錯誤信息一起塞回上下文讓它重新輸出。這些容錯策略要組合使用形成一個解析管線。我的經驗是解析管線要盡量寬松能救則救實在救不回來再重試。因為端側模型推理一次的成本不低能少重試一次就少一次。3.3 多輪工具調用的狀態(tài)管理單輪工具調用相對簡單模型輸出一個工具調用執(zhí)行完把結果塞回去模型生成最終回復結束。但實際場景里很多任務需要多輪工具調用比如“幫我查一下明天北京的天氣如果下雨就提醒我?guī)恪边@需要先查天氣再根據天氣結果決定是否設置提醒。多輪工具調用的狀態(tài)管理核心是維護一個清晰的對話狀態(tài)。每一輪工具調用的輸入、輸出、模型的中間推理都要記錄下來作為下一輪推理的上下文。同時要有一個終止條件判斷什么時候任務完成可以生成最終回復了。終止條件通常有兩種一種是模型明確表示不再需要調用工具直接輸出自然語言回復另一種是達到了預設的最大輪次強制終止。端側場景里最大輪次要設得小一些比如三輪到五輪因為端側模型推理慢輪次太多用戶體驗會很差。狀態(tài)管理還有一個容易忽略的點是工具調用的去重。模型有時候會重復調用同一個工具傳相同的參數(shù)這時候要判斷是不是真的需要重復調用還是模型陷入了循環(huán)。如果是后者要主動打斷避免無限循環(huán)消耗資源。3.4 工具執(zhí)行失敗的降級處理工具執(zhí)行失敗在端側是常態(tài)網絡不穩(wěn)定、權限不足、參數(shù)不合法都可能導致工具執(zhí)行失敗。工程化要做的是在工具失敗的時候Agent 能優(yōu)雅降級而不是直接崩潰。降級策略分幾個層次。第一層是重試對于網絡超時這類臨時性失敗可以自動重試一到兩次。第二層是參數(shù)修正如果失敗原因是參數(shù)不合法可以嘗試修正參數(shù)后重新執(zhí)行比如把城市名從“北京市”改成“北京”。第三層是工具替換如果某個工具不可用可以嘗試用功能相近的替代工具。第四層是告知用戶如果以上都不行就如實告訴用戶工具執(zhí)行失敗讓用戶決定下一步。這四層降級策略要按順序嘗試能自動解決的就自動解決解決不了的再交給用戶。端側 Agent 的用戶體驗很大程度上取決于這些降級邏輯做得好不好。4. MCP 在端側 Agent 中的落地實踐4.1 MCP 協(xié)議的核心機制與端側適配MCP 的核心是 JSON-RPC 通信客戶端和服務端通過請求-響應模式交互。在端側 Agent 場景里Agent 本身是客戶端工具實現(xiàn)是服務端??蛻舳税l(fā)送工具調用請求服務端執(zhí)行工具并返回結果。MCP 協(xié)議定義了三種核心能力Tools可調用的函數(shù)、Resources可讀取的數(shù)據、Prompts預定義的提示模板。端側 Agent 最常用的是 ToolsResources 和 Prompts 在端側場景里用得相對少一些。端側適配 MCP 的關鍵在于通信方式的選擇。MCP 支持多種傳輸方式包括標準輸入輸出、HTTP、WebSocket 等。端側環(huán)境里如果工具和 Agent 跑在同一個進程里可以直接用函數(shù)調用不需要走 MCP 協(xié)議如果工具跑在獨立進程里可以用標準輸入輸出或本地 socket如果工具跑在遠端才需要走 HTTP 或 WebSocket。我的建議是端側 Agent 的工具盡量和 Agent 跑在同一個進程里用函數(shù)調用直接交互避免 MCP 協(xié)議的開銷。只有在工具確實需要獨立部署的時候才引入 MCP。這樣既能享受 MCP 帶來的解耦好處又能避免不必要的性能損耗。4.2 端側 MCP Server 的實現(xiàn)要點如果確實需要在端側實現(xiàn) MCP Server有幾個要點需要注意。第一是輕量化。端側的 MCP Server 不要用重量級的框架盡量用輕量的實現(xiàn)。JSON-RPC 的序列化和反序列化可以用現(xiàn)成的庫但不要引入太多依賴端側的包體積和內存都很寶貴。第二是啟動速度。端側應用的啟動速度直接影響用戶體驗MCP Server 的啟動要盡可能快。避免在啟動時做耗時的初始化比如加載大模型、建立網絡連接這些可以延遲到第一次調用時再做。第三是資源隔離。MCP Server 和 Agent 主進程之間要做好資源隔離避免一個工具的內存泄漏影響整個 Agent。如果工具的執(zhí)行可能耗時較長要考慮放到獨立線程或進程里執(zhí)行避免阻塞主線程。第四是錯誤處理。MCP Server 要把工具執(zhí)行的各種錯誤都捕獲住轉換成 MCP 協(xié)議定義的錯誤格式返回給客戶端。不要讓異常直接拋到協(xié)議層導致通信中斷。4.3 工具注冊與動態(tài)發(fā)現(xiàn)MCP 的一個好處是支持工具的注冊和動態(tài)發(fā)現(xiàn)。Agent 啟動時可以向 MCP Server 查詢有哪些工具可用然后把這些工具的 Schema 加載進來。這樣新增工具的時候Agent 側不需要改代碼只要 MCP Server 注冊了新工具Agent 就能自動發(fā)現(xiàn)。在端側場景里動態(tài)發(fā)現(xiàn)的價值主要體現(xiàn)在插件化上。你可以把 Agent 的核心邏輯和工具實現(xiàn)分開工具以插件的形式提供用戶按需安裝。Agent 啟動時掃描已安裝的插件通過 MCP 協(xié)議獲取插件的工具列表然后把這些工具納入可用工具集。這種架構的靈活性很高但實現(xiàn)復雜度也不低。端側做插件化要考慮插件的加載、卸載、版本管理、權限控制等一系列問題。如果工具集相對固定我建議還是用靜態(tài)注冊的方式簡單可靠。4.4 MCP 與 Function Calling 的協(xié)同MCP 和 Function Calling 不是替代關系而是協(xié)同關系。Function Calling 解決的是“模型怎么表達要調用哪個工具、傳什么參數(shù)”MCP 解決的是“工具調用請求怎么從 Agent 傳到工具實現(xiàn)”。在一個端側 Agent 里典型的協(xié)同流程是這樣的模型通過 Function Calling 輸出一個工具調用請求Agent 側解析這個請求轉換成 MCP 協(xié)議的格式通過 MCP 客戶端發(fā)送給 MCP ServerMCP Server 執(zhí)行工具把結果返回給 AgentAgent 再把結果塞回對話上下文讓模型生成最終回復。這個流程里Function Calling 和 MCP 各司其職Function Calling 負責模型和 Agent 之間的接口MCP 負責 Agent 和工具之間的接口。兩者解耦各自可以獨立演進。5. 端側 Agent 工程化的常見坑與排查5.1 模型輸出不穩(wěn)定的排查思路模型輸出不穩(wěn)定是端側 Agent 最常見的問題表現(xiàn)是同樣的輸入有時候能正確調用工具有時候不能。排查這個問題我通常按以下順序檢查。先看prompt 是否太長。端側模型的上下文窗口有限prompt 太長會導致模型注意力分散輸出質量下降。檢查方法是把 prompt 打印出來數(shù)一下 token 數(shù)如果接近模型的上下文窗口上限就要考慮精簡。再看Schema 是否太復雜。Schema 里的字段太多、嵌套太深、描述太長都會增加模型的負擔。檢查方法是把 Schema 單獨拿出來看看能不能再精簡。然后看示例是否充分。如果 prompt 里沒有給示例或者示例太少模型可能不知道該怎么輸出。檢查方法是加一兩個示例看看輸出是否穩(wěn)定。最后看模型本身的能力。如果以上都排查了還是不穩(wěn)定可能是模型本身的能力不夠需要考慮換一個更大的模型或者用量化程度更低的版本。5.2 工具調用參數(shù)錯誤的修復技巧參數(shù)錯誤是另一個高頻問題模型輸出的參數(shù)不符合 Schema導致工具執(zhí)行失敗。修復參數(shù)錯誤我常用的技巧有以下幾個。類型轉換如果模型把數(shù)字輸出成了字符串解析時自動轉成數(shù)字。如果模型把布爾值輸出成了字符串解析時自動轉成布爾值。枚舉映射如果模型輸出的枚舉值不在列表里嘗試做模糊匹配找到最接近的枚舉值。比如模型輸出“經濟艙”枚舉列表里是“economy”就做一個中文到英文的映射。默認值填充如果模型漏填了某個可選字段用默認值填充。默認值要在 Schema 里定義好解析時如果發(fā)現(xiàn)字段缺失就用默認值。參數(shù)修正如果模型輸出的參數(shù)明顯不對比如城市名寫錯了可以嘗試用規(guī)則或小模型做修正。比如“北京市”修正為“北京”“上海是”修正為“上?!?。這些技巧要組合使用形成一個參數(shù)修復管線。修復管線要盡量寬松能修則修修不了再報錯。5.3 內存與性能瓶頸的優(yōu)化方向端側 Agent 的內存和性能瓶頸主要集中在模型推理和工具執(zhí)行兩個環(huán)節(jié)。模型推理的優(yōu)化方向包括量化用 4bit 或 8bit 量化減小模型體積和內存占用、剪枝去掉模型中不重要的參數(shù)、蒸餾用大模型教小模型提升小模型的能力、緩存緩存常用的推理結果避免重復計算。工具執(zhí)行的優(yōu)化方向包括異步執(zhí)行工具執(zhí)行不阻塞主線程、結果緩存緩存工具執(zhí)行結果避免重復調用、批量執(zhí)行多個工具調用合并成一次執(zhí)行、超時控制給工具執(zhí)行設置超時避免長時間阻塞。這些優(yōu)化手段要根據具體場景選擇不是所有手段都適用。比如量化會損失一定的模型能力如果模型本身能力就不夠量化后可能更差。所以優(yōu)化要在保證功能的前提下進行不能為了性能犧牲功能。5.4 常見問題速查表問題現(xiàn)象可能原因排查方法解決思路模型不調用工具prompt 約束不明確檢查 system prompt加明確的輸出格式約束和示例模型輸出非 JSON模型能力不足換更大模型測試加輸出前綴引導或換模型JSON 解析失敗格式錯誤打印原始輸出用寬松解析器加修復邏輯參數(shù)類型錯誤Schema 定義不清檢查 Schema加類型說明解析時做轉換工具執(zhí)行超時工具實現(xiàn)問題單獨測試工具加超時控制異步執(zhí)行多輪調用死循環(huán)終止條件缺失檢查輪次控制設最大輪次加去重邏輯內存占用過高模型太大監(jiān)控內存量化模型優(yōu)化緩存策略推理速度慢硬件限制測推理耗時用更小模型加推理緩存這張表是我在實際項目里總結出來的覆蓋了端側 Agent 工程化里八成以上的常見問題。遇到問題的時候可以先對照這張表快速定位然后再深入排查。6. 工程化實踐中的經驗沉淀6.1 Schema 設計的迭代方法Schema 設計不是一次成型的需要反復迭代。我的做法是先設計一個初版 Schema然后在真實場景里跑一批測試用例統(tǒng)計調用成功率。對于成功率低的工具分析失敗原因是字段太多、描述不清、還是類型不對然后針對性調整 Schema再跑一批測試看成功率是否提升。這個迭代過程通常要重復三到五輪才能把 Schema 打磨到一個比較穩(wěn)定的狀態(tài)。迭代的時候要注意每次只改一個變量這樣才能知道是哪個改動帶來了提升。如果一次改多個地方成功率變了也不知道是哪個改動起的作用。迭代過程中要積累一個測試用例集覆蓋各種典型的用戶輸入和邊界情況。這個用例集是寶貴的資產每次改 Schema 都用它來回歸測試確保改動不會引入新的問題。6.2 工具粒度的權衡工具粒度是端側 Agent 設計里的一個重要決策。粒度太粗一個工具干太多事模型很難正確傳參粒度太細工具數(shù)量太多上下文裝不下模型也容易選錯工具。我的經驗是一個工具只做一件事但這件事的邊界要清晰。比如“查天氣”和“查空氣質量”可以是一個工具因為它們都是查詢環(huán)境信息參數(shù)也相似但“查天氣”和“設鬧鐘”必須是兩個工具因為它們的功能完全不同。工具的數(shù)量控制在十個以內比較合適超過十個就要考慮分組或動態(tài)加載。端側模型的上下文窗口有限工具太多會擠占對話歷史的空間影響多輪對話的體驗。6.3 端側 Agent 的測試策略端側 Agent 的測試比云端 Agent 更難因為端側環(huán)境復雜模型行為不確定很難用傳統(tǒng)的單元測試覆蓋。我的測試策略分三層。第一層是單元測試測試 Schema 解析、參數(shù)校驗、工具執(zhí)行這些確定性邏輯用 mock 數(shù)據模擬模型輸出確保這些環(huán)節(jié)的正確性。第二層是集成測試用真實的模型跑一批測試用例統(tǒng)計工具調用成功率和最終回復質量這層測試要跑多次因為模型輸出有隨機性。第三層是端到端測試在真實的端側設備上跑完整的用戶場景測試內存占用、推理延遲、功耗等指標。這三層測試里集成測試是最重要的也是最花時間的。我通常會準備一個包含幾十個用例的測試集覆蓋各種工具調用場景每次改動后都跑一遍看成功率有沒有下降。6.4 從工程化到產品化的最后一公里工程化做完了離產品化還有一段路。產品化要考慮的東西更多比如用戶體驗、錯誤提示、隱私保護、版本更新。用戶體驗方面端側 Agent 的響應速度是關鍵。模型推理慢的時候要有 loading 提示工具執(zhí)行慢的時候要有進度反饋任務完成的時候要有清晰的回復。這些細節(jié)直接影響用戶對產品的感知。錯誤提示方面端側 Agent 出錯的時候要給用戶友好的提示而不是一堆技術術語。比如工具執(zhí)行失敗不要直接說“HTTP 500”而是說“網絡好像不太穩(wěn)定稍后再試試”。隱私保護方面端側 Agent 的優(yōu)勢就是數(shù)據不出本地這個優(yōu)勢要在產品里體現(xiàn)出來。用戶的數(shù)據、對話歷史、工具調用記錄都要存在本地不上傳云端。版本更新方面端側 Agent 的模型和工具都可能需要更新要設計好更新機制支持增量更新和回滾。這一公里的路技術含量可能不如前面的工程化但重要性一點不低。很多端側 Agent 項目就是死在這一公里上技術做得很漂亮但產品體驗一塌糊涂用戶用一次就再也不用了。我個人在實際操作中的體會是端側 Agent 的工程化沒有銀彈每一個環(huán)節(jié)都要摳細節(jié)每一個問題都要有兜底方案。模型能力不夠就用工程手段補工程手段補不了的就用產品設計繞。整個過程就是不斷地在能力、資源、體驗之間找平衡。這個平衡點每個項目都不一樣需要根據具體情況去摸索。但有一點是共通的先把最簡單的場景做到極致穩(wěn)定再逐步擴展復雜度。貪多求快最后往往什么都做不好。