0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【ADK2.0】実践② Google ADKで複数レビュアーを並列実行(Fan-out / Fan-in)

0
Posted at

はじめに

前回、ADK 2.0のグラフワークフローを触ってみて、「下書き生成→文字数チェック」という直列+条件分岐のワークフローを作りました。

今回はその続きとして、下書きを複数のレビュアーで並列チェックさせ、指摘があれば差し戻すという、より実践的な構成に拡張してみます。

※薄いグレーが前回まで、濃いグレーが今回追加する部分(並列レビューとループ)。

図では3人のレビュアーを1つの箱にまとめていますが、実際にはgrammar_reviewerfact_reviewerseo_reviewerの3つが並列実行されます(詳細は後述のコードを参照)。


1. Fan-out/Fan-inとは

  • Fan-out:1つのノードの出力を、複数のノードに同時に渡して並列実行させること
  • Fan-in:並列実行した結果を1箇所に集約すること

ADK 2.0では、JoinNodeというノードを使ってFan-inを実現します。
JoinNodeは、上流にあるすべてのノードの出力が揃うまで待ってから、次のノードに処理を渡してくれます。

参考:Build graph routes for workflow agents(公式ドキュメント)

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の構造が組めます。

judgeEvent(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のブログ記事を書いてみます。

image.png

下書き作成後、3つのレビュアーがチェックを実施し、結果として「SEOチェック」でNGが出ているのが分かります。
NGの場合は、判定理由もエージェントが生成→それを元に、revice_agentが再度記事を修正する形です。

image.png

全レビュアーのチェックがOKになったら、finalize_agentが動き、最終記事を出力します。

5. 触ってみた所感

Fan-out/Fan-inとループは、実際に書いてみるとエッジの足し算でしかなく、思ったよりシンプルに組めました。
特にループが特別なクラスや構文を必要とせず、普通のエッジ定義の延長線上で表現できる点は、実装しやすくて良かったです。


まとめ

ADK 2.0のFan-out/Fan-inループについて、「ブログ執筆エージェント」のレビュー工程を題材に検証してみました。

  • 複数のノードから同じJoinNodeに向かうエッジを書くだけでFan-outが組める
  • ループは特別な構文ではなく、エッジで前のノードに戻すだけで表現できる
  • 無限ループ対策は別途自分で考慮する必要がある

次回は、この一連のワークフローに人間(編集長)の承認を挟むHuman-in-the-Loopを実装してみます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?