智能體的構建依然是個 “髒活累活”...

新聞
HongKong
12
03:26 PM · Jan 19 ,2026

編者按: 構建真正可靠的智能體(Agent)為什麼依然如此困難?儘管大模型能力日新月異,工具調用、多步推理、狀態管理等核心環節卻仍充滿“髒活累活” —— 是抽象層不夠好?平台差異太大?還是我們尚未找到正確的工程範式?

我們今天為大家帶來的這篇文章,作者以一線實踐者的視角明確指出:在智能體開發生態遠未成熟的當下,直面底層複雜性而非依賴抽象層,通過精細的工程實踐才能應對真實場景中的複雜性與不確定性。

作者 | Armin Ronacher

編譯 | 嶽揚

我覺得現在可能是時候寫一寫最近學到的一些新東西了。其中大部分內容會圍繞構建智能體(agents)展開,也會稍微分享一點關於使用具有智能體特性的編碼工具的經驗。

TL;DR:構建智能體依然是個髒活累活。一旦涉及到真實的工具調用,SDK 抽象層就容易出問題。對於緩存,我們自己手動管理效果更好,但不同模型的緩存管理方式各不相同。強化機制最終承擔了比預期更重的職責,而失敗情況必須嚴格隔離,否則整個循環很容易跑偏。通過一個類似文件系統的結構來管理共享狀態,是構建 Agent 系統中非常重要的一塊基石。處理輸出結果所需的工具鏈,出人意料地棘手。而模型的選擇,依然取決於具體的任務。

01 該選用哪個智能體 SDK?

我們構建智能體時,可以選擇直接基於底層 SDK(比如 OpenAI SDK 或 Anthropic SDK),也可以使用更高層的抽象框架,比如 Vercel AI SDK 或 Pydantic。我們之前的選擇是:採用 Vercel AI SDK,但只使用其中的模型供應商抽象層,自己實現智能體的主循環邏輯。現在來看,我們不會再做同樣的選擇了。這並不是説 Vercel AI SDK 本身有什麼問題,但在實際構建智能體時,有兩件事超出了我們最初的預料:

第一,不同模型之間的差異非常大,以至於我們需要自行構建適合自身需求的智能體抽象層。 我們發現,目前這些 SDK 提供的智能體抽象層都不夠貼合實際需求。 我認為部分原因在於,雖然智能體的基本設計只是一個循環,但一旦我們引入不同的工具,就會產生微妙卻關鍵的差異。這些差異會影響找到合適抽象層的難易程度(例如緩存控制、強化機制的不同需求、工具提示詞的寫法、模型供應商側的工具等)。由於目前尚無清晰統一的最佳抽象層,直接使用各平台的原生 SDK 能讓我們保持完全的控制權。而使用某些高層 SDK 時,我們不得不在它們已有的抽象層之上繼續構建,而這些抽象層可能根本不是我們真正想要的。

此外,我們在使用 Vercel SDK 處理模型供應商側的工具時也遇到了極大的困難。 它試圖統一消息格式的做法實際上並不完全奏效。 比如,Anthropic 的網頁搜索工具在搭配 Vercel SDK 使用時,經常會破壞整個消息歷史,我們至今還沒完全搞清楚原因。另外,在 Anthropic 的場景下,如果直接調用其原生 SDK(而不是通過 Vercel),緩存管理要簡單得多,而且出錯時返回的錯誤信息也清晰很多。

這種情況未來或許會改善,但在現階段,我們在構建智能體時很可能不會再使用這類抽象層了 —— 至少要等整個技術生態稍微穩定下來再説。對我們來説,現階段抽象層帶來的收益還不足以抵消成本。

02 對不同平台緩存機制的使用體會、經驗與策略總結

不同的平台在緩存策略上有着非常不同的做法。關於這一點,技術社區其實已經有很多討論了,但 Anthropic 的做法是:用户要為使用緩存付費。它要求我們顯式地管理緩存點,而這從 Agent 工程的層面上改變了我們與其交互的方式。最初,我覺得手動管理很愚蠢,平台為什麼不幫我做?但我現在已經完全轉變了看法,現在更傾向於顯式的緩存管理。這讓成本和緩存利用率變得更加可預測。

由開發者主動、明確地啓用緩存(Explicit caching)讓我們能夠實現某些原本極其困難的操作 例如,我們讓一個對話同時朝兩個不同的方向運行。我們還可以上下文編輯(context editing)。雖然最優策略尚不明確,但我們顯然擁有了更多的控制權,而我非常享受這種掌控感,這也讓我們更容易理解底層智能體的運行成本。我們也可以更準確地預估緩存的利用效果,而使用其他平台時,緩存的效果卻時好時壞、難以預測。

