Back to Blog
This post is Part 11 of 13 in the series: AI Architect Foundations

Designing a Production-Ready Toolset for AI Agents

Design robust APIs for LLMs. Master tool sequencing prerequisites, semantic description engineering, policy verification gates, and atomic execution boundaries.

Share Editorial
Designing a Production-Ready Toolset for AI Agents

TL;DR: Designing tools for an AI agent is fundamentally different from designing APIs for human software engineers. An LLM cannot inspect your backend code β€” it only sees the tool name, parameter schemas, and text descriptions. In this guide, we establish the 5 core principles of agent tool design, enforce execution prerequisites, and separate policy checks from state mutations.

πŸ“¦ Interactive Notebook: Test and run this code live on GitHub: 11_production_toolset.ipynb in the ai-architect-foundation-gemini repository.


What You'll Learn

How to architect a coherent toolset for production agents, how to write descriptions that prevent hallucinated parameters, how to enforce verification prerequisites (e.g. verify customer $\to$ lookup order $\to$ process action), and how to prevent dangerous unauthorized actions.

diagram
Rendering diagram…

The Core Concept: Tools Are the Agent's API

In traditional software development, developers read Swagger/OpenAPI docs and write deterministic client calls.

In an agentic system:

  • The model reads tool descriptions at runtime as part of its prompt context.
  • The model decides invocation order based solely on its reasoning.
  • Ambiguous descriptions lead to catastrophic failures (e.g. calling process_refund before checking whether the customer actually returned the item).

The 5 Principles of Agent Tool Design

1. Granular Single Responsibility

Avoid creating monolithic "God tools" like handle_customer_issue(type, details). Create distinct, single-purpose tools:

  • verify_customer(email, session_token)
  • lookup_order(order_id)
  • check_return_eligibility(order_id, reason)
  • process_refund(order_id, amount)

2. Enforce Strict Execution Sequencing via Preconditions

Tools that execute financial or destructive actions must require proof of earlier verification steps:

python
# ❌ Dangerous: Any agent can call this with arbitrary numbers
def process_refund(order_id: str, amount: float):
    ...

# βœ… Safe: Requires verified customer_id and eligibility_token
def process_refund(customer_id: str, order_id: str, amount: float, eligibility_token: str):
    ...

3. Separate Policy Checks from Mutation Actions

Never combine "check if allowed" and "execute action" into a single opaque step. When policy evaluation is a separate tool (check_return_eligibility), the agent can report policy rules back to the user without executing unnecessary transactions.

4. Provide Self-Documenting Return Schemas

Ensure tool return values explicitly guide the next logical step:

json
{
  "status": "success",
  "eligible_for_refund": true,
  "max_refundable_amount": 89.99,
  "next_step": "Call process_refund with amount <= 89.99"
}

5. Backend Hard Limits Over Prompt Soft Limits

Never rely on prompt instructions alone to prevent large financial transfers. Enforce hard safety gates in your Python backend:

python
def process_refund(order_id: str, amount: float) -> dict:
    # HARD GATE: Never allow autonomous refunds > $100
    if amount > 100.00:
        return {
            "status": "blocked",
            "error_category": "policy_limit",
            "message": f"Refund of ${amount:.2f} exceeds automatic agent threshold ($100.00).",
            "suggested_action": "Call escalate_to_human tool with case summary."
        }
    ...

Complete EcommAssist Toolset Implementation

Here is how a production-grade customer support toolset is defined in Gemini:

python
from google import genai
from google.genai import types

# Tool 1: Verification
verify_tool = types.FunctionDeclaration(
    name="verify_customer",
    description="Verifies customer session token. MUST be called first before accessing private order data.",
    parameters=types.Schema(
        type="OBJECT",
        properties={
            "session_token": types.Schema(type="STRING", description="Active user session token.")
        },
        required=["session_token"]
    )
)

# Tool 2: Order Lookup
lookup_tool = types.FunctionDeclaration(
    name="lookup_order",
    description="Fetch order status, items, delivery dates, and return flags. Requires verified customer_id.",
    parameters=types.Schema(
        type="OBJECT",
        properties={
            "customer_id": types.Schema(type="STRING"),
            "order_id": types.Schema(type="STRING")
        },
        required=["customer_id", "order_id"]
    )
)

# Tool 3: Policy Evaluator
policy_tool = types.FunctionDeclaration(
    name="check_return_policy",
    description="Evaluates store return policy against order delivery date and item condition. Returns eligibility boolean and max allowed refund.",
    parameters=types.Schema(
        type="OBJECT",
        properties={
            "order_id": types.Schema(type="STRING"),
            "return_reason": types.Schema(type="STRING", enum=["damaged", "wrong_size", "buyer_remorse"])
        },
        required=["order_id", "return_reason"]
    )
)

# Tool 4: Refund Execution (Gated)
refund_tool = types.FunctionDeclaration(
    name="process_refund",
    description="Issues monetary refund to customer card. Only call if check_return_policy returned eligible=true.",
    parameters=types.Schema(
        type="OBJECT",
        properties={
            "customer_id": types.Schema(type="STRING"),
            "order_id": types.Schema(type="STRING"),
            "amount": types.Schema(type="NUMBER", description="Refund amount in USD.")
        },
        required=["customer_id", "order_id", "amount"]
    )
)

# Tool 5: Human Escalation
escalate_tool = types.FunctionDeclaration(
    name="escalate_to_human",
    description="Escalates case to a human support agent when policy limits are exceeded or customer dispute occurs.",
    parameters=types.Schema(
        type="OBJECT",
        properties={
            "order_id": types.Schema(type="STRING"),
            "reason": types.Schema(type="STRING"),
            "case_summary": types.Schema(type="STRING")
        },
        required=["reason", "case_summary"]
    )
)

production_toolset = [
    types.Tool(function_declarations=[
        verify_tool,
        lookup_tool,
        policy_tool,
        refund_tool,
        escalate_tool
    ])
]

# Test sequential tool execution
print("1. Customer Verification:", verify_customer("alex@example.com"))
print("2. Order Lookup:", lookup_order("ORD-12345678"))
print("3. Policy Evaluation:", check_return_policy("ORD-12345678"))

# --- Execution Output ---
# 1. Customer Verification: {"status": "success", "customer_id": "CUS-1001", "name": "Alex Morgan", "verified": true}
# 2. Order Lookup: {"status": "success", "order_id": "ORD-12345678", "item": "Mechanical Keyboard", "delivered_days_ago": 12}
# 3. Policy Evaluation: {"status": "success", "eligible_for_refund": true, "reason": "Order delivered 12 days ago (within 30-day window)."}

Key Takeaways

  1. Tool Descriptions Guide Planning: Invest in precise, clear documentation for each function declaration.
  2. Backend Enforcement Over Prompt Prompts: Enforce validation, authentication, and financial limits in Python code.
  3. Always Provide Escape Hatches: Include an escalate_to_human tool whenever automated rules cannot resolve a dispute.

What's Next

In Part 12: Multi-Agent Architecture: Coordinator and Specialist Agents, we explore decomposing complex workflows across specialized child agents with narrow scopes.