Agent Loop 架構

@djfarrelly
英語4 週前 · 2026年6月18日
559K
800
104
22
3.1K

TL;DR

本文探討了 Agent 系統的三層架構——Loop、技能與編排,強調了持久化執行對於防止故障及實現 AI 自我演進的重要性。

大家都問:「迴圈到底是什麼?」但沒人問的是:是什麼在驅動迴圈?

AI 領域的討論已經聚焦在「迴圈」作為代理系統的核心原語。Matt Van Horn(@mvanhorn)追溯了代理迴圈的演進,從 ReAct 到工具使用,再到編排迴圈,最後到迴圈監管迴圈。Addy Osmani(@addyosmani)則拆解了迴圈內部的建構模塊:自動化、工作樹、技能、連接器和子代理。Van Horn 的結論落在持久化上,他認為無法在重啟後存續的就不是迴圈。Osmani 的核心主軸則是編排:設計一個能替你提示代理的系統。

我想將他們的觀點再往前推一步。持久化不僅僅是迴圈的一個屬性,它更是整個底層執行層。關鍵在於,持久化編排是建構代理迴圈架構的基礎。讓我們來拆解這個架構。

迴圈在哪裡會失效

/loop/goal 模式在處理單一代理、單一會話的工作時表現良好。一個代理會不斷迴圈,直到任務完成。這涵蓋了大部分情況。但下一階段(Van Horn 框架中的第五階段)就會出現問題:

  • 迴圈監管其他迴圈
  • 迴圈按照排程執行,而非僅由人類觸發
  • 迴圈能在進程重啟、部署和崩潰後存續
  • 迴圈會生成子代理並等待其結果(有時會等上數小時)
  • 迴圈需要能夠在事後被觀察

這不是提示工程的問題。這是基礎設施的問題。

Van Horn 引用了 @runes_leo 的話:「在 AI 編碼中,成本最高的不再是寫程式碼,而是管理代理迴圈。」在終端機裡跑個 while True 無法提供上述任何功能。在虛擬機或沙盒中跑一個長期運行的進程也無法做到。

想想看,當你在伺服器上運行一個代理迴圈時會發生什麼。進程可能會掛掉或重啟。一次部署、一次記憶體不足、一次競價實例回收。迴圈重啟了。但它剛才在做什麼?它執行到哪一步了?它已經送出那條 Slack 訊息了嗎?它已經呼叫子代理了嗎?

你不知道。它從頭開始。重新獲取已經擁有的資料。重新呼叫 LLM 來做已經做出的決定。發送重複的通知。生成重複的子代理。你醒來看到三條一模一樣的 Slack 訊息和一個困惑的團隊。

解決方案不是「更好的錯誤處理」——而是一個執行模型,其中每一步都被檢查點記錄,每個決定都被持久化儲存,而恢復意味著從最後成功的步驟繼續執行。

三層代理迴圈架構

三層。每一層都對應一個具體的原語。

第一層:迴圈

迴圈就是一個 cron 任務加上一個決策者。它按照排程(或觸發條件)運行,評估狀態,然後決定下一步要做什麼。

這將 Van Horn 的定義具體化:cron 任務一直缺少的就是中間的決策環節。是代理在做決定,而不是你。Cron 任務是心跳,LLM 是決策者。步驟是記錄進度的持久化執行單元。

typescript
1export const infraHealthCheck = inngest.createFunction(
2 { id: "infra-health-check" },
3 { cron: "*/30 * * * *" }, // 每 30 分鐘執行一次
4 async ({ step }) => {
5 const metrics = await step.run("fetch-service-metrics", async () => {
6 return await fetchServiceMetrics(); // 錯誤率、延遲、記憶體、CPU
7 });
8
9 const assessment = await step.run("assess-health", async () => {
10 return await callLLM({
11 prompt: `根據這些服務指標,將整體系統健康狀態分類為「正常」、「降級」或「嚴重」。解釋你的理由。
12 指標:${JSON.stringify(metrics)}`,
13 });
14 });
15
16 if (assessment.status === "degrated" || assessment.status === "critical") {
17 await step.invoke("triage-incident", {
18 function: incidentTriage,
19 data: { metrics, assessment, services: assessment.affectedServices },
20 });
21 }
22 }
23);

