
恭喜你!歷經 30 天的深度探索,我們從最底層的 JVM Agentic AI 核心哲學(Day 01~04),走過 Embabel GOAP 目標導向規劃與黑板狀態機(Day 05~10),完成了 工程維運、測試體系與可觀測性防線(Day 11~20),接著掌握了 現代 Generative UI 核心框架 json-render 的 Flat Spec、Catalog 與串流漸進解析(Day 21~25),最後在過去五天將這兩個強大技能合成為一個具備生產級強度的動態儀表板專案(Day 26~29)。
而這 30 天談過的所有觀念——GOAP 規劃、黑板狀態、確定性資料、Flat Spec、串流容錯、稽核與測試——最後都被濃縮成一套可以直接載入使用的技能(Skill)。換句話說,你不需要把 30 篇文章重讀一遍,也不必每開一個新專案就重新摸索一次架構:把技能載入 Claude Code 這類支援 Skill 的環境,就能用接近對話的方式,把一個 Embabel + json-render 的全端 AI 專案從零建起來。
所以最後一天,我們要完成三件收官大事:
技能不是拿來查 API 的線上文件,它更像一位已經讀完這 30 天、並且會逼你把防線補齊的資深隊友:先把需求整理成可執行的開發計畫,再建立 Embabel 後端 Agent 與資料流程,接著長出 json-render 前端與串流契約,最後補上測試、觀測、下鑽與上線檢核。你仍然要提供真實的業務規則與資料來源——這是技能不會、也不該替你決定的部分——但架構不必從頭想,關鍵防護也不會漏掉。
這套技能來自 ai-agent-dev-guide,包含三個互相銜接的開發角色:
| 建立/開發工作 | 使用技能 | 技能如何協助 | 主要產物 |
|---|---|---|---|
| 釐清需求與專案範圍 | ai-agent-dev-guide |
判斷是否適合使用 Agent,安排後端、契約、串流與前端的開發順序 | Fit assessment、MVP 範圍、全端開發計畫 |
| 建立 AI 後端 | embabel-agent-backend |
把業務流程拆成 GOAP actions,建立 Java records、@Agent、工具、Goal、測試與觀測 |
可規劃、可測試、可稽核的 Embabel Agent |
| 開發動態前端 | json-render-ui |
定義 {root, elements} UI 契約,對齊 catalog/registry,處理 POST SSE、半截 JSON、fallback 與下鑽 |
可漸進渲染的 json-render Dashboard |
| 串接與交付專案 | 三者一起使用 | 將自然語言輸入、Agent、確定性資料、DashboardSpec、SSE 和 E2E 驗收串成閉環 | Embabel + json-render 全端 AI 專案 |
DashboardSpec 的 flat tree,讓後端 catalog 與前端 registry 對帳;以 fetch + ReadableStream 接收 POST SSE,經過 lenientParse → sanitize → render 後逐步呈現畫面。換句話說,30 天課程提供的是觀念與範例,技能則把這些內容固化成「建立專案 → 開發後端 → 開發前端 → 串接 → 測試 → 上線」的可重複工作流。觀念會忘,工作流不會——這正是有了技能之後,要生出一個 Embabel + json-render 全端 AI 專案會輕鬆這麼多的原因。
理解了工具,接下來我們就用它來完成最後一塊拼圖:讓儀表板從「看完就結束」變成「可以一路追問下去」。