我們在使用 Anthropic 的 Agent 時進行緩存的方式非常簡單直接。一個緩存點位於系統提示詞之後。兩個緩存點放置在對話開頭,其中最後一個緩存點會隨着對話的尾部移動。在此過程中,我們還可以進行一些優化。

由於系統提示詞和工具選擇現在必須基本保持靜態,我們會隨後插入一條動態消息來提供諸如當前時間之類的信息,否則會破壞緩存。我們在智能體循環中也更多地利用了強化機制。

03 智能體循環中的強化機制

每當智能體調用工具時,我們不僅有機會返回該工具產生的數據,還可以向智能體循環中注入更多信息。例如,我們可以提醒智能體當前的總體目標以及各個子任務的進展狀態。當某個工具調用失敗時,我們也可以提供關於如何成功調用工具的提示詞,幫助它下次更可能成功。此外,強化機制的另一個用途,是向系統通報在後台發生的狀態變化。如果你的智能體使用了並行處理,那麼每當後台狀態發生變化,並且該變化與任務完成相關時,你就可以在每次工具調用之後注入相應的信息。

有時候,智能體進行自我強化就足夠了。比如在 Claude Code 中,todo write 工具就是一個自我強化工具:它只是接收智能體認為自己應該執行的任務列表,然後原樣回顯出來。本質上它就是一個“回顯工具”,不做任何其他處理。但這種簡單機制已經足夠了 —— 比起只在上下文開頭一次性給出所有任務和子任務(而中間又發生了大量操作),這種方式能更好地推動智能體向前推進。

我們也利用強化機制,在執行過程中環境發生對智能體不利的變化時及時告知系統。例如,如果智能體在某一步運行失敗並嘗試重試,但重試所依賴的數據本身已損壞,我們就會注入一條消息,提示它可能需要回退幾步,重新執行更早的步驟。

04 將失敗的副作用限制在局部範圍,不讓它擴散到整個系統或干擾後續決策

如果我們預期在代碼執行過程中會頻繁出現失敗,那麼可以把這些失敗從主上下文中隱藏起來。這可以通過兩種方式來實現:

第一種是將可能需要進行多次迭代的任務單獨運行。 可以在一個子智能體(subagent)中反覆執行該任務,直到成功,然後只將成功結果(以及哪些方法未奏效的簡要總結)彙報回主循環。讓智能體瞭解子任務中哪些方法行不通是有幫助的,因為它可以把這些信息帶入下一個任務,從而儘量避開類似的失敗路徑。

第二種方式並非所有智能體或基礎模型都支持,但在 Anthropic 上你可以進行上下文編輯(context editing)。 到目前為止,我們在上下文編輯上還沒有太多成功經驗,但我們認為這是一個非常值得深入探索的方向。我們也非常希望瞭解是否有其他人在這方面取得了成效。上下文編輯的妙處在於,它理論上能為你節省一些 token,留到後續的迭代循環中使用:你可以將某些對完成任務沒有推動作用、僅對執行過程中的某些嘗試產生負面影響的失敗記錄,從上下文中移除。不過,正如我之前提到的 —— 讓智能體知道“什麼方法行不通”仍然是有價值的,只是未必需要保留每一次失敗的完整狀態和完整輸出。

不幸的是,上下文編輯會自動使緩存失效,且這一點幾乎無法避免。 因此,很難判斷在什麼情況下,這種操作帶來的收益能抵消因緩存被破壞而產生的額外成本。

05 子智能體 / 子推理過程

我們的大多數智能體都基於代碼執行與代碼生成,這就要求智能體必須有一個共享的數據存儲位置。 我們選擇的是文件系統 —— 具體來説是一個虛擬文件系統,但這就需要不同的工具都能訪問它。如果你使用了諸如子智能體(subagent)或子推理(subinference)之類的機制,這一點就尤為重要。

你應該儘量構建一個沒有“死衚衕”的智能體。所謂“死衚衕”,是指任務只能在你自己構建的特定子工具內繼續執行。例如,你可能會構建一個生成圖像的工具,但它只能把圖像反饋給另一個工具。這就有問題,因為你可能之後會想用代碼執行工具把這些圖像打包進壓縮文件。因此,需要一個系統能讓圖像生成工具將圖像寫入代碼執行工具能夠讀取的位置。本質上,這就是文件系統。

顯然,反過來也需要可行。你可能希望先用代碼執行工具解壓壓縮包,然後回到推理環節,讓模型描述解壓出的所有圖像,接着再回到代碼執行環節進行下一步操作,如此往復。我們正是用這個文件系統來實現這種跨工具協作的。但這要求所有工具在設計時,都必須支持以文件路徑作為輸入/輸出接口,且這些路徑指向同一個共享的虛擬文件系統。

所以基本上,ExecuteCode 和 RunInference 這樣的工具需要能訪問同一個共享文件系統。這樣,後者只需接收一個文件路徑參數,就能直接處理前者在共享空間裏生成的文件。

