ADK Middleware for AG-UI Protocol
Project description
ADK Middleware for AG-UI Protocol
This Python middleware enables Google ADK agents to be used with the AG-UI Protocol, providing a bridge between the two frameworks.
Prerequisites
The examples use ADK Agents using various Gemini models along with the AG-UI Dojo.
- A Gemini API Key. The examples assume that this is exported via the GOOGLE_API_KEY environment variable.
Quick Start
To use this integration you need to:
-
Clone the AG-UI repository.
git clone https://github.com/ag-ui-protocol/ag-ui.git
-
Change to the
integrations/adk-middleware/pythondirectory.cd integrations/adk-middleware/python
-
Install the
adk-middlewarepackage from the local directory. For example,pip install .
or
uv pip install .
This installs the package from the current directory which contains:
src/ag_ui_adk/- The middleware source codeexamples/- Example servers and agentstests/- Test suite
-
Install the requirements for the
examples, for example:uv pip install -r requirements.txt
-
Run the example fast_api server.
export GOOGLE_API_KEY=<My API Key> cd examples uv sync uv run dev
-
Open another terminal in the root directory of the ag-ui repository clone.
-
Start the integration ag-ui dojo:
pnpm install && pnpm run dev
-
Select View
ADK Middlewarefrom the sidebar.
Development Setup
If you want to contribute to ADK Middleware development, you'll need to take some additional steps. You can either use the following script of the manual development setup.
# From the adk-middleware directory
chmod +x setup_dev.sh
./setup_dev.sh
Manual Development Setup
# Create virtual environment
python -m venv venv
source venv/bin/activate
# Install this package in editable mode
pip install -e .
# For development (includes testing and linting tools)
pip install -e ".[dev]"
# OR
pip install -r requirements-dev.txt
This installs the ADK middleware in editable mode for development.
Testing
# Run tests (271 comprehensive tests)
pytest
# With coverage
pytest --cov=src/ag_ui_adk
# Specific test file
pytest tests/test_adk_agent.py
Usage options
Option 1: Direct Usage
from ag_ui_adk import ADKAgent
from google.adk.agents import Agent
# 1. Create your ADK agent
my_agent = Agent(
name="assistant",
instruction="You are a helpful assistant."
tools=[
AGUIToolset(), # Add the tools provided by the AG-UI client
]
)
# 2. Create the middleware with direct agent embedding
agent = ADKAgent(
adk_agent=my_agent,
app_name="my_app",
user_id="user123"
)
# 3. Use directly with AG-UI RunAgentInput
async for event in agent.run(input_data):
print(f"Event: {event.type}")
Option 2: FastAPI Server
from fastapi import FastAPI
from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint
from google.adk.agents import Agent
# 1. Create your ADK agent
my_agent = Agent(
name="assistant",
instruction="You are a helpful assistant."
tools=[
AGUIToolset(), # Add the tools provided by the AG-UI client
]
)
# 2. Create the middleware with direct agent embedding
agent = ADKAgent(
adk_agent=my_agent,
app_name="my_app",
user_id="user123"
)
# 3. Create FastAPI app
app = FastAPI()
add_adk_fastapi_endpoint(
app, agent, path="/chat",
extract_headers=["x-user-id", "x-tenant-id"] # Extract HTTP headers into state.headers
)
# Run with: uvicorn your_module:app --host 0.0.0.0 --port 8000
For detailed configuration options, see CONFIGURATION.md
Option 3: Using ADK App with ResumabilityConfig (HITL)
Requires
google-adk >= 1.16.0
For human-in-the-loop (HITL) workflows where the agent pauses for user approval and resumes afterward, use ADKAgent.from_app() with ADK's ResumabilityConfig. This enables ADK to persist FunctionCall events before pausing, allowing seamless resumption when the user provides tool results.
from fastapi import FastAPI
from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint, AGUIToolset
from google.adk.agents import Agent
from google.adk.apps import App, ResumabilityConfig
# 1. Create your ADK agent with client-side tools
my_agent = Agent(
name="assistant",
instruction="You are a helpful assistant.",
tools=[
AGUIToolset(), # Client-side tools for HITL workflows
]
)
# 2. Wrap in an ADK App with ResumabilityConfig
adk_app = App(
name="my_app",
root_agent=my_agent,
resumability_config=ResumabilityConfig(is_resumable=True),
)
# 3. Create the middleware using from_app()
agent = ADKAgent.from_app(
adk_app,
user_id="user123",
session_timeout_seconds=3600,
use_in_memory_services=True,
)
# 4. Add FastAPI endpoint
app = FastAPI()
add_adk_fastapi_endpoint(app, agent, path="/chat")
How it works:
- The agent calls a client-side tool (e.g.,
generate_task_steps) — ADK persists theFunctionCallevent and pauses execution - The middleware emits
TOOL_CALL_START,TOOL_CALL_ARGS, andTOOL_CALL_ENDevents to the frontend - The user reviews and responds (approve/reject) — the frontend sends a
ToolMessagewith the result - The middleware resumes ADK execution with the stored
invocation_id, restoring the agent's position - The agent continues from where it left off with the user's response
When to use from_app() vs direct ADKAgent():
| Feature | ADKAgent(adk_agent=...) |
ADKAgent.from_app(app) |
|---|---|---|
| Basic HITL | Yes (native resumability) | |
| Session persistence across pause/resume | Manual | Automatic |
| SequentialAgent sub-agent position restore | No | Yes |
Requires google-adk |
Any version | >= 1.16.0 |
Deprecation notice: The fire-and-forget HITL flow via
ADKAgent(adk_agent=...)is deprecated and will be removed in a future version. For human-in-the-loop workflows, useADKAgent.from_app()withResumabilityConfig(is_resumable=True). The direct constructor remains fully supported for agents without client-side tools. See USAGE.md for migration instructions.
See examples/server/api/human_in_the_loop.py for a complete working example.
Running the ADK Backend Server for Dojo App
To run the ADK backend server that works with the Dojo app, use the following command:
python -m examples.fastapi_server
This will start a FastAPI server that connects your ADK middleware to the Dojo application.
Examples
Simple Conversation
import asyncio
from ag_ui_adk import ADKAgent
from google.adk.agents import Agent
from ag_ui.core import RunAgentInput, UserMessage
async def main():
# Setup
my_agent = Agent(
name="assistant",
instruction="You are a helpful assistant.",
tools=[
AGUIToolset(), # Add the tools provided by the AG-UI client
]
)
agent = ADKAgent(
adk_agent=my_agent,
app_name="demo_app",
user_id="demo"
)
# Create input
input = RunAgentInput(
thread_id="thread_001",
run_id="run_001",
messages=[
UserMessage(id="1", role="user", content="Hello!")
],
context=[],
state={},
tools=[],
forwarded_props={}
)
# Run and handle events
async for event in agent.run(input):
print(f"Event: {event.type}")
if hasattr(event, 'delta'):
print(f"Content: {event.delta}")
asyncio.run(main())
Multiple AG-UI Endpoints
# Create multiple ADKAgent instances with different ADK agents
general_agent_wrapper = ADKAgent(
adk_agent=general_agent,
app_name="demo_app",
user_id="demo"
)
technical_agent_wrapper = ADKAgent(
adk_agent=technical_agent,
app_name="demo_app",
user_id="demo"
)
creative_agent_wrapper = ADKAgent(
adk_agent=creative_agent,
app_name="demo_app",
user_id="demo"
)
# Use different endpoints for each agent
from fastapi import FastAPI
from ag_ui_adk import add_adk_fastapi_endpoint
app = FastAPI()
add_adk_fastapi_endpoint(app, general_agent_wrapper, path="/agents/general")
add_adk_fastapi_endpoint(app, technical_agent_wrapper, path="/agents/technical")
add_adk_fastapi_endpoint(app, creative_agent_wrapper, path="/agents/creative")
Context Support
The middleware automatically passes context from RunAgentInput to your ADK agents, following the pattern established by LangGraph. Context is stored in session state under the _ag_ui_context key and is accessible in both tools and instruction providers.
In Tools via Session State
from google.adk.tools import ToolContext
from ag_ui_adk import CONTEXT_STATE_KEY
def my_tool(tool_context: ToolContext) -> str:
context_items = tool_context.state.get(CONTEXT_STATE_KEY, [])
for item in context_items:
print(f"{item['description']}: {item['value']}")
return "Done"
In Instruction Providers via Session State
from google.adk.agents.readonly_context import ReadonlyContext
from ag_ui_adk import CONTEXT_STATE_KEY
def dynamic_instructions(ctx: ReadonlyContext) -> str:
instructions = "You are a helpful assistant."
context_items = ctx.state.get(CONTEXT_STATE_KEY, [])
for item in context_items:
instructions += f"\n- {item['description']}: {item['value']}"
return instructions
agent = LlmAgent(
name="assistant",
instruction=dynamic_instructions, # Callable instruction provider
)
Alternative: Via RunConfig custom_metadata (ADK 1.22.0+)
For users on ADK 1.22.0 or later, context is also available via RunConfig.custom_metadata:
def dynamic_instructions(ctx: ReadonlyContext) -> str:
# Alternative access via custom_metadata (ADK 1.22.0+)
if ctx.run_config and ctx.run_config.custom_metadata:
context_items = ctx.run_config.custom_metadata.get('ag_ui_context', [])
Note: Session state is the recommended approach as it works with all ADK versions.
See examples/other/context_usage.py for a complete demonstration.
Tool Support
The middleware provides complete bidirectional tool support, enabling AG-UI Protocol tools to execute within Google ADK agents. All tools supplied by the client are currently implemented as long-running tools that emit events to the client for execution and can be combined with backend tools provided by the agent to create a hybrid combined toolset.
Adk Agent Agui Tool Support
Use the AGUIToolset to expose tools from the AG-UI client to the ADK agent. By default all agui client tools are added to the context. You can filter which tools to expose using the tool_filter parameter and fix name conflicts with the tool_name_prefix parameter. In google adk tools with the same name override previously defined tools of the same name. You can order the tools array to control which tool takes precedence.
from ag_ui_adk import ADKAgent, AGUIToolset
from google.adk.agents import Agent
hello_agent = LlmAgent(
name='HelloAgent',
model='gemini-2.5-flash',
description="An agent that greets users",
instruction="""
You are a friendly assistant that greets users.
Use the sayHello tool to greet the user.
""",
tools=[
AGUIToolset(tool_filter=['sayHello']) # Add only the sayHello tool exposed by the AG-UI client
],
)
goodbye_agent = LlmAgent(
name='GoodbyeAgent',
model='gemini-2.5-flash',
description="An agent that says goodbye",
instruction="""
You are a friendly assistant that says goodbye to users.
Use the sayGoodbye tool to say goodbye to the user.
""",
tools=[
AGUIToolset(tool_filter=lambda tool, readonly_context=None: tool.name.endswith('Goodbye') ) # Add tools ending with Goodbye exposed by the AG-UI client
],
)
# create an agent
agent = LlmAgent(
name='QaAgent',
model='gemini-2.5-flash',
description="The QaAgent helps users by answering their questions.",
instruction="""
You are a helpful assistant. Help users by answering their questions and assisting with their needs.
""",
tools=[
# This agent doesn't see any tools provided by the AG-UI client
],
sub_agents=[
hello_agent,
goodbye_agent,
],
)
For detailed information about tool support, see TOOLS.md.
Additional Documentation
- CONFIGURATION.md - Complete configuration guide
- TOOLS.md - Tool support documentation
- USAGE.md - Usage examples and patterns
- ARCHITECTURE.md - Technical architecture and design details
Migration Guide
Migrating from v0.4.x
If you are upgrading from version 0.4.x, please note the following changes:
-
Agui tools are no longer automatically included in the root agent's toolset. You must explicitly add the
AGUIToolsetto your agent's tools list to access AG-UI client tools. -
Agui tools with names that conflict with existing agent tools will no longer be automatically removed. Use the
tool_name_prefixandtool_filterparameters ofAGUIToolsetto manage tool name conflicts and filter which tools to include. -
If you want to maintain the previous behavior of only the root agent having access to AG-UI tools, and ensure no name conflicts, you can add the
AGUIToolsetwith a custom filter as the first tool in the root agent like this:tools=[ AGUIToolset( tool_filter=lambda tool, readonly_context=None: tool.name not in [ "transfer_to_agent", "any other tools provided to this agent that overlap with agui tools...", ], ), ...other tools... ]
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ag_ui_adk-0.6.1.tar.gz.
File metadata
- Download URL: ag_ui_adk-0.6.1.tar.gz
- Upload date:
- Size: 73.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d55982a48b28e9dab7e3cff60735e53e82861fa0175688ea2b58ce4d7925a76
|
|
| MD5 |
682828bffd64b3a3921c644bdb180e7c
|
|
| BLAKE2b-256 |
a086c8e98ed5882929f012f1b480f18b9ce0297536234e0e1d329fb8c2698de2
|
File details
Details for the file ag_ui_adk-0.6.1-py3-none-any.whl.
File metadata
- Download URL: ag_ui_adk-0.6.1-py3-none-any.whl
- Upload date:
- Size: 80.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
72e465d0861eeaabe82cf76a9100acfee70bacfa8ec5e535216f5e0b4bc72890
|
|
| MD5 |
9cde2512c6eef3723889f2a6793db1ab
|
|
| BLAKE2b-256 |
4ba9f274078b048401427f0e9534aed78a1c5647b9a008938f706e17d3b85317
|