
到目前為止,我們討論的 Embabel 都是基於預設的 單線 GOAP(Goal-Oriented Action Planning):從當前的世界狀態出發,透過 A* 演算法在狀態空間中計算出一條總成本(Cost)最低的唯一最佳路徑,然後一步一步執行直到達成 Goal。
然而,在複雜的企業真實世界中,業務需求往往不是單一路徑能解決的:
今天我們將一口氣拆解 Embabel 提供的四種規劃器(Planners)、Scatter-Gather 並行模式與多模型共識機制。

Value - Cost),適用於開放式客服與即時分流。rightarrow 合成 rightarrow 終止,適用於 Reducer 數據管線。
在選擇規劃器時,核心判斷準則是「目標是否明確」與「路徑是否動態」:若有固定終點且講求最小代價選 GOAP;若為開放式互動、即時最大價值選 Utility;多來源並行聚合則選 Hybrid 與 Scatter-Gather。
與 GOAP 尋找「整體最小成本」不同,Utility Planner 在每一步評估中,計算的是當前所有可用 Action 的 淨效用(Net Utility):
Utility = Value - Cost
value:代表執行該 Action 能為系統帶來的業務價值(0.0 ~ 1.0)。cost:代表調用延遲、Token 費用與資源開銷。Utility 最高的 Action 執行,天然適用於客服對話中動態判斷「該優先安撫情緒、直接退款,還是引導填表」。Embabel 內建 ScatterGatherBuilder,能夠將一個資料物件同時扇出(Fan-out)給多個獨立 Action / Sub-Agent 進行非同步並行處理,並透過一個強型別聚合器(Aggregator / Reducer)進行扇入(Fan-in)合併:
專為高合規場景設計的共識架構:
以下實作一個完整的進階範例,展示:
package com.antechinus.travel.agent;
import com.embabel.agent.annotation.AchievesGoal;
import com.embabel.agent.annotation.Action;
import com.embabel.agent.annotation.Agent;
import com.embabel.agent.annotation.Cost;
import com.embabel.agent.api.PlannerType;
import org.springframework.stereotype.Component;
/**
* 基於 Utility Planner 的客服意圖分流 Agent
* 每一步由演算法根據 (Value - Cost) 最大化自動選取最佳動作
*/
@Component
@Agent(
description = "智慧客服動態分流與處置",
planner = PlannerType.UTILITY
)
public class SupportUtilityAgent {
public record UserInquiry(String text, boolean isVip, boolean isAngry) {}
public record RoutingDecision(String department, String priorityLevel) {}
public record FinalHandledTicket(String ticketId, String actionSummary) {}
/**
* 處理 VIP 客戶緊急投訴 Action
* 設定極高 Value,確保符合條件時優先觸發
*/
@Action(cost = 0.05, value = 0.98)
public RoutingDecision handleVipUrgent(UserInquiry inquiry) {
if (inquiry.isVip() && inquiry.isAngry()) {
return new RoutingDecision("VIP_EXECUTIVE_DESK", "P0_CRITICAL");
}
// 若條件不符回傳 null,Utility 評估將自動忽略
return null;
}
/**
* 處理一般帳單問題 Action
*/
@Action(cost = 0.01, value = 0.70)
public RoutingDecision handleBilling(UserInquiry inquiry) {
if (inquiry.text().contains("發票") || inquiry.text().contains("退費")) {
return new RoutingDecision("BILLING_TEAM", "P2_NORMAL");
}
return null;
}
/**
* 達成終端處理目標
*/
@AchievesGoal(description = "完成客服工單建檔與分流")
@Action(cost = 0.02, value = 0.95)
public FinalHandledTicket finalizeTicket(RoutingDecision decision) {
String ticketId = "TICK-" + System.currentTimeMillis();
return new FinalHandledTicket(ticketId, "已分派至: " + decision.department() + ",等級: " + decision.priorityLevel());
}
}
package com.antechinus.travel.agent;
import com.antechinus.travel.domain.OfferDraft;
import com.antechinus.travel.domain.ReviewedOffer;
import com.embabel.agent.api.Ai;
import com.embabel.agent.api.ScatterGatherBuilder;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* 多模型並行共識審核服務
* 利用 Scatter-Gather 同時調用多個 LLM 交叉比對方案合規性
*/
@Service
public class MultiModelConsensusService {
private final Ai ai;
public MultiModelConsensusService(Ai ai) {
this.ai = ai;
}
public record ModelAuditVote(String modelName, boolean approved, String reason) {}
/**
* 執行多模型並行投票審核
*
* @param draft 方案草稿
* @return 最終審核結果
*/
public ReviewedOffer evaluateOfferConsensus(OfferDraft draft) {
String prompt = "請以嚴格風控標準審核此優惠方案是否合規(折扣不可大於20%):" + draft.description();
// 1. 定義多個並行審核任務 (Fan-out)
var gpt4Task = CompletableFuture.supplyAsync(() -> {
var res = ai.withModel("gpt-4o").createObject(prompt, ModelAuditVote.class);
return new ModelAuditVote("GPT-4o", res.approved(), res.reason());
});
var claudeTask = CompletableFuture.supplyAsync(() -> {
var res = ai.withModel("claude-3-5-sonnet").createObject(prompt, ModelAuditVote.class);
return new ModelAuditVote("Claude-3.5", res.approved(), res.reason());
});
var miniTask = CompletableFuture.supplyAsync(() -> {
var res = ai.withModel("gpt-4o-mini").createObject(prompt, ModelAuditVote.class);
return new ModelAuditVote("GPT-4o-mini", res.approved(), res.reason());
});
// 2. 聚合多模型結果 (Fan-in / Reducer)
List<ModelAuditVote> votes = List.of(gpt4Task.join(), claudeTask.join(), miniTask.join());
long approvalCount = votes.stream().filter(ModelAuditVote::approved).count();
boolean consensusReached = approvalCount >= 2; // 多數決 (2/3)
String summaryNote = String.format("共識投票: %d/3 通過 (明細: %s)",
approvalCount,
votes.stream().map(v -> v.modelName() + ":" + v.approved()).toList());
return new ReviewedOffer(
draft.customerId(),
draft.discountPercent(),
draft.description(),
consensusReached ? "APPROVED" : "REJECTED",
summaryNote,
Instant.now()
);
}
}
429 Too Many Requests。String 或通用 Map<String, Object>,導致後續聚合器根本無法透過型別精確比對資料來源。CrmRiskResult、FinanceCreditResult)。
| 業務場景 | ❌ 錯誤選型 (Bad) | ✅ 正確選型 (Good) | 原因說明 |
|---|---|---|---|
| 金融貸款審批 | Supervisor Planner | GOAP Planner | 審核流程必須 100% 確定、可重現且具備法律合規性 |
| 動態智慧客服 | 硬編碼 if-else / GOAP | UTILITY Planner | 使用者提問充滿隨機性,需依淨效用(V - C)動態反應 |
| 百萬級優惠方案 | 單一 LLM Action 審查 | Scatter-Gather 共識審查 | 透過多模型投票防範單一模型的幻覺與誤判風險 |
| 大規模數據萃取 | 串行 for 迴圈執行 | 並行 Fan-out + Reducer | 充分利用 Java 21 虛擬執行緒大幅縮短端到端延遲 |
PlannerType.UTILITY 的分流器,宣告 2 個具備不同 cost 與 value 的 Action。