May
1st,
2026
前情提要 在解決了 LINE Bot 的 Vertex AI 遷移後,我開始思考:能不能有一個「更具主動性」且「擁有長期記憶」的 AI 助手?這時候我盯上了 NousResearch 開源的 Hermes Agent 。 不同於一般的 Chatbot,Hermes 被設計成一個「會呼吸的操作系統」,它能自己執行 Shell 指令、寫 Python 腳本、管理長期記憶,甚至能透過不同的 Gateway (Telegram, Discord) 隨時與你保持聯繫。 為了讓它能 24/7 待命,我選擇將它部署在 Google Compute Engine (GCE) 上。這篇文章將紀錄從零開始的部署過程,以及我在配置最新的 Gemini 2.5 Flash 模型時踩過的那些坑。 環境參數預備 在開始之前,請確保你手手上這這些必要的參數: PROJECT_ID: YOUR_PROJECT_ID LOCATION: global GOOGLE_API_KEY: YOUR_GOOGLE_API_KEY (Google AI Studio 取得) 第一步:建立 GCE 實例 Hermes Agent 需要一點運算能力來處理工具調用(Tool Use),建議使用 e2-medium 規格。 gcloud compute instances create hermes-agent-vm \ --project=YOUR_PROJECT_ID \ --zone=us-central1-a \ --machine-type=e2-medium \ --image-family=ubuntu-2204-lts \ --image-project=ubuntu-os-cloud \ --boot-disk-size=30GB \ --metadata=startup-script='#!/bin/bash apt-get update apt-get install -y git curl python3-pip python3-venv nodejs npm ' 第二步:安裝 Hermes Agent SSH 進入 VM 後,直接使用官方提供的一鍵安裝腳本。 進入 VM: gcloud compute ssh hermes-agent-vm --zone=us-central1-a 執行安裝: curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash source ~/.bashrc 第三步:配置 Gemini 2.5 Flash (SOP 實戰) 這是整場演習最容易踩坑的地方。Hermes 預設可能會指向不存在或過期的模型標識符。 建立配置文件: 在 ~/.hermes/config.yaml 中,我們必須精確指定 Gemini 2.5 Flash,且 不可帶 google/ 前綴。 model: provider: gemini default: "gemini-2.5-flash" terminal: backend: local gateway: provider: telegram auxiliary: # ⚠️ 非常重要:Hermes 內部會硬編碼輔助模型,必須手動覆寫 title_generation: { provider: gemini,...
繼續閱讀
April
30th,
2026
前情提要 最近為了讓 AI 編碼助理(如 Claude Code 或 Gemini CLI)能直接在聊天平台上使用,我開始研究 OpenAB **。這是一個強大的橋接器,能將 Slack、Discord 或 Telegram 對接到符合 **ACP (Agent Client Protocol) 標準的 CLI 工具。 這篇文章紀錄了我將 OpenAB 部署在 Google Cloud 上的完整實戰過程,特別是如何繞過認證限制、處理 Telegram 的 HTTPS 需求,以及解決容器化部署中的路徑與權限問題。 OpenAB 參考文件: https://openabdev.github.io/openab/ OpenAB Repo: https://github.com/openabdev/openab 部署決策:為什麼選 GCE 而不是 Cloud Run? 雖然 Cloud Run 是我的首選,但在處理 OpenAB 時,Google Compute Engine (GCE) 才是最佳解決方案。原因有二: Stateful Session (狀態化對話):OpenAB 會為每個對話 thread 啟動一個子進程(如 Gemini CLI)。這些進程必須長期駐留以維持對話上下文。Cloud Run 的自動縮減機制會殺死這些進程,導致對話中斷。 認證持久化:AI CLI 的 Token 需要儲存在本地磁碟。GCE 配合 Persistent Disk 能保證重啟後登入狀態不消失。 實戰步驟:Step-by-Step 部署過程 第一步:撰寫自動化啟動腳本 (Startup Script) 為了讓部署標準化,我們撰寫了一個 setup-openab.sh。其核心任務是安裝 Docker、建立持久化目錄,並動態生成 config.toml。 最關鍵的部分是 自定義 Docker Image。由於 OpenAB 官方鏡像不一定包含所有 AI 工具,我們透過 Dockerfile 現場安裝 Node.js 與 @google/gemini-cli: FROM ghcr.io/openabdev/openab:latest USER root RUN apt-get update && apt-get install -y curl && \ curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ apt-get install -y nodejs && \ npm install -g @google/gemini-cli USER 1000 第二步:使用 gcloud 建立 GCE 實例 我們選擇 e2-medium 規格,並透過 Metadata 傳遞敏感資訊(如 Bot Token),避免寫死在腳本中。 gcloud compute instances create openab-server \ --project=your-project-id \ --zone=asia-east1-b \...
繼續閱讀
April
20th,
2026
前情提要 最近我們部署在 Google Cloud Run 上的 LINE 名片助理機器人 (linebot-namecard-python) 突然罷工了。透過 gcloud logging read 查了一下日誌,迎面而來的是這個無情的錯誤: google.api_core.exceptions.ResourceExhausted: 429 Your billing account has exceeded its monthly spending cap. 這是一個慘痛的教訓:我們當初為了快速開發,直接使用了 Google AI Studio 提供的 API Key(走 google.generativeai 套件)。隨著流量增加,我們很快就撞上了 Google AI Studio Tier 1 的 429 限制(Rate Limit / Quota 爆炸),結果默默把每月的免費額度給打爆了。 為了徹底解決這個問題,我們決定將所有依賴 AI Studio Gemini API Key 的應用程式,全面轉換成 Google Cloud Vertex AI,直接走 GCP 的企業級架構與計費系統。 這篇文章就來分享這次遷移的完整過程,以及途中發現「Vertex AI 的資訊實在太少,導致模型設定常常踩坑」的各種血淚經驗。 身份驗證升級:推薦使用 Workload Identity 在遷移的過程中,最重要的一環就是身分驗證。過去我們習慣在環境變數塞一個 GEMINI_API_KEY,簡單粗暴但充滿資安隱患。 來到 GCP 的世界,很多人第一直覺是去開一個 Service Account(服務帳戶),然後把 JSON 金鑰載下來放進程式裡。但強烈建議不要走 Service Account 金鑰這條路! 搭配 gcloud cli,其實可以很快速地為 Cloud Run 設定 Workload Identity。程式碼中不需要任何金鑰,只要在執行環境中賦予該 Identity 對應的 Vertex AI User 權限,Google Cloud SDK 就會自動取得憑證。這樣不僅大幅降低金鑰外洩的風險,管理起來也更輕鬆。 程式碼升級:從 AI Studio 轉向 Vertex AI 要將專案從 Google AI Studio SDK 遷移到 Vertex AI,主要有三個步驟: 替換依賴套件: 在 requirements.txt 中,移除舊的 google.generativeai,換成 google-cloud-aiplatform。 更新環境變數設定: 在 config.py 中,我們不再需要 API Key,而是改用 GCP 的 PROJECT_ID 和 LOCATION: PROJECT_ID = os.getenv("PROJECT_ID", None) LOCATION = os.getenv("LOCATION", "global") # 預設使用 global 核心程式碼改寫 (gemini_utils.py): Vertex AI 的 SDK 介面雖然類似,但對於多模態(如圖片)的處理稍微嚴格一點。我們需要將 PIL.Image 轉換成 vertexai.generative_models.Part 格式: import vertexai from...
繼續閱讀
April
15th,
2026
前情提要 在上一篇實戰中,我們利用 Gemini 3.1 Flash Live 實現了語音辨識,並透過 Gemini 2.5 Live API 的「側擊」方式勉強達成了朗讀摘要(TTS)功能。 但就在 2026 年 4 月,Google 正式發佈了 Gemini 3.1 Flash TTS。這是一個專門為語音輸出設計的原生模型,不再需要掛載 Live WebSocket,直接透過標準的 generate_content 流程就能輸出高品質音訊。 身為開發者,有更優雅、更原生的方案當然要立刻跟上。這篇文章就來分享如何把 LINE Bot 的朗讀摘要功能升級到 Gemini 3.1 Native TTS,以及過程中踩到的「異步大坑」。 技術升級:從 Live API 轉向 Native TTS 之前的朗讀功能是利用 Gemini 2.5 Live API 模擬出來的,雖然可用,但有幾個缺點: 複雜度高:需要管理 WebSocket 連線生命週期。 模型限制:必須使用特定的 native-audio 模型,且主要支援在 us-central1。 回傳格式固定:採樣率通常固定在 16kHz。 Gemini 3.1 Flash TTS 的出現改變了這一切: 模型名稱:gemini-3.1-flash-tts-preview。 介面一致:使用熟悉的 generate_content_stream。 動態參數:支援從回傳的 MIME type 自動偵測採樣率(通常提升到了 24kHz,音質更好)。 核心程式碼進化(tools/tts_tool.py) 新的實作變得更加簡潔,重點在於 response_modalities=["audio"] 這個設定: async def text_to_speech(text: str) -> tuple[bytes, int]: client = genai.Client(api_key=GOOGLE_AI_API_KEY, http_options={"api_version": "v1beta"}) contents = [ types.Content( role="user", parts=[ # 加入在地化指令,讓語氣更自然 types.Part.from_text(text=f"請使用台灣用語的繁體中文,以親切且自然的語氣朗讀以下摘要內容。## Transcript:\n{text}"), ], ), ] config = types.GenerateContentConfig( response_modalities=["audio"], speech_config=types.SpeechConfig( voice_config=types.VoiceConfig( prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name="Zephyr") ) ), ) pcm_chunks = [] sample_rate = 24000 # 預設值 try: # ⚠️ 這裡就是那個差點讓我修到天亮的大坑 response_stream = await client.aio.models.generate_content_stream( model="gemini-3.1-flash-tts-preview", contents=contents, config=config, ) async for chunk in response_stream: if chunk.parts: for part in chunk.parts: if part.inline_data: pcm_chunks.append(part.inline_data.data) # 從 MIME type 動態取得採樣率(例如 audio/L16;rate=24000) if part.inline_data.mime_type: sample_rate...
繼續閱讀
March
28th,
2026
前情提要 Google 在 2026 年 3 月底發佈了 Gemini 3.1 Flash Live,主打「讓音訊 AI 更自然、更可靠」。這個模型專門針對即時雙向語音對話設計,低延遲、可中斷、支援多語言。 剛好手邊有一個 LINE Bot 專案(linebot-helper-python)——它已經能處理文字、圖片、URL、PDF、YouTube,唯獨語音訊息完全不理: 用戶傳送語音訊息 Bot:(沉默) 這次就把語音支援補進去,順便分享幾個踩過的坑。 設計決策:Flash Live 還是標準 Gemini API? 第一個問題:Gemini 3.1 Flash Live 是為即時串流設計的,但 LINE 的語音訊息是預錄好的 m4a 檔案,不是即時音訊流。 用 Flash Live 處理預錄檔案,就像用直播攝影機拍照——技術上可行,但工具選錯了。 決定用標準 Gemini API——直接把音訊 bytes 當 inline data 傳進去,一次呼叫拿到轉錄文字。更簡單、更適合這個場景。 架構設計 整合思路 這個 repo 已經有完整的 Orchestrator 架構,會依照訊息內容自動路由到不同 Agent(Chat、Content、Location、Vision、GitHub)。語音訊息的目標很明確: 把語音轉成文字,然後當成一般文字訊息丟進 Orchestrator——讓現有的所有功能自動支援語音輸入。 使用者說「幫我搜尋附近的加油站」→ 轉錄成文字 → Orchestrator 判斷是地點查詢 → LocationAgent 處理。不需要為語音另外實作邏輯。 完整流程 用戶傳送 AudioMessage(m4a) │ ▼ handle_audio_message() │ ├─ ① LINE SDK 下載音訊 bytes │ get_message_content(message_id) → iter_content() │ ├─ ② Gemini 轉錄 │ tools/audio_tool.py → transcribe_audio() │ model: gemini-3.1-flash-lite-preview │ ├─ ③ Reply #1:「你說的是:{transcription}」 │ reply_message()(消耗 reply token) │ └─ ④ Reply #2:Orchestrator 路由 handle_text_message_via_orchestrator(push_user_id=user_id) ↓ push_message()(reply token 已用掉,改用 push) 為什麼要兩段回覆? 回覆分成兩則,讓使用者立刻看到轉錄結果,不用等 Orchestrator 處理完才知道 Bot 有沒有聽懂自己說什麼。 核心程式碼詳解 Step 1:音訊轉錄工具(tools/audio_tool.py) from google import genai from google.genai import types TRANSCRIPTION_MODEL = "gemini-3.1-flash-lite-preview" async def transcribe_audio(audio_bytes: bytes, mime_type: str = "audio/mp4") -> str: """ Transcribe audio bytes to text...
繼續閱讀
March
26th,
2026
參考文章: Gemini API tooling updates: context circulation, tool combos and Maps grounding for Gemini 3 Google Places API (New) - searchNearby GitHub: linebot-spot-finder 完整程式碼 GitHub (聚會小幫手 LINE Bot Spot Finder) 前情提要 LINE Bot + Gemini 的組合已經很常見,不管是用 Google Search Grounding 讓模型查即時資訊,還是用 Function Calling 讓模型呼叫自訂邏輯,單獨使用都很成熟。 但如果你想在同一個問題裡同時做到「地圖定位情境」和「查詢真實評分」呢? 以餐廳搜尋來說,傳統做法通常長這樣: 用戶: "幫我找附近評價4星以上的熱炒店" 方案 A(只用 Maps Grounding): Gemini 有地圖情境,但評分資訊是 AI 自行描述,不保證準確。 方案 B(只用 Places API): 可以拿到真實評分,但沒有地圖情境,Gemini 不知道用戶在哪裡。 要兩者兼得,通常需要分兩次 API 呼叫,或是自己手動串接。 AI 能查地圖、也能呼叫外部 API,但要在一次呼叫裡同時做到這兩件事——在 Gemini API 的舊架構下一直是個尷尬的空白。 直到 2026 年 3 月 17 日,Google 發布了 Gemini API Tooling Updates(作者:Mariano Cocirio),這個問題才有了官方解法。 什麼是 Tool Combinations? Google 在這次更新中宣布了三個核心功能: 1. Tool Combinations(工具組合) 開發者現在可以在單次 Gemini API 呼叫中同時掛上 built-in 工具(如 Google Search、Google Maps)以及自訂 Function Declarations。模型自行決定要呼叫哪個工具、何時呼叫,最後整合結果生成回答。 2. Maps Grounding Gemini 現在可以直接感知地圖資料,不再只是文字描述「位置」,而是真正具備空間情境——知道用戶在哪裡、附近有什麼。 3. Context Circulation 讓多輪工具呼叫之間的情境能自然流通,模型在第二次呼叫時能完整記憶第一次的工具呼叫結果。 這次改動的關鍵在於: # 舊的做法(兩個工具不能並存) types.Tool(google_search=types.GoogleSearch()) types.Tool(function_declarations=[MY_FN]) # 新的做法(同一個 Tool 物件,兩者共存) types.Tool( google_maps=types.GoogleMaps(), function_declarations=[MY_FN], ) 一行改動,打開了全新的組合方式。 專案目標 這次我用 Tool Combinations 改造了既有的 linebot-spot-finder,讓它從「只能 Maps Grounding 粗略回答」升級到「Google Maps 情境 + Places API 真實資料」: 用戶傳送 GPS 位置後輸入:「請找評價 4 顆星以上、適合多人聚餐的熱炒店,列出名稱、地址和評論摘要。」 Bot(舊版 Maps Grounding):「附近有幾間熱炒店,評價都不錯。」(AI 自行描述,可能不準) Bot(新版...
繼續閱讀