
前面幾天談的都是「宣告」:action 有哪些、goal 長什麼樣、條件怎麼寫。但宣告本身不會動。真正把一次使用者請求跑起來的,是 AgentProcess。
AgentProcess 是一次 agent 任務的容器。它包含使用者輸入、初始 facts、目標、候選 actions、目前 plan、Blackboard、事件與終止狀態。當你要除錯 agent 為什麼走某條路時,AgentProcess 是最重要的觀察對象——因為所有線索都在裡面。
Embabel 的流程不是一次排完就盲目跑到底。每個 action 執行後,Blackboard 狀態會更新,planner 可以根據新狀態重新規劃。這就是官方文件說的 OODA loop:Observe 觀察 blackboard 目前狀態與 action 結果、Orient 理解上一輪之後發生了什麼變化、Decide 依新資訊重新規劃、Act 執行計畫中的下一個 action。這個概念原本來自軍事決策理論,用在這裡的意思很單純:不要假設計畫一定會照著走。
這種設計讓錯誤恢復變得具體。如果 fetchActivity 找不到客戶,流程不會假裝摘要;如果 reviewOffer 擋下草稿,系統可以改走重新生成或人工審核。每個停點都有條件可解釋。
AgentProcess 是一次任務的容器,可以把它想成「這次 agent 執行的案件資料夾」。

它會保存使用者輸入、目前 blackboard、已選 goal、執行過的 action、事件與結束狀態。每跑完一步,系統就重新看狀態,判斷是否已經達標或還要繼續。

實務上不會只靠 shell 測試 agent,而會從 REST controller 或 service 呼叫。AgentInvocation 的價值,就是讓 agent 執行變成一般後端流程的一部分——指定目標型別、同步拿結果,跟呼叫一個 service 方法沒兩樣。
如果你的系統需要更彈性的入口,Embabel 還提供 Autonomy API,讓 LLM 自己挑要跑哪個 agent:
| 方式 | 誰選 agent | 適合場景 |
|---|---|---|
| AgentInvocation | 開發者指定 goal 型別 | REST 端點、確定性流程 |
| Autonomy(Closed mode) | LLM 從已註冊 agent 中挑 | 多 agent 系統的統一入口 |
| Autonomy(Open mode) | LLM 挑 goal,可跨 agent 組合 action | 使用者意圖不確定的探索式呼叫 |
大多數生產場景用 AgentInvocation 就夠了:REST 端點進來、指定 goal 型別、同步拿結果。Autonomy 要等到你真的有「使用者意圖不確定、需要 LLM 幫忙挑 agent」的需求時再說。
觀念講完了,來看一次真實執行長什麼樣。客服輸入「客戶 4711 的近一年活動」後,Blackboard 會逐步累積物件:
| 步驟 | Blackboard 內容 | Planner 決策 |
|---|---|---|
| 開始 | CustomerQuery | 推導出 fetchActivity → summarize → proposeOffer → reviewOffer |
| fetchActivity 完成 | + TravellerActivity | 照計畫執行 summarize |
| summarize 完成 | + ActivitySummary(標記 high spender) | 照計畫執行 proposeOffer |
| proposeOffer 完成 | + OfferDraft(升等方案) | 照計畫執行 reviewOffer |
| reviewOffer 未通過 | (沒有 ReviewedOffer) | Replanning:改走 escalateToHuman |
| escalateToHuman 完成 | + EscalationTicket | Goal 達成(安全失敗終點),process 終止 |
這張表最值得看的是倒數第二列。reviewOffer 沒通過時,planner 不是無限重試,而是改走 escalateToHuman——因為那條路也標了 @AchievesGoal,planner 知道走那邊也能收工。這就是為什麼一條流程通常至少要設計兩個 goal:一個正常終點,一個安全失敗終點。
看到 replanning 這個機制,多數人第一個疑問都是這個:如果只有一個 goal,而那條路一直失敗,不就無限迴圈了嗎?
Embabel 對此有三層防護,責任由上而下遞減:
@AchievesGoal,讓 planner 在主路徑走不通時有替代出口可收斂。StuckHandler.handleStuck()。agent 可以自行補資料進 blackboard 後請求 REPLAN,或乾脆回報失敗。好的設計應該在第一層就解決問題。如果你的 agent 經常觸發 StuckHandler,或常被 EarlyTerminationPolicy 中止,那代表 GOAP 模型本身需要重新設計——保險絲一直燒,不是換更粗的保險絲,是去看電路哪裡有問題。
執行視角要補上的重點是:程式碼宣告的是能力,AgentProcess 負責把一次使用者任務跑起來,保存輸入、目前狀態、執行事件與最後結果。也就是說,action 是零件,AgentProcess 是一次流程實例。
前面那張表提到 Autonomy 可以讓 LLM 自己挑 agent,但實務上還有一種更常見的情況:使用者一句話裡包含好幾個問題。像「客戶流失風險 + 上月營收 + 客訴摘要」這種複合查詢,通常不是靠單一 agent 一口氣完成,而是:
值得注意的是,這段編排通常不寫在 @Action 裡,而是寫在 service 或 controller 層:
public record SubQuerySet(List<String> items) {}
public record PartialInsight(String topic, String summary) {}
public record FusedInsight(List<PartialInsight> insights) {}
@Service
public class InsightOrchestrator {
private final Autonomy autonomy;
private final AgentPlatform agentPlatform;
public InsightOrchestrator(Autonomy autonomy, AgentPlatform agentPlatform) {
this.autonomy = autonomy;
this.agentPlatform = agentPlatform;
}
/**
* 將複合查詢拆解後,逐一交給最合適的 agent,再融合成單一結果。
*/
public FusedInsight analyze(String userIntent) {
SubQuerySet queries = new SubQuerySet(List.of(
"分析流失風險",
"分析上月營收",
"整理近期客訴"
));
List<PartialInsight> results = new ArrayList<>();
for (String item : queries.items()) {
AgentProcessExecution execution =
autonomy.chooseAndRunAgent(item, ProcessOptions.DEFAULT);
results.add((PartialInsight) execution.getOutput());
}
return new FusedInsight(results);
}
}
這段有三個地方值得留意:
autonomy.chooseAndRunAgent(...) 就是上表 Closed mode 的實際樣子——讓 LLM 從已註冊 agent 中挑一個最適合的FusedInsight
換句話說,多 agent 協作不是把更多東西塞回單一 @Action,而是讓每個 agent 各自完整,再由上層決定怎麼組合。
寫一份 AgentProcess 執行軌跡表:每列包含 action、輸入物件、輸出物件、Blackboard 新增內容與可能的 replanning。
接著再想一題:如果把這條流程拆成多個 agent,哪個輸出型別應該獨立成融合專用的 record?
如果你也想進一步學習如何透過 AI 開發 Spring Framework 應用,讓 AI 協助理解框架、撰寫程式、除錯與驗>證,歡迎到 Hahow 看凱文大叔的最新課程【駕馭 AI 的全端實戰養成班:從零打造企業級智慧應用系統】。一起>學習如何駕馭 AI,提升 Spring 應用的開發效率與品質。
課程連結