📣 工商時間

這期示範的法律關係圖網站,是用 Embabel 框架開發的系統,細節都在鐵人賽 30+1 天的文章裡。但能看懂它、審查它、改動它,靠的不是哪一個框架,是基本功。

《AI 賦能全端開發:從零打造企業級智慧應用》教的就是這套基本功:用 Spring Boot + React 建好後端 API、資料庫、認證授權與前端介面,再加上 Spring AI、RAG 檢索、tool calling 與 MCP,親手完成一套企業級 AI CRM。

基本功打穩之後,換成 Embabel 也好、換成別的框架也好,你都能讓 AI 用不同的工具做出你需要的功能,交出一套完整的系統,而不是只會跟著某一個框架的範例走。

👉 前往 Hahow 課程頁

把 Agent 的「技能包」搬上網頁,中間到底要補什麼?

嗨,我是凱文大叔。

寫完 30 天鐵人賽之後,我又補了第 31 篇。因為有個問題一直沒回答:

技能(Skill)只能在 CLI 裡用嗎?不會用 CLI 的人怎麼辦?

我拿自己的台灣法律技能包 law-powers 做了實驗,把它整包搬進一個 Spring Boot 4.1 + Embabel 1.5.1 的網站,跑在這裡:

👉 law-graph-webmcp.zeabur.app

貼上案情,網站會照技能的流程做:腦力激盪 → 追問 → 檢索法條判決 → 要件涵攝 → 抗辯評估 → 起草書狀 → 畫出 3D 法律關係圖。

順帶一提,這個網站本身也是用技能蓋出來的:後端的 Embabel 寫法、前端把能力開給 Agent 的 WebMCP 寫法,都來自我在鐵人賽整理的技能套件 ai-agent-dev-guide。文末會告訴你怎麼一行指令裝進 Claude Code。

以下是搬家過程中,我覺得最值得分享的幾件事

021-a-skill-to-web-pipeline

① 技能本體其實不用改寫

這是最少人知道的一招:embabel-agent-skills 可以直接讀 Claude Code 格式的技能目錄,變成能掛進 prompt 的 reference。

// 直接載入 Claude Code 格式的技能目錄,不需要改寫成別的格式
Skills skills = new Skills("law-powers", ..., new DefaultDirectorySkillDefinitionLoader(false))
        .withLocalSkill("skills/legal-research");

llm(context).withReference(skills)          // ← 技能包在這裡進 prompt
            .createObject(prompt, BrainstormResult.class);

路徑就是:CLI 技能 → withLocalSkill() → withReference() → 網頁服務。

技能包還是原本那個 repo,還能在 Claude Code 裡用,兩邊不會走鐘。

順帶一提:我的技能包有 10 個技能,網站只載入 5 個。技能不是載滿就好,每一份都佔 prompt 空間,跟工具白名單是同一個道理。

② 技能寫的是「敘述」,網頁要的是「不可跳過的骨架」

技能說「先腦力激盪,再檢索,再涵攝」。

在 CLI 裡,這是 Agent 自己決定要照做;在網頁上,使用者可能拿到一個跳過檢索的結果,那就完了。

所以我把每個步驟寫成一個 @Action,用 Java record 的型別串起來。analyze 要 ResearchResult,而 ResearchResult 只有 research 產得出來——Embabel 的 GOAP 會自己排出順序,程式裡沒有任何一行 if (step == 3)。

技能的章節標題,通常就是你的 Action 清單。

③ 流程要能停下來等人

法律案件第一次貼進來的案情,幾乎不可能夠用。有沒有簽書面契約?對方是自然人還是公司?這些會直接改變結論。

Embabel 一行解決:

@Action
public UserAnswers askUser(BrainstormResult brainstorm) {
    // 沒有問題就直接回空物件往下走,不為了對稱硬停一次
    if (brainstorm.questions().isEmpty()) return new UserAnswers(List.of());
    // 有問題才暫停流程,等使用者作答後再繼續
    return WaitFor.awaitable(new QuestionsAwaitable(brainstorm.questions()));
}

