🐺

Google ADK SequentialAgent で出力を安定させる

に公開

はじめに

AIアプリケーション開発において、LLMエージェントの出力の安定性と一貫性は重要な課題です。特にビジネスユースケースでは、エージェントの応答が予測可能で構造化されていて欲しいケースもあると思います。

Google Agent Development Kit (ADK)は、エージェントベースのアプリケーション開発をサポートするフレームワークで、中でもSequentialAgentは複数のエージェントを順次連動してくれます。
しかし、SequentialAgentを使ってLLMの出力を安定させるには、いくつかの制約事項と対処法を理解する必要があるので、それらをまとめてみた記事になります。

安定した出力形式が求められるプロジェクトに関わっている方にとって参考になれば幸いです。

結論

Google ADK を活用する際は、メイン処理とフォーマットを分ける

Google ADK について

Google Agent Development Kit (ADK) は、Googleが提供するオープンソースのフレームワークで、エージェントベースのAIアプリケーション開発を支援するツールです。ADKを使用することで、Gemini APIを活用したインテリジェントなエージェントの作成が容易になります。

https://google.github.io/adk-docs/

エージェントの種類

ADKでは、主に3種類のエージェントがあります:

  1. LlmAgent: 大規模言語モデル(LLM)を利用して推論や自然言語理解、意思決定を行うエージェント(非決定論的な振る舞いをします)

  2. Workflow Agent: 事前に定義された実行パターンに従って処理を行うエージェント(決定論的な振る舞いをします)

    • SequentialAgent: サブエージェントを指定された順序で順次実行
    • LoopAgent: 特定の条件が満たされるまでサブエージェントを繰り返し実行
    • ParallelAgent: 複数のサブエージェントを並列に実行
  3. Custom Agent: 独自の制御フローを実装することで、任意のオーケストレーションロジックを定義することができ、非常に特殊で複雑なエージェントのワークフローを構築することができます。

ADKを使ったエージェント開発では、これらの基本コンポーネントを組み合わせることで、複雑なタスクを効率的に処理できるシステムを設計できます。

https://google.github.io/adk-docs/agents/

workflow の実現

ADKでは、Workflow Agentsを用いることで、複数のエージェントを組み合わせた複雑なワークフローを実現できます。Workflow Agentは、サブエージェント間の制御フローを管理し、決定論的な実行パターンを提供します。

Workflow Agentには主に3種類あります:

  1. SequentialAgent: サブエージェントを順番に実行
  2. LoopAgent: 条件を満たすまでサブエージェントを繰り返し実行
  3. ParallelAgent: 複数のサブエージェントを並列に実行

これらを組み合わせることで、複雑な処理フローを構築できます。

https://google.github.io/adk-docs/agents/workflow-agents/

Sequential Agent

今回は タイトルにもあるようにSequential Agentを例に話していきます。

SequentialAgentは、複数のエージェントを指定された順序で実行するWorkflow Agentです。各サブエージェントは前のエージェントの出力を入力として受け取り、順次処理を進めていきます。

https://google.github.io/adk-docs/agents/workflow-agents/sequential-agents/

基本的な実装方法

from google.adk.agents.sequential_agent import SequentialAgent
from google.adk.agents.llm_agent import LlmAgent

# 複数のサブエージェントを定義
agent1 = LlmAgent(
    name="FirstAgent",
    model="gemini-2.0-flash",
    instruction="最初の処理を行うエージェントです。"
)

agent2 = LlmAgent(
    name="SecondAgent",
    model="gemini-2.0-flash",
    instruction="前のエージェントの結果を受け取り、さらに処理を行います。"
)

# SequentialAgentでサブエージェントを順序付けて実行
sequential_agent = SequentialAgent(
    name="ProcessingPipeline",
    sub_agents=[agent1, agent2]
)

サブエージェント間でのデータ共有

SequentialAgentの重要な特徴は、output_keyを使ってサブエージェント間でデータを共有できることです。あるエージェントが生成した出力は、セッションのステート(session.state)に保存され、後続のエージェントから参照できます。

# エージェントは結果をセッションステートに保存
first_agent = LlmAgent(
    # ...その他の設定...
    output_key="extracted_data"  # 結果をextracted_dataキーで保存
)

SequentialAgentの利点

  1. 明確な処理フロー: 各ステップが順序どおりに実行されるため、処理の流れが明確で予測可能
  2. 責任の分離: 各サブエージェントが特定の役割に集中できるため、保守性が向上
  3. 段階的処理: 複雑なタスクを小さなステップに分解して処理できる

SequentialAgentは特に、情報の抽出→分析→要約→フォーマット整形といった段階的なプロセスに適しています。
次のセクションでは、このSequentialAgentを活用して出力を安定させる方法について話していきます。

出力の安定について

LLMを利用したエージェントを開発する際に、出力の安定性は重要な課題の一つです。
特にGoogle ADKでは、出力形式の一貫性を確保するためにいくつかの方法が提供されていますが、それらには組み合わせの制限があります。

Agentでの制約事項

Agentを使用する場合、以下の制約に注意する必要があります:

  • output_schemaを使用すると、toolssub_agentを同時に利用できません
  • LlmAgentでoutput_schemaを指定すると、LLMの出力を構造化できますが、エージェントのツール使用能力や他のエージェントへの制御移譲が無効になります

これは公式ドキュメントに明記されている制約で、次のように説明されています:

Constraint: Using output_schema enables controlled generation within the LLM but disables the agent's ability to use tools or transfer control to other agents. Your instructions must guide the LLM to produce JSON matching the schema directly.
https://google.github.io/adk-docs/agents/llm-agents/#fine-tuning-llm-generation-generate_content_config

format専用のAgentを作る

この制約を回避するための効果的な方法は、処理とフォーマット整形を分離することです。

  1. 処理を行う主要なエージェント(toolsやsub_agentsを使用)
  2. 出力を整形するためだけのフォーマット専用エージェント(output_schemaを使用)

を作成し、SequentialAgentで連携させる方法が有効です。

# フォーマット専用のエージェントの例
from pydantic import BaseModel, Field
from google.adk.agents.llm_agent import LlmAgent

# 出力スキーマを定義
class FormattedOutput(BaseModel):
    title: str
    summary: str
    details: list

# フォーマット専用エージェント
format_agent = LlmAgent(
    name="FormatAgent",
    model="gemini-2.0-flash",
    instruction="""
    あなたは出力フォーマッターです。
    入力された情報を整理して、指定されたJSONスキーマに変換してください。
    入力には処理結果が含まれています。それを title, summary, details に分けて出力してください。
    """,
    output_schema=FormattedOutput,
    output_key="formatted_result"
)

output_keyを指定することで、セッションステートに結果が保存され、後続の処理で参照が容易になります。この方法により、処理の柔軟性と出力の一貫性を両立させることができます。

出力の活用

SequentialAgentを使用して、処理エージェントとフォーマットエージェントを連携させる例を示します

from google.adk.agents.sequential_agent import SequentialAgent
from google.adk.runners import Runner

main_processing_agent = Agent(...)

format_agent = Agent(...)

# 処理を行うエージェントとフォーマットエージェントを連携
pipeline_agent = SequentialAgent(
    name="PipelineAgent",
    sub_agents=[main_processing_agent, format_agent]
)

session_service = InMemorySessionService()
session = session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
# Runnerを使用して実行
runner = Runner(agent=pipeline_agent, app_name=APP_NAME, session_service=session_service)


# Agent Interaction
def call_agent(query):
    content = types.Content(role='user', parts=[types.Part(text=query)])
    events = runner.run(user_id=USER_ID, session_id=SESSION_ID, new_message=content)

    for event in events:
        if event.is_final_response():
            final_response = event.content.parts[0].text
            print("Agent Response: ", final_response)

call_agent("perform math addition")

このようにSequentialAgentとフォーマット専用エージェントを組み合わせることで、処理の柔軟性を維持しながら、一貫性のある構造化された出力を得ることができます。

完全な実装例については、公式ドキュメントのCode Development Pipelineの例が参考になります。

https://google.github.io/adk-docs/agents/workflow-agents/sequential-agents/#full-example-code-development-pipeline

まとめ

まとめると、以下ハマりポイントがあるので注意しながら進められると良いかなと考えています。

  1. 出力の安定性と一貫性の確保

    • output_schemaを使ったフォーマット専用エージェントにより、一貫した構造化出力が可能
    • output_keyによるセッションステートの活用で、エージェント間のデータ連携が容易
  2. 処理の柔軟性と出力の構造化を両立

    • 複雑なタスクを担当する処理エージェントではツールや外部APIを活用
    • フォーマット専用エージェントで最終出力を整形
  3. 段階的プロセスの実現

    • 抽出→分析→要約→フォーマットなどの段階的処理の実装
    • 各ステップを専門的なエージェントが担当し、品質を向上

Discussion