iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0

Day 23 | Audio, Images, Video:讓 Agent 真的能聽能看

主張:語音 agent 十次有八次出問題,出在音訊格式跟模型架構選錯,而不是 ADK 本身有 bug。
讀完能做到:分清楚 Native Audio 跟 Half-Cascade 兩種模型的取捨,並知道你的多 agent 語音應用有一個藏起來、你關不掉的行為。

https://ithelp.ithome.com.tw/upload/images/20260919/20183762PE2MXStSP7.png

一段雜訊開場

如果你昨天已經跑起了語音 agent,今天大概率會遇到這個場景:你把麥克風接上,講了一句話,結果 agent 完全聽不懂,或者回應變成一串電流雜音。八成不是你的程式邏輯錯了——是音訊格式不對

官方文件講得很直白:ADK 不做音訊格式轉換。送錯格式進去,結果就是爛品質或直接報錯。所以在寫任何邏輯之前,先把格式釘死。

本篇延續 Day 22〈RunConfig 深潛:BIDI、Session Resumption 與 Streaming Tools〉的環境:LiveRequestQueue(往模型送即時資料的佇列)、RunConfig(控制串流行為的設定物件)、Runner.run_live()(啟動雙向串流的入口)都在那篇介紹過,今天直接拿來用。下面的程式碼會用到這幾個 import:

from google.genai import types
from google.adk.agents import Agent
from google.adk.agents.run_config import RunConfig
from google.adk.models.google_llm import Gemini

音訊規格:輸入 16kHz,輸出 24kHz

送進去的音訊要求:16-bit PCM(有號整數)、取樣率 16,000 Hz、單聲道。呼叫 send_realtime() 之前,資料必須已經是這個格式:

from google.genai import types

audio_blob = types.Blob(
    mime_type="audio/pcm;rate=16000",
    data=audio_data
)
live_request_queue.send_realtime(audio_blob)

模型回傳的音訊格式不一樣——16-bit PCM、24,000 Hz(native audio 模型)、單聲道,MIME type 是 audio/pcm;rate=24000。輸入輸出取樣率不同,這是瀏覽器端最容易漏掉的一個細節:錄音跟播放要用兩個不同取樣率的 AudioContext,如果你複製貼上同一份設定給兩邊,播放出來的聲音會是變速的。

送音訊的最佳實踐有四條:

  • 分小塊串流傳送:超低延遲用 10–20ms、平衡取 50–100ms、想省頻寬用 100–200ms,整個 session 保持一致的分塊大小。
  • 不用等模型回應再送下一塊LiveRequestQueue 會立即轉發每個分塊,不做合併或批次處理。
  • 持續串流就好:模型是連續處理音訊,不是一問一答。VAD(Voice Activity Detection,語音活動偵測,負責判斷使用者何時開始、何時講完,後面會展開)預設開啟時,你只要一直送就行。
  • activity 訊號通常用不到send_activity_start() / send_activity_end() 只有在你手動關閉 VAD 時才需要。

瀏覽器端要用 Web Audio API 的 AudioWorklet 處理器捕捉麥克風輸入,核心步驟是:用 16kHz 的 AudioContext 存取麥克風、AudioWorklet 在獨立執行緒即時捕捉音框、把 Float32 樣本轉成 16-bit PCM、透過 WebSocket 二進位訊框傳送(比 base64 編碼省約 33% 頻寬)。其中轉換公式值得記下來——Web Audio API 給的 Float32 樣本範圍是 [-1.0, 1.0],乘上 0x7fff(32767)就轉成 16-bit 有號整數:

function convertFloat32ToPCM(inputData) {
    const pcm16 = new Int16Array(inputData.length);
    for (let i = 0; i < inputData.length; i++) {
        pcm16[i] = inputData[i] * 0x7fff;
    }
    return pcm16.buffer;
}

播放端反過來,收到 24kHz 的 16-bit PCM,用**環形緩衝區(ring buffer)**吸收網路抖動,除以 32768 轉回 Float32。官方 demo 用一個 24000×180(三分鐘)的緩衝區,並在收到中斷事件時把 readIndex 直接跳到 writeIndex 位置清空緩衝——這是處理「使用者打斷 agent 說話」時,防止播放到過期音訊的關鍵技巧。

影像與視訊:其實都是 JPEG 幀

ADK 處理影像跟視訊的方式,跟你想的可能不太一樣——不是用 HLS、mp4、H.264 那套典型的視訊串流,而是把兩者都當成逐張 JPEG 影格處理。規格是:JPEG 格式、建議最高 1 FPS、建議解析度 768×768。前端把影格 base64 編碼後用 JSON 送上來,伺服器端解碼再丟進佇列:

import base64
from google.genai import types

image_data = base64.b64decode(json_message["data"])
mime_type = json_message.get("mimeType", "image/jpeg")

