背景:広告プラットフォームの構造的問題
Spotifyは広告事業を運営する上で、構造的な問題に直面していました。ダイレクト購入、セルフサービス、プログラマティックなど複数の購入チャネルが統合されたバックエンド上に存在するものの、各チャネルごとに独自のワークフローと意思決定ロジックが存在していました。その結果、同じコアロジック(予算配分、インベントリ選択、効率性とリーチのバランスなど)がチャネルごとに重複実装され、時間とともに異なる動作をする「振る舞いの断片化(behavior fragmentation)」が発生していました。
従来の「新しいバックエンドサービスを作る」というアプローチではこの問題を解決できませんでした。ワークフローは組み合わせ的(combinatorial)であり、同じ意思決定が複数のサーフェスで一貫して現れる必要があり、システムは「目標」を理解して行動できるインテントレイヤー(intent layer)が不足していました。Spotifyチームはこのギャップを埋めるために、エージェンティック(agentic)アプローチを選択しました。(参考:バックグラウンドコーディングエージェントのためのコンテキストエンジニアリングガイド)
アーキテクチャ:マルチエージェントシステムの設計
Spotifyはメディアプランニング(Media Planning)を最初のユースケースに選びました。広告販売、広告主、インベントリ、ペーシング、広告商品がすべて衝突するポイントだからです。システムは大きく3つのコンポーネントに分かれます:
- ルーターエージェント(RouterAgent):ユーザーメッセージを分析し、どの情報が含まれているかを素早く判断し、必要な専門エージェントにタスクを委譲します。
- 専門解決エージェント(Resolution Agents):目標(GoalResolverAgent)、ターゲットオーディエンス(AudienceResolverAgent)、予算(BudgetAgent)、スケジュール(ScheduleAgent)など、各ドメインを担当します。
- メディアプランナーエージェント(MediaPlannerAgent):すべての解決情報を基に、過去のパフォーマンスデータを活用したヒューリスティックベースの最適化レコメンデーションを生成します。
このアーキテクチャの核心的な利点は並列実行です。独立したエージェントが同時に実行されるため、全体の応答時間が3〜5秒に短縮されました。各エージェントはGoogle ADK(Agent Development Kit)のFunctionToolを通じて実際のデータにアクセスし、@Schemaアノテーションを介してLLMに構造化されたパラメータ情報を提供します。
![]()
核心コード例:エージェントツール定義
以下は、Google ADKでFunctionToolを使用してエージェントが実際のデータにアクセスできるようにする例です。
from google.adk.tools import FunctionTool
from pydantic import BaseModel, Field
from typing import Optional
class BudgetInput(BaseModel):
"""予算入力スキーマ:様々な形式の予算をパースします。"""
budget_text: str = Field(description="ユーザーが入力した予算テキスト(例:'$5000', '5k', '€10,000')")
currency: Optional[str] = Field(default="USD", description="通貨コード(USD, EUR, JPYなど)")
def parse_budget(input: BudgetInput) -> dict:
"""
予算テキストをパースしてマイクロユニットに変換します。
実際の実装では正規表現と為替レート変換ロジックが含まれます。
"""
# 例: "$5,000" -> 5000000000(マイクロ単位)
# 実務では正規表現で数字と通貨を分離
import re
numeric_part = re.sub(r'[^0-9.]', '', input.budget_text)
amount = float(numeric_part)
micro_amount = int(amount * 1_000_000)
return {
"amount_micro": micro_amount,
"currency": input.currency,
"formatted": f"{input.currency} {amount:,.2f}"
}
# ADK FunctionToolとして登録
budget_tool = FunctionTool(
name="parse_budget",
description="ユーザーの自然言語による予算入力をパースしてマイクロユニットに変換します。",
func=parse_budget,
input_schema=BudgetInput
)
# エージェントにツールを接続
from google.adk.agents import Agent
budget_agent = Agent(
name="BudgetAgent",
instruction="""
あなたは予算解釈の専門家です。ユーザーが様々な形式で入力した予算をパースします。
- '$5000', '5k', '€10,000', '1000万円'など、すべての形式を処理します。
- 結果は常にマイクロユニット(1/1,000,000単位)で返します。
- 通貨情報がない場合はデフォルトでUSDを使用します。
- パース失敗時は明確なエラーメッセージを返します。
""",
tools=[budget_tool]
)
主要最適化ルール(擬似コード)
def generate_recommendations(budget, goals, audience, schedule, historical_data):
"""
過去のパフォーマンスデータを基に最適な広告セットのレコメンデーションを生成します。
"""
recommendations = []
# 予算規模に応じたレコメンデーション数の決定
if budget < 1_000_000_000: # €1,000未満(マイクロ単位)
max_recommendations = 1
elif budget < 5_000_000_000: # €5,000未満
max_recommendations = 2
elif budget < 15_000_000_000: # €15,000未満
max_recommendations = 3
else:
max_recommendations = 5
# 過去キャンペーンのスコアリング
scored_campaigns = []
for campaign in historical_data:
score = 0
# コスト最適化:CPM、CPC、CPIが中央値より低い場合に高スコア
if campaign.cpm < historical_median_cpm:
score += 10
# 配信率最適化:100%に近いほど高スコア
if 0.95 <= campaign.delivery_rate <= 1.05:
score += 20
# 予算マッチング:類似予算規模のキャンペーン
budget_ratio = campaign.budget / budget
if 0.8 <= budget_ratio <= 1.2:
score += 15
# ターゲットマッチング:人口統計および関心の重複度に基づく
score += calculate_target_overlap(campaign, audience) * 5
scored_campaigns.append((score, campaign))
# 上位N個を選択(多様性確保:フォーマット/目標の組み合わせ重複防止)
selected = select_diverse(scored_campaigns, max_recommendations)
return selected

