🔁

ADKのeventストリームをVercel AI SDK v6に変換してみよう!

に公開

こんにちは。hironowです。
これはADK Advent Calendar 2025の12月17日の記事です!

タイトル通り、ADKをVercel AI SDK v6として扱ってみる試みを行ったのでその解説をします!

一言で言えば、「裏側で動くADK(SSEモード/BIDIモード)のAgentと、Vercel AI SDKの useChat() を使ってチャットしたい!」という願望を実現するための取り組みを紹介します。

実装コードはこちらにて公開しています。
https://github.com/hironow/adk-stream-protocol


ADKにおいてAgentを動かす際 agent_runner.run_async() と agent_runner.run_live() を主に使うと思います。

どちらもeventが降ってきます。
live modeでは、live_events (run_liveの返り値) と live_request_queue (run_liveの引数) を用いることで双方向通信が可能になります。

登場要素

ADKとVercel AI SDK v6

主なライブラリ

google-adk: 1.21.0
ai (Vercel AI SDK): 6.0.0-beta.148
@ai-sdk/react: 3.0.0-beta.151

本日時点で上記のversionで固定しています

ADKサイドのAPI

  • Tool Confirmation with ADK Tool: toolsの設定時にフラグを渡すと承認機構を付与できます
root_agent = Agent(
   ...
   tools=[
        FunctionTool(reimburse, require_confirmation=True),
    ],
...

Vercel AI SDK v6サイドのAPI

  1. onToolCall、 addToolOutput と addToolApprovalResponse: toolの承認結果や実行結果をchatに加えることができます。
  2. sendAutomaticallyWhen: trueを返す時、chatを自動送信できます。
  3. transport: DefaultChatTransport が使われますが、カスタムな通信レイヤーを指定できます。
import { useChat } from '@ai-sdk/react';

const { messages, addToolOutput, addToolApprovalResponse } = useChat({
  sendAutomaticallyWhen: ...,
  transport: ...,
  onToolCall: ...,
});

sendAutomaticallyWhenへの指定は lastAssistantMessageIsCompleteWithToolCalls や lastAssistantMessageIsCompleteWithApprovalResponses がSDKから提供されていますが、自作関数も指定できます(今回は承認機能付きtoolをこちらで実装)。

Vercel AI SDK (UI) v6のStream Protocol

登場当時からあるData Stream Protocolを含むプロトコルです。
Server-Sent Events (SSE)で流れる以下のようなformatです。
詳細割愛して 公式ドキュメント を参照ください。

data: {"type":"tool-input-available","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","toolName":"getWeatherInformation","input":{"city":"San Francisco"}}
data: {"type":"tool-output-available","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","output":{"city":"San Francisco","weather":"sunny"}}

Data Parts に記載されているように任意の data-hoge の指定でそれに応じた任意のデータを送ることができます。
今回、BIDIモードで送信するPCM音声ストリーミングを扱うシーンで利用しています。

このプロトコルはSSE formatですので、通信レイヤーをwebsocketに変えて、上記の要素の transport 指定でカスタム指定を行い、適切に処理すれば、Vercel AI SDK (UI) v6 Stream Protocol over websocketが実現できます(今回はBIDIモードをこちらで実装)。

今回作成したAgentが使えるツール

  1. get_weather: serverから情報取得のみ。承認なし即時実行
  2. process_payment: server内で実行。承認あり (walletは仮実装です)
  3. change_bgm: client内で実行。承認なし即時実行
  4. get_location: client内で実行、serverへ情報送信あり。承認あり

と、どれもreal world exampleに近い4ツールを実装しています。

ADKのツールはどんなイベント?

いきなりですがADK Agentが返すTool Confirmation付きのToolイベントをみてみましょう。

{
    "timestamp": 1765949166921,
    "session_id": "real-1",
    "mode": "adk-sse",
    "location": "backend-adk-event",
    "direction": "in",
    "sequence_number": 3,
    "chunk": "Event(model_version=None, content=Content(\n  parts=[\n    Part(\n      function_call=FunctionCall(\n        args={\n          'originalFunctionCall': {\n            'args': {<... 3 items at Max depth ...>},\n            'id': 'adk-1cad21d0-5adf-40d7-ad0e-376f603c769f',\n            'name': 'process_payment'\n          },\n          'toolConfirmation': {\n            'confirmed': False,\n            'hint': 'Please approve or reject the tool call process_payment() by responding with a FunctionResponse with an expected ToolConfirmation payload.'\n          }\n        },\n        id='adk-4a15b31a-6564-4e1b-8994-1f0a0576e37c',\n        name='adk_request_confirmation'\n      )\n    ),\n  ],\n  role='user'\n), grounding_metadata=None, partial=None, turn_complete=None, finish_reason=None, error_code=None, error_message=None, interrupted=None, custom_metadata=None, usage_metadata=None, live_session_resumption_update=None, input_transcription=None, output_transcription=None, avg_logprobs=None, logprobs_result=None, cache_metadata=None, citation_metadata=None, interaction_id=None, invocation_id='e-e56ea51d-6a51-440b-848d-be39e5540d8e', author='adk_assistant_agent_sse', actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}, requested_tool_confirmations={}, compaction=None, end_of_agent=None, agent_state=None, rewind_before_invocation_id=None), long_running_tool_ids={'adk-4a15b31a-6564-4e1b-8994-1f0a0576e37c'}, branch=None, id='4abe9ca9-a7bd-48dd-8185-a6783c3db77f', timestamp=1765949166.920993)",
    "metadata": null
}

"chunk" 内部を見やすくします:

承認を求めるイベントが "adk_request_confirmation" として confirmedの boolean と hint そして originalFunctionCall として受け取れます。

Event(model_version=None, 
    content=Content(\n  
        parts=[\n    
          Part(\n      
            function_call=FunctionCall(\n        
            args={\n          
                'originalFunctionCall': {\n            
                  'args': {<... 3 items at Max depth ...>},\n            
                    'id': 'adk-1cad21d0-5adf-40d7-ad0e-376f603c769f',\n            
                    'name': 'process_payment'\n          },\n          
                'toolConfirmation': {\n            
                  'confirmed': False,\n            
                  'hint': 'Please approve or reject the tool call process_payment() by responding with a FunctionResponse with an expected ToolConfirmation payload.'\n          }\n        },\n        
            id='adk-4a15b31a-6564-4e1b-8994-1f0a0576e37c',\n        
            name='adk_request_confirmation'\n      )\n    ),\n  ],\n  
    role='user'\n), 
    grounding_metadata=None, 
    partial=None, 
    turn_complete=None, 
    finish_reason=None, 
    error_code=None, 
    error_message=None, 
    interrupted=None, 
    custom_metadata=None, 
    usage_metadata=None, 
    live_session_resumption_update=None, 
    input_transcription=None, 
    output_transcription=None, 
    avg_logprobs=None, 
    logprobs_result=None, 
    cache_metadata=None, 
    citation_metadata=None, 
    interaction_id=None, 
    invocation_id='e-e56ea51d-6a51-440b-848d-be39e5540d8e', 
    author='adk_assistant_agent_sse', 
    actions=EventActions(skip_summarization=None, 
        state_delta={}, 
        artifact_delta={}, 
        transfer_to_agent=None, 
        escalate=None, 
        requested_auth_configs={}, 
        requested_tool_confirmations={}, 
        compaction=None, 
        end_of_agent=None, 
        agent_state=None,
        rewind_before_invocation_id=None),
        long_running_tool_ids={'adk-4a15b31a-6564-4e1b-8994-1f0a0576e37c'}, 
        branch=None, 
        id='4abe9ca9-a7bd-48dd-8185-a6783c3db77f',
        timestamp=1765949166.920993)

Vercel AI SDK v6のStream Protocolへ

上記のADKイベントらをstreamのまま変換して、Vercel AI SDK (UI) v6のStream ProtocolのSSE formatにします。

例えば ADK Agentが get_weather ツールを呼ぶと、下記のようなレスポンスを受信するように変換しました。

data: {"type": "start", "messageId": "6cc29e6a-b272-450f-98c3-b4778c07ad7a"}

data: {"type": "tool-input-start", "toolCallId": "adk-02a52abc-ab37-4a7d-adc4-ce57578dfcf0", "toolName": "get_weather"}

data: {"type": "tool-input-available", "toolCallId": "adk-02a52abc-ab37-4a7d-adc4-ce57578dfcf0", "toolName": "get_weather", "input": {"location": "Tokyo"}}

data: {"type": "tool-output-available", "toolCallId": "adk-02a52abc-ab37-4a7d-adc4-ce57578dfcf0", "output": {"temperature": 9.8, "condition": "Clear", "description": "clear sky", "humidity": 64, "feels_like": 9.8, "wind_speed": 1.11, "cached": true}}

data: {"type": "text-start", "id": "0"}

data: {"type": "text-delta", "id": "0", "delta": "\u6771\u4eac\u306f\u73fe\u5728\u3001\u6c17\u6e29\u304c9.8\u2103\u3067\u3001\u5feb\u6674\u3067\u3059\u3002"}

data: {"type": "text-end", "id": "0"}

data: {"type": "finish", "finishReason": "stop", "messageMetadata": {"usage": {"promptTokens": 1295, "completionTokens": 16, "totalTokens": 1311}, "modelVersion": "gemini-2.5-flash"}}

data: [DONE]

上記で得られたデータをVercel AI SDK UIとして描画してあげるとこのような画面になります。

ADK SSEモード get_weather: DefaultChatTransportの指定でHTTP内をStream Protocolが流れています

ADK SSEモード get_weatherのUI表示例

ADK BIDIモード: カスタム指定 WebSocketChatTransport でwebsocket内をStream Protocolが流れています。また音声は data-pcm でPCM音声ストリーミングを行い、client内でwav変換してRecorded Audioとして再生できるようにしています。

ADK BIDIモード get_weatherのUI表示例

動作中の録画視聴はこちらから

ADK BIDIモードのRunConfigではtranscriptionを有効にして、発話中字幕も出るようにしています。

run_config = RunConfig(
    streaming_mode=StreamingMode.BIDI,
    response_modalities=["AUDIO"],
    input_audio_transcription=types.AudioTranscriptionConfig(),
    output_audio_transcription=types.AudioTranscriptionConfig(),
    session_resumption=types.SessionResumptionConfig() if use_vertexai else None,
    context_window_compression=types.ContextWindowCompressionConfig(
        trigger_tokens=100000,
        sliding_window=types.SlidingWindow(target_tokens=80000),
    ),
)

その他のtool実行例

ADK SSEモードでのchange_bgm動作中の録画視聴はこちらから:

実際にclient内でbgm変更が即時実行で行われます。

ADK SSEモードでのprocess_payment動作中の録画視聴はこちらから:


ADK SSEモード process_paymentのUI表示例

approve/deny処理はadk_request_confirmation toolとして受信することで承認処理を1つのツールに変換しました。

ADK SSEモード get_location とBIDIモードの承認ありの実装

これらのtool実行はFrontendへのdelegateを行い、tool内での処理をawaitableにすることで実現していますしようとしています。現状のgithub mainブランチの先頭ではうまく動作しないことがあります。

Frontend delegate flow図

またテストのためにeventストリームのchunkを録画/再生する機構を作成して、それらを再生、e2eテストしています。
この機能はchunk recorderとchunk playerとして、ADKとStream Protocol、clientのtransportの両方でinとoutを記録するものです。

全体通して非常に多くのトピックを扱ったので最後に全体像をまとめました:


全体アーキテクチャ図: FastAPI Serverのエンドポイントは /stream と /live が正しい

以上、お読みいただき誠にありがとうございました!
実装はこちらで続いています!!
https://github.com/hironow/adk-stream-protocol


特に役立った参考リンク

AI SDK by Vercel ver. 6 Data Stream Protocol

Format: Server-Sent Event with JSON object

https://v6.ai-sdk.dev/docs/ai-sdk-ui/stream-protocol
https://v6.ai-sdk.dev/docs/announcing-ai-sdk-6-beta

ADK BIDI or SSE: SSE fit Data Stream Protocol

https://google.github.io/adk-docs/streaming/dev-guide/part4/#streamingmode-bidi-or-sse
https://google.github.io/adk-docs/streaming/dev-guide/part5/#live-api-models-compatibility-and-availability
https://google.github.io/adk-docs/streaming/dev-guide/part5/

Action confirm:
https://google.github.io/adk-docs/tools-custom/confirmation/#boolean-confirmation
https://github.com/google/adk-python/blob/fc90ce968f114f84b14829f8117797a4c256d710/contributing/samples/human_tool_confirmation/agent.py

ToolContext:
https://google.github.io/adk-docs/context/
https://github.com/google/adk-python/discussions/2784

Discussion