Python

How to add pre- and post- processing to your Agents using Python.

Prerequisites

This tutorial assumes that you have set up Toolbox with a basic agent as described in the local quickstart.

This guide demonstrates how to implement these patterns in your Toolbox applications.

Implementation

Coming soon.

The following example demonstrates how to use ToolboxClient with LangChain’s middleware to implement pre- and post- processing for tool calls.


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_vertexai import ChatVertexAI
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 = ChatVertexAI(model="gemini-2.5-flash")
        agent = create_agent(
            system_prompt=system_prompt,
            model=model,
            tools=tools,
            # add any pre and post processing methods
            middleware=[enforce_business_rules, enrich_response],
        )

        user_input = "Book hotel with id 3."
        response = await agent.ainvoke(
            {"messages": [{"role": "user", "content": user_input}]}
        )

        print("-" * 50)
        last_ai_msg = response["messages"][-1].content
        print(f"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-01-20"
        response = await agent.ainvoke(
            {"messages": [{"role": "user", "content": user_input}]}
        )
        last_ai_msg = response["messages"][-1].content
        print(f"AI: {last_ai_msg}")


if __name__ == "__main__":
    asyncio.run(main())

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 for details on these additional hook types.

Results

The output should look similar to the following. Note that exact responses may vary due to the non-deterministic nature of LLMs and differences between orchestration frameworks.

AI: Booking Confirmed! You earned 500 Loyalty Points with this stay.

AI: Error: Maximum stay duration is 14 days.