はじめに
前回、ADK 2.0のグラフワークフローを触ってみて、「下書き生成→文字数チェック」という直列+条件分岐のワークフローを作りました。
今回はその続きとして、下書きを複数のレビュアーで並列チェックさせ、指摘があれば差し戻すという、より実践的な構成に拡張してみます。
※薄いグレーが前回まで、濃いグレーが今回追加する部分(並列レビューとループ)。
図では3人のレビュアーを1つの箱にまとめていますが、実際にはgrammar_reviewer/fact_reviewer/seo_reviewerの3つが並列実行されます(詳細は後述のコードを参照)。
1. Fan-out/Fan-inとは
- Fan-out:1つのノードの出力を、複数のノードに同時に渡して並列実行させること
- Fan-in:並列実行した結果を1箇所に集約すること
ADK 2.0では、JoinNodeというノードを使ってFan-inを実現します。
JoinNodeは、上流にあるすべてのノードの出力が揃うまで待ってから、次のノードに処理を渡してくれます。
2. 複数レビュアーを並列実行する
下書きに対して、「文法チェック」「事実確認」「SEOチェック」を担当する3つのエージェントを用意し、並列にレビューさせます。
from google.adk import Agent
from google.adk import Workflow
from google.adk import Event
from google.adk.workflow import JoinNode
# (前回作成)下書き生成エージェント
draft_agent = Agent(
name="draft_agent",
model="gemini-3.6-flash",
instruction="""あなたはブログ記事のライターです。
与えられたトピックについて、800字程度の日本語のブログ記事の下書きを書いてください。
本文のみを出力し、前置きや後書きは不要です。""",
output_schema=str,
output_key="draft", # 出力をセッション状態 state["draft"] に保存する
)
def make_reviewer(name: str, viewpoint: str) -> Agent:
return Agent(
name=name,
model="gemini-3.6-flash",
# 観点(viewpoint)だけ差し替えて、同じ形式で判定
instruction=f"""あなたはブログ記事のレビュアーです。「{viewpoint}」という観点で下書きをチェックしてください。
問題がなければ1行目に「OK」とだけ書いてください。
問題があれば1行目に「NG: 」に続けて具体的な理由を書いてください。""",
output_schema=str,
)
# 観点の異なる3レビュアーを生成
grammar_reviewer = make_reviewer("grammar_reviewer", "誤字脱字や文法の誤りがないか")
fact_reviewer = make_reviewer("fact_reviewer", "事実関係に誤りがないか")
seo_reviewer = make_reviewer("seo_reviewer", "検索されやすいキーワードが含まれているか")
# 全レビュアーの出力が揃うまで待って集約するノード(Fan-in役)
join_node = JoinNode(name="review_join")
("START", draft_agent, grammar_reviewer, join_node)のように、3つのレビュアーそれぞれから同じjoin_nodeに向かうエッジを書くだけで、Fan-out/Fan-inの構造が組めます。
judgeはEvent(route=[...])しか返さないため、その後ろのノードには下書き本文が引き継がれません。
そのため、output_key="draft"を指定してstateに保存し、{draft}として参照する形で対応しています。
3. 差し戻しループを実装する
レビュー結果を判定するjudge関数と、修正を行うrevise_agentを追加し、NGなら差し戻し、OKなら次に進むという分岐を実装します。
def judge(node_input: dict):
"""3つのレビュー結果をまとめて確認する"""
verdicts = {name: result.splitlines()[0] for name, result in node_input.items()}
if all(v == "OK" for v in verdicts.values()):
return Event(route=["OK"]) # 全員OKなら次へ
return Event(route=["NG"]) # 誰か1人でもNGなら差し戻し
# NGだった場合に下書きを修正するエージェント
revise_agent = Agent(
name="revise_agent",
model="gemini-3.6-flash",
instruction="""以下の下書きを、レビュー結果(NGの理由)をもとに修正してください。
本文のみを出力してください。
下書き:
{draft}""",
output_schema=str,
output_key="draft", # 修正後の下書きでstate["draft"]を上書きする
)
# (前回作成)OKだった場合に記事として仕上げるエージェント
finalize_agent = Agent(
name="finalize_agent",
model="gemini-3.6-flash",
instruction="""以下の下書きに、タイトルと簡単な導入文を追加して、
ブログ記事として仕上げてください。
下書き:
{draft}""",
output_schema=str,
)
root_agent = Workflow(
name="blog_writer_workflow",
edges=[
("START", draft_agent),
# --- Fan-out: draft_agentの出力を3つのレビュアーに同時に渡す ---
(draft_agent, grammar_reviewer, join_node),
(draft_agent, fact_reviewer, join_node),
(draft_agent, seo_reviewer, join_node),
# --- Fan-in: 全てのレビュー結果がjoin_nodeに揃ったらjudgeで判定 ---
(join_node, judge),
(judge, {
"OK": finalize_agent, # 全員OKなら仕上げへ
"NG": revise_agent, # 誰かNGなら修正へ
}),
# --- ループ: 修正後、再レビュー ---
(revise_agent, grammar_reviewer),
(revise_agent, fact_reviewer),
(revise_agent, seo_reviewer),
],
)
ポイントは、ループを実現するための特別な構文がないことです。
revise_agentからgrammar_reviewerたちに向けて、既存のエッジと同じように書くだけで、「修正→再レビュー」のループが表現できています。
ADKのグラフでは一度実行したノードも再度呼び出せる(サイクルを許容する)ため、これだけでループとして成立します。
4. 動かしてみる
前回に引き続き、本件のエージェントを使用してBigQueryのブログ記事を書いてみます。
下書き作成後、3つのレビュアーがチェックを実施し、結果として「SEOチェック」でNGが出ているのが分かります。
NGの場合は、判定理由もエージェントが生成→それを元に、revice_agentが再度記事を修正する形です。
全レビュアーのチェックがOKになったら、finalize_agentが動き、最終記事を出力します。
5. 触ってみた所感
Fan-out/Fan-inとループは、実際に書いてみるとエッジの足し算でしかなく、思ったよりシンプルに組めました。
特にループが特別なクラスや構文を必要とせず、普通のエッジ定義の延長線上で表現できる点は、実装しやすくて良かったです。
まとめ
ADK 2.0のFan-out/Fan-inとループについて、「ブログ執筆エージェント」のレビュー工程を題材に検証してみました。
- 複数のノードから同じ
JoinNodeに向かうエッジを書くだけでFan-outが組める - ループは特別な構文ではなく、エッジで前のノードに戻すだけで表現できる
- 無限ループ対策は別途自分で考慮する必要がある
次回は、この一連のワークフローに人間(編集長)の承認を挟むHuman-in-the-Loopを実装してみます。

