はじめに
本記事は、ADK2.0の新機能を、実際に手を動かして検証した結果をまとめています。
前回までで、「AIブログ執筆エージェント」の「下書き→3人のレビュアーが並列チェック→NGなら差し戻し→OKなら仕上げ」という一連のワークフローができあがりました。
今回は、この仕上がった記事を公開する前に人間(編集者)の承認を挟むステップを追加してみます。
※薄いグレーが前回まで(1〜2本目)、濃いグレーが今回追加する部分
過去の記事はこちら↓
| 記事 | 内容 |
|---|---|
| 【ADK2.0】実践① Google ADKで構築するグラフ構造のAIエージェント | 下書き生成→文字数チェック(グラフワークフローの基本) |
| 【ADK2.0】実践② Google ADKで複数レビュアーを並列実行(Fan-out / Fan-in) | 並列レビュー+差し戻しループ |
1. なぜHuman-in-the-Loopが必要か
全て自動化したくなる一方で、
- 記事の公開
- 送金処理
- データベースへの破壊的な操作
といった、後戻りしにくい・影響範囲が大きい処理は、AIだけに任せきりにするのは不安が残ります。
こうした人間の判断を挟むステップ(Human-in-the-Loop, HITL)自体は、ADK 1.xでも実現は可能でした。ただし、いずれの方法も"確実性"という点で曖昧さが残っていました。
1.xでのHITL
ADK 1.xでHITLを組む場合、大きく2つのやり方があるかと思います。
-
プロンプトで「承認を得てから進めて」と指示:
- 一番手軽ですが、あくまでLLMの解釈・判断に委ねる形
- 聞き方次第で承認を飛ばして進めてしまうことがある
-
LongRunningFunctionToolの使用:- 承認待ちの処理をツールとして実装する、より作り込んだ方法
- ただしこちらも「承認ツールを呼ぶかどうか」自体はLLMの判断次第
- 再開時のID管理や状態の保存・復元を自分で実装する必要あり
どちらの方法も、「承認を必ず通過する」という保証をコード側で持てず、最終的にはLLMの振る舞いに依存してしまう、という曖昧さが共通しています。
参考:
ADK 2.0でのHITL
ADK 2.0のRequestInputは、LLMの判断や解釈に頼らない、グラフのノードレベルでの一時停止・再開の仕組みです。
承認ノードを経由するかどうかはグラフ構造によって決定的に決まるため、プロンプトの書き方やLLMの気まぐれで承認がスキップされる、という曖昧さがなくなります。
2. RequestInputの基本
ADK 2.0では、RequestInputというクラスを使ってワークフローを一時停止し、人間の入力を待つことができます。公式ドキュメントには、以下のようなシンプルな例が載っていました。
from google.adk.events import RequestInput
def step1(node_input):
"""人間の入力を求め、一時停止する"""
yield RequestInput(message="Enter a number:")
def step2(node_input):
"""人間の回答(node_input)を受け取って処理する"""
return node_input * 2
root_agent = Workflow(
name="root_agent",
edges=[("START", step1, step2)],
)
状態管理も書かず、関数からRequestInputをyieldするだけで成立します。step1が一時停止した後、人間が回答するとstep1が裏側で再実行され、その回答が自動的にstep2へのnode_inputとして渡される、という一連の流れをフレームワーク側が面倒を見てくれます。
3. 承認ノードを追加する
finalize_agentの後に、編集長の承認を求めるget_editor_approvalノードを追加します。承認(Yes)なら公開処理へ、却下(No)ならrevise_agentに差し戻します。
from google.adk import Agent
from google.adk import Workflow
from google.adk import Event
from google.adk.events import RequestInput
# draft_agent, grammar_reviewer, fact_reviewer, seo_reviewer,
# join_node, judge, revise_agent は前回までと同じものを使用
# (前回記事から更新)最終仕上げエージェント
finalize_agent = Agent(
name="finalize_agent",
model="gemini-3.6-flash",
instruction="""以下の下書きに、タイトルと簡単な導入文を追加して、
ブログ記事として仕上げてください。
下書き:
{draft}""",
output_schema=str,
output_key="final_article", # 完成した記事をstate["final_article"]に保存する
)
# 編集長に承認を求めるノード
def get_editor_approval(node_input: str):
"""仕上がった記事を提示し、編集長の承認を待つ"""
yield RequestInput(
message=f"以下の記事を公開してよいですか?(Yes/No)\n\n{node_input}"
)
def check_approval(node_input: str):
"""承認結果を見て、公開するか差し戻すかを判定する"""
if node_input.strip().lower() == "yes":
return Event(route=["APPROVED"])
return Event(route=["REJECTED"])
def publish_node(ctx, node_input: Any):
return f"記事を公開しました。\n\n{ctx.state['final_article']}"
root_agent = Workflow(
name="blog_writer_workflow",
edges=[
# ===================================================
# 前回記事までに作成したワークフローを埋め込む(そのまま)
# ===================================================
# --- 今回追加した部分 ---
(finalize_agent, get_editor_approval, check_approval),
(check_approval, {
"APPROVED": publish_node, # 承認されたら公開
"REJECTED": revise_agent, # 却下されたら2本目の修正ループに合流
}),
],
)
get_editor_approvalがRequestInputをyieldした時点でワークフローは一時停止し、人間が「Yes」または「No」を返すのを待ちます。
回答が返ってくると、その内容がcheck_approvalへのnode_inputとして渡され、そこから処理が再開される、という流れです。
4. 一時停止・再開の挙動を確認する
実際にワークフローを実行すると、get_editor_approvalのところで処理が止まり、承認待ちの状態になりました。adk web上では、入力フォームが表示されるようになります。
ここで「No」を返すとrevise_agentに差し戻され、2本目で作ったレビューのループにもう一度入っていく様子も確認できました。
「Yes」を返すと、そのまま最終記事を出力してくれます。
5. 触ってみた所感
特別な仕組みをほとんど必要とせず、承認フローを実装することができました。
1.x時代と比べると、LLMの解釈に左右されず承認ステップが必ず通る、という確実性も相まって、2.0ではかなり実践的なワークフローを構築できると感じました。
まとめ
ADK 2.0のHuman-in-the-Loop(HITL)について、「ブログ執筆エージェント」の公開フローを題材に検証してみました。
- ADK 1.xでは「プロンプトで指示する」「LongRunningFunctionToolを使う」のどちらも、最終的にLLMの判断に依存する曖昧さが残っていた
- ADK 2.0では
RequestInputをyieldするだけでワークフローを一時停止でき、グラフ構造として承認ステップを確実に通せる - 承認結果に応じて、公開ノードと差し戻し(既存のループ)に振り分けられる
次回は、ユーザーとチャットするコーディネーターが、裏でこのワークフロー全体をタスクとして動かす、Chat/Task/SingleTurnモードを試してみます。