每週一早上 9 點,迴圈啟動。它獲取數據,詢問 LLM 是否需要產出報告,如果需要,就呼叫一個技能。如果在步驟之間進程重啟了,已經完成的步驟不會重新執行。這就是迴圈。不是 LLM,而是 LLM 周圍 的迴圈。

第二層:技能

在本文語境下,技能不是一個提示詞。它是一個持久化的工作流程。多步驟、可重試、可組合、可獨立部署。

Van Horn 說:「迴圈是管道。技能才是它呼叫的資產。」這是能產生複合效應的部分。系統每學會一個新技能,每個迴圈的能力就變得更加強大。

typescript
1export const incidentTriage = inngest.createFunction(
2 { id: "incident-triage", retries: 3 },
3 { event: "infra.incident.triage" },
4 async ({ event, step }) => {
5 const details = await step.run("fetch-detailed-metrics", async () => {
6 return await fetchDetailedMetrics({ services: event.data.services });
7 });
8
9 const deploys = await step.run("fetch-deploy-history", async () => {
10 return await fetchRecentDeploys({ since: hoursAgo(2) });
11 });
12
13 const analysis = await step.run("correlate-incident", async () => {
14 return await callLLM({
15 prompt: `將這些服務指標與最近的部署進行關聯分析。
16 找出可能的根本原因和嚴重程度。
17 指標:${JSON.stringify(details)}
18 近期部署:${JSON.stringify(deploys)}`,
19 });
20 });
21
22 await step.run("post-triage-summary", async () => {
23 await slack.postMessage({
24 channel: "#incidents",
25 text: formatTriageSummary({
26 analysis,
27 affectedServices: event.data.services,
28 recommendedActions: analysis.recommendations,
29 }),
30 });
31 });
32
33 return analysis;
34 }
35);

這個技能負責獲取資料、分類和分發路由。它是一個內建容錯機制的單元工作。技能可以是包含 LLM 的 AI 工作流程,也可以是確定性的程式碼。

第三層:編排器

編排器是運行一切的引擎:排程 cron 任務、執行步驟、管理重試、強制執行並發限制、儲存運行歷史,以及在不中斷正在運行的任務情況下,熱部署新功能或工作流程。

這是最少被談論的一層,因為它理應是不可見的。但它是一切基礎。

大多數人認為代理就是「LLM + 工具」。代理迴圈架構則將其重新定義為:代理是「迴圈 + 技能 + 編排」。LLM 和工具在迴圈內部。LLM 和工具可以被替換或調整,而架構保持不變。編排使這個架構得以實現。

當事情出錯時會發生什麼

順利的路徑很簡單。但這是運行在生產環境中的軟體,事情真的總會按計劃進行嗎?

你的事件分類技能啟動了,但指標 API 超時了。讀取需要從硬碟進行,而記憶體緩存中沒有數據。呼叫這個 API 的步驟現在重試並再次訪問 API。數據現在被部分快取,API 完成了。技能繼續執行下一步,就像什麼都沒發生過一樣。

但有時候,事情可能沒那麼簡單。如果 API 金鑰過期了,或者你的託管商當機了 30 分鐘呢?所有的重試次數都用完了。現在怎麼辦?你還需要處理失敗情況。

typescript
1export const incidentTriage = inngest.createFunction(
2 {
3 id: "incident-triage",
4 retries: 3,
5 onFailure: async ({ error, event, step }) => {
6 // 函數在耗盡所有重試次數後失敗。
7 // 我們仍然擁有原始事件數據。沒有任何遺失。
8 await step.run("notify-failure", async () => {
9 await slack.postMessage({
10 channel: "#agent-ops",
11 text: `⚠️ 事件分類失敗:${error.message}` +
12 `將在下一次健康檢查週期重試。` +
13 `受影響服務:${event.data.services.join(", ")}`,
14 });
15 });
16 },
17 },
18 { event: "infra.incident.triage" },
19 async ({ event, step }) => {
20 /* 與上述技能相同的邏輯 */
21 }
22);

onFailure 處理器在所有重試次數耗盡後觸發。它會發送訊息到一個運營頻道,以便有人知道。事件被保存下來,沒有任何東西遺失。下一次排程執行會接續失敗任務未能完成的部分。

