Skip to content

Commit 0158206

Browse files
docs: add pre/post processing docs for ADK Python (googleapis#2534)
## Description Created again on accidental deletion of googleapis#2433 ## PR Checklist > Thank you for opening a Pull Request! Before submitting your PR, there are a > few things you can do to make sure it goes smoothly: - [ ] Make sure you reviewed [CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md) - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [ ] Appropriate docs were updated (if necessary) - [ ] Make sure to add `!` if this involve a breaking change 🛠️ Fixes #<issue_number_goes_here> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 36edfd3 commit 0158206

File tree

3 files changed

+148
-1
lines changed

3 files changed

+148
-1
lines changed

docs/en/samples/pre_post_processing/python.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ This guide demonstrates how to implement these patterns in your Toolbox applicat
1616

1717
{{< tabpane persist=header >}}
1818
{{% tab header="ADK" text=true %}}
19-
Coming soon.
19+
The following example demonstrates how to use `ToolboxToolset` with ADK's pre and post processing hooks to implement pre and post processing for tool calls.
20+
21+
```py
22+
{{< include "python/adk/agent.py" >}}
23+
```
24+
You can also add model-level (`before_model_callback`, `after_model_callback`) and agent-level (`before_agent_callback`, `after_agent_callback`) hooks to intercept messages at different stages of the execution loop.
25+
26+
For more information, see the [ADK Callbacks documentation](https://google.github.io/adk-docs/callbacks/types-of-callbacks/).
2027
{{% /tab %}}
2128
{{% tab header="Langchain" text=true %}}
2229
The following example demonstrates how to use `ToolboxClient` with LangChain's middleware to implement pre- and post- processing for tool calls.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import asyncio
2+
from datetime import datetime
3+
from typing import Any, Dict, Optional
4+
from copy import deepcopy
5+
6+
from google.adk import Agent
7+
from google.adk.apps import App
8+
from google.adk.runners import Runner
9+
from google.adk.sessions.in_memory_session_service import InMemorySessionService
10+
from google.adk.tools.tool_context import ToolContext
11+
from google.genai import types
12+
from toolbox_adk import CredentialStrategy, ToolboxToolset, ToolboxTool
13+
14+
SYSTEM_PROMPT = """
15+
You're a helpful hotel assistant. You handle hotel searching, booking and
16+
cancellations. When the user searches for a hotel, mention it's name, id,
17+
location and price tier. Always mention hotel ids while performing any
18+
searches. This is very important for any operations. For any bookings or
19+
cancellations, please provide the appropriate confirmation. Be sure to
20+
update checkin or checkout dates if mentioned by the user.
21+
Don't ask for confirmations from the user.
22+
"""
23+
24+
25+
# Pre processing
26+
async def enfore_business_rules(
27+
tool: ToolboxTool, args: Dict[str, Any], tool_context: ToolContext
28+
) -> Optional[Dict[str, Any]]:
29+
"""
30+
Callback fired before a tool is executed.
31+
Enforces business logic: Max stay duration is 14 days.
32+
"""
33+
tool_name = tool.name
34+
print(f"POLICY CHECK: Intercepting '{tool_name}'")
35+
36+
if tool_name == "update-hotel" and "checkin_date" in args and "checkout_date" in args:
37+
start = datetime.fromisoformat(args["checkin_date"])
38+
end = datetime.fromisoformat(args["checkout_date"])
39+
duration = (end - start).days
40+
41+
if duration > 14:
42+
print("BLOCKED: Stay too long")
43+
return {"result": "Error: Maximum stay duration is 14 days."}
44+
return None
45+
46+
47+
# Post processing
48+
async def enrich_response(
49+
tool: ToolboxTool,
50+
args: Dict[str, Any],
51+
tool_context: ToolContext,
52+
tool_response: Any,
53+
) -> Optional[Any]:
54+
"""
55+
Callback fired after a tool execution.
56+
Enriches response for successful bookings.
57+
"""
58+
if isinstance(tool_response, dict):
59+
result = tool_response.get("result", "")
60+
elif isinstance(tool_response, str):
61+
result = tool_response
62+
else:
63+
return None
64+
65+
tool_name = tool.name
66+
if isinstance(result, str) and "Error" not in result:
67+
if tool_name == "book-hotel":
68+
loyalty_bonus = 500
69+
enriched_result = f"Booking Confirmed!\n You earned {loyalty_bonus} Loyalty Points with this stay.\n\nSystem Details: {result}"
70+
71+
if isinstance(tool_response, dict):
72+
modified_response = deepcopy(tool_response)
73+
modified_response["result"] = enriched_result
74+
return modified_response
75+
else:
76+
return enriched_result
77+
return None
78+
79+
80+
async def run_chat_turn(
81+
runner: Runner, session_id: str, user_id: str, message_text: str
82+
):
83+
"""Executes a single chat turn and prints the interaction."""
84+
print(f"\nUSER: '{message_text}'")
85+
response_text = ""
86+
async for event in runner.run_async(
87+
user_id=user_id,
88+
session_id=session_id,
89+
new_message=types.Content(role="user", parts=[types.Part(text=message_text)]),
90+
):
91+
if event.content and event.content.parts:
92+
for part in event.content.parts:
93+
if part.text:
94+
response_text += part.text
95+
96+
print(f"AI: {response_text}")
97+
98+
99+
async def main():
100+
toolset = ToolboxToolset(
101+
server_url="http://127.0.0.1:5000",
102+
toolset_name="my-toolset",
103+
credentials=CredentialStrategy.toolbox_identity(),
104+
)
105+
tools = await toolset.get_tools()
106+
root_agent = Agent(
107+
name="root_agent",
108+
model="gemini-2.5-flash",
109+
instruction=SYSTEM_PROMPT,
110+
tools=tools,
111+
# add any pre and post processing callbacks
112+
before_tool_callback=enfore_business_rules,
113+
after_tool_callback=enrich_response,
114+
)
115+
app = App(root_agent=root_agent, name="my_agent")
116+
runner = Runner(app=app, session_service=InMemorySessionService())
117+
session_id = "test-session"
118+
user_id = "test-user"
119+
await runner.session_service.create_session(
120+
app_name=app.name, user_id=user_id, session_id=session_id
121+
)
122+
123+
# First turn: Successful booking
124+
await run_chat_turn(runner, session_id, user_id, "Book hotel with id 3.")
125+
print("-" * 50)
126+
# Second turn: Policy violation (stay > 14 days)
127+
await run_chat_turn(
128+
runner,
129+
session_id,
130+
user_id,
131+
"Book a hotel with id 5 with checkin date 2025-01-18 and checkout date 2025-02-10",
132+
)
133+
await toolset.close()
134+
135+
136+
if __name__ == "__main__":
137+
asyncio.run(main())
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
google-adk[toolbox]==1.23.0
2+
toolbox-adk==0.5.8
3+
google-genai==1.62.0

0 commit comments

Comments
 (0)