Skip to content

Commit d5ddc14

Browse files
Merge pull request #33 from syedfakher27/adk-middleware
Adk middleware
2 parents 20bce0b + 8e13a52 commit d5ddc14

File tree

10 files changed

+302
-49
lines changed

10 files changed

+302
-49
lines changed

typescript-sdk/apps/dojo/src/agents.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [
6767
tool_based_generative_ui: new ServerStarterAgent({ url: "http://localhost:8000/adk-tool-based-generative-ui" }),
6868
human_in_the_loop: new ServerStarterAgent({ url: "http://localhost:8000/adk-human-in-loop-agent" }),
6969
shared_state: new ServerStarterAgent({ url: "http://localhost:8000/adk-shared-state-agent" }),
70+
predictive_state_updates: new ServerStarterAgent({ url: "http://localhost:8000/adk-predictive-state-agent" }),
7071
};
7172
},
7273
},

typescript-sdk/apps/dojo/src/menu.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const menuIntegrations: MenuIntegrationConfig[] = [
2626
{
2727
id: "adk-middleware",
2828
name: "ADK Middleware",
29-
features: ["agentic_chat","tool_based_generative_ui","human_in_the_loop","shared_state"],
29+
features: ["agentic_chat","tool_based_generative_ui","human_in_the_loop","shared_state","predictive_state_updates"],
3030
},
3131
{
3232
id: "server-starter-all-features",

typescript-sdk/integrations/adk-middleware/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111
- **NEW**: SystemMessage support for ADK agents (issue #22) - SystemMessages as first message are now appended to agent instructions
1212
- **NEW**: Comprehensive tests for SystemMessage functionality including edge cases
13+
- **NEW**: Long running tools can be defined in backend side as well
14+
- **NEW**: Predictive state demo is added in dojo App
1315

1416
### Fixed
1517
- **FIXED**: Race condition in tool result processing causing "No pending tool calls found" warnings
1618
- **FIXED**: Tool call removal now happens after pending check to prevent race conditions
1719
- **IMPROVED**: Better handling of empty tool result content with graceful JSON parsing fallback
1820
- **FIXED**: Pending tool call state management now uses SessionManager methods (issue #25)
21+
- **FIXED**: Pending tools issue for normal backend tools is now fixed (issue #32)
22+
- **FIXED**: TestEventTranslatorComprehensive unit test cases fixed
1923

2024
### Enhanced
2125
- **LOGGING**: Added debug logging for tool result processing to aid in troubleshooting

typescript-sdk/integrations/adk-middleware/examples/fastapi_server.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .tool_based_generative_ui.agent import haiku_generator_agent
1313
from .human_in_the_loop.agent import human_in_loop_agent
1414
from .shared_state.agent import shared_state_agent
15+
from .predictive_state_updates.agent import predictive_state_updates_agent
1516

1617
# Basic logging configuration
1718
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
@@ -33,14 +34,15 @@
3334
sample_agent = LlmAgent(
3435
name="assistant",
3536
model="gemini-2.0-flash",
36-
instruction="You are a helpful assistant.",
37+
instruction="You are a helpful assistant. Help users by answering their questions and assisting with their needs.",
3738
tools=[adk_tools.preload_memory_tool.PreloadMemoryTool()]
3839
)
3940
# Register the agent
4041
registry.set_default_agent(sample_agent)
4142
registry.register_agent('adk-tool-based-generative-ui', haiku_generator_agent)
4243
registry.register_agent('adk-human-in-loop-agent', human_in_loop_agent)
4344
registry.register_agent('adk-shared-state-agent', shared_state_agent)
45+
registry.register_agent('adk-predictive-state-agent', predictive_state_updates_agent)
4446
# Create ADK middleware agent
4547
adk_agent = ADKAgent(
4648
app_name="demo_app",
@@ -70,6 +72,13 @@
7072
use_in_memory_services=True
7173
)
7274

75+
adk_predictive_state_agent = ADKAgent(
76+
app_name="demo_app",
77+
user_id="demo_user",
78+
session_timeout_seconds=3600,
79+
use_in_memory_services=True
80+
)
81+
7382
# Create FastAPI app
7483
app = FastAPI(title="ADK Middleware Demo")
7584

@@ -78,6 +87,7 @@
7887
add_adk_fastapi_endpoint(app, adk_agent_haiku_generator, path="/adk-tool-based-generative-ui")
7988
add_adk_fastapi_endpoint(app, adk_human_in_loop_agent, path="/adk-human-in-loop-agent")
8089
add_adk_fastapi_endpoint(app, adk_shared_state_agent, path="/adk-shared-state-agent")
90+
add_adk_fastapi_endpoint(app, adk_predictive_state_agent, path="/adk-predictive-state-agent")
8191

8292
@app.get("/")
8393
async def root():

typescript-sdk/integrations/adk-middleware/examples/human_in_the_loop/agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838

3939
human_in_loop_agent = Agent(
40-
model='gemini-1.5-flash',
40+
model='gemini-2.5-flash',
4141
name='human_in_loop_agent',
4242
instruction=f"""
4343
You are a human-in-the-loop task planning assistant that helps break down complex tasks into manageable steps with human oversight and approval.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""
2+
A demo of predictive state updates using Google ADK.
3+
"""
4+
5+
from dotenv import load_dotenv
6+
load_dotenv()
7+
8+
import json
9+
import uuid
10+
from typing import Dict, List, Any, Optional
11+
from google.adk.agents import LlmAgent
12+
from google.adk.agents.callback_context import CallbackContext
13+
from google.adk.sessions import InMemorySessionService, Session
14+
from google.adk.runners import Runner
15+
from google.adk.events import Event, EventActions
16+
from google.adk.tools import FunctionTool, ToolContext
17+
from google.genai.types import Content, Part, FunctionDeclaration
18+
from google.adk.models import LlmResponse, LlmRequest
19+
from google.genai import types
20+
21+
22+
def write_document(
23+
tool_context: ToolContext,
24+
document: str
25+
) -> Dict[str, str]:
26+
"""
27+
Write a document. Use markdown formatting to format the document.
28+
It's good to format the document extensively so it's easy to read.
29+
You can use all kinds of markdown.
30+
However, do not use italic or strike-through formatting, it's reserved for another purpose.
31+
You MUST write the full document, even when changing only a few words.
32+
When making edits to the document, try to make them minimal - do not change every word.
33+
Keep stories SHORT!
34+
35+
Args:
36+
document: The document content to write in markdown format
37+
38+
Returns:
39+
Dict indicating success status and message
40+
"""
41+
try:
42+
# Update the session state with the new document
43+
tool_context.state["document"] = document
44+
45+
return {"status": "success", "message": "Document written successfully"}
46+
47+
except Exception as e:
48+
return {"status": "error", "message": f"Error writing document: {str(e)}"}
49+
50+
51+
def on_before_agent(callback_context: CallbackContext):
52+
"""
53+
Initialize document state if it doesn't exist.
54+
"""
55+
if "document" not in callback_context.state:
56+
# Initialize with empty document
57+
callback_context.state["document"] = None
58+
59+
return None
60+
61+
62+
def before_model_modifier(
63+
callback_context: CallbackContext, llm_request: LlmRequest
64+
) -> Optional[LlmResponse]:
65+
"""
66+
Modifies the LLM request to include the current document state.
67+
This enables predictive state updates by providing context about the current document.
68+
"""
69+
agent_name = callback_context.agent_name
70+
if agent_name == "DocumentAgent":
71+
current_document = "No document yet"
72+
if "document" in callback_context.state and callback_context.state["document"] is not None:
73+
try:
74+
current_document = callback_context.state["document"]
75+
except Exception as e:
76+
current_document = f"Error retrieving document: {str(e)}"
77+
78+
# Modify the system instruction to include current document state
79+
original_instruction = llm_request.config.system_instruction or types.Content(role="system", parts=[])
80+
prefix = f"""You are a helpful assistant for writing documents.
81+
To write the document, you MUST use the write_document tool.
82+
You MUST write the full document, even when changing only a few words.
83+
When you wrote the document, DO NOT repeat it as a message.
84+
Just briefly summarize the changes you made. 2 sentences max.
85+
This is the current state of the document: ----
86+
{current_document}
87+
-----"""
88+
89+
# Ensure system_instruction is Content and parts list exists
90+
if not isinstance(original_instruction, types.Content):
91+
original_instruction = types.Content(role="system", parts=[types.Part(text=str(original_instruction))])
92+
if not original_instruction.parts:
93+
original_instruction.parts.append(types.Part(text=""))
94+
95+
# Modify the text of the first part
96+
modified_text = prefix + (original_instruction.parts[0].text or "")
97+
original_instruction.parts[0].text = modified_text
98+
llm_request.config.system_instruction = original_instruction
99+
100+
return None
101+
102+
103+
# Create the predictive state updates agent
104+
predictive_state_updates_agent = LlmAgent(
105+
name="DocumentAgent",
106+
model="gemini-2.5-pro",
107+
instruction="""
108+
You are a helpful assistant for writing documents.
109+
To write the document, you MUST use the write_document tool.
110+
You MUST write the full document, even when changing only a few words.
111+
When you wrote the document, DO NOT repeat it as a message.
112+
Just briefly summarize the changes you made. 2 sentences max.
113+
114+
IMPORTANT RULES:
115+
1. Always use the write_document tool for any document writing or editing requests
116+
2. Write complete documents, not fragments
117+
3. Use markdown formatting for better readability
118+
4. Keep stories SHORT and engaging
119+
5. After using the tool, provide a brief summary of what you created or changed
120+
6. Do not use italic or strike-through formatting
121+
122+
Examples of when to use the tool:
123+
- "Write a story about..." → Use tool with complete story in markdown
124+
- "Edit the document to..." → Use tool with the full edited document
125+
- "Add a paragraph about..." → Use tool with the complete updated document
126+
127+
Always provide complete, well-formatted documents that users can read and use.
128+
""",
129+
tools=[write_document],
130+
before_agent_callback=on_before_agent,
131+
before_model_callback=before_model_modifier
132+
)

typescript-sdk/integrations/adk-middleware/examples/shared_state/agent.py

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ class Recipe(BaseModel):
6969
def generate_recipe(
7070
tool_context: ToolContext,
7171
skill_level: str,
72-
special_preferences: str = "",
72+
title: str,
73+
special_preferences: List[str] = [],
7374
cooking_time: str = "",
7475
ingredients: List[dict] = [],
7576
instructions: List[str] = [],
@@ -79,14 +80,20 @@ def generate_recipe(
7980
Generate or update a recipe using the provided recipe data.
8081
8182
Args:
83+
"title": {
84+
"type": "string",
85+
"description": "**REQUIRED** - The title of the recipe."
86+
},
8287
"skill_level": {
8388
"type": "string",
8489
"enum": ["Beginner","Intermediate","Advanced"],
8590
"description": "**REQUIRED** - The skill level required for the recipe. Must be one of the predefined skill levels (Beginner, Intermediate, Advanced)."
8691
},
8792
"special_preferences": {
88-
"type": "string",
89-
"description": "**OPTIONAL** - Special dietary preferences for the recipe as comma-separated values. Example: 'High Protein, Low Carb, Gluten Free'. Leave empty or omit if no special preferences."
93+
"type": "array",
94+
"items": {"type": "string"},
95+
"enum": ["High Protein","Low Carb","Spicy","Budget-Friendly","One-Pot Meal","Vegetarian","Vegan"],
96+
"description": "**OPTIONAL** - Special dietary preferences for the recipe as comma-separated values. Example: 'High Protein, Low Carb, Gluten Free'. Leave empty array if no special preferences."
9097
},
9198
"cooking_time": {
9299
"type": "string",
@@ -123,6 +130,7 @@ def generate_recipe(
123130

124131
# Create RecipeData object to validate structure
125132
recipe = {
133+
"title": title,
126134
"skill_level": skill_level,
127135
"special_preferences": special_preferences ,
128136
"cooking_time": cooking_time ,
@@ -143,8 +151,7 @@ def generate_recipe(
143151

144152
tool_context.state["recipe"] = current_recipe
145153

146-
# Log the update
147-
print(f"Recipe updated: {recipe.get('change')}")
154+
148155

149156
return {"status": "success", "message": "Recipe generated successfully"}
150157

@@ -158,18 +165,19 @@ def on_before_agent(callback_context: CallbackContext):
158165
"""
159166
Initialize recipe state if it doesn't exist.
160167
"""
161-
print('recipe state ==>',callback_context.state.get('recipe'))
168+
162169
if "recipe" not in callback_context.state:
163170
# Initialize with default recipe
164171
default_recipe = {
172+
"title": "Make Your Recipe",
165173
"skill_level": "Beginner",
166174
"special_preferences": [],
167175
"cooking_time": '15 min',
168176
"ingredients": [{"icon": "🍴", "name": "Sample Ingredient", "amount": "1 unit"}],
169177
"instructions": ["First step instruction"]
170178
}
171179
callback_context.state["recipe"] = default_recipe
172-
print("Initialized default recipe state")
180+
173181

174182
return None
175183

@@ -181,7 +189,6 @@ def before_model_modifier(
181189
) -> Optional[LlmResponse]:
182190
"""Inspects/modifies the LLM request or skips the call."""
183191
agent_name = callback_context.agent_name
184-
print(f"[Callback] Before model call for agent: {agent_name}")
185192
if agent_name == "RecipeAgent":
186193
recipe_json = "No recipe yet"
187194
if "recipe" in callback_context.state and callback_context.state["recipe"] is not None:
@@ -212,6 +219,28 @@ def before_model_modifier(
212219
return None
213220

214221

222+
# --- Define the Callback Function ---
223+
def simple_after_model_modifier(
224+
callback_context: CallbackContext, llm_response: LlmResponse
225+
) -> Optional[LlmResponse]:
226+
"""Stop the consecutive tool calling of the agent"""
227+
agent_name = callback_context.agent_name
228+
# --- Inspection ---
229+
if agent_name == "RecipeAgent":
230+
original_text = ""
231+
if llm_response.content and llm_response.content.parts:
232+
# Assuming simple text response for this example
233+
if llm_response.content.role=='model' and llm_response.content.parts[0].text:
234+
original_text = llm_response.content.parts[0].text
235+
callback_context._invocation_context.end_invocation = True
236+
237+
elif llm_response.error_message:
238+
return None
239+
else:
240+
return None # Nothing to modify
241+
return None
242+
243+
215244
shared_state_agent = LlmAgent(
216245
name="RecipeAgent",
217246
model="gemini-2.5-pro",
@@ -224,16 +253,18 @@ def before_model_modifier(
224253
3. When modifying an existing recipe, include the changes parameter to describe what was modified
225254
4. Be creative and helpful in generating complete, practical recipes
226255
5. After using the tool, provide a brief summary of what you created or changed
256+
6. If user ask to improve the recipe then add more ingredients and make it healthier
257+
7. When you see the 'Recipe generated successfully' confirmation message, wish the user well with their cooking by telling them to enjoy their dish.
227258
228259
Examples of when to use the tool:
229260
- "Create a pasta recipe" → Use tool with skill_level, ingredients, instructions
230-
- "Make it vegetarian" → Use tool with special_preferences="vegetarian" and changes describing the modification
261+
- "Make it vegetarian" → Use tool with special_preferences=["vegetarian"] and changes describing the modification
231262
- "Add some herbs" → Use tool with updated ingredients and changes describing the addition
232263
233264
Always provide complete, practical recipes that users can actually cook.
234265
""",
235266
tools=[generate_recipe],
236267
before_agent_callback=on_before_agent,
237-
before_model_callback=before_model_modifier
238-
)
239-
268+
before_model_callback=before_model_modifier,
269+
after_model_callback = simple_after_model_modifier
270+
)

0 commit comments

Comments
 (0)