image_blob = types.Blob(
    mime_type=mime_type,
    data=image_data
)
live_request_queue.send_realtime(image_blob)

1 FPS 這個數字直接劃出了它的能力邊界:不適合做即時動作辨識或運動追蹤——快速移動的畫面在 1 FPS 下等於什麼都沒拍到。它適合的場景是「使用者舉起一件東西問這是什麼」「鏡頭掃過一份文件請 agent 讀取內容」這類靜態或緩慢變化的視覺理解,而不是體感遊戲或運動分析。

Native Audio vs Half-Cascade:選模型架構決定一切

Live API 的音訊模型分兩種根本不同的架構,這個選擇會直接影響對話自然度、工具呼叫可靠度、延遲特性,以及功能可用性。

Native Audio——端到端音訊架構,模型直接處理音訊輸入並生成音訊輸出,不經過文字中介。這帶來更自然的語調與抑揚頓挫,支援更廣的聲音庫,能自動偵測對話語言不需明確設定,還有進階的**主動對話(Proactive Audio)情緒感知對話(Affective Dialog)**功能。代價是:只支援 AUDIO 回應模式,不支援 TEXT,初始回應速度也較慢。

型號要看你走哪個平台:

  • Gemini Live API(用 Gemini API key 直接呼叫):gemini-2.5-flash-native-audio-preview-12-2025,公開可用。
  • Gemini Live API on Agent Platform(走 Google Cloud 的 Vertex AI):gemini-live-2.5-flash-native-audio,公開預覽階段。

Half-Cascade(也叫 Cascaded)——混合架構,音訊輸入是原生處理,但回應先生成文字再轉語音(TTS)。這個文字中介層讓它在生產環境更可靠,工具呼叫執行更穩健。它支援 TEXT 回應模式(意味著純文字場景可以跳過語音合成,回應快得多),也支援明確的 language_code 語言設定。8 種內建聲音:Puck、Charon、Kore、Fenrir、Aoede、Leda、Orus、Zephyr。注意gemini-2.0-flash-live-001 已於 2025 年 12 月 9 日棄用——如果你看到的教學還在用這個型號名,那份教學已經過期了。

怎麼選?追求最自然的對話體驗、需要主動對話與情緒感知,選 Native Audio;追求正式環境的可靠性、需要文字回應模式、工具呼叫是核心功能,選 Half-Cascade。官方建議把型號名寫進環境變數而不是寫死在程式碼裡,因為模型的上市與棄用節奏很快:

import os
from google.adk.agents import Agent

agent = Agent(
    name="my_agent",
    model=os.getenv("DEMO_AGENT_MODEL", "gemini-2.5-flash-native-audio-preview-12-2025"),
    tools=[...],
    instruction="..."
)

這裡有個 Python import 順序要注意:用 python-dotenv.env 時,load_dotenv() 必須在 import 任何會讀環境變數的模組之前呼叫。上面的 agent 模組在 import 當下就執行 os.getenv(),如果 load_dotenv() 排在它後面,拿到的只會是預設值,.env 設定完全不生效。

音訊轉錄:預設開啟,而且有個你關不掉的例外

Live API 內建語音轉文字,RunConfig 透過 input_audio_transcriptionoutput_audio_transcription 兩個欄位控制,預設兩者都是開啟的

from google.adk.agents.run_config import RunConfig

# 明確關閉轉錄
run_config = RunConfig(
    response_modalities=["AUDIO"],
    input_audio_transcription=None,
    output_audio_transcription=None
)

「預設開啟」在程式裡長這樣:print(RunConfig().input_audio_transcription) 印出的不是 None,而是一個所有子欄位都是 NoneAudioTranscriptionConfig 空物件。看起來像沒設定,其實已經開著了;要關掉就得像上面那樣明確寫 None

轉錄結果會出現在 Event.input_transcriptionEvent.output_transcription,各自有 .text(文字內容)跟 .finished(是否已完成,還是部分轉錄)兩個屬性。存取時務必做兩層 null 檢查——先確認轉錄物件存在,再確認文字非空——因為轉錄可能還在進行中,文字可能是空字串。

現在講那個「你關不掉的例外」:只要你的 agent 定義了 sub_agentsrun_live() 會自動強制開啟轉錄——即使你把兩個欄位都明確設成 None。原因是 agent 之間的轉移(transfer)需要文字上下文才能運作,轉錄是這個機制的必要條件。細看規則是這樣:

  • input_audio_transcription:只要有 sub_agents 就一定被打開。
  • output_audio_transcription:有 sub_agents,而且 response_modalities 包含 AUDIO 時被打開。

這代表多 agent 語音應用沒辦法關閉轉錄,你的應用一定會收到轉錄事件,必須有邏輯去處理它們——即使你原本完全不想要轉錄功能。這段邏輯在 run_live() 建立執行上下文時就套用了,你在 RunConfig 裡寫什麼都擋不住。

