chore: import upstream snapshot with attribution
CF: Deploy Dev Docs / deploy (push) Has been cancelled
Sync Labels / build (push) Has been cancelled
tests / unit tests (macos-latest) (push) Has been cancelled
tests / unit tests (windows-latest) (push) Has been cancelled
tests / unit tests (ubuntu-latest) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:32:45 +08:00
commit e04ed9c211
1798 changed files with 307905 additions and 0 deletions
@@ -0,0 +1,19 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This file makes the 'pre_post_processing/python' directory a Python package.
# You can include any package-level initialization logic here if needed.
# For now, this file is empty.
@@ -0,0 +1,50 @@
---
title: "Python: Pre & Post Processing"
type: docs
weight: 1
description: >
How to add pre- and post- processing to your Agents using Python.
sample_filters: ["Pre & Post Processing", "Python", "ADK", "LangChain"]
is_sample: true
---
## Prerequisites
This tutorial assumes that you have set up MCP Toolbox with a basic agent as described in the [local quickstart](../../../getting-started/local_quickstart.md).
This guide demonstrates how to implement these patterns in your Toolbox applications.
## Implementation
{{< tabpane persist=header >}}
{{% tab header="ADK" text=true %}}
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.
{{< include "adk/agent.py" "python">}}
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.
For more information, see the [ADK Callbacks documentation](https://google.github.io/adk-docs/callbacks/types-of-callbacks/).
{{% /tab %}}
{{% tab header="Langchain" text=true %}}
The following example demonstrates how to use `ToolboxClient` with LangChain's middleware to implement pre- and post- processing for tool calls.
{{< include "langchain/agent.py" "python" >}}
You can also add model-level (`wrap_model`) and agent-level (`before_agent`, `after_agent`) hooks to intercept messages at different stages of the execution loop. See the [LangChain Middleware documentation](https://docs.langchain.com/oss/python/langchain/middleware/custom#wrap-style-hooks) for details on these additional hook types.
{{% /tab %}}
{{< /tabpane >}}
## Results
The output should look similar to the following.
{{< notice note >}}
The exact responses may vary due to the non-deterministic nature of LLMs and differences between orchestration frameworks.
{{< /notice >}}
```
AI: Booking Confirmed! You earned 500 Loyalty Points with this stay.
AI: Error: Maximum stay duration is 14 days.
```
@@ -0,0 +1,137 @@
import asyncio
from datetime import datetime
from typing import Any, Dict, Optional
from copy import deepcopy
from google.adk import Agent
from google.adk.apps import App
from google.adk.runners import Runner
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.tools.tool_context import ToolContext
from google.genai import types
from toolbox_adk import CredentialStrategy, ToolboxToolset, ToolboxTool
SYSTEM_PROMPT = """
You're a helpful hotel assistant. You handle hotel searching, booking and
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel ids while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
update checkin or checkout dates if mentioned by the user.
Don't ask for confirmations from the user.
"""
# Pre processing
async def enfore_business_rules(
tool: ToolboxTool, args: Dict[str, Any], tool_context: ToolContext
) -> Optional[Dict[str, Any]]:
"""
Callback fired before a tool is executed.
Enforces business logic: Max stay duration is 14 days.
"""
tool_name = tool.name
print(f"POLICY CHECK: Intercepting '{tool_name}'")
if tool_name == "update-hotel" and "checkin_date" in args and "checkout_date" in args:
start = datetime.fromisoformat(args["checkin_date"])
end = datetime.fromisoformat(args["checkout_date"])
duration = (end - start).days
if duration > 14:
print("BLOCKED: Stay too long")
return {"result": "Error: Maximum stay duration is 14 days."}
return None
# Post processing
async def enrich_response(
tool: ToolboxTool,
args: Dict[str, Any],
tool_context: ToolContext,
tool_response: Any,
) -> Optional[Any]:
"""
Callback fired after a tool execution.
Enriches response for successful bookings.
"""
if isinstance(tool_response, dict):
result = tool_response.get("result", "")
elif isinstance(tool_response, str):
result = tool_response
else:
return None
tool_name = tool.name
if isinstance(result, str) and "Error" not in result:
if tool_name == "book-hotel":
loyalty_bonus = 500
enriched_result = f"Booking Confirmed!\n You earned {loyalty_bonus} Loyalty Points with this stay.\n\nSystem Details: {result}"
if isinstance(tool_response, dict):
modified_response = deepcopy(tool_response)
modified_response["result"] = enriched_result
return modified_response
else:
return enriched_result
return None
async def run_chat_turn(
runner: Runner, session_id: str, user_id: str, message_text: str
):
"""Executes a single chat turn and prints the interaction."""
print(f"\nUSER: '{message_text}'")
response_text = ""
async for event in runner.run_async(
user_id=user_id,
session_id=session_id,
new_message=types.Content(role="user", parts=[types.Part(text=message_text)]),
):
if event.content and event.content.parts:
for part in event.content.parts:
if part.text:
response_text += part.text
print(f"AI: {response_text}")
async def main():
toolset = ToolboxToolset(
server_url="http://127.0.0.1:5000",
toolset_name="my-toolset",
credentials=CredentialStrategy.toolbox_identity(),
)
tools = await toolset.get_tools()
root_agent = Agent(
name="root_agent",
model="gemini-3-flash-preview",
instruction=SYSTEM_PROMPT,
tools=tools,
# add any pre and post processing callbacks
before_tool_callback=enfore_business_rules,
after_tool_callback=enrich_response,
)
app = App(root_agent=root_agent, name="my_agent")
runner = Runner(app=app, session_service=InMemorySessionService())
session_id = "test-session"
user_id = "test-user"
await runner.session_service.create_session(
app_name=app.name, user_id=user_id, session_id=session_id
)
# First turn: Successful booking
await run_chat_turn(runner, session_id, user_id, "Book hotel with id 3.")
print("-" * 50)
# Second turn: Policy violation (stay > 14 days)
await run_chat_turn(
runner,
session_id,
user_id,
"Update hotel with id 5 with checkin date 2025-01-18 and checkout date 2025-02-10",
)
await toolset.close()
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,2 @@
google-adk[toolbox]==1.28.1
google-genai==2.3.0
@@ -0,0 +1,51 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
import importlib
import os
from pathlib import Path
import pytest
ORCH_NAME = os.environ.get("ORCH_NAME")
module_path = f"python.{ORCH_NAME}.agent"
agent = importlib.import_module(module_path)
GOLDEN_KEYWORDS = [
"AI:",
"Loyalty Points",
"POLICY CHECK: Intercepting 'update-hotel'",
]
# --- Execution Tests ---
class TestExecution:
"""Test framework execution and output validation."""
@pytest.fixture(scope="function")
def script_output(self, capsys):
"""Run the agent function and return its output."""
asyncio.run(agent.main())
return capsys.readouterr()
def test_script_runs_without_errors(self, script_output):
"""Test that the script runs and produces no stderr."""
assert script_output.err == "", f"Script produced stderr: {script_output.err}"
def test_keywords_in_output(self, script_output):
"""Test that expected keywords are present in the script's output."""
output = script_output.out
print(f"\nAgent Output:\n{output}\n")
missing_keywords = [kw for kw in GOLDEN_KEYWORDS if kw not in output]
assert not missing_keywords, f"Missing keywords in output: {missing_keywords}"
@@ -0,0 +1,118 @@
import asyncio
from datetime import datetime
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain_core.messages import ToolMessage
from langchain_google_genai import ChatGoogleGenerativeAI
from toolbox_langchain import ToolboxClient
system_prompt = """
You're a helpful hotel assistant. You handle hotel searching, booking and
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel ids while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
update checkin or checkout dates if mentioned by the user.
Don't ask for confirmations from the user.
"""
# Pre processing
@wrap_tool_call
async def enforce_business_rules(request, handler):
"""
Business Logic Validation:
Enforces max stay duration (e.g., max 14 days).
"""
tool_call = request.tool_call
name = tool_call["name"]
args = tool_call["args"]
print(f"POLICY CHECK: Intercepting '{name}'")
if name == "update-hotel":
if "checkin_date" in args and "checkout_date" in args:
try:
start = datetime.fromisoformat(args["checkin_date"])
end = datetime.fromisoformat(args["checkout_date"])
duration = (end - start).days
if duration > 14:
print("BLOCKED: Stay too long")
return ToolMessage(
content="Error: Maximum stay duration is 14 days.",
tool_call_id=tool_call["id"],
)
except ValueError:
pass # Ignore invalid date formats
# PRE: Code here runs BEFORE the tool execution
# EXEC: Execute the tool (or next middleware)
result = await handler(request)
# POST: Code here runs AFTER the tool execution
return result
# Post processing
@wrap_tool_call
async def enrich_response(request, handler):
"""
Post-Processing & Enrichment:
Adds loyalty points information to successful bookings.
Standardizes output format.
"""
# PRE: Code here runs BEFORE the tool execution
# EXEC: Execute the tool (or next middleware)
result = await handler(request)
# POST: Code here runs AFTER the tool execution
if isinstance(result, ToolMessage):
content = str(result.content)
tool_name = request.tool_call["name"]
if tool_name == "book-hotel" and "Error" not in content:
loyalty_bonus = 500
result.content = f"Booking Confirmed!\n You earned {loyalty_bonus} Loyalty Points with this stay.\n\nSystem Details: {content}"
return result
async def main():
async with ToolboxClient("http://127.0.0.1:5000") as client:
tools = await client.aload_toolset("my-toolset")
model = ChatGoogleGenerativeAI(model="gemini-3-flash-preview")
agent = create_agent(
system_prompt=system_prompt,
model=model,
tools=tools,
# add any pre and post processing methods
middleware=[enforce_business_rules, enrich_response],
)
# Test post-processing
user_input = "Book hotel with id 3."
print(f"\n[INPUT] User: {user_input}")
response = await agent.ainvoke(
{"messages": [{"role": "user", "content": user_input}]}
)
print("-" * 50)
last_ai_msg = response["messages"][-1].content
print(f"[OUTPUT] AI: {last_ai_msg}")
# Test Pre-processing
print("-" * 50)
user_input = "Update my hotel with id 3 with checkin date 2025-01-18 and checkout date 2025-02-20."
print(f"\n[INPUT] User: {user_input}")
response = await agent.ainvoke(
{"messages": [{"role": "user", "content": user_input}]}
)
last_ai_msg = response["messages"][-1].content
print(f"[OUTPUT] AI: {last_ai_msg}")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,3 @@
langchain==1.3.9
langchain-google-genai==4.2.1
toolbox-langchain==1.0.0