[Agent Platform 實戰 (3)] 破解無狀態容器的 HITL 失憶症:用 ADK 2.0 GCS Artifact Service 與 Agent Platform Sessions 實現跨實例名片辨識與暫停恢復

前情提要 在前兩篇系列文章中: 《[Agent Platform 實戰 (1)] 用 Go 與 Google ADK 2.0 Graph Workflow 打造 LINE AI 名片王》:我們用 google.golang.org/adk/v2/workflow 實作了包含 Deterministic Router、AddFanOut / AddFanIn 三路平行背景調查,以及 NewRequestInputEvent Human-in-the-Loop (HITL) 暫停與迴圈修正 的 12 節點名片助理。 《[Agent Platform 實戰 (2)] 從單體 Cloud Run 邁向 Google Cloud Agent Runtime:用 agents-cli 與 ADK Go 重構 LINE AI 名片王》:我們導讀了 agents-cli 官方開發生命週期,解答了 Agent Runtime 的 Scale-to-Zero 機制,並完成 Phase 1(PR #10),將專案改造為同時支援 LINE Webhook、ADK WebUI Playground 與 A2A 協定的 adk_go 標準雙通道架構。 進入 Phase 2(Issue #3 & Issue #4),我們要面對把這套系統推向生產環境(Cloud Run / Agent Runtime 自動擴縮容叢集)時最經典、也最容易踩坑的分散式系統難題:當容器隨時會 Scale to Zero 或負載平衡到不同實例時,多模態名片照片(Binary Image)該放哪裡?正在等待使用者按按鈕的 HITL 暫停狀態又該如何跨實例恢復? 這篇系列文的第三集,就帶大家深入拆解我們在 PR #11 中如何用 ADK Go v2.6.0 的 artifact/gcsartifact (GCS Artifact Service) 與 session/vertexai (Agent Platform Sessions) 徹底治好無狀態容器的「HITL 失憶症」! 一、為什麼原本的 HITL 在雲端多實例環境會「失憶」與「爆肥」? 在單機或單一 Container 實例開發時,把所有東西塞進記憶體都跑得飛快;但一旦部署到會自動水平擴縮(Horizontal Auto-scaling)或縮容至 0(Scale-to-Zero)的 Google Cloud Run 或 Vertex AI Agent Runtime,原本第一版的設計就會馬上撞上兩座高牆: 痛點 1:把幾 MB 的名片照片 []byte 塞進 Session.State 的「狀態爆肥」反模式 在第一版中,當 LINE 使用者傳來一張高解析度名片照片時,我們是直接把下載下來的 imageBytes []byte 放進 ADK 的 Session.State["image_bytes"]: // ❌ 第一版作法:直接把幾 MB 的原始圖片 []byte 塞進 Session...
繼續閱讀

[Agent Platform 實戰 (2)] 從單體 Cloud Run 邁向 Google Cloud Agent Runtime:用 agents-cli 與 ADK Go 重構 LINE AI 名片王

前情提要 在上一篇文章《[Agent Platform 實戰 (1)] 用 Go 與 Google ADK 2.0 Graph Workflow 打造 LINE AI 名片王》中,我們用 Golang (google.golang.org/adk/v2/workflow) 與 LINE Messaging API SDK v8 從零打造了 line-card-assistant(LINE AI 名片王),把 ADK 2.0 Graph Workflow 的四大核心能力 —— Deterministic Router 確定性路由、AddFanOut / AddFanIn 平行背景調查與匯聚、以及 NewRequestInputEvent Human-in-the-Loop (HITL) 暫停與迴圈修正 —— 完整跑通並部署到了 Google Cloud Run。 不過,當我們把這個單體式(Monolithic)的 LINE Bot 推上雲端後,若要進一步往企業級 AI Agent 平台(Production-grade Agent Platform)演進,馬上就會遇到幾個架構層級的叩問: 跨實例的 HITL Session 狀態保存在哪裡? 在第一版中,我們使用 session.InMemoryService() 搭配本地 SQLite。當 Cloud Run 因為流量擴縮(Auto-scaling)或重新部署而切換 Container 實例時,正在等待使用者按下「✅ 確認儲存」的 HITL 暫停狀態(Checkpoint)就會因為記憶體重置而消失。 如何讓這個名片 Agent 被其他 Agent 呼叫(Agent-to-Agent, A2A)? 如果公司內部還有另一個「CRM 業務助理 Agent」或「行事曆排程 Agent」,希望能直接透過標準 A2A 協定 查詢我們名片王裡的聯絡人或請它代寫破冰信,綁死在 LINE Webhook /webhook 入口的單體伺服器是無法直接對外提供標準 Agent 介面的。 如何有系統地評估(Evaluate)與觀測(Observe)整張 Graph? 改了一行 Prompt 或調整了一個節點後,要怎麼確保 OCR 準確率、Fan-Out 平行工具呼叫軌跡(Tool Trajectory)與搜尋品質沒有退化? 為了解決從「寫出一個 Agent 原型」到「上線維運企業級 Agent」的鴻溝,Google 官方推出了全新的命令列與技能工具組 —— agents-cli (Google Agents CLI),作為 Google Cloud Agent Platform(現整合為 Gemini Enterprise Agent Platform) 的統一開發入口。 這篇系列文的第二集,我們就來深入導讀 agents-cli 官方開發指南,解答大家最常問的 Agent Runtime 擴縮容(Scale-to-Zero)與計費機制,並帶大家一步步看我們如何完成 Phase 1 架構升級(PR #10):在不破壞原本 LINE Bot 功能的前提下,將專案重構為符合 adk_go + agent_runtime 規範的雙通道架構! 一、深入解析 Google Cloud Agent Platform 與 agents-cli 官方指南 在動手改程式碼之前,我們先透過 Google 官方的《Getting...
繼續閱讀

[Agent Platform 實戰 (1)] 用 Go 與 Google ADK 2.0 Graph Workflow 打造 LINE AI 名片王:從 Fan-Out 平行檢索到 HITL 暫停迴圈修正

前情提要 在過去一年多的 AI Agent 開發熱潮中,許多開發者第一個寫出來的 Agent 往往長這樣:寫一段超長的 System Instruction,再把五、六個 Tools 全部塞給同一個 LLMAgent,然後祈禱模型能夠乖乖照著步驟 A → B → C → D 執行。 一開始做 Demo 很驚艷,但只要一上生產環境,問題馬上浮現: 步驟越多、失控率越高:當 Prompt 裡塞滿了「先做 OCR、再去查資料庫有沒有重複、同時上網查這家公司在做什麼、然後組出預覽卡片問使用者、如果使用者說要改職稱就重新產生預覽、確認後才能寫入 DB 並寫一封破冰信」,模型偶爾就會跳過查重、或是還沒等使用者確認就直接寫入資料庫。 不該花 Token 的地方也在燒錢與等待:像是「依據 LINE 事件種類分流」或「正規化電話號碼格式」這種 100% 確定性的邏輯,交給 LLM 判斷不僅慢,還可能出錯。 獨立任務被迫排隊執行:查資料庫、Google 搜尋公司背景、比對近期對話紀錄明明互不相干,卻因為單一 Agent 的線性對話迴圈而變成循序等待。 為了解決這個痛點,Google 在 Agent Development Kit (ADK) 2.0 正式引入了重磅功能 —— Graph Workflows(圖形工作流),並且同步支援 Python 與 Go (google.golang.org/adk/v2/workflow)! 看到這個更新後,我立刻決定用最熟悉的 Golang + LINE Messaging API SDK v8 實際打造一個完整的開源專案 line-card-assistant(LINE AI 名片王),把 ADK 2.0 Graph Workflow 的所有核心武器(AddRoute 條件路由、AddFanOut / AddFanIn 平行處理與匯聚、NewRequestInputEvent Human-in-the-Loop 暫停等待與迴圈修正)全部實戰跑過一遍,並部署上 Google Cloud Run。 這篇文章會先帶大家導讀 Google 官方針對 ADK Graph Workflows 必讀的兩篇核心技術文件,接著一步步拆解我們如何用 Go 程式碼將這些圖形工作流模式落地到 LINE Bot 中! 一、Google 官方怎麼定義 Graph Workflow?兩篇必讀指南解析 在開始看程式碼之前,強烈建議大家先理解 ADK 團隊設計 Graph Workflows 的核心哲學。Google 官方在近期發布了兩份非常關鍵的開發者指南: Google Cloud 官方部落格:Graph Workflows in ADK: Everything You Need to Know(由 ADK 團隊的 Annie Wang 與 Shangjie Chen 撰寫) ADK 官方技術文件:Graph-based agent workflows (adk.dev/graphs) 我們把這兩篇官方文章的精華濃縮整理如下: 1. 什麼是「Graph Engineering(圖形工程)」? 在《Graph Workflows in ADK: Everything You Need to Know》一文中,作者以一個「客服退費審核流程(Customer Refund Workflow)」為例,點出了一個核心觀念: 當工作流程變複雜時,可靠性來自於將「工作流控制權(Workflow Control)」與「模型推理(Model Reasoning)」分離。 Graph Engineering...
繼續閱讀

[AI 實戰] 從 Gemini 3.5 / 3.6 升級到 Gemini 4 Argon:寫給 LINE Bot 開發者的踩坑與遷移筆記

前情提要 我有一個自己每天在用的 LINE Bot,linebot-helper-python。日常拿來丟網址回摘要、丟 YouTube 連結針對影片內容追問、查地圖景點跟管書籤。在模型配置上,為了平衡回應速度與荷包,許多工作流先前陸續跑在 Gemini 3.5(像是負責高吞吐、低延遲分類與資料擷取的 gemini-3.5-flash-lite)以及 Gemini 3.6(負責多步驟工具呼叫與多模態推理的 gemini-3.6-flash)上。 十月中 Google 正式把 Gemini 4 Argon (gemini-4-argon) 推上 GA 穩定版(搭配 Python google-genai >= 2.30.1)。 看完官方公告與遷移文件,表面上最吸引人的是那些大數字:輸入、思考與輸出共用的 1M 統一 Token 視窗、單次呼叫自動接力跑到 100 萬輸出 Token 的 Long Decode、正式 GA 且省下 88% Token 的 Agentic Video Understanding,還有已經成為預設主流的 Interactions API (/v1beta/interactions)。 但如果你的目標跟我一樣,是想把現有 LINE Bot 裡面的 Gemini 3.5 / 3.6 升級(或部分切換)到 Gemini 4 Argon,千萬不要以為只是把 model="gemini-3.6-flash" 改成 model="gemini-4-argon" 就收工了。 這一代在參數規範、Token 預算計算方式、快取機制,甚至等待時間的假設上,都有幾個剛好打在 LINE Bot 痛點上的改變。這篇把從 3.5 / 3.6 轉移到 Gemini 4 需要注意的細節全部整理起來。 先看規格對照:3.5 Flash-Lite / 3.6 Flash vs. Gemini 4 Argon 在動手改設定之前,先把這三顆模型放在同一張表上看清楚它們的定位差異: 項目 gemini-3.5-flash-lite gemini-3.6-flash gemini-4-argon 定位 極速、低成本、高吞吐分類與結構化萃取 速度與多步驟 Agent / 多模態平衡 旗艦深度推理、複雜寫扣、長輸出與自主代理 Context Window 1,048,576 1,048,576 1,048,576(Input + Thinking + Output 共用) 單次最大輸出 65,536 65,536 generateContent:非串流 32,768 / 串流 65,536Interactions API:同步 65,536(Long Decode 最高皆可接續至 1M) 預設 thinking_level "minimal" "medium" "medium"(僅支援 low / medium / high) Context Caching 支援 支援 僅支援 Implicit Caching(呼叫 Explicit 會報錯) temperature / top_p / top_k 已棄用(忽略) 已棄用(忽略) 已棄用(忽略,官方要求移除) 定價 (Input /...
繼續閱讀

[AI 實戰] 用 Gemini 3.5 Transcribe 做跟讀評分:讓 Song Lingo 聽你念歌詞

前情提要 第一篇我用 Gemini 3.8 Flash TTS 做了 Song Lingo:貼 YouTube MV 網址,Gemini 轉錄歌詞、加上拼音翻譯文法,再由一位用 voice design 設計的老師逐句示範發音。第二篇把它部署到 Cloud Run,用 IAP 鎖到只有我自己能用。 部署完我在 GitHub 開了一份 roadmap,排了三件事:手機版面、跟讀評分、單字卡。手機版面已經做完了,這篇講的是第二件。 老師會示範,但我一直不知道自己念得對不對。聽十遍老師念,不如自己念一遍、被指出哪裡錯。這是 Song Lingo 從「聽歌看歌詞」變成「練發音」的關鍵一步。 Gemini 3.5 Transcribe 是什麼 Google 在 8/26 發布了 Gemini 3.5 Transcribe,是兩個專門做語音轉文字的模型: 模型 用途 gemini-3.5-transcribe 錄好的音檔,整段送出轉錄 gemini-3.5-transcribe-live 透過 Live API 的 WebSocket 即時串流,一邊講一邊出字 幾個重點功能: 85 種以上語言,會自動偵測,也能處理講到一半換語言的情況。 逐字時間戳:每個詞都有開始和結束時間(整段轉錄的版本才有)。 講者分辨:標出每句話是誰講的。 自訂詞彙:最多 1,000 個專有名詞,提高特殊用語的辨識率。 兩種模式:smart 會處理說話時的自我修正、拿掉語助詞、自動排版;verbatim 則是逐字照實轉錄。 官方部落格引用 Artificial Analysis 的測量:整段轉錄的平均字錯誤率(WER)2.6%,串流版 4.0%;和前一代 Chirp 3 比,拿到最終轉錄結果的時間快了 70%。價格沒有公布。 實際呼叫的方式和 TTS 一樣走 interactions API,但音檔要先用 Files API 上傳: uploaded = client.files.upload(file=audio_path, config={"mime_type": "audio/m4a"}) interaction = client.interactions.create( model="gemini-3.5-transcribe", input=[{"type": "audio", "uri": uploaded.uri, "mime_type": uploaded.mime_type}], generation_config={ "transcription_config": { "language_codes": ["ja-JP"], "mode": {"type": "verbatim", "timestamp_granularities": ["word"]}, } }, ) 支援的格式很多,其中 audio/webm 剛好是 Chrome 錄音的格式,audio/m4a 則對應 iPhone Safari 錄出來的 AAC。 選哪個模型 做跟讀評分,其實有三個候選: 模型 適合 用在跟讀 gemini-3.5-transcribe 錄完整段再轉錄 ✅ 有逐字時間戳,實測約 5 秒回來 gemini-3.5-transcribe-live 即時串流 一句歌詞只有幾秒,等念完再轉錄就夠了;要在 Cloud Run 和 IAP 後面維持 WebSocket 也麻煩得多 gemini-3.8-flash 這類多模態模型 直接聽錄音給文字講評 結果每次不一定一樣,也沒有保證的逐字時間戳,不適合當評分的主體 最後選了 gemini-3.5-transcribe。專門的語音轉文字模型,結果穩定,有逐字資訊,而且每次只用大約 40 個 token,不占 TTS 那每天 100 次的額度。...
繼續閱讀

[AI 實戰] 把 Song Lingo 搬上 Cloud Run:讓一個放著完整歌詞的網站,真的只有我自己看得到

前情提要 上一篇我用 Gemini 3.8 Flash TTS 做了 Song Lingo:貼 YouTube MV 網址,Gemini 轉錄歌詞、加上拼音翻譯文法,再由一位用 voice design 設計出來的老師一句一句念給你聽。 它一直只跑在我自己的電腦上,但我希望拿起手機就能用。所以這篇要做的事很單純:把它搬上 Cloud Run,讓手機也能用。 但這個網站有一個特殊的地方:頁面上是完整的歌詞和翻譯。 需求:「只有我看得到」要多準確 一般的 side project 部署上去,被別人看到頂多有點尷尬。Song Lingo 不一樣,公開有兩個實際的後果: 版權:上一篇花了一整節講,這是個人學習工具,歌詞只存在我自己的電腦。網站一旦公開,性質就從「個人學習」變成「對外提供歌詞」。 費用:加一首歌會呼叫 Gemini Flash,第一次播放示範音會呼叫 TTS。任何人都能用,就等於任何人都能花我的額度。 所以目標不是「有登入功能」,而是從頭到尾每一層都確認過,只有我的帳號能碰到歌詞和 API。 我先比較了兩個方案:   A. IAP + 程式內驗證 B. 不開放外部,用 gcloud run services proxy 能用的裝置 任何瀏覽器,包含手機 只有登入 gcloud 的電腦 設定難度 中等 低 出錯的可能 低 最低,根本沒有公開入口 B 是最安全的,但手機不能用,而手機正是這次部署的理由。所以選 A。 第一版設計的漏洞:簽署網址 我最初的規劃是這樣:音檔放 GCS,播放時由 API 產生一個短時效的簽署網址(signed URL),把瀏覽器重新導向過去。好處是音檔不經過 Cloud Run,省流量,GCS 本身也支援 Range 請求。 寫到一半重新想「怎樣才能準確地只有我看得到」時,才發現這是一個漏洞: 簽署網址在有效期限內,任何拿到網址的人都能直接下載,完全不經過 IAP。 它本質上是一張不記名的通行證。網址出現在瀏覽器歷史、被貼到哪裡、被某個擴充功能記下來,別人就能繞過前面所有的登入檢查。 原因與解法:「只有我能存取」是一整條鏈,強度取決於最弱的那一環。最後的做法是不使用簽署網址,音檔一律由 Cloud Run 讀出來再送給瀏覽器,每一次播放都要先通過 IAP 和程式內的驗證。一段音檔 250KB 左右,多繞一段路的流量成本可以忽略。 架構 部分 做法 容器 一個映像同時裝 Node 22 和 uv/Python,Next.js 直接呼叫原本的 Python 腳本 歌曲資料與音檔 私人的 Cloud Storage bucket,掛載成 /data 資料夾 API key Secret Manager,以環境變數提供 存取控制 IAP + 程式內驗證 IAP 的簽章 執行個體 最多 1 個,最少 0 個;CPU 持續分配 幾個決定的理由: 把 bucket 掛載成資料夾,而不是改寫成呼叫 GCS API。程式原本就是讀寫 output/ 資料夾,掛載之後只要把 SONG_DATA_DIR 指到 /data,幾乎不用改程式。 只跑 1 個執行個體。避免重複產生音檔、額度用完時暫停呼叫、加入新歌的進度,這些狀態都存在記憶體裡;掛載的 bucket 也沒有跨執行個體的鎖定。個人使用,1 個就夠,而且這樣上一篇那些防止重複計費的機制才會有效。 CPU 持續分配(--no-cpu-throttling)。「加入新歌」是在回應送出後繼續在背景跑轉錄和分析的,Cloud Run 預設會在回應送出後限制 CPU,背景工作會卡住。 四層防護 最後的存取控制是四層,任何一層單獨出錯,其他層還是擋得住: Cloud Run 權限:--no-allow-unauthenticated,只有 IAP 的服務帳號能呼叫這個服務。 IAP:只有被授予 roles/iap.httpsResourceAccessor...
繼續閱讀