iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
Software Development

事件驅動電力交易平台:Spring Boot 實戰系列 第 26

Day 26 | eap-ai-client 技術棧解剖:讓模型會想、工具會做、工程師少加班

  • 分享至 

  • xImage
  •  

把技術選得對,之後每一次需求變動都像換鞋帶,而不是換腳。以下是我在 eap-ai-client 採用的技術棧與背後取捨,聚焦「可維護、可治理」。

在講完我的mcp工具實作後,此篇文章要來介紹我用來串接LLM模型的服務,這個服務初衷是希望我能透過自然語言來對我的eap工具進行操作,像是我可以輸入"請幫我依據現在市場最低賣價,買入20單位的電力"那LLM就會幫我們呼叫mcp工具來進行操作,那也可以使用自然語言來進行整體市場環境的模擬。

指導原則(為什麼這樣選)

  • 可替換:LLM provider、MCP 端點、策略模組都能平滑替換,避免被單一實作綁死。
  • 可治理:對話 → 規劃(Plan)→ 工具執行 全程可控、能審計,狀態改變需「顯式確認」。

核心 Runtime 與建置

  • Java 17:長期支援、工具鏈成熟、企業穩定度高。對我們這類服務導向專案,17 版已足以支撐效能與生產維運,不必為了追新特性而增加風險。
  • Spring Boot 3.x + Spring AI 1.x(Stable):這組合在 Java 17 上表現穩定。Spring AI 主要提供 ChatClient 等門面與統一 API,配合 Boot 的自動配置即可落地;後續若更換 LLM provider,僅調整設定而不動核心碼。
  • Gradle(版本目錄 + BOM):用 version catalog 管版本、用 Spring BOM 管相依,減少「鑲嵌版號地獄」。

LLM 整合層(Spring AI / ChatClient)

  • ChatClient 作為 LLM 門面:把 provider(Ollama / OpenAI / 其他)抽象化,支援流式/非流式回應。
  • 結構化輸出:要求模型回傳 Plan JSON(例如 actiontoolNameparamsplanVersion),
    Jackson + Jakarta Validation@NotNull@Pattern)做 Schema 檢核,拒收不合格回覆。
  • 安全護欄:Prompt 注入防護(只允許白名單工具)、可執行動作需二次確認(見下文狀態變更閘門)。
  • 多供應商切換(LLM × MCP 解耦):我把 ai-service 寫成對外是介面,對內是策略規劃
    • 對 LLM:以 @ConfigurationProperties 設定 llm.provider/baseUrl/model/apiKey,由 Profile/配置決定注入哪個 Adapter(OpenAI、Ollama…)。切換供應商=改設定+換 Adapter Bean,不動業務程式。
    • 對 MCP 工具:只依賴 McpToolClient 這個 Port;工具清單或傳輸協定(HTTP → gRPC)變動由底層 Adapter 吸收。ai-service 不管工具實作細節,維持和 LLM 一樣的解耦關係。

ai-service 只負責「理解與下決策」→「呼叫工具」的編排;供應商與工具實作細節都被 Adapter 吃掉,日後要換 LLM 或擴工具,不會牽動核心服務。


MCP Client 層(呼叫 eap-mcp 的工具)

  • Port/Adapter 設計McpToolClient 作為 Port,底層以 WebClient/Feign Adapter 實作;
    日後工具清單擴充、換傳輸協定(HTTP→gRPC)都不影響 Service。
  • 狀態變更閘門
    • Read-only 工具(如 getOrderBook, getMarketMetrics)直接放行。
    • State-changing 工具placeOrder, cancelOrder, registerUser)走確認機制:
      1. LLM 輸出 Plan → 2) AiChatService 檢視 action → 3) 未帶「確認旗標」則回要求確認的自然語言 → 4) 收到確認後才呼叫。
  • 韌性Resilience4j 限流/重試/熔斷;對 MCP 設 connect/read timeout 與退避策略。

組態與密鑰管理

  • @ConfigurationPropertiesllm.*mcp.*resilience.* 一次綁定,避免硬編碼。
  • Profiledev/staging/prod 切換 endpoint、重試策略、模型名稱。

錯誤處理與回饋策略

  • 分類錯誤
    • UserInputError(使用者/模型參數不完整)
    • ToolRejectedError(未經確認或政策禁止)
    • DownstreamError(MCP 或下游)
    • SystemError(不可預期)
  • 對話回饋:把錯誤翻成人話(含建議下一步),避免只丟 stacktrace。
  • 分級告警:工具拒執行 ≠ 服務掛點;DownstreamError 需告警與抖動保護。

資料契約與版本化

  • Plan Schema:固定欄位 + extra 擴充位,避免破壞性變更。
  • 工具契約:對 MCP 的 API 以 OpenAPI/JSON Schema 管理,避免「對話說得出、工具做不到」。

安全與治理

  • 工具白名單:只允許註冊過的工具被呼叫;陌生 toolName 直接拒絕。
  • 參數白名單/黑名單:對金錢、數量、標的做「合理範圍」檢核;超出門檻必須二次確認。
  • 審計日誌:誰的 prompt 觸發了什麼工具、帶了哪些參數、執行結果如何,需可追溯。

架構關係圖(文字版)

使用者/系統 → AiChatController → AiChatService
  → ChatClient(LLM)
      ↘ Plan(JSON) 驗證/正規化
         →(Read-only? 直接)
         →(State-changing? 要確認)
             → McpToolClient → eap-mcp(工具)→ 下游服務

我為什麼喜歡這組合?

  • 低耦合:LLM 只是「產生意圖」,真正的執行在 MCP 工具,權責清晰。
  • 高可控:有 Schema、有閘門、有觀測,壞事發生得了、也抓得到。
  • 能演進:換 LLM、擴工具、加策略,不需要「推倒重練」。

下一篇(Day 27)預告

把上面理念落進程式:application.yml@ConfigurationProperties 的切分、ChatClient/MCP 的 Bean 設計、Resilience4j 與 OTel 的最小實用配置,一鍵換 Provider、不改核心碼



上一篇
Day 25 | Mcp Server 技術總結
系列文
事件驅動電力交易平台:Spring Boot 實戰26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言