主要な教訓と注意点
教訓1:プロンプトエンジニアリングはソフトウェアエンジニアリングである
プロンプトをコードとして扱う必要があります。バージョン管理、テスト、反復改善が必須です。小さなプロンプトの変更が出力の一貫性に大きな影響を与える可能性があります。出力形式を明確に指定し、具体的な例を提供し、プロンプトとパースレイヤーの両方にガードレールを構築する必要があります。
教訓2:エージェントの境界設定が重要
エージェントが多すぎるとレイテンシと調整オーバーヘッドが増加します。少なすぎるとプロンプトが巨大化し、メンテナンスが困難になります。経験則として、エージェントあたり1つの固有スキルまたはデータソースを割り当てるのが良いでしょう。
教訓3:ツールによるグラウンディング
LLMは強力ですが、幻覚(hallucination)を起こす可能性があります。実際のデータ(地域ターゲット、広告カテゴリ、過去のパフォーマンス)にアクセスできるツールをエージェントに提供することで、出力を現実に基づかせることができます。LLMは「何をするか」を推論し、ツールは正確なデータを提供するという役割分担が重要です。
限界と注意点
- 同期 vs ストリーミング:現在は同期応答を使用していますが、長時間実行タスクにはストリーミング(SSE)の方が優れたUXを提供します。
- メモリ vs DBキャッシュ:ヒストリカルデータをインメモリキャッシュに保存してレイテンシを最小化しましたが、メモリ使用量とデータの鮮度のトレードオフがあります。
- エージェント調整オーバーヘッド:マルチエージェントシステムは単一エージェントよりも複雑性が高くなります。エージェント間のコンテキスト共有と評価ロジックの一貫性を維持することが重要です。
日本の開発エコシステムにおける適用コンテキスト
国内の広告プラットフォーム(例:Yahoo!広告、LINE広告、Amazon広告など)も同様の構造的問題を抱えています。複数の購入チャネル(DSP、ネットワーク、直接購入)が存在し、各チャネルごとに最適化ロジックが重複実装されているケースが多く見られます。特にレガシーシステムとの統合は日本のSI環境ではさらに困難です。Spotifyのアプローチは以下の点で国内に適用可能です:
- 段階的導入:既存サービスをすべて書き換えるのではなく、最も複雑なドメイン(例:メディアプランニング)から始めて段階的に拡張
- 既存APIの活用:新機能をゼロから実装するのではなく、既存の広告APIをエージェントの「ツール」として活用
- 並列実行によるパフォーマンス確保:国内環境でもエージェントの並列実行は3〜5秒以内の応答時間を維持するための鍵
次のステップ学習方向
- Google ADK公式ドキュメント:エージェントオーケストレーションの深堀り学習
- Agentic Design Patterns:ReAct、Plan-and-Execute、Multi-agent Debateなど様々なパターンの研究
- Vertex AI Agent Builder:プロダクションレベルのエージェントデプロイとモニタリング方法の学習
- RAG(Retrieval-Augmented Generation):エージェントがより多くのコンテキストを活用できるようにRAGパターンを適用

まとめ:エージェンティックプラットフォームが広告の未来を変える
Spotifyの「Ads AI」事例は、複雑なマルチステップワークフローがマルチエージェントアーキテクチャにどれほど適しているかを示しています。メディアプランニング問題を専門化されたエージェントに分解することで、各エージェントが集中したプロンプトと適切なツール、明確な責任を持つようになり、保守性とパフォーマンスを同時に確保しました。
特に印象的なのは、「単にAI機能を追加する」のではなく、「構造的問題を解決するためのプラットフォーム的アプローチ」を取ったことです。ハードコードされたワークフローの代わりに、意思決定を中央集権化し、すべての購入チャネルとサーフェスに一貫して投影できるエージェンティックプラットフォームを構築しました。
核心メッセージ:複雑なドメイン問題を解決する際は、「より賢い単一システム」を作るよりも、「適切に定義された複数のエージェントの協調システム」を構築する方が効果的である可能性があります。(参考:Cloudflare TurnstileチャレンジページリデザインUXインサイト)