Aller au contenu principal
A2A series · 3 / 4

Multi-agent business case:
optimize GAM delivery

Two specialists in parallel (forecast ∥ formats/CTR) then a synthesis — a composed, read-only business deliverable using the real OrbiAds tools. Plus the prompt discipline that prevents hallucinations.

OA
OrbiAds Engineering
Published June 30, 2026 · 11 min read

So far, one agent. Let's move to a real multi-agent ad-ops business case: estimate delivery potential and recommend formats for CTR, in parallel, then merge it all into an actionable plan — all read-only, using the real OrbiAds tools.

The architecture

Three agents, orchestrated by ADK:

SequentialAgent("gam_optimizer")
├─ ParallelAgent("specialists")          # async
│  ├─ forecast_agent   -> output_key = "forecast_result"
│  └─ format_agent     -> output_key = "format_result"
└─ synthesis_agent     # reads {forecast_result?} + {format_result?}
forecast_agent

Estimates impression potential (traffic / forecast).

format_agent

Computes CTR per size and flags legacy sizes.

synthesis_agent

Merges into a plan: potential → formats → sizes to retire.

ADK gotcha: instruction templating {var} is strict and raises a KeyError if a sub-agent's key is not set yet. Use the optional form {var?}.

Honesty over filling blanks

On a traffic-less test network, there is no CTR to compute. A good prompt makes the agent answer "data missing" rather than invent. That's what the trace shows: the forecast is "not quantifiable" for lack of data, and the format_agent surfaces the real GAM frictions instead of fabricating a result.

Async multi-agent: forecast ∥ format → synthesis
Async multi-agent: two specialists in parallel (forecast ∥ format/CTR) → a synthesis agent. A composed business deliverable.

The real GAM frictions (worth knowing)

  • AD_REQUEST_SIZES is incompatible with IMPRESSIONS/CLICKS in the default historical report.
  • The dateRange parameter isn't accepted as-is by run_custom_report — the agent adapts and reports it.
  • Responsive/fluid sizes come back as width=0/height=0: those are not obsolete sizes, and the agent doesn't confuse them.

Prompt discipline

Multi-agent is only worth it if each agent is reliable. The rules that make the difference: name the tool's exact action, forbid invented tool names and bracketed placeholders, require a real value or an explicit "data missing", and only call read-only tools. Add a NETWORK CHECK so it never reads a production network by mistake. The model itself stays a choice: it's the approach we validate, not a specific model.

What's next

Final episode: make an agent speak the AdCP media-buy standard — validate and preview a GAM media buy, still read-only.

The full code, ready to clone

All 4 agents, the step-by-step tutorial and the examples — on GitHub, MIT licensed.

FAQ

Frequently asked questions

How do I run several agents in parallel then merge?

With ADK: a SequentialAgent chains a ParallelAgent (which runs the specialists async) then a synthesis agent. Each specialist writes its result to an output_key; the synthesis reads them via optional {var?} templating to avoid a KeyError if a key is not set yet.

What does the agent do if the test inventory has no data?

It says so. On a traffic-less network there is no CTR to compute: a well-written prompt makes it answer 'data missing' instead of inventing a number or placeholders. Deliverable honesty beats filling in blanks.

What real GAM frictions show up in per-format reporting?

Two concrete ones: the AD_REQUEST_SIZES dimension is incompatible with IMPRESSIONS/CLICKS in the default historical report type; and "responsive" inventory sizes come back as width=0/height=0 — those are fluid formats, not obsolete sizes.

How do you stop the LLM from inventing tools or numbers?

Through prompt discipline: name the exact action, forbid invented tool names and bracketed placeholders, require a real value or an explicit "data missing", and only call read-only tools. We also add a NETWORK CHECK so it never reads a production network by mistake.