Voice Config:聲音怎麼選、優先順序是什麼

聲音設定可以在兩個層級指定:agent 層級(透過自訂的 Gemini 模型物件)跟 session 層級(透過 RunConfig)。這讓多 agent 場景可以做出「每個 agent 講話聲音不一樣」的效果:

from google.genai import types
from google.adk.agents import Agent
from google.adk.models.google_llm import Gemini

customer_service_llm = Gemini(
    model="gemini-2.5-flash-native-audio-preview-12-2025",
    speech_config=types.SpeechConfig(
        voice_config=types.VoiceConfig(
            prebuilt_voice_config=types.PrebuiltVoiceConfig(
                voice_name="Aoede"  # 友善、溫暖的聲音
            )
        )
    )
)

customer_service_agent = Agent(
    name="customer_service",
    model=customer_service_llm,
    instruction="You are a friendly customer service representative."
)

優先順序很明確:Agent 層級設定優先於 session 層級。如果 Gemini 物件有設 speech_config,不管 RunConfig 寫了什麼都會被蓋掉;如果 agent 沒設,才會用 RunConfig 裡的值;兩邊都沒設,就用 Live API 的預設聲音。

language_code 的行為要注意模型差異:Half-Cascade 模型會照你設的語言代碼合成語音,Native Audio 模型則傾向自動從對話上下文判斷語言,明確設定的 language_code 可能不會被理睬。

VAD:預設幫你做好回合偵測

VAD 自動偵測使用者何時開始講話、何時講完,讓對話能自然輪替,不需要手動控制。所有 Live API 模型預設開啟 VAD

什麼時候該關掉它?三種場景:

  • push-to-talk:使用者手動控制何時送出音訊,例如吵雜環境或多人交叉發言的場合。
  • client-side VAD:你自己在前端做語音偵測,只在偵測到語音時才傳送 activity 訊號,省下持續串流的頻寬與運算。
  • 特定 UX 設計:要求使用者明確標示自己講完了。

關掉 VAD 之後,你必須改用手動的 activity 訊號(ActivityStart / ActivityEnd,也就是 Day 22 提過的 send_activity_start() / send_activity_end())來控制對話輪次。

Proactivity 與 Affective Dialog:Native Audio 專屬的兩個進階功能

這兩個功能只有 Native Audio 模型才有,都在 RunConfig 裡開:

from google.genai import types
from google.adk.agents.run_config import RunConfig

run_config = RunConfig(
    # Model can initiate responses without explicit prompts
    proactivity=types.ProactivityConfig(proactive_audio=True),

    # Model adapts to user emotions
    enable_affective_dialog=True
)

Proactive Audioproactivity)開啟後,模型能基於上下文主動發起回應:沒被問也會提出建議、主動補充後續資訊、忽略離題的輸入、預判使用者接下來需要什麼。

Affective Dialogenable_affective_dialog)開啟後,模型會從語氣與內容判讀情緒(挫折、開心、困惑),據此調整回應的風格、語調與正式程度。

放進實際場景,一個需要同理心的客服機器人會把兩者跟串流設定擺在一起:

from google.genai import types
from google.adk.agents.run_config import RunConfig, StreamingMode

# Configure for empathetic customer service
run_config = RunConfig(
    response_modalities=["AUDIO"],
    streaming_mode=StreamingMode.BIDI,

    # Model can proactively offer help
    proactivity=types.ProactivityConfig(proactive_audio=True),

    # Model adapts to customer emotions
    enable_affective_dialog=True
)

客戶抱怨「訂單等了三個禮拜」時,模型可能察覺語氣裡的不耐而先道歉、再主動詢問訂單編號;對話後段,它也可能主動問要不要幫忙開啟出貨通知。要記得這兩種行為都是機率性的,同一句話不保證每次得到一樣的反應,別拿它們當成可以寫測試斷言的確定行為。

銜接

今天把 Live API Toolkit 系列收在音訊、影像、視訊的具體實作細節上:格式規格、Native Audio 跟 Half-Cascade 的取捨、轉錄的隱藏行為、Voice Config 優先順序、VAD 的開關時機。三天下來,你手上已經有一個能講話、能斷線重連、能用適合的模型架構跑起來的語音 agent。明天要換一個完全不同的執行模式——不是等使用者開口,而是讓 agent 在沒有人盯著的時候,自己對外部世界的變化做出反應。


Google ADK 官方網站
GitHub - Agent Development Kit (ADK) 2.0
延伸閱讀:ADK Live API Toolkit 開發指南 Part 5

GitHub 開源實作:https://github.com/SeanLinH/adk_tutor


上一篇
Day 22 - RunConfig 深潛:BIDI、Session Resumption 與 Streaming Tools
系列文
Google ADK Agent 教戰:30 天從原型到可上線的 AI Agent 系統23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言