在 json-render 規範中,我們不讓前端寫死業務路由,而是由後端 Action 在組裝 DataTable 或 MetricCard 時,於 Props 中宣告 drillTemplate:
{
"type": "DataTable",
"props": {
"columns": [...],
"rows": [...],
"onRowClickAction": {
"type": "DRILL_DOWN",
"template": "深入分析客戶 {customerId} 在 {date} 的 {destination} 行程明細"
}
}
}
前端只需要一個通用的 onDrill(interpolatedQuery) 回呼函式,就能自動實現無縫的深層數據鑽取!
| 檢核項目 | 核心要求與標準 | 狀態 |
|---|---|---|
| 1. 雙 Goal 防護 | GOAP Agent 是否已具備「正常完成(DashboardSpec)」與「安全降級(EscalationTicket)」雙 Goal? |
✅ |
| 2. 資料確定性 | 大陣列與核心金額是否 100% 由 Java 填入,嚴禁交由 LLM 轉抄以杜絕幻覺? | ✅ |
| 3. 契約對帳 | 前端 Catalog 是否與後端 100% 對齊,且具備 SmartFallbackComponent 容錯容器? |
✅ |
| 4. 容錯解析 | 前端 SSE 是否啟用 lenientParse 與 sanitizeSpec 防護半截未閉合 JSON? |
✅ |
| 5. 資源清理 | SseEmitter 是否在 completion、timeout 與 error 回呼中徹底 unregister? |
✅ |
| 6. 連線緩衝 | Nginx / 反向代理是否已配置 proxy_buffering off; 避免 SSE 推播遭快取延遲? |
✅ |
| 7. 成本防護 | 是否已建立 ActionAudit 與單一請求 Token / 費用上限預警機制? |
✅ |
| 8. 安全防護 | 是否對使用者自然語言輸入實作 Prompt Injection 與惡意指令過濾? | ✅ |
| 9. 虛擬執行緒 | Java 21 Virtual Threads 是否已啟用以支援大量非同步 SSE 併發調度? | ✅ |
| 10. 測試覆蓋 | 是否涵蓋 Guardrail 單元測試、PromptRunner Mock 邊界測試與端到端驗收測試? | ✅ |
以下提供:
// src/components/interactive/DrillDownHandler.tsx
import React, { useState } from 'react';
interface DrillDownProps {
onExecuteQuery: (query: string) => void;
isBusy: boolean;
}
/**
* 通用動態下鑽處理器
*/
export const useDrillDown = ({ onExecuteQuery, isBusy }: DrillDownProps) => {
/**
* 處理表格點擊下鑽
* @param template 下鑽模板字串 (如: "深入分析客戶 {customerId} 的消費")
* @param rowData 當前點擊的資料列物件
*/
const handleRowDrill = (template: string, rowData: Record<string, any>) => {
if (isBusy) {
console.warn("當前正在生成中,略過重複點擊");
return;
}
// 將模板中的 {key} 替換為真實資料
let interpolated = template;
for (const [key, value] of Object.entries(rowData)) {
interpolated = interpolated.replace(new RegExp(`\\{${key}\\}`, 'g'), String(value));
}
console.log("[Drill-down] 觸發下鑽查詢:", interpolated);
onExecuteQuery(interpolated);
};
return { handleRowDrill };
};
package com.antechinus.travel.integration;
import com.antechinus.travel.domain.UserQuery;
import com.antechinus.travel.spec.DashboardSpec;
import com.embabel.agent.api.EmbabelClient;
import com.embabel.agent.api.ProcessResult;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import static org.assertj.core.api.Assertions.assertThat;
/**
* 全系統端到端驗收測試
* 驗證 3 大核心業務場景是否均能穩定產出合規之 DashboardSpec
*/
@SpringBootTest
@ActiveProfiles("test")
class FullstackE2EVerificationTest {
@Autowired
private EmbabelClient embabelClient;
@Test
@DisplayName("驗收場景 A:查詢客戶 1001,應成功產出包含 Stack, MetricCard, DataTable 的合法 Spec")
void testScenarioA_CustomerOverview() {
UserQuery query = new UserQuery("查詢客戶 1001 的年度消費與旅遊偏好");
ProcessResult<DashboardSpec> result = embabelClient.goal(DashboardSpec.class)
.withInput(query)
.run();
// 1. 流程必須成功
assertThat(result.isSuccess()).isTrue();
DashboardSpec spec = result.getOutput();
// 2. 驗證 Flat Spec 結構完整性
assertThat(spec.root()).isEqualTo("root_stack");
assertThat(spec.elements()).containsKey("root_stack");
assertThat(spec.elements()).containsKey("elem_table");
// 3. 驗證資料確定性 (DataTable rows 不可為空)
var tableElement = spec.elements().get("elem_table");
assertThat(tableElement.type()).isEqualTo("DataTable");
assertThat(tableElement.props()).containsKey("rows");
var rows = (java.util.List<?>) tableElement.props().get("rows");
assertThat(rows).isNotEmpty();
}
@Test
@DisplayName("驗收場景 B:輸入無效客戶編號,系統應順利降級而非崩潰")
void testScenarioB_InvalidCustomerFallback() {
UserQuery query = new UserQuery("查詢客戶 -999 的資料");
// 嘗試以通用目標執行
var result = embabelClient.run(query);
// 系統必須優雅終止,不可拋出未捕獲例外
assertThat(result.isCompleted()).isTrue();
}
}
最終成果:先查「營收前三的客戶」得到排行表格(第 1 層),點選任一列即下鑽出該客戶的 360 綜合分析(第 2 層)——LTV、訂單歷史、工單分類、AI 風險提醒與行動建議一次展開,上一層儀表板凍結保留在上方形成可回溯的探索軌跡。這就是 30 天旅程的終點:不只是生成一張畫面,而是一個可以一路追問下去的資料應用。

FullstackE2EVerificationTest,確保測試全部綠燈通過。「演算法管邏輯,Java 管數據,LLM 管溝通,Catalog 管邊界。」
當我們把不可預測的語言模型,妥善安置在 Embabel 確定性的 GOAP 導航軌道 與 json-render 受控的 UI 白名單 之中時,AI 就不再是一個隨機說胡話的玩具,而是真正能夠被企業信任、維運、測試與規模化落地的強大生產力工具。
如果這 30 天只讓你帶走一句話,我希望是這句:AI 應用真正的門檻,不在於它能不能生出畫面,而在於出事那天你能不能說清楚它為什麼這樣做。
Demo 只需要跑得起來,生產系統卻要面對「這個數字哪來的?」「誰核准的?」「這次為什麼比上次貴三倍?」這些會被追問到底的問題。而這正是前面那些看似麻煩的設計真正的用意:
ActionAudit 讓每次 Action 執行都帶著 traceId、耗時、Token 與成本落地,事後可以整條回放,而不是翻著一片狼藉的日誌猜測。不可追蹤的 AI 系統,會在第一次出錯時失去所有人的信任;可追蹤的 AI 系統,就算出錯也還能被修好、被交代、被繼續用下去。能不能上線,從來不是看它跑得多順,而是看它能不能被審查。
感謝你完成這趟 30 天的旅程,這套架構與這份可稽核的紀律,將成為你未來打造任何企業級 AI 應用的最堅實地基!🚀