Skip to content

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.


The fork is hosted at github.com/vidaiUK/adk-python. It adds two things to stock ADK:

  1. A precedence ladder of env vars read by every LLM class at instantiation. ADK_LLM_BASE_URL is the framework-wide default; each provider also honours its own native var.
  2. An AnthropicLlm class: direct Anthropic protocol, fully test-covered.

Install

pip install "git+https://github.com/vidaiUK/adk-python.git@stable"

pyproject.toml:

dependencies = [
    "google-adk @ git+https://github.com/vidaiUK/adk-python.git@stable",
]

requirements.txt:

git+https://github.com/vidaiUK/adk-python.git@stable#egg=google-adk

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_URL or OPENAI_API_BASE set from a previous direct-vendor setup, those take precedence over ADK_LLM_BASE_URL. Either unset them or point them at the control plane too.

⚠️ Watch out. For LiteLlm, a URL inherited from ADK_LLM_BASE_URL automatically gets /v1 appended if it lacks a version path (LiteLLM's OpenAI-compat transport requires it). Gemini and AnthropicLlm use 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

  1. Replace google-adk with the fork in your install (see above).
  2. Set ADK_LLM_BASE_URL.
  3. 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 against 1.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
agent = Agent(model=LiteLlm(model="anthropic/claude-haiku-4-5"), name="my_agent")

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:

  1. Swap your install line: pip install "git+https://github.com/vidaiUK/adk-python.git@stable".
  2. Set ADK_LLM_BASE_URL.
  3. 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.