06 輸出工具的使用

我們構建智能體的一個有趣之處在於:它並不代表一次聊天會話。它最終確實會向用户或外界傳遞某些信息,但中間產生的所有消息通常不會暴露給用户。那麼問題來了:它如何生成最終要傳遞的消息?我們的做法是提供一個專門的輸出工具(output tool),智能體會顯式調用這個工具來與人類溝通。我們通過提示詞明確告訴它何時該使用這個工具。在我們的場景中,這個輸出工具會發送一封電子郵件。

但這樣做也帶來了一些意料之外的挑戰。其中一個問題是:相比直接讓主智能體循環輸出文本給用户,通過輸出工具來控制措辭和語氣要困難得多。 我説不清具體原因,但很可能與這些模型的訓練方式有關。

我們曾嘗試過一種方法:讓輸出工具調用另一個輕量級 LLM(比如 Gemini 2.5 Flash)來將語氣調整為我們喜歡的風格。但效果並不好 —— 不僅增加了延遲,反而還降低了輸出質量。部分原因我認為是模型本身的措辭就不夠準確,而子工具也沒有足夠的上下文。向子工具提供更多主智能體上下文的片段會使成本升高,且未能完全解決問題。更糟的是,有時子工具還會在最終輸出中意外泄露我們不希望用户看到的信息,比如得出最終結果前的步驟細節。

輸出工具的另一個問題是,有時它根本不會被調用。 為了解決這個問題,我們加入了一個機制:記錄輸出工具是否已被調用。如果循環結束時仍未調用,我們就注入一條強化消息,明確鼓勵(甚至強制)它使用輸出工具來完成最終輸出。

07 模型選擇

總體而言,我們目前在模型選擇上的核心思路並沒有發生劇烈變化。我依然認為 Haiku 和 Sonnet 是目前(譯者注:原文發表時間為 2025 年 11 月 21 日)市面上最出色的工具調用模型,因此它們是構建 Agent 主循環的絕佳選擇。同時,它們在工具調用、多步推理等過程中表現出的策略性行為也更加可預測、可解釋、可調試。另一個顯而易見的選擇是 Gemini 系列模型。至於 GPT 家族,目前我們在將其用於主循環任務時,尚未獲得理想的效果。

對於那些可能涉及推理的子工具插件,如果你需要總結超長文檔或處理 PDF 等任務,我們的首選是 Gemini 2.5。在處理圖像信息提取任務時,它同樣表現優異,而 Sonnet 系列模型經常會觸發安全過濾機制,這在實際操作中相當令人頭疼。

還有一個顯而易見但常被忽視的事實:Token 的單價並不能完全決定一個智能體的運行成本。一個更擅長工具調用的模型,往往能用更少的 Token 完成任務。雖然目前市面上有一些比 Sonnet 更便宜的模型,但在智能體循環(Loop)中使用時,它們的綜合成本未必更低。

總的來説,過去幾周內,模型格局其實並沒有太大變化。

08 測試與評估

我們發現,測試和評估(Evals)依然是目前最棘手的難題。 這其實並不令人意外,但智能體的特性讓這一問題變得更加複雜。與簡單的 Prompt 不同,我們無法直接在外部系統中進行評估,因為需要注入的上下文信息實在太多了。這意味着我們必須基於可觀測數據(Observability data)或在運行過程中進行埋點(Instrumenting)來完成評估。

遺憾的是,目前我們嘗試過的各種方案,還沒有一個讓我們覺得真正找到了正確方向。我必須承認,目前我們還沒有找到令人滿意的評估方法。我由衷希望未來能有更好的解決方案出現,因為這已經成為 Agent 開發過程中最令人沮喪的一環。

09 Coding Agent 的進展

關於編程智能體(Coding Agent)的使用體驗,近期變動也不大。最主要的進展是,我最近在更多地試用 Amp。如果你好奇原因,倒不是因為它在客觀指標上比我正在用的工具更強,而是我非常認可他們在社交媒體上分享的關於 Agent 的設計邏輯。Amp 中不同子智能體(如 Oracle)與主循環之間的交互設計得非常優雅,目前很少有其他框架能達到這種水平。

同時,通過 Amp 也可以很好地驗證不同 Agent 設計方案的實際效果。Amp 和 Claude Code 類似,給人的感覺是真正由開發者為自己量身打造,自己真正在使用的產品。説實話,業內並不是所有的智能體產品都能給我這種感覺。

END

本期互動內容 🍻

❓在構建智能體時,你更傾向於直接使用原生 SDK(如 OpenAI、Anthropic),還是選擇高層抽象框架(如 Vercel AI SDK)?

原文鏈接:

https://lucumr.pocoo.org/2025/11/21/agents-are-hard/

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.