兩個細節:這個 Action 完全不呼叫 LLM(零 token);沒問題就直接回空物件往下衝,不為了對稱硬停一次。

④ 最容易漏掉的一層:把技能裡的「請務必」變成程式擋得住的規則

技能文件寫「請務必引用真實存在的法條」。

這句話在 CLI 靠 Agent 自律;在公開網站上,它必須是程式。

所以建圖那步 LLM 產出之後,一定會再過一層純函式 GraphRules.apply():

  • 法條節點的條號必須真的出現在檢索結果裡,否則整個節點移除。
  • 要件是否該當,一律以涵攝結果覆寫,不採信建圖那步的說法。
  • 被移除的還會寫進 notes,不默默吃掉。

021-b-must-to-code-guard

👉 搬家時請對技能裡每一句「務必/禁止/只能」問一次:如果模型沒照做,我的程式擋得住嗎? 擋不住的,就補一段後處理。

⑤ 還有一層技能完全沒寫:治理

因為 CLI 是你自己的機器。上線就全部要補:

  • 每日 token 預算
  • 每人每日配額
  • 登入身分
  • 用量統計
  • 個資告知與刪除
  • 授權排除條款

這層不做完,不要公開網址。

最後一個有趣的迴圈

網站再透過 WebMCP 把 22 個工具暴露給 ChatGPT/Chrome Agent,讓 Agent 也能操作這個頁面。

但我刻意沒有做 submitQuestions 這個工具——Agent 只能把建議答案填進欄位,送出永遠要人按。

技能從 Agent 手上搬到網頁給人用,網頁又把能力還給 Agent,只是這次在人的監督之下。

搬家前先自問的五句話

把這期壓成一張清單,下次要把任何技能搬上網頁時拿出來對一次:

層 技能裡長什麼樣 網頁上必須變成什麼
① 技能本體 Claude Code 格式目錄 原封不動,用 withReference() 掛進 prompt;只載入用得到的
② 流程 章節敘述「先 A 再 B」 每章一個 @Action,用型別串起順序
③ 追問 「資訊不足時請追問」 零 token 的 WaitFor Action,沒問題就直接往下
④ 規則 「請務必/禁止/只能」 LLM 產出後的純函式後處理,擋不住的補一段
⑤ 治理 (完全沒寫) 預算、配額、身分、用量、個資、授權——做完才公開

蓋這個網站用的技能套件,也開源了

鐵人賽 30 天寫完後,我把整個系列整理成一套 Claude Code 技能,放在 GitHub:

👉 github.com/kevintsai1202/ai-agent-dev-guide

裡面有四個技能,互相交叉引用:

技能 負責哪一層 這期用在哪
ai-agent-dev-guide 入口/路由 不確定該用哪個技能時從這裡進;含「值不值得用 Agent 框架」判準
embabel-agent-backend 後端(Embabel) ②③④ 的 @Action、GOAP、WaitFor、@Condition 寫法
json-render-ui 前端(不綁後端) LLM 產出 UI 規格、SSE 漸進渲染
webmcp-development-guide 對外給 Agent(選用) 「最後一個迴圈」那 22 個 WebMCP 工具

裝法只要兩行,在 Claude Code 裡輸入:

/plugin marketplace add kevintsai1202/ai-agent-dev-guide
/plugin install ai-agent-dev-guide@ai-agent-dev-guide

或者用 skills CLI 一次裝進所有支援的 Agent:

npx skills add kevintsai1202/ai-agent-dev-guide

基準版本是 Spring Boot 4.1 + Embabel 1.5.1 + Spring AI 2.0。還在 Spring Boot 3.5 的專案請留在 Embabel 1.0 那條線,技能裡的版本相容章節有寫清楚兩條線不能混。

完整文章(含四項「值不值得用 Agent 框架」的判準、@Condition 踩坑實錄)在鐵人賽 Day 31:

👉 鐵人賽 Day 31 全文

—— 凱文大叔


延伸閱讀: