Google ADK: using the VIDAI Control Plane as the backend¶
Google's Agent Development Kit
(google-adk) is the framework you build agents in. The VIDAI
Control Plane is provider-agnostic (OpenAI, Anthropic, Gemini work
as upstreams equally), so your agent code is free to use whichever
ADK class reads naturally. There are two supported ways to point
ADK at the control plane: our maintained fork at
vidaiUK/adk-python, or
the upstream google-adk package from PyPI. Same agent code either
way; what differs is how you wire the endpoint.
Choose your path¶
Path A: our fork ✅ Recommended |
Path B: upstream google-adk (PyPI) |
|
|---|---|---|
| Install | pip install "git+https://github.com/vidaiUK/adk-python.git@stable" |
pip install google-adk |
| Pointing at the control plane | one env var (ADK_LLM_BASE_URL) for every ADK class |
per-class base_url= kwarg, threaded per agent |
| Release cadence | auto-synced with upstream daily; @stable only advances on green tests |
official Google PyPI releases |
Why we recommend the fork¶
The fork exists for one reason: provider independence with one
environment variable. Set ADK_LLM_BASE_URL once and every LLM
class in your agent code (Gemini, AnthropicLlm, LiteLlm)
routes through the control plane. No per-agent base_url=
threading, no per-vendor env-var book-keeping, no code changes
when ops swaps the upstream a public model points at.
A second, smaller benefit: the fork auto-tracks Google's releases.
A daily job merges upstream and runs the test suite, and the
@stable pin only advances when a sync passes, so you stay
current without manual intervention and hold at the last working
version if a sync ever goes red.
Why this fork exists (backstory)
Environment-variable support for base_url was
proposed upstream
and declined: a reasonable call for a framework whose primary
audience runs Google's own models. ADK already accepts a base_url=
kwarg on the model constructor; adding an env var for every
configuration option isn't a tradeoff the upstream maintainers
wanted to make.
That position has a real cost on the developer side: endpoint configuration ends up wired into code, per class, in every project. The fork makes the other tradeoff (configuration in the environment, vendor-independent by default) and lives here permanently. Different priorities, both legitimate; that's why this is a fork and not an argument.
Where to file what. Fork-specific issues and PRs go to
vidaiUK/adk-python;
general ADK changes go upstream.
Full env-var precedence reference + sync mechanics are in the
fork's README and
FORK.md.
Path A: our fork (recommended)¶
The fork is hosted at github.com/vidaiUK/adk-python. It adds two things to stock ADK:
- A precedence ladder of env vars read by every LLM class at
instantiation.
ADK_LLM_BASE_URLis the framework-wide default; each provider also honours its own native var. - An
AnthropicLlmclass: direct Anthropic protocol, fully test-covered.
Install¶
pyproject.toml:
requirements.txt:
The package still imports as import google.adk; only the install
source changes. The @stable pin auto-advances on green daily syncs
and is held back on red ones; for a frozen, never-moving pin, use a
fork-vX.Y.Z tag instead.
Environment setup¶
# Framework-wide default — read by Gemini, AnthropicLlm, and LiteLlm
export ADK_LLM_BASE_URL=https://your-vidaiserver.example.com
# Vendor API keys — filled with your VIDAI Server-issued key
export GOOGLE_API_KEY=sk-your-vidai-key
export ANTHROPIC_API_KEY=sk-your-vidai-key
export OPENAI_API_KEY=sk-your-vidai-key
Per-class precedence. Each class checks its own native env var
first, then falls back to ADK_LLM_BASE_URL. An explicit
base_url= constructor argument always wins over environment
variables.
| Model class | Env vars, in resolution order |
|---|---|
Gemini |
ADK_GEMINI_BASE_URL → ADK_VERTEX_BASE_URL → ADK_LLM_BASE_URL |
AnthropicLlm |
ANTHROPIC_BASE_URL → ADK_LLM_BASE_URL |
LiteLlm |
LITELLM_API_BASE → OPENAI_API_BASE → OPENAI_BASE_URL → ADK_LLM_BASE_URL |
📌 Worth knowing. If you already have
ANTHROPIC_BASE_URLorOPENAI_API_BASEset from a previous direct-vendor setup, those take precedence overADK_LLM_BASE_URL. Either unset them or point them at the control plane too.⚠️ Watch out. For
LiteLlm, a URL inherited fromADK_LLM_BASE_URLautomatically gets/v1appended if it lacks a version path (LiteLLM's OpenAI-compat transport requires it).GeminiandAnthropicLlmuse the root URL unchanged.
Gemini¶
from google.adk.agents import Agent
from google.adk.models.google_llm import Gemini
agent = Agent(
model=Gemini(model="gemini-2.5-flash"), # picks up ADK_LLM_BASE_URL
name="my_agent",
)
AnthropicLlm¶
from google.adk.agents import Agent
from google.adk.models.anthropic_llm import AnthropicLlm
agent = Agent(
model=AnthropicLlm(model="claude-haiku-4-5"), # picks up ADK_LLM_BASE_URL
name="my_agent",
)
LiteLlm: optional¶
Gemini and AnthropicLlm cover every case. LiteLlm is
optional: keep it if you already have OpenAI-shape code and don't
want to rewrite it, drop it if you don't. The only difference is
one extra library on disk.
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm
agent = Agent(
model=LiteLlm(model="openai/gpt-4o-mini"), # picks up ADK_LLM_BASE_URL
name="my_agent",
)
Full example: new project¶
pip install "git+https://github.com/vidaiUK/adk-python.git@stable"
export ADK_LLM_BASE_URL=https://your-vidaiserver.example.com
export GOOGLE_API_KEY=sk-your-vidai-key
from google.adk.agents import Agent
from google.adk.runners import InMemoryRunner
from google.adk.models.google_llm import Gemini
agent = Agent(
model=Gemini(model="gemini-2.5-flash"),
name="assistant",
instruction="Reply tersely.",
)
runner = InMemoryRunner(agent=agent, app_name="my_app")
# Use runner.run_async(...) as in the ADK docs.
Migrating an existing upstream-ADK project¶
- Replace
google-adkwith the fork in your install (see above). - Set
ADK_LLM_BASE_URL. - Remove any per-agent
base_url=kwargs; the env var handles them now.
Your tool definitions, session code, runner setup, and agent
behaviour are unchanged. The package import path stays
google.adk.*.
Path B: upstream google-adk¶
Upstream PyPI works fine. The trade is mechanical: you wire
base_url= per class instead of setting one env var. Same agents,
same providers, same control plane.
Prerequisites¶
google-adk(any recent version; tests run against1.18.0)- The control plane URL and an API key
B.1 Gemini¶
Gemini(...) accepts a base_url= kwarg. Because every Agent(...)
needs a fresh Gemini instance, the cleanest pattern is a factory:
# llm_factory.py
import os
from google.adk.models.google_llm import Gemini
def gemini(model: str) -> Gemini:
return Gemini(
model=model,
base_url=os.environ["VIDAI_SERVER_URL"],
api_key=os.environ["VIDAI_API_KEY"],
)
Every agent:
from google.adk.agents import Agent
from llm_factory import gemini
agent = Agent(
model=gemini("gemini-2.5-flash"),
name="my_agent",
instruction="You are a helpful assistant.",
)
B.2 LiteLlm: optional¶
Optional on this path too. Gemini covers every case; keep
LiteLlm if you have OpenAI-shape or Anthropic-shape code you
don't want to rewrite. It reads env vars; no per-agent
configuration:
export OPENAI_API_BASE=https://your-vidaiserver.example.com/v1
export OPENAI_API_KEY=sk-your-vidai-key
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm
agent = Agent(model=LiteLlm(model="openai/gpt-4o-mini"), name="my_agent")
The same pattern works for Anthropic-shape code:
export ANTHROPIC_API_BASE=https://your-vidaiserver.example.com
export ANTHROPIC_API_KEY=sk-your-vidai-key
B.3 Full example: new project¶
pip install google-adk
export VIDAI_SERVER_URL=https://your-vidaiserver.example.com
export VIDAI_API_KEY=sk-your-vidai-key
# llm_factory.py
import os
from google.adk.models.google_llm import Gemini
def gemini(model: str) -> Gemini:
return Gemini(
model=model,
base_url=os.environ["VIDAI_SERVER_URL"],
api_key=os.environ["VIDAI_API_KEY"],
)
# app.py
from google.adk.agents import Agent
from google.adk.runners import InMemoryRunner
from llm_factory import gemini
agent = Agent(
model=gemini("gemini-2.5-flash"),
name="assistant",
instruction="Reply tersely.",
)
runner = InMemoryRunner(agent=agent, app_name="my_app")
B.4 Migrating to the fork¶
If you're on upstream today and the per-class base_url= wiring
has grown old:
- Swap your install line:
pip install "git+https://github.com/vidaiUK/adk-python.git@stable". - Set
ADK_LLM_BASE_URL. - Drop the factory helper and any
base_url=kwargs.
Tools, sessions, streaming, multi-turn: unchanged.
Cross-provider routing (both paths)¶
Your ADK class is a code-style choice; your upstream is an ops choice. The control plane handles every combination. Ops can change which upstream a public model name routes to, and your agent code doesn't change.
Concrete example: one agent, three upstream strategies¶
Your team has a single agent called corporate-bot. Ops decides
where it actually routes, and can change that decision without
touching your code.
from google.adk.agents import Agent
from google.adk.models.google_llm import Gemini
agent = Agent(
model=Gemini(model="corporate-bot"), # the public name
name="support_bot",
instruction="Answer support questions tersely.",
)
| Week | Ops decision | Where corporate-bot actually goes |
|---|---|---|
| Week 1: MVP | "Use OpenAI GPT-4o for now." | Aliased to gpt-4o on an OpenAI provider. |
| Week 4: cost pressure | "Switch to Gemini 2.5 Flash, cheaper." | Re-aliased to gemini-2.5-flash. |
| Week 12: quality tuning | "Try Claude Haiku for support tickets." | Re-aliased to claude-haiku-4-5 on an Anthropic provider. |
Your code didn't change once. No redeploy, no config change on your side, no environment variable to rewire.
Which combinations work¶
Every cell is verified and test-covered.
| Your ADK class ↓ / Upstream → | OpenAI | Anthropic | Gemini |
|---|---|---|---|
Gemini (either path) |
✅ (translated) | ✅ (translated) | ✅ native |
AnthropicLlm (fork) |
✅ (translated) | ✅ native | ✅ (translated) |
LiteLlm("openai/…") |
✅ native | ✅ (translated) | ✅ (translated) |
LiteLlm("anthropic/…") |
✅ (translated) | ✅ native | ✅ (translated) |
Pick the ADK class that fits your code style. Ops picks the upstream that fits the deployment. The two choices don't constrain each other.
How to tell what actually served your request¶
Response headers carry the routing trail. See
routing-and-headers.md for the full list.
ADK doesn't surface these headers through its Event stream, but
ops can see them in the control plane's usage logs.
One ADK class on the roadmap: Claude (Vertex-Anthropic)¶
First, the proportion. Every ADK path covered above is
test-covered and production-ready. Claude models run through
ADK fine: use AnthropicLlm (Path A) or
LiteLlm(model="anthropic/...") (either path). This section is
about one ADK class only, with one narrow hosting arrangement
behind it.
ADK ships a Claude class specifically for Claude models served
from Google Vertex AI. That class currently can't be redirected
to the control plane by configuration. Direct support for the
Vertex-Anthropic path is on the roadmap. For anything else
Claude-related (Claude on Anthropic's own API, Claude through any
of our other supported upstreams), the test-covered classes above
have you covered.
💡 If your Claude access is specifically via Google Vertex AI tell your ops team. Customer demand is what we use to prioritise roadmap items, and a clean direct path for this is something we want to ship. Until then, ops can advise on the right setup for your deployment.
Other languages: Go, Java, TypeScript¶
Google maintains adk-go and adk-java. We haven't forked either yet, and there's no first-party ADK for TypeScript.
If a control-plane integration in another language would unblock
your team, please file an issue at
vidaiUK/adk-python/issues
(or thumbs-up an existing one). The ADK_LLM_BASE_URL pattern
carries cleanly to any language, and we'll prioritise based on
demand.
Reading the control plane's response headers¶
ADK's Event stream doesn't surface raw HTTP response headers, so
the x-vidai-* routing headers aren't directly readable from your
agent code. For debug dashboards, ops has the control plane's usage
logs; for per-request debug during development, you can call the
vendor SDK directly (bypass ADK briefly; see e.g.
openai-sdk.md).
Tool use, streaming, multi-turn¶
All ADK features work unchanged on both paths. Tools, streaming
via RunConfig(streaming_mode=...), InMemorySessionService
multi-turn, runtime routing changes: same code as a stock ADK app.
Verify it works: probe¶
For Path A (fork), using Gemini:
import asyncio
from google.adk.agents import Agent
from google.adk.runners import InMemoryRunner
from google.adk.models.google_llm import Gemini
from google.genai import types
agent = Agent(
model=Gemini(model="gemini-2.5-flash"), # picks up ADK_LLM_BASE_URL
name="probe",
)
runner = InMemoryRunner(agent=agent, app_name="probe_app")
async def main():
session = await runner.session_service.create_session(
app_name="probe_app", user_id="u1"
)
async for event in runner.run_async(
user_id="u1",
session_id=session.id,
new_message=types.Content(
role="user", parts=[types.Part(text="say: ok")]
),
):
if event.content and event.content.parts:
for p in event.content.parts:
if p.text:
print(p.text, end="")
asyncio.run(main())
For Path B (upstream), the same probe works; just pass
base_url= and api_key= to the Gemini(...) constructor
explicitly instead of relying on the env var.
If the probe prints a response, your setup is correct. If it raises an auth error, the key is wrong. If the model is unknown, check with ops which models are registered for your key.