ag_ui_adk 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,486 @@
1
+ Metadata-Version: 2.4
2
+ Name: ag_ui_adk
3
+ Version: 0.6.0
4
+ Summary: ADK Middleware for AG-UI Protocol
5
+ Author: Mark Fogle
6
+ Author-email: Mark Fogle <mark@contextable.com>
7
+ License-Expression: MIT
8
+ Requires-Dist: ag-ui-protocol>=0.1.15
9
+ Requires-Dist: aiohttp>=3.12.0
10
+ Requires-Dist: asyncio>=3.4.3
11
+ Requires-Dist: fastapi>=0.115.2
12
+ Requires-Dist: google-adk>=1.16.0,<2.0.0
13
+ Requires-Dist: pydantic>=2.11.7
14
+ Requires-Dist: uvicorn>=0.35.0
15
+ Requires-Python: >=3.10, <3.15
16
+ Project-URL: Homepage, https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/adk-middleware/python
17
+ Project-URL: Issues, https://github.com/ag-ui-protocol/ag-ui/issues
18
+ Description-Content-Type: text/markdown
19
+
20
+ # ADK Middleware for AG-UI Protocol
21
+
22
+ This Python middleware enables [Google ADK](https://google.github.io/adk-docs/) agents to be used with the AG-UI Protocol, providing a bridge between the two frameworks.
23
+
24
+ ## Prerequisites
25
+
26
+ The examples use ADK Agents using various Gemini models along with the AG-UI Dojo.
27
+
28
+ - A [Gemini API Key](https://makersuite.google.com/app/apikey). The examples assume that this is exported via the GOOGLE_API_KEY environment variable.
29
+
30
+ ## Quick Start
31
+
32
+ To use this integration you need to:
33
+
34
+ 1. Clone the [AG-UI repository](https://github.com/ag-ui-protocol/ag-ui).
35
+
36
+ ```bash
37
+ git clone https://github.com/ag-ui-protocol/ag-ui.git
38
+ ```
39
+
40
+ 2. Change to the `integrations/adk-middleware/python` directory.
41
+
42
+ ```bash
43
+ cd integrations/adk-middleware/python
44
+ ```
45
+
46
+ 3. Install the `adk-middleware` package from the local directory. For example,
47
+
48
+ ```bash
49
+ pip install .
50
+ ```
51
+
52
+ or
53
+
54
+ ```bash
55
+ uv pip install .
56
+ ```
57
+
58
+ This installs the package from the current directory which contains:
59
+ - `src/ag_ui_adk/` - The middleware source code
60
+ - `examples/` - Example servers and agents
61
+ - `tests/` - Test suite
62
+
63
+ 4. Install the requirements for the `examples`, for example:
64
+
65
+ ```bash
66
+ uv pip install -r requirements.txt
67
+ ```
68
+
69
+ 5. Run the example fast_api server.
70
+
71
+ ```bash
72
+ export GOOGLE_API_KEY=<My API Key>
73
+ cd examples
74
+ uv sync
75
+ uv run dev
76
+ ```
77
+
78
+ 6. Open another terminal in the root directory of the ag-ui repository clone.
79
+
80
+ 7. Start the integration ag-ui dojo:
81
+
82
+ ```bash
83
+ pnpm install && pnpm run dev
84
+ ```
85
+
86
+ 8. Visit [http://localhost:3000/adk-middleware](http://localhost:3000/adk-middleware).
87
+
88
+ 9. Select View `ADK Middleware` from the sidebar.
89
+
90
+ ### Development Setup
91
+
92
+ 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.
93
+
94
+ ```bash
95
+ # From the adk-middleware directory
96
+ chmod +x setup_dev.sh
97
+ ./setup_dev.sh
98
+ ```
99
+
100
+ ### Manual Development Setup
101
+
102
+ ```bash
103
+ # Create virtual environment
104
+ python -m venv venv
105
+ source venv/bin/activate
106
+
107
+ # Install this package in editable mode
108
+ pip install -e .
109
+
110
+ # For development (includes testing and linting tools)
111
+ pip install -e ".[dev]"
112
+ # OR
113
+ pip install -r requirements-dev.txt
114
+ ```
115
+
116
+ This installs the ADK middleware in editable mode for development.
117
+
118
+ ## Testing
119
+
120
+ ```bash
121
+ # Run tests (271 comprehensive tests)
122
+ pytest
123
+
124
+ # With coverage
125
+ pytest --cov=src/ag_ui_adk
126
+
127
+ # Specific test file
128
+ pytest tests/test_adk_agent.py
129
+ ```
130
+ ## Usage options
131
+
132
+ ### Option 1: Direct Usage
133
+ ```python
134
+ from ag_ui_adk import ADKAgent
135
+ from google.adk.agents import Agent
136
+
137
+ # 1. Create your ADK agent
138
+ my_agent = Agent(
139
+ name="assistant",
140
+ instruction="You are a helpful assistant."
141
+ tools=[
142
+ AGUIToolset(), # Add the tools provided by the AG-UI client
143
+ ]
144
+ )
145
+
146
+ # 2. Create the middleware with direct agent embedding
147
+ agent = ADKAgent(
148
+ adk_agent=my_agent,
149
+ app_name="my_app",
150
+ user_id="user123"
151
+ )
152
+
153
+ # 3. Use directly with AG-UI RunAgentInput
154
+ async for event in agent.run(input_data):
155
+ print(f"Event: {event.type}")
156
+ ```
157
+
158
+ ### Option 2: FastAPI Server
159
+
160
+ ```python
161
+ from fastapi import FastAPI
162
+ from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint
163
+ from google.adk.agents import Agent
164
+
165
+ # 1. Create your ADK agent
166
+ my_agent = Agent(
167
+ name="assistant",
168
+ instruction="You are a helpful assistant."
169
+ tools=[
170
+ AGUIToolset(), # Add the tools provided by the AG-UI client
171
+ ]
172
+ )
173
+
174
+ # 2. Create the middleware with direct agent embedding
175
+ agent = ADKAgent(
176
+ adk_agent=my_agent,
177
+ app_name="my_app",
178
+ user_id="user123"
179
+ )
180
+
181
+ # 3. Create FastAPI app
182
+ app = FastAPI()
183
+ add_adk_fastapi_endpoint(
184
+ app, agent, path="/chat",
185
+ extract_headers=["x-user-id", "x-tenant-id"] # Extract HTTP headers into state.headers
186
+ )
187
+
188
+ # Run with: uvicorn your_module:app --host 0.0.0.0 --port 8000
189
+ ```
190
+
191
+ For detailed configuration options, see [CONFIGURATION.md](./CONFIGURATION.md)
192
+
193
+ ### Option 3: Using ADK App with ResumabilityConfig (HITL)
194
+
195
+ > **Requires `google-adk >= 1.16.0`**
196
+
197
+ 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.
198
+
199
+ ```python
200
+ from fastapi import FastAPI
201
+ from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint, AGUIToolset
202
+ from google.adk.agents import Agent
203
+ from google.adk.apps import App, ResumabilityConfig
204
+
205
+ # 1. Create your ADK agent with client-side tools
206
+ my_agent = Agent(
207
+ name="assistant",
208
+ instruction="You are a helpful assistant.",
209
+ tools=[
210
+ AGUIToolset(), # Client-side tools for HITL workflows
211
+ ]
212
+ )
213
+
214
+ # 2. Wrap in an ADK App with ResumabilityConfig
215
+ adk_app = App(
216
+ name="my_app",
217
+ root_agent=my_agent,
218
+ resumability_config=ResumabilityConfig(is_resumable=True),
219
+ )
220
+
221
+ # 3. Create the middleware using from_app()
222
+ agent = ADKAgent.from_app(
223
+ adk_app,
224
+ user_id="user123",
225
+ session_timeout_seconds=3600,
226
+ use_in_memory_services=True,
227
+ )
228
+
229
+ # 4. Add FastAPI endpoint
230
+ app = FastAPI()
231
+ add_adk_fastapi_endpoint(app, agent, path="/chat")
232
+ ```
233
+
234
+ **How it works:**
235
+
236
+ 1. The agent calls a client-side tool (e.g., `generate_task_steps`) — ADK persists the `FunctionCall` event and pauses execution
237
+ 2. The middleware emits `TOOL_CALL_START`, `TOOL_CALL_ARGS`, and `TOOL_CALL_END` events to the frontend
238
+ 3. The user reviews and responds (approve/reject) — the frontend sends a `ToolMessage` with the result
239
+ 4. The middleware resumes ADK execution with the stored `invocation_id`, restoring the agent's position
240
+ 5. The agent continues from where it left off with the user's response
241
+
242
+ **When to use `from_app()` vs direct `ADKAgent()`:**
243
+
244
+ | Feature | `ADKAgent(adk_agent=...)` | `ADKAgent.from_app(app)` |
245
+ |---|---|---|
246
+ | Basic HITL | ~~Yes (fire-and-forget)~~ **Deprecated** | Yes (native resumability) |
247
+ | Session persistence across pause/resume | Manual | Automatic |
248
+ | SequentialAgent sub-agent position restore | No | Yes |
249
+ | Requires `google-adk` | Any version | >= 1.16.0 |
250
+
251
+ > **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, use `ADKAgent.from_app()` with `ResumabilityConfig(is_resumable=True)`. The direct constructor remains fully supported for agents without client-side tools. See [USAGE.md](./USAGE.md#migrating-to-resumable-hitl) for migration instructions.
252
+
253
+ See `examples/server/api/human_in_the_loop.py` for a complete working example.
254
+
255
+ ## Running the ADK Backend Server for Dojo App
256
+
257
+ To run the ADK backend server that works with the Dojo app, use the following command:
258
+
259
+ ```bash
260
+ python -m examples.fastapi_server
261
+ ```
262
+
263
+ This will start a FastAPI server that connects your ADK middleware to the Dojo application.
264
+
265
+ ## Examples
266
+
267
+ ### Simple Conversation
268
+
269
+ ```python
270
+ import asyncio
271
+ from ag_ui_adk import ADKAgent
272
+ from google.adk.agents import Agent
273
+ from ag_ui.core import RunAgentInput, UserMessage
274
+
275
+ async def main():
276
+ # Setup
277
+ my_agent = Agent(
278
+ name="assistant",
279
+ instruction="You are a helpful assistant.",
280
+ tools=[
281
+ AGUIToolset(), # Add the tools provided by the AG-UI client
282
+ ]
283
+ )
284
+
285
+ agent = ADKAgent(
286
+ adk_agent=my_agent,
287
+ app_name="demo_app",
288
+ user_id="demo"
289
+ )
290
+
291
+ # Create input
292
+ input = RunAgentInput(
293
+ thread_id="thread_001",
294
+ run_id="run_001",
295
+ messages=[
296
+ UserMessage(id="1", role="user", content="Hello!")
297
+ ],
298
+ context=[],
299
+ state={},
300
+ tools=[],
301
+ forwarded_props={}
302
+ )
303
+
304
+ # Run and handle events
305
+ async for event in agent.run(input):
306
+ print(f"Event: {event.type}")
307
+ if hasattr(event, 'delta'):
308
+ print(f"Content: {event.delta}")
309
+
310
+ asyncio.run(main())
311
+ ```
312
+
313
+ ### Multiple AG-UI Endpoints
314
+
315
+ ```python
316
+ # Create multiple ADKAgent instances with different ADK agents
317
+ general_agent_wrapper = ADKAgent(
318
+ adk_agent=general_agent,
319
+ app_name="demo_app",
320
+ user_id="demo"
321
+ )
322
+
323
+ technical_agent_wrapper = ADKAgent(
324
+ adk_agent=technical_agent,
325
+ app_name="demo_app",
326
+ user_id="demo"
327
+ )
328
+
329
+ creative_agent_wrapper = ADKAgent(
330
+ adk_agent=creative_agent,
331
+ app_name="demo_app",
332
+ user_id="demo"
333
+ )
334
+
335
+ # Use different endpoints for each agent
336
+ from fastapi import FastAPI
337
+ from ag_ui_adk import add_adk_fastapi_endpoint
338
+
339
+ app = FastAPI()
340
+ add_adk_fastapi_endpoint(app, general_agent_wrapper, path="/agents/general")
341
+ add_adk_fastapi_endpoint(app, technical_agent_wrapper, path="/agents/technical")
342
+ add_adk_fastapi_endpoint(app, creative_agent_wrapper, path="/agents/creative")
343
+ ```
344
+
345
+ ## Context Support
346
+
347
+ 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.
348
+
349
+ ### In Tools via Session State
350
+
351
+ ```python
352
+ from google.adk.tools import ToolContext
353
+ from ag_ui_adk import CONTEXT_STATE_KEY
354
+
355
+ def my_tool(tool_context: ToolContext) -> str:
356
+ context_items = tool_context.state.get(CONTEXT_STATE_KEY, [])
357
+ for item in context_items:
358
+ print(f"{item['description']}: {item['value']}")
359
+ return "Done"
360
+ ```
361
+
362
+ ### In Instruction Providers via Session State
363
+
364
+ ```python
365
+ from google.adk.agents.readonly_context import ReadonlyContext
366
+ from ag_ui_adk import CONTEXT_STATE_KEY
367
+
368
+ def dynamic_instructions(ctx: ReadonlyContext) -> str:
369
+ instructions = "You are a helpful assistant."
370
+
371
+ context_items = ctx.state.get(CONTEXT_STATE_KEY, [])
372
+ for item in context_items:
373
+ instructions += f"\n- {item['description']}: {item['value']}"
374
+
375
+ return instructions
376
+
377
+ agent = LlmAgent(
378
+ name="assistant",
379
+ instruction=dynamic_instructions, # Callable instruction provider
380
+ )
381
+ ```
382
+
383
+ ### Alternative: Via RunConfig custom_metadata (ADK 1.22.0+)
384
+
385
+ For users on ADK 1.22.0 or later, context is also available via `RunConfig.custom_metadata`:
386
+
387
+ ```python
388
+ def dynamic_instructions(ctx: ReadonlyContext) -> str:
389
+ # Alternative access via custom_metadata (ADK 1.22.0+)
390
+ if ctx.run_config and ctx.run_config.custom_metadata:
391
+ context_items = ctx.run_config.custom_metadata.get('ag_ui_context', [])
392
+ ```
393
+
394
+ **Note:** Session state is the recommended approach as it works with all ADK versions.
395
+
396
+ See `examples/other/context_usage.py` for a complete demonstration.
397
+
398
+ ## Tool Support
399
+
400
+ 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.
401
+
402
+ ### Adk Agent Agui Tool Support
403
+
404
+ 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.
405
+
406
+ ```python
407
+ from ag_ui_adk import ADKAgent, AGUIToolset
408
+ from google.adk.agents import Agent
409
+
410
+ hello_agent = LlmAgent(
411
+ name='HelloAgent',
412
+ model='gemini-2.5-flash',
413
+ description="An agent that greets users",
414
+ instruction="""
415
+ You are a friendly assistant that greets users.
416
+ Use the sayHello tool to greet the user.
417
+ """,
418
+ tools=[
419
+ AGUIToolset(tool_filter=['sayHello']) # Add only the sayHello tool exposed by the AG-UI client
420
+ ],
421
+ )
422
+
423
+ goodbye_agent = LlmAgent(
424
+ name='GoodbyeAgent',
425
+ model='gemini-2.5-flash',
426
+ description="An agent that says goodbye",
427
+ instruction="""
428
+ You are a friendly assistant that says goodbye to users.
429
+ Use the sayGoodbye tool to say goodbye to the user.
430
+ """,
431
+ tools=[
432
+ AGUIToolset(tool_filter=lambda tool, readonly_context=None: tool.name.endswith('Goodbye') ) # Add tools ending with Goodbye exposed by the AG-UI client
433
+ ],
434
+ )
435
+
436
+ # create an agent
437
+ agent = LlmAgent(
438
+ name='QaAgent',
439
+ model='gemini-2.5-flash',
440
+ description="The QaAgent helps users by answering their questions.",
441
+ instruction="""
442
+ You are a helpful assistant. Help users by answering their questions and assisting with their needs.
443
+ """,
444
+ tools=[
445
+ # This agent doesn't see any tools provided by the AG-UI client
446
+ ],
447
+ sub_agents=[
448
+ hello_agent,
449
+ goodbye_agent,
450
+ ],
451
+ )
452
+ ```
453
+
454
+
455
+ For detailed information about tool support, see [TOOLS.md](./TOOLS.md).
456
+
457
+ ## Additional Documentation
458
+
459
+ - **[CONFIGURATION.md](./CONFIGURATION.md)** - Complete configuration guide
460
+ - **[TOOLS.md](./TOOLS.md)** - Tool support documentation
461
+ - **[USAGE.md](./USAGE.md)** - Usage examples and patterns
462
+ - **[ARCHITECTURE.md](./ARCHITECTURE.md)** - Technical architecture and design details
463
+
464
+ ## Migration Guide
465
+
466
+ ### Migrating from v0.4.x
467
+
468
+ If you are upgrading from version 0.4.x, please note the following changes:
469
+
470
+ - Agui tools are no longer automatically included in the root agent's toolset. You must explicitly add the `AGUIToolset` to your agent's tools list to access AG-UI client tools.
471
+
472
+ - Agui tools with names that conflict with existing agent tools will no longer be automatically removed. Use the `tool_name_prefix` and `tool_filter` parameters of `AGUIToolset` to manage tool name conflicts and filter which tools to include.
473
+
474
+ - 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 `AGUIToolset` with a custom filter as the first tool in the root agent like this:
475
+
476
+ ```python
477
+ tools=[
478
+ AGUIToolset(
479
+ tool_filter=lambda tool, readonly_context=None: tool.name not in [
480
+ "transfer_to_agent",
481
+ "any other tools provided to this agent that overlap with agui tools...",
482
+ ],
483
+ ),
484
+ ...other tools...
485
+ ]
486
+ ```