:從系統(tǒng)編排到工具調(diào)用實戰(zhàn))
1. 從“調(diào)模型”到“搭系統(tǒng)”前端視角下的Agent開發(fā)認知糾偏很多前端同行第一次聽到“Agent開發(fā)”這個詞腦子里蹦出來的第一反應往往是這是不是又要搞模型訓練是不是得先學會PyTorch再啃幾篇Transformer的論文最好還能自己微調(diào)一個模型出來我當初也是這么想的甚至一度覺得這玩意兒離自己挺遠——畢竟前端日常打交道的是DOM、組件生命周期、狀態(tài)管理跟梯度下降、損失函數(shù)八竿子打不著。但真正上手做了幾個Agent項目之后我發(fā)現(xiàn)這個認知偏差大得離譜。Agent開發(fā)的核心根本不是模型訓練而是系統(tǒng)編排。模型是別人訓好的你拿過來用就行就像你寫前端不會自己去實現(xiàn)一個瀏覽器內(nèi)核一樣。你要做的事情是把模型當成一個能力很強的“外部服務”然后圍繞它設計一套能穩(wěn)定運轉的流程。這個認知轉變非常關鍵。一旦你把它理解成“系統(tǒng)編排”前端工程師的很多既有技能就立刻變得有用了狀態(tài)管理、異步流程控制、錯誤處理、接口設計、用戶體驗——這些全是前端每天都在干的事情。你不需要成為算法工程師你需要的是一個“AI應用架構師”的思維。這篇文章就是寫給那些對Agent開發(fā)感興趣、但被“模型訓練”四個字嚇住的前端同行。我會把Agent開發(fā)拆成幾個核心模塊講清楚每個模塊到底在干什么、為什么這么設計、前端背景的人怎么快速上手。全文不會涉及任何模型訓練的技術細節(jié)因為那根本不是入門階段該關心的事。提示如果你現(xiàn)在還在糾結“要不要先學深度學習”我的建議是先放下這個念頭。Agent開發(fā)的門檻在工程側不在算法側。先把系統(tǒng)跑起來再回頭補理論效率高得多。2. Agent的本質(zhì)一個帶工具調(diào)用能力的循環(huán)系統(tǒng)2.1 為什么說Agent不是“更聰明的聊天機器人”很多人對Agent的理解停留在“能聊天的AI”這個理解會導致你在設計系統(tǒng)時走偏。聊天機器人的核心是“一問一答”而Agent的核心是“目標驅(qū)動的循環(huán)”。這兩者的區(qū)別就像“計算器”和“會自己按計算器的助手”之間的區(qū)別。一個典型的Agent運行邏輯是這樣的用戶給一個目標Agent先思考需要做什么然后選擇一個工具去執(zhí)行拿到執(zhí)行結果后再思考下一步如此循環(huán)直到目標完成或者達到終止條件。這個“思考-行動-觀察”的循環(huán)才是Agent的靈魂。我用一個前端熟悉的類比來解釋聊天機器人像是一個純函數(shù)輸入A輸出B沒有副作用而Agent像是一個帶有狀態(tài)機的異步任務隊列每一步的輸出都會影響下一步的輸入而且中間會調(diào)用各種外部API。你寫過的那些帶輪詢、帶重試、帶狀態(tài)流轉的前端邏輯本質(zhì)上就是Agent循環(huán)的簡化版。2.2 循環(huán)系統(tǒng)的三個核心組件拆開來看一個最小可用的Agent循環(huán)需要三個東西第一是決策器。它負責根據(jù)當前狀態(tài)決定下一步做什么。這個決策器通常就是大語言模型你給它一段提示詞它輸出一個結構化的決策結果比如“調(diào)用搜索工具參數(shù)是XXX”。注意這里模型只負責“決策”不負責“執(zhí)行”。第二是工具集。工具就是Agent能調(diào)用的外部能力可以是一個HTTP接口、一個數(shù)據(jù)庫查詢、一個文件讀寫操作甚至是一個前端頁面上的按鈕點擊。工具的定義要清晰輸入什么、輸出什么、什么情況下用。這部分工作對前端來說非常親切本質(zhì)上就是設計API。第三是狀態(tài)管理器。它記錄Agent執(zhí)行過程中的所有信息用戶目標、歷史步驟、每步的觀察結果、當前進度。狀態(tài)管理做得好不好直接決定Agent能不能處理復雜任務。前端的狀態(tài)管理經(jīng)驗在這里能直接遷移過來。2.3 一個容易踩的坑把決策和執(zhí)行混在一起我見過不少初學者寫的Agent把“決定調(diào)用哪個工具”和“實際調(diào)用工具”寫在同一個函數(shù)里模型輸出什么就直接執(zhí)行什么。這種寫法在Demo階段沒問題但一旦任務變復雜就會失控。正確的做法是把決策和執(zhí)行徹底分離。模型只輸出一個結構化的決策對象比如{ action: search, params: { query: 前端性能優(yōu)化方案 }, reasoning: 需要先獲取相關資料 }然后由你的代碼去解析這個對象校驗參數(shù)合法性再決定是否真的執(zhí)行。這樣做的好處是你可以在執(zhí)行前加校驗、加日志、加人工確認也可以在模型輸出格式錯誤時優(yōu)雅降級。這個思路跟前端處理用戶輸入是一樣的——永遠不要信任外部輸入哪怕這個輸入來自模型。3. 工具調(diào)用前端工程師最容易上手的部分3.1 工具定義的本質(zhì)就是接口設計如果說Agent開發(fā)有什么部分是前端能無縫銜接的那一定是工具調(diào)用。工具的定義、注冊、調(diào)用、錯誤處理這一整套流程跟前端設計SDK接口幾乎一模一樣。一個工具的定義通常包含這幾個要素名稱、描述、參數(shù)schema、執(zhí)行函數(shù)。名稱和描述是給模型看的模型根據(jù)這些信息判斷什么時候該用這個工具。參數(shù)schema用來約束輸入格式執(zhí)行函數(shù)就是實際干活的代碼。我習慣用這樣的結構來定義一個工具const searchTool { name: web_search, description: 根據(jù)關鍵詞搜索網(wǎng)頁信息返回摘要列表, parameters: { type: object, properties: { query: { type: string, description: 搜索關鍵詞 }, limit: { type: number, description: 返回結果數(shù)量默認5 } }, required: [query] }, execute: async ({ query, limit 5 }) { // 實際調(diào)用搜索接口 const results await searchApi(query, limit); return results.map(r ({ title: r.title, snippet: r.snippet })); } };這個結構是不是很眼熟跟你在前端封裝一個請求函數(shù)沒有本質(zhì)區(qū)別。唯一的區(qū)別是你需要額外寫一段“給模型看的描述”讓模型知道這個工具是干什么的。3.2 描述寫得好不好直接決定Agent聰不聰明這里有一個非常關鍵的細節(jié)工具描述的質(zhì)量比工具本身的實現(xiàn)更重要。模型只能通過描述來理解工具的用途如果描述寫得含糊模型就會用錯工具或者該用的時候不用。我踩過的一個坑是早期我寫了一個“獲取用戶信息”的工具描述只寫了“獲取用戶信息”。結果模型在需要查訂單的時候也調(diào)這個工具因為它覺得“用戶信息”可能包含訂單。后來我把描述改成“根據(jù)用戶ID查詢用戶的基本資料包括昵稱、注冊時間、會員等級不包含訂單和交易記錄”模型的使用準確率立刻上去了。寫工具描述有幾個經(jīng)驗說清楚“做什么”和“不做什么”邊界要明確參數(shù)說明要具體最好給示例值如果有使用前提一定要寫出來比如“需要先獲取用戶ID”描述長度控制在兩三句話太長了模型反而抓不住重點3.3 工具調(diào)用的錯誤處理策略工具調(diào)用一定會出錯網(wǎng)絡超時、參數(shù)錯誤、接口返回異常這些都是常態(tài)。前端處理接口錯誤的經(jīng)驗在這里完全適用但有一點需要注意錯誤信息要反饋給模型而不是直接拋給用戶。舉個例子如果搜索工具返回了“請求超時”你不應該直接告訴用戶“搜索失敗了”而應該把錯誤信息作為觀察結果傳回給模型讓模型決定是重試、換工具、還是告訴用戶暫時無法完成。這個設計思路叫“錯誤即觀察”是Agent魯棒性的關鍵。我通常會把工具執(zhí)行結果統(tǒng)一成這樣的格式{ success: true/false, data: {...}, // 成功時的數(shù)據(jù) error: ..., // 失敗時的原因 suggestion: ... // 可選的修復建議 }模型看到success為false時會結合error和suggestion來決定下一步。實測下來這種設計能讓Agent在遇到偶發(fā)錯誤時自動恢復而不是直接卡死。4. 提示詞工程不是寫作文是設計協(xié)議4.1 把提示詞當成API契約來寫前端工程師對“契約”這個概念應該很熟悉前后端約定好接口格式各自按約定實現(xiàn)。Agent的提示詞本質(zhì)上就是你和模型之間的契約——你告訴模型輸入是什么格式、輸出是什么格式、有哪些約束條件。很多人寫提示詞像寫作文堆一大堆形容詞結果模型輸出不穩(wěn)定。正確的做法是像寫接口文檔一樣寫提示詞結構化、無歧義、有示例。一個我常用的提示詞骨架是這樣的你是一個任務執(zhí)行助手。你的目標是幫助用戶完成指定任務。 你可以使用以下工具 {tools_description} 請嚴格按照以下JSON格式輸出你的決策 { thought: 你的思考過程, action: 工具名稱或finish, action_input: {參數(shù)對象} } 約束條件 1. 每次只能調(diào)用一個工具 2. 如果任務已完成action設為finish 3. 參數(shù)必須符合工具定義的schema這種結構化提示詞的好處是輸出格式穩(wěn)定解析起來不容易出錯。前端處理JSON解析的容錯邏輯在這里能直接復用。4.2 少樣本示例比長篇說明更有效如果你發(fā)現(xiàn)模型總是輸出格式不對最有效的解決辦法不是加更多說明文字而是給兩三個示例。模型對示例的敏感度遠高于對規(guī)則描述的敏感度。我一般會準備三類示例一個正常調(diào)用的、一個任務完成的、一個參數(shù)復雜的。這三類覆蓋了大部分場景模型看了之后輸出穩(wěn)定性會明顯提升。注意示例要真實可信不要編造不存在的工具名或參數(shù)。示例里的工具必須和實際注冊的工具一致否則模型會混淆。4.3 提示詞版本管理別把提示詞硬編碼在代碼里這是一個很容易被忽視的工程問題。初期大家習慣把提示詞直接寫在代碼里改一次就要重新部署。但提示詞是需要反復迭代的硬編碼會讓迭代效率極低。我的做法是把提示詞抽成獨立的配置文件或者模板文件代碼里只負責讀取和填充變量。更進一步可以給提示詞加版本號記錄每次修改的效果對比。這個思路跟前端做AB測試是一樣的——提示詞就是你的實驗變量。5. 狀態(tài)管理與上下文控制Agent的記憶問題5.1 為什么Agent需要“記憶”Agent執(zhí)行任務往往需要多輪循環(huán)每一輪的決策都依賴之前的結果。如果模型沒有記憶它就會重復調(diào)用同一個工具或者忘記已經(jīng)獲取的信息。所以你需要把歷史步驟整理成上下文每次調(diào)用模型時一起傳進去。但這里有個矛盾模型的上下文長度是有限的任務步驟多了之后歷史信息會超出限制。這就引出了Agent開發(fā)中最核心的工程問題之一上下文管理。5.2 三種上下文壓縮策略我實踐下來比較有效的策略有三種第一種是滑動窗口。只保留最近N步的歷史更早的丟棄。簡單粗暴但會丟失早期的重要信息。適合步驟少、每步信息量小的任務。第二種是摘要壓縮。把早期步驟交給模型總結成一段簡短摘要只保留摘要和最近幾步的詳細記錄。這種方式能保留關鍵信息但摘要本身可能丟失細節(jié)。第三種是結構化記憶。把歷史信息按類型拆開存儲比如“已獲取的事實”“已執(zhí)行的行動”“待完成的目標”每次只把相關的部分放進上下文。這種方式最復雜但效果最好。實際項目中我通常是組合使用近期步驟用滑動窗口保留原文遠期步驟用摘要壓縮關鍵事實用結構化存儲單獨維護。5.3 前端狀態(tài)管理經(jīng)驗的直接遷移如果你用過Redux或者Pinia你會發(fā)現(xiàn)Agent的狀態(tài)管理思路跟它們高度相似都是維護一個中心化的狀態(tài)樹每次操作產(chǎn)生一個新的狀態(tài)快照UI或者模型根據(jù)當前狀態(tài)渲染或者決策。區(qū)別在于前端的狀態(tài)變更通常是同步的、可預測的而Agent的狀態(tài)變更依賴模型輸出帶有不確定性。所以你需要額外的校驗層確保模型輸出的狀態(tài)變更符合預期。這個校驗層的設計可以參考前端表單校驗的思路定義schema、逐字段校驗、給出明確的錯誤提示。6. 從Demo到可用那些只有踩過才知道的坑6.1 循環(huán)終止條件別讓Agent陷入死循環(huán)Agent最危險的情況是無限循環(huán)模型一直調(diào)用工具但永遠不認為任務完成。我遇到過最夸張的一次Agent連續(xù)調(diào)用了二十多次搜索工具每次都說“還需要更多信息”。解決辦法是設置硬性終止條件最大循環(huán)次數(shù)、最大執(zhí)行時間、連續(xù)無進展檢測。其中“連續(xù)無進展檢測”最有用——如果連續(xù)三步的觀察結果高度相似就強制終止并返回當前結果。另外提示詞里要明確告訴模型“如果信息足夠就盡快結束”并且給出判斷標準。模型有時候會過度謹慎你需要引導它果斷收尾。6.2 工具返回結果太長怎么辦有些工具返回的數(shù)據(jù)量很大比如一個搜索接口返回了二十條結果每條都有幾百字。如果全部塞進上下文很快就會撐爆。我的處理方式是在工具執(zhí)行層做預處理只返回最相關的幾條并且截斷過長的文本。比如搜索結果只返回前三條的標題和摘要摘要超過兩百字就截斷。這個預處理邏輯放在工具內(nèi)部模型不需要知道原始數(shù)據(jù)有多長。這個思路跟前端做列表虛擬滾動是一樣的——數(shù)據(jù)可以很多但渲染出來的只有可視區(qū)域那部分。6.3 模型輸出格式不穩(wěn)定解析失敗的兜底方案不管你提示詞寫得多好模型偶爾還是會輸出格式錯誤的內(nèi)容。這時候不能直接崩潰要有兜底方案。我的做法是分三層處理第一層用正則提取JSON部分第二層用JSON.parse嘗試解析第三層如果還失敗就調(diào)用一次模型讓它重新格式化。三層都失敗才報錯。實測下來第一層就能解決大部分問題因為模型通常只是多輸出了一些前后綴文字。6.4 成本控制Token是要花錢的Agent每循環(huán)一次就要調(diào)用一次模型Token消耗是普通對話的好幾倍。如果不加控制一個復雜任務可能燒掉大量Token??刂瞥杀镜氖侄斡袔讉€精簡提示詞、壓縮上下文、限制循環(huán)次數(shù)、對簡單任務用更小的模型。其中“對簡單任務用更小的模型”效果最明顯——決策步驟用大模型格式化、摘要這類任務用小模型就夠了。我在項目里會做一個簡單的路由根據(jù)任務復雜度選擇模型。判斷復雜度的依據(jù)包括歷史步驟數(shù)、工具數(shù)量、用戶輸入的模糊程度。這個路由邏輯本身不需要模型用規(guī)則判斷就行。7. 前端做Agent開發(fā)的獨特優(yōu)勢與需要補的課7.1 優(yōu)勢工程化思維和用戶體驗敏感度前端工程師做Agent開發(fā)有兩個天然優(yōu)勢。第一是工程化思維你知道怎么組織代碼、怎么管理狀態(tài)、怎么處理異步、怎么做錯誤邊界。這些在Agent開發(fā)里全是核心技能。第二是用戶體驗敏感度你知道怎么給用戶反饋進度、怎么展示中間狀態(tài)、怎么設計交互流程。Agent執(zhí)行任務往往需要幾秒到幾十秒這期間的體驗設計非常關鍵。我見過一些算法背景的人做的Agent功能沒問題但用起來很難受——沒有進度提示、錯誤信息直接拋給用戶、中間狀態(tài)完全黑盒。這些問題前端一眼就能看出來也最知道怎么解決。7.2 需要補的課理解模型的“脾氣”前端需要補的課主要是對模型行為的理解。模型不是確定性函數(shù)同樣的輸入可能給出不同的輸出模型會有幻覺會編造不存在的信息模型對提示詞的措辭很敏感換個說法結果可能完全不同。這些特性意味著你不能用寫傳統(tǒng)代碼的思維來寫Agent。傳統(tǒng)代碼里if-else是確定的Agent里模型的決策是不確定的。你需要接受這種不確定性并通過工程手段去約束它、引導它、兜底它。我的建議是花幾天時間專門做實驗同一個提示詞改幾個字看輸出怎么變同一個任務跑十次看結果穩(wěn)不穩(wěn)定。這種“摸脾氣”的過程是必須的沒有捷徑。7.3 一個實用的學習路徑如果你是一個前端想入門Agent開發(fā)我建議的路徑是這樣的第一步先用現(xiàn)成的API跑通一個最簡單的循環(huán)不需要任何框架就是手寫一個while循環(huán)調(diào)用模型、解析輸出、執(zhí)行工具、把結果塞回上下文。這一步的目的是理解Agent的運行機制。第二步把工具調(diào)用封裝成標準接口加上參數(shù)校驗和錯誤處理。這一步的目的是建立工程規(guī)范。第三步處理上下文管理實現(xiàn)滑動窗口或者摘要壓縮。這一步的目的是讓Agent能處理稍復雜的任務。第四步加上日志和可觀測性記錄每一步的輸入輸出。這一步的目的是方便調(diào)試和優(yōu)化。第五步才是考慮用框架。LangChain這類框架能幫你省一些事但也會帶來抽象泄漏的問題。我建議先手寫一遍理解每個環(huán)節(jié)在干什么再用框架。8. 關于Agent開發(fā)我踩過之后才明白的幾件事第一個體會是Agent的瓶頸往往不在模型能力而在工程細節(jié)。模型再聰明如果工具描述寫得爛、上下文管理做得差、錯誤處理不到位Agent照樣跑不起來。反過來一個中等能力的模型配上扎實的工程能做出相當可用的東西。第二個體會是不要追求一步到位。我一開始總想設計一個能處理所有情況的通用Agent結果越做越復雜最后什么都做不好。后來改成針對具體場景做專用Agent每個Agent只解決一類問題反而效果好得多。這跟前端做組件是一樣的——先做專用組件有共性了再抽象。第三個體會是可觀測性比什么都重要。Agent執(zhí)行過程是個黑盒如果不記錄每一步的輸入輸出出了問題根本沒法排查。我現(xiàn)在每個Agent項目第一件事就是加日志把每次模型調(diào)用、每次工具執(zhí)行、每次狀態(tài)變更都記下來。這個習慣幫我省了無數(shù)調(diào)試時間。第四個體會是提示詞是需要持續(xù)迭代的資產(chǎn)。不要指望一次寫好要建立快速迭代的機制。我現(xiàn)在的做法是把提示詞放在獨立文件里改完立刻能測測完記錄效果。積累下來這些提示詞模板就是項目最值錢的部分。第五個體會是前端做Agent有天然優(yōu)勢但別被優(yōu)勢限制住。工程化思維是優(yōu)勢但也要主動去理解模型的行為特性。兩者結合才能做出真正好用的Agent產(chǎn)品。