ADK 2.0は、GoogleのAIエージェント開発フレームワーク「Agent Development Kit」のメジャー版です。Python版は2026年5月19日にGAとなり、処理の順番をノードとエッジのグラフで書くWorkflowが加わりました。この記事では、2.0で何が変わったか、Workflowで直列・分岐・並列・ループ・人の入力待ちをどう書くか、1.xのコードを上げるときにどこが壊れるかを、Python版2.11.0で実行したコードとあわせて説明します。
ADKそのものの位置づけや、インストールからadk webで動かすまでの手順はADKとは?GoogleのAgent Development Kitの仕組みと始め方で扱っています。
まとめ:ADK 2.0の要点
- 2.0の中心は
Workflow。エージェント・関数・ツールをノードとして並べ、エッジで実行順と分岐を決める。モデルに手順を任せず、コードで経路を固定できる - 2.0はPython・TypeScript・Goで提供。最新版はPython 2.11.0、TypeScript 2.2.1、Go v2.5.0(2026年10月9日時点)。JavaとKotlinは1.x系のまま
- SequentialAgent・ParallelAgent・LoopAgentは2.0.0から非推奨。まだ動くが、新しい処理フローはWorkflowで書く
- Eventに
node_infoとoutputが増えた。列を固定した独自のセッションDBはスキーマ変更が要り、2.0が書いたセッションは1.28より前の1.xでは読めない - 移行の要点は、セッションDBの確認、イベントの直接追加の廃止、広すぎる
exceptの削除、実行ロジックのコールバックへの移動の4つ
以下はPython 3.11とgoogle-adk 2.11.0で検証した処理の抜粋です。検証ではモデルをスタブに差し替え、Runnerとセッション作成処理を追加しています。入力待ちの例は、停止イベントから取得した呼び出しIDを使い、同じセッションで再開します。
ADK 2.0で変わったことと言語別の提供状況
公式の2.0ガイドは、2.0を「階層型のエージェント実行から、グラフベースの実行エンジンへの移行」と説明しています。1.xでは親エージェントが子エージェントを呼ぶ木構造で処理が進みましたが、2.0ではエージェントもツールも関数も、グラフ上の1つのノードとして評価されます。2.0.0のREADMEは新機能を2つ挙げています。
- Workflow Runtime:分岐、並列実行と合流、ループ、再試行、状態管理、動的ノード、人の入力待ち、入れ子のワークフローを扱うグラフ実行エンジン
- Task API:エージェント間の委任を構造化する仕組み。
LlmAgentのmodeにchat・task・single_turnを指定し、ユーザーと対話するか、1回で結果だけ返すかを決める
2.0の提供時期は言語ごとに異なります。各レジストリで確認した版は次のとおりです。
| 言語 | 2.0.0のGA | 最新版 | 最新版の公開日 |
|---|---|---|---|
| Python | 2026-05-19 | 2.11.0 | 2026-10-02 |
| Go | 2026-06-30 | v2.5.0 | 2026-09-30 |
| TypeScript | 2026-08-21 | 2.2.1 | 2026-10-06 |
| Java | 未提供 | 1.11.0 | 2026-10-02 |
| Kotlin | 未提供 | 1.3.1 | 2026-10-05 |
Pythonは2週間ほどの間隔でマイナー版が出ており、5月の2.0.0から10月の2.11.0までにマイナー版が11回上がっています。Goはモジュールパスがgoogle.golang.org/adk/v2に変わったため、import文の書き換えが必要です。Javaの1.11.0を使っている場合、2.0の機能はまだ使えません。
Workflowの書き方:ノード・エッジ・ルート
Workflowはedgesにタプルを並べてグラフを作ります。開始経路のタプルは"START"から書き、途中の接続は接続元ノードから書きます。単純な直列タプルでは、続くノードが左から順に実行されます。ノードにはAgent(LlmAgent)、普通のPython関数、ツール、別のWorkflowを置けます。関数はADKが自動でFunctionNodeに包みます。
直列ノード間の出力と入力の受け渡し
前のノードが返した値は、次のノードのnode_input引数に入ります。セッション状態へ書き込まなくても値が流れる点が、1.xのoutput_keyを使った受け渡しとの違いです。
from google.adk import Agent, Event, Workflow
summarizer = Agent(
name="summarizer",
model="gemini-3.5-flash",
instruction="ユーザーの問い合わせを1文で要約する",
)
def add_ticket_no(node_input: str):
return f"[#1024] {node_input}"
def notify(node_input: str):
return Event(message=f"起票しました: {node_input}")
root_agent = Workflow(
name="ticket_flow",
edges=[("START", summarizer, add_ticket_no, notify)],
)
実行すると、イベントのnode_info.pathがticket_flow@1/summarizer@1、ticket_flow@1/add_ticket_no@1の順に並びます。関数が普通の値を返すとイベントのoutputに入り、Event(message=...)を返すとユーザーに見えるメッセージになります。
条件分岐:Event(route=…)と辞書のルーティング
分岐させるノードはEvent(route=...)を返します。次の要素に「ルート値→行き先ノード」の辞書を置くと、一致したノードだけが動きます。ルート値をリストで返すと、一致した行き先がすべて実行されます。
from google.adk import Event, Workflow
from google.adk.workflow import DEFAULT_ROUTE
def classify(node_input: str):
labels = [x.strip() for x in node_input.split(",")]
return Event(route=labels)
def handle_bug():
return Event(message="開発チームへ回す")
def handle_billing():
return Event(message="経理チームへ回す")
def handle_other():
return Event(message="一次窓口で対応する")
root_agent = Workflow(
name="router",
edges=[
("START", classify, {
"BUG": handle_bug,
"BILLING": handle_billing,
DEFAULT_ROUTE: handle_other,
}),
],
)
入力がBUGならhandle_bugだけ、BUG,BILLINGなら2つとも、どれにも当たらないSPAMならDEFAULT_ROUTEのhandle_otherが動きました。実務ではclassifyの前に分類用のAgentを置き、モデルの判定結果をルート値に変換します。判定はモデルに任せ、どこへ進むかはコードで決める分担です。
並列と合流:JoinNode
"START"から複数のノードへ枝を出すと並列に動きます。結果を1つにまとめるにはJoinNodeを挟みます。JoinNodeは上流のノードがすべて終わるまで待ち、ノード名をキーにした辞書を次へ渡します。
from google.adk import Event, Workflow
from google.adk.workflow import JoinNode
def search_docs():
return "docs: 3件"
def search_tickets():
return "tickets: 5件"
def report(node_input: dict):
return Event(message=f"{node_input['search_docs']} / {node_input['search_tickets']}")
join = JoinNode(name="join")
root_agent = Workflow(
name="research",
edges=[
("START", search_docs, join),
("START", search_tickets, join),
(join, report),
],
)
joinの出力は{'search_docs': 'docs: 3件', 'search_tickets': 'tickets: 5件'}でした。上流の関数がNoneを返した場合も、2.11.0のJoinNodeは止まらず、その枝の値をNoneとして後続へ渡しました。欠けた値の扱いは後続ノード側で決めておく必要があります。外部APIを呼ぶ枝の一時的な失敗には、後述のRetryConfigが使えます。ただし更新系のAPIは、再実行で二重登録が起きないよう、冪等キーや処理済みの判定と組み合わせてください。同時に動くノード数の上限はWorkflowのmax_concurrencyで決められます。
ループの条件付きエッジと終了条件
ループは、後ろのノードから前のノードへ戻るエッジで表します。ルートの付かないエッジだけで輪を作ると、2.11.0ではUnconditional cycle detectedの検証エラーになり、Workflowを作る時点で止まります。循環経路には少なくとも1つ、ルート条件付きのエッジを含めます。この例では戻るエッジに条件を付けています。
from google.adk import Event, Workflow
def write_draft(ctx, node_input):
n = ctx.state.get("rounds", 0) + 1
ctx.state["rounds"] = n
return f"draft v{n}"
def review(ctx, node_input: str):
if ctx.state["rounds"] >= 3:
return Event(route="ok", output=node_input)
return Event(route="retry", output=node_input)
def publish(node_input: str):
return Event(message=f"公開: {node_input}")
root_agent = Workflow(
name="draft_loop",
edges=[
("START", write_draft, review),
(review, {"retry": write_draft, "ok": publish}),
],
)
実行するとwrite_draft@1から@3まで3周し、publishで終わりました。パスの@の後ろの数字は、そのノードの実行回数を表します。この例は新規セッションを前提とし、同じセッションで再度実行すると状態のroundsが残るため、毎回3周させる場合は開始時に初期化します。ルート付きの輪は検証を通りますが、回数の上限は自動では付きません。この例のように状態で回数を数え、必ず抜ける条件を書いてください。
RequestInputによる入力待ちと再開
ノードがRequestInputを返すと、ワークフローはそこで止まり、adk_request_inputという名前の関数呼び出しイベントを出します。モデルを使わずに止まるので、承認や金額入力のような人の判断を確実に挟めます。
from google.adk import Event, Workflow
from google.adk.events import RequestInput
from google.genai import types
def ask_amount():
return RequestInput(message="返金額を入力してください")
def confirm(node_input):
return Event(message=f"{node_input}円の返金を登録しました")
root_agent = Workflow(name="refund", edges=[("START", ask_amount, confirm)])
# 再開:止まったときの関数呼び出しIDに対して応答を送る
reply = types.Content(role="user", parts=[types.Part(
function_response=types.FunctionResponse(
name="adk_request_input", id=call_id, response={"result": 3000},
)
)])
call_idには、止まったときのイベントに含まれるfunction_call.idを入れます。同じセッションにreplyを送ると、confirmが3000円の返金を登録しましたを返しました。RequestInputにはpayloadとresponse_schemaも渡せますが、response_schemaは返答を変換してくれません。決まった形で受け取りたい場合は、画面側で入力形式を固定するか、後ろに整形用のエージェントを置きます。
ツールの実行前に確認を挟むrequire_confirmationや、セッションを止めて後で再開するResumabilityConfigは、ADKのコンテキスト圧縮と停止・再開の使い方で扱っています。
失敗時の再試行:RetryConfig
2.0ではノードで起きた例外をフレームワークが受け取り、RetryConfigに従って同じノードを再実行します。
from google.adk import Workflow
from google.adk.workflow import FunctionNode, RetryConfig
attempts = {"n": 0}
def call_external_api():
attempts["n"] += 1
if attempts["n"] < 3:
raise ConnectionError("timeout")
return "200 OK"
fetch = FunctionNode(
func=call_external_api,
retry_config=RetryConfig(max_attempts=3, initial_delay=0.5, jitter=0.0),
)
root_agent = Workflow(name="sync", edges=[("START", fetch)])
2回失敗して3回目に成功する関数で試すと、3回目の結果がoutputに入りました。ソースに書かれた既定値は、試行回数5回、初回待機1.0秒、最大待機60秒、待機時間の倍率2.0です。失敗のたびにretry count is not persisted across resumingという警告が出ます。人の入力待ちで止めて再開すると、試行回数は0から数え直しになります。
グラフ型・動的ワークフロー・従来のワークフローエージェントの使い分け
公式ドキュメントは、複数ステップの処理の組み方を3通りに分けています。
| 方式 | 書き方 | 向く処理 |
|---|---|---|
| グラフ型 | edgesにノードとルートを書く | 手順が決まった業務フロー |
| 動的ワークフロー | @nodeの関数内でctx.run_node() | 回数や分岐がデータで変わる処理 |
| 従来の3クラス | SequentialAgentなど | 既存コードの維持 |
新しく書くなら、まずグラフ型を選びます。ループの回数や枝の数が実行時のデータで決まり、グラフに書き切れないときだけ動的ワークフローにします。動的ワークフローでは、普通のPythonのforやifの中でctx.run_node()を使って子ノードを呼びます。子ノードを呼ぶ関数には@node(rerun_on_resume=True)を付けます。人の入力待ちから再開したとき関数が頭から再実行され、終わっている子ノードは記録済みの結果で飛ばされます。
SequentialAgent・ParallelAgent・LoopAgentは、2.0.0で非推奨のデコレーターが付き、インスタンスを作るとDeprecationWarningが出ます。2.11.0の警告文には「WorkflowはまだLlmAgentのサブエージェントとして使えない」と添えられています。親のLlmAgentがsub_agentsで処理フローへ処理を渡す構成は、今のところ従来クラスのまま残すしかありません。それ以外の構成は、新規のコードからWorkflowに寄せるべきです。
リモートのエージェントをA2Aで呼ぶ構成はA2Aリモートエージェントとは?ADKでの公開・呼び出し、A2Aの仕様そのものはA2A(Agent2Agent)プロトコルとは?で解説しています。
ADK 1.xから2.0への破壊的変更
公式の2.0移行ガイドは「2.0は1.xのエージェントと互換を保つよう設計した」としつつ、次の非互換を挙げています。影響が大きい順に並べます。
Eventスキーマとセッションの読み書き
2.0ではEventにnode_info(どのノードが出したか)とoutput(ノードの出力値)が加わりました。2.0.0のREADMEによると、2.0が書いたセッションは1.28以降の1.xなら読めますが(増えた項目は無視される)、それより古い1.xとは互換がありません。
影響を受けるのは、BaseSessionServiceを自作し、イベントを列ごとにSQLやNoSQLへ保存している場合です。2.0のイベントを1.xの表にそのまま入れると、挿入やデシリアライズで失敗します。イベントをJSONの塊で保存していれば、スキーマ変更は要りません。API GatewayやフロントエンドでadditionalProperties: falseの厳格なJSONスキーマ検証をしている場合も、検証側の定義を先に直します。
BaseAgentがBaseNodeを継承する変更とカスタムエージェント
2.0ではBaseAgentがBaseNodeを継承し、エージェントもグラフのノードとして扱われます。公式ガイドは「_run_async_impl()やgenerate_content()の上書きは正しい実行方法ではなくなり、Workflowエンジンはこれを迂回する。上書きで入れたテレメトリや状態管理は黙って無視される」と警告し、BeforeAgentCallbackとAfterAgentCallbackへの移行を求めています。
ただし、google-adk 2.0.0と2.11.0でBaseAgentとLlmAgentを継承して_run_async_impl()を上書きし、単体とWorkflowの中の両方で実行したところ、4通りとも上書きしたメソッドが呼ばれました。2.11.0のソースでも、ノードとしての実行(_run_impl)はrun_asyncを経由して_run_async_implを呼んでいます。現行版で既存のカスタムエージェントがすぐ止まるわけではありません。とはいえ公式が非推奨の経路と明言している以上、ログ出力や状態の書き込みはコールバックへ移し、_run_async_implの上書きは、エージェントの動き自体を変える必要がある場合に限るのが安全です。
イベントの直接追加の禁止
1.xではcontext.session.events.append(custom_event)でセッションにイベントを足すコードが書けました。2.0ではWorkflowエンジンがイベントの保存・ルーティング・ストリーミングを管理するため、直接の追加はエンジンを迂回し、実行の再現性を壊します。enqueue_eventの直接呼び出しも同じ扱いです。ノードやエージェントの中でyieldして返す形に書き換えます。
広すぎるexceptによる再試行と入力待ちへの影響
1.xにはフレームワークの自動再試行がなかったため、ツールの中をtryとexcept Exception:で囲んで握りつぶすコードがよくありました。2.0でこれを残すと、例外がフレームワークに届かず、RetryConfigの再試行が効きません。
except BaseException:はさらに危険です。ワークフローを止めるためのNodeInterruptedErrorはBaseExceptionの子クラスなので、これを捕まえると人の入力待ちで止まれなくなります。捕まえる場合は必ず再送出してください。
依存パッケージとPythonの要件
PyPIのメタデータで比べると、必須の依存パッケージは1.34.0の45個から2.0.0では23個に減り、mcpは必須から外れました。MCPのツールを使うコードはpip install "google-adk[mcp]"で入れないと動きません。A2Aは[a2a]、LiteLLM経由で他社モデルを使う機能は[extensions]で入れます。2.0.0ではREADMEがPython 3.11以上、パッケージのメタデータが3.10以上と不一致でしたが、2.11.0では配布パッケージのREADMEとメタデータがともに3.10以上です。一方、GitHubのmainブランチのREADMEは3.11以上を掲げており、今後の版で引き上げられる可能性があります。新しく環境を作るなら3.11以上が無難です。
1.xから2.xへの移行手順
移行は「影響箇所の洗い出し→検証環境での実行→セッションDBの切り替え」の順で進めます。2.0が書いたセッションは古い1.xで読めないため、本番のDBへ書き込む前に戻せる状態を作っておくことが要になります。
- すぐ上げない環境は
pip install "google-adk~=1.0"のように1.x系へ固定し、意図しない2.x化を防ぐ - リポジトリを検索し、2.0で扱いが変わる書き方の場所を一覧にする(下のコマンド)
- 独自のセッションサービスがあれば、
node_infoとoutputを保存できるようにスキーマを直す。読み手の1.xアプリは先に1.28以上へ上げる - 検証環境で2.11.0を入れ、MCP・A2A・LiteLLMを使う場合は対応するextrasも指定する
- 1.xの会話ログを再生し、最終応答、ツール呼び出しの順番、セッション状態のキーが一致するかを比べる
- 新しく書く処理フローからWorkflowを使い、SequentialAgentなどは動作確認が取れた範囲で置き換える
grep -rnE "_run_async_impl|generate_content\(|session\.events\.append|enqueue_event|except (BaseException|Exception)|SequentialAgent|ParallelAgent|LoopAgent|McpToolset" --include="*.py" .
見つかった箇所のうち、session.events.appendとexcept BaseExceptionは2.0の機能を直接壊すので必ず直します。_run_async_implの上書きと従来の3クラスは2.11.0でも動くため、後回しにできます。McpToolsetが見つかったら、extrasの指定漏れがないかを確認します。
2.xへ上げるべき場面と1.xに留める場面
2.xへ上げる価値が大きいのは、処理の順番をプロンプトに書いていて、モデルが手順を飛ばしたり分岐を間違えたりしている場合です。分類はモデル、行き先の決定はコード、という分担をWorkflowで書けば、どの経路を通ったかがnode_info.pathに残り、障害時の追跡も楽になります。承認待ちを挟む業務フローや、外部APIの失敗を再試行したい処理も、2.0の機能をそのまま使えます。
反対に、1つのエージェントがツールを数個呼ぶだけの構成なら、急いで上げる理由は薄いです。次の条件に当てはまる場合は1.xに固定したまま、計画を立ててから移行してください。
- 独自のセッションDBを列ごとの表で持ち、1.28より前の1.xアプリが同じDBを読んでいる
- Javaで開発している(2026年10月時点でJava版は1.11.0で、2.0の機能は無い)
- 親のLlmAgentが
sub_agentsで処理フローへ処理を渡す構成が中心(Workflowをサブエージェントにできない)
Gemini APIを直接呼ぶSDK側の移行(google-generativeaiからgoogle-genaiへ)は、ADKとは別の話です。Google Gen AI SDKとは?google-genaiの導入とAgent Platform切替・旧SDK移行で整理しています。
よくある質問
ADK 2.0はいつリリースされましたか?
Python版の2.0.0は2026年5月19日、Go版は2026年6月30日、TypeScript版は2026年8月21日にGAとなりました。2026年10月9日時点の最新版はPython 2.11.0、Go v2.5.0、TypeScript 2.2.1です。
ADK 2.0でSequentialAgentはまだ使えますか?
使えます。2.0.0から非推奨となり、インスタンスを作ると警告が出ますが、2.11.0でも動作します。将来の版で削除予定のため、新しい処理フローはWorkflowで書いてください。
ADK 1.xのセッションDBのまま2.0を使えますか?
イベントをJSONの塊で保存している場合はそのまま使えます。列ごとの表で保存している場合は、node_infoとoutputを受け取れるようにスキーマを直す必要があります。2.0が書いたセッションは1.28より前の1.xでは読めません。
ADKのWorkflowで条件分岐やループは書けますか?
書けます。分岐はEvent(route=...)を返すノードとルート値の辞書で、ループはルート付きのエッジで前のノードへ戻して表します。ルートの付かないエッジだけの輪は検証エラーになります。
ADK 2.0はJavaでも使えますか?
2026年10月時点では使えません。Java版の最新は1.11.0で、公式の2.0ガイドが対象に挙げているのはPython・TypeScript・Goの3言語です。
関連記事
- ADKとは?GoogleのAgent Development Kitの仕組みと始め方【2.11対応】
- ADKのコンテキスト圧縮と停止・再開の使い方|EventsCompactionConfig・ResumabilityConfig・HITL【2.11対応】
- A2Aリモートエージェントとは?ADKでの公開・呼び出しとCloud Run・Agent Runtimeへのデプロイ【ADK 2.11対応】
- A2A(Agent2Agent)プロトコルとは?v1.0の仕組み・MCPとの違い・実装手順
- Google Gen AI SDKとは?google-genaiの導入とAgent Platform切替・旧SDK移行【2026年10月】