持久化編排必須為短暫性錯誤提供步驟級別的重試機制,並為不可恢復的錯誤提供失敗處理鉤子。沒有這些,當錯誤發生時(它們總是會發生),你可能要過幾小時或幾天後才會發現。

短暫性錯誤的代價也很高昂。如果你的技能或代理從頭開始重試,你會多次呼叫 LLM,白白消耗 tokens。LLM 呼叫可以被檢查點記錄。現在,將這個情況乘以系統中 10 個或 30 個代理。這成本非常可觀。

步驟級別的檢查點記錄不僅僅是正確性功能。它還能省錢。

能自行建立技能的代理

這是更有趣的部分。系統不是靜態的,它被設計為可以自我進化和擴展。

代理不僅僅是在迴圈內運行——它還能創作新的迴圈,並將其註冊到編排引擎中。每個部署的函數都是一個持久的技能,可以獨立運行,可以由迴圈或代理觸發,也可以按照排程運行,並擁有自己的重試邏輯。技能會產生複合效應。

這是一個具備編排感知能力的代理。

運作方式如下。AI 代理將編排 SDK 作為一個工具來使用。它可以編寫新的函數,將其註冊到引擎中,然後這些函數會立即開始運行。代理進程可以熱載入新函數,而無需重啟或中斷正在進行的執行。

讓我們逐步說明一個具體的例子:

1. 人類表達需求。 工程師說:「我們的服務總是在夜間出現延遲高峰,但直到早上才有人發現。」這就是觸發訊號。代理不需要從環境數據中推斷模糊的模式。它有明確的指示。

2. 代理編寫一個技能。 兩個多步驟函數:一個是每 30 分鐘運行一次的健康檢查迴圈,負責提取錯誤率、延遲和資源使用情況,並讓 LLM 將系統健康狀態分類為正常、降級或嚴重。另一個是事件分類技能,負責獲取詳細的指標和近期部署歷史,使用 LLM 關聯根本原因,並將包含建議措施的分類摘要發布到 Slack。錯誤處理:如果指標 API 掛了,就退避重試。如果 LLM 失敗,就回退到基於規則的嚴重性分類。

3. 代理部署技能。 代理編寫的函數程式碼由一個 sidecar 進程接收。新的函數會自動註冊。它們會立即生效,無需部署管線,無需 PR。

4. 技能自主運行。 每隔 30 分鐘,引擎觸發健康檢查。如果發現問題,它會呼叫分類技能。不需要人類介入。完全持久化。

5. 代理根據訊號迭代。 這是人們經常忽略的部分,讓我具體說明「迭代」是什麼意思。代理不會神奇地注意到模式。它有一個單獨的審查迴圈:一個由 cron 觸發的函數,每週運行一次,讀取編排器的運行歷史並評估效能:

typescript
1export const reviewSkillPerformance = inngest.createFunction(
2 { id: "review-skill-performance" },
3 { cron: "0 10 * * 5" }, // 每週五早上 10 點
4 async ({ step }) => {
5 const runs = await step.run("fetch-run-history", async () => {
6 return await getInngestRuns({
7 functionId: "incident-triage",
8 since: daysAgo(7),
9 });
10 });
11
12 const analysis = await step.run("analyze-performance", async () => {
13 const successRate = runs.filter(r => r.status === "completed").length / runs.length;
14 const avgDuration = average(runs.map(r => r.duration));
15 const incidents = await fetchIncidentOutcomes(); // 事件是否與實際故障相關?
16
17 return await callLLM({
18 prompt: `檢視這個技能過去一週的效能表現。
19 成功率:${successRate}
20 平均持續時間:${avgDuration}ms
21 與實際故障相關的事件數:${incidents.confirmed}/${incidents.total}
22 誤報數:${incidents.falsePositives}
23 團隊針對警報採取行動的次數:${incidents.actedOn}/${incidents.total}
24
25 我們是否應該調整閾值或分類方式?具體的變更建議是什麼?`,
26 });
27 });
28
29 if (analysis.shouldModify) {
30 await step.invoke("update-skill", {
31 function: coreAgent,
32 data: { prompt: `根據以下建議變更更新事件分類技能:${analysis.proposedChanges}` },
33 });
34 }
35 }
36);

「審查」本身是一個函數。它讀取運行歷史,檢查事件是否與實際故障相關,並將這些訊號輸入給 LLM。如果健康檢查持續將某個服務標記為降級,但團隊因為閾值過於敏感而忽略它,審查迴圈會發現這個問題,技能也會被更新以調整分類方式。這不是魔法。這只是一個 cron 任務,讓 LLM 坐在決策者的位置上。

驗證方面呢? 代理編寫的程式碼好壞取決於圍繞它的防護措施。程式碼可以進行型別檢查。代理可以自己呼叫該函數來測試它,因為它能夠與編排引擎本身互動。雖然這不是萬無一失,但你賦予了核心代理在其運行的系統內原生除錯它所編寫技能的能力。審查迴圈會捕捉初始除錯未能發現的問題。

再進一步,代理可以使用 onFailure 鉤子來觸發自身評估特定失敗。這是一個持續改進的反饋迴圈。

衝突問題呢? 流程控制,特別是並發控制或單一實例(singleton),可以處理簡單情況(並發:[{ limit: 1, key: "[event.data](//event.data).service" }]),這表示每個服務在同一時間只會執行一個事件分類。但更深層的問題是:如果兩個健康檢查同時在同一個服務上檢測到問題會怎樣?編排器會將它們排入佇列。第二個分類任務會等待第一個完成。不會有重複的警報,不會有競爭條件。這不是理論推測。這與你在任何任務佇列中使用的並發原語相同。

代理不僅僅是在執行任務。它是在為自己建立基礎設施。每個持久的技能都會超越創造它的對話而持續存在。終止代理進程並重啟它,技能仍然繼續運行。更換底層模型,技能仍然繼續運行。代理是短暫的——它的產出是持久的。

Dan Farrelly | Inngest.com - inline image

代理迴圈架構系統概覽

開發者的視角

這點很重要,因為如果開發者無法看到代理部署了什麼、無法除錯故障原因、無法審計凌晨三點運行的內容,那麼整個架構就是一個巨大的負擔。

編排引擎儲存每一次運行、每一步、每一個輸入、每一個輸出、每一次重試。代理上週二部署的一個技能在凌晨四點失敗了?你可以確切看到是哪個步驟失敗了、輸入是什麼、拋出了什麼錯誤,以及在放棄之前重試了多少次。一直到步驟層級的完整追蹤資訊,正是編排引擎本身的輸出。

這不是事後才加上去的儀表板。這是持久化執行內在的一部分。每一次 [step.run](//step.run)() 都是一個檢查點。每一個檢查點都是可觀察的。當編寫程式碼的不是人類時,可觀測性就不再是可有可無的功能——它是信任的基礎。

在日常工作中,開發者的工作流程如下:早上檢查運行儀表板。查看哪些技能在夜間運行、哪些成功了、哪些失敗了。如果代理編寫的一個技能行為異常,你可以直接讀取程式碼、編輯它、刪除它,或者告訴代理修復它。程式碼是代理創作的,但所有權是你的。代理和它的技能仍然是一個需要你照料的花園。

為什麼持久化是基礎

Van Horn 說:「這些東西必須能在重啟後存活下來。」

以下是持久化在實踐中的含義:

需求

含義

為什麼基本的 while 迴圈做不到

獨立步驟重試

如果 5 個步驟中的第 3 步失敗,只重試第 3 步,而不是第 1 和第 2 步

迴圈重啟會從頭重新執行所有內容

子代理生命週期

生成一個子任務,等待它完成(可能數小時),如果父任務被取消則取消子任務

沒有內建的父子生命週期管理

保證事件交付

如果代理當機時有事件觸發,該事件仍應被處理

如果進程未運行,事件會遺失

事後可觀測性

事後能看到發生的一切:每一步、每一個決定、每一次重試

日誌是你唯一的選擇,而且它們是短暫的

不間斷熱部署

部署新版本的函數而不中斷正在進行中的運行

進程重啟會殺死所有正在運行的工作

並發控制

一個技能同時只運行 N 個實例

沒有內建的並發原語

「只要在容器裡跑就行了」可以讓你獲得正常運行時間,但無法讓你獲得正確性。一個在崩潰後重啟的容器會恢復進程,但每一個正在進行的迴圈都得從頭開始。每一步重新執行。每一次 LLM 呼叫重新進行。迴圈看起來像是在運行,但它是在盲目地運行。

與現有工具的比較

一些工具可能為這類系統提供一個「漂亮」的現成解決方案,或者你可能選擇拼湊一些底層工具來建立自己的系統。這兩種選擇都沒有 ,但正確的架構層應該允許你,以及你的代理,隨著時間推移而進化。靈活、動態、持久化。

持久化執行的原語需要非常適合代理使用,代理也能輕易編寫,並且需要提供可觀測性和 API 來觀察代理,並使代理本身具備編排感知能力。

一個可運行的範例

我們正在 Inngest 內部測試這些模式,你可以在這個「utah」專案的儲存庫中看到相關概念:https://github.com/inngest/utah:這是一個建立在 Inngest 持久化編排基礎上的代理框架,同時它也具備編排感知能力。

該系統有一個 sidecar 進程,允許主代理在自己的工作空間中編寫和編輯 Inngest 函數,從而使用「技能」(在此文章語境下)來擴展自身。很快,我們計劃提供一個完整的系統,其中包含作為範例的入門迴圈,但該專案中的想法可以更清晰地展示本文中的概念。

產生複合效應的迴圈

Satya Nadella 最近的一篇文章指出了業界一直以來的感受:護城河不是模型本身——而是迴圈。

他的論點是:有兩種資本。人力資本,是你的團隊多年積累的知識和判斷力。以及他所稱的令牌資本,是公司在基礎模型之上建構的 AI 工作流程、決策模式和學到的技能。

其論點是:這兩者會產生複合效應。每一個改進的工作流程都會產生更好的訊號。更好的訊號會產生更精準的 AI 行為。更精準的行為能釋放人類注意力,使其投入更高判斷力的工作。這就像一個爬山機器。

這就是代理迴圈架構能具體實現的目標:

  • 代理部署的每個持久化技能,都是被編碼為可執行基礎設施的制度化知識。它持續存在。無論有沒有人類監控,它都能運行。
  • 一個由 cron 觸發的審查迴圈,負責評估技能效能並進行迭代。這就是爬山機器的具體實現。它不是投影片中的飛輪圖表。而是一個帶有 cron 觸發器的函數。
  • 如果你的技能在進程重啟後就失效了,複合效應就會重置為零。持久化是讓投資持續積累的關鍵。

Nadella 的關鍵觀點是:「一家公司應該能夠更換『通用型』模型,而不會丟失其學習系統中內建的『資深員工』專業知識。」這就是技能庫模式。持久化函數不在乎是哪個 LLM 呼叫它們。

據此建構

過去的討論集中在代理做什麼:迴圈、工具、推理、上下文工程。接下來的討論將是關於什麼在驅動代理。

三個層次:迴圈、技能、編排器。迴圈是工作單元。技能是資產。編排引擎是讓兩者持久化的關鍵。Sidecar 模式是模型:代理編寫自己的持久化技能,部署它們,審查它們的表現,然後迭代。這不是思想實驗。這是一個可運行的模型。

我們建立了 Inngest 來作為實現此架構的編排引擎:[step.run](//step.run)()step.invoke()、cron 觸發器、事件驅動的控制流程、並發控制,以及完整的步驟級別可觀測性。但這個架構模式超越了任何單一工具。如果你正在生產環境中建構代理迴圈,請定義這三個層次。

這些原語今天已經存在。據此建構。

一鍵儲存

使用 YouMind AI 深度閱讀爆款文章

保存原文、追問細節、總結觀點,並在一個 AI 工作空間裡把爆款文章沉澱成可複用筆記。

了解 YouMind
寫給創作者

把你的 Markdown 變成乾淨的 𝕏 文章

圖片上傳、表格、程式碼區塊,往 𝕏 上手動重排太痛苦。YouMind 把整篇 Markdown 一鍵轉成乾淨、可直接發佈的 𝕏 文章草稿。

試試 Markdown 轉 𝕏

更多可拆解樣本

近期爆款文章

探索更多爆款文章