> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Lab Walkthrough Design Better Tools

> Hands-on lab teaching precise tool descriptions, strict parameter contracts, and input normalization to ensure reliable agent tool selection and predictable, validated backend calls.

In this hands-on lab you'll start with an agent that exposes two ambiguously described tools and observe how the model can pick the wrong one. Then you'll apply three API design techniques—precise descriptions, strict parameter contracts, and input normalization—to make tool selection reliable and predictable.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/z7NmHsFQN9LCEiD0/images/AI-Agents-for-Beginners-OpenClaw-Case-Study/Production-OpenClaw/Lab-Walkthrough-Design-Better-Tools/hands-on-lab-design-better-tools.jpg?fit=max&auto=format&n=z7NmHsFQN9LCEiD0&q=85&s=3aa4ff6172c54bb306bf1b8b42cbebe3" alt="A dark grid background features a pixelated magnifying-glass icon and a cyan label reading &#x22;HANDS-ON LAB&#x22; above large yellow pixelated text: &#x22;DESIGN BETTER TOOLS.&#x22; Below is a white subtitle saying &#x22;Bad descriptions in · ACI principles out.&#x22;" width="1920" height="1080" data-path="images/AI-Agents-for-Beginners-OpenClaw-Case-Study/Production-OpenClaw/Lab-Walkthrough-Design-Better-Tools/hands-on-lab-design-better-tools.jpg" />
</Frame>

## Setup

Create a file named `better-tools.py` with:

* an OpenAI API client
* a short system prompt
* a loop that inspects model responses for function (tool) calls and dispatches them to handlers

Minimum agent skeleton:

```python theme={null}
from openai import OpenAI
import os, json

client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url=os.getenv("OPENAI_API_BASE")
)

system = "You are a helpful personal assistant."
messages = [{"role": "system", "content": system}]

while True:
    response = client.chat.completions.create(
        model="openai/gpt-4.1-mini",
        messages=messages
    )

    # Access the model's first choice
    choice = response.choices[0]
    finish_reason = choice.get("finish_reason")
    if finish_reason == "stop":
        break

    # If the model requested a function/tool call, handle it here
    func_call = choice.get("message", {}).get("function_call")
    if func_call:
        # Extract name and arguments and dispatch to the handler
        name = func_call.get("name")
        args = json.loads(func_call.get("arguments", "{}"))
        # dispatch to your tool handlers, append the tool response,
        # then continue the loop so the model can finish the conversation.
        # ...
```

(If you need the OpenAI API reference, see the official docs: [https://platform.openai.com/docs/api-reference](https://platform.openai.com/docs/api-reference))

## Demonstration: vague tool definitions

Begin with two intentionally vague tools so you can observe the problem:

```json theme={null}
[
  {
    "name": "search",
    "description": "searches things",
    "parameters": {
      "type": "object",
      "properties": {
        "query": {"type": "string"}
      },
      "required": ["query"]
    }
  },
  {
    "name": "get_info",
    "description": "gets information",
    "parameters": {
      "type": "object",
      "properties": {
        "category": {"type": "string"}
      },
      "required": ["category"]
    }
  }
]
```

Try the user prompt:

```text theme={null}
find Italian restaurants near me
```

Because the tool descriptions are so vague, the model may be uncertain which tool to call. Running the agent multiple times often yields different tool choices.

## Goals

Make the agent:

* consistently choose the right tool for a given user intent
* pass well-formed, normalized parameters that your backend expects
* avoid brittle values that cause downstream failures

You can accomplish this using three API-level improvements:

* Precise descriptions (teach the model when to call each tool)
* Parameter constraints (enums, types, required fields)
* Poka-yoke input normalization inside handlers (normalize inputs in code)

## 1) Precise descriptions (rename tools and explain when to use them)

Provide explicit tool names and descriptions that tell the model:

* What the tool does
* When to call it (in plain language)
* The return shape or what the tool will return

Example improved tool definitions:

```json theme={null}
[
  {
    "name": "search_restaurants",
    "description": "Search restaurants by cuisine, location, and optional price range. Returns a list of restaurants with names, ratings, and availability (open/closed). Use when the user wants to find a place to eat.",
    "parameters": {
      "type": "object",
      "properties": {
        "cuisine": {"type": "string"},
        "location": {"type": "string"},
        "price_range": {"type": "string"}
      },
      "required": ["cuisine", "location"]
    }
  },
  {
    "name": "get_user_preferences",
    "description": "Retrieve stored user preferences by category for personalizing recommendations (e.g., food, travel, schedule, contacts). Use when the model needs the user's saved preferences.",
    "parameters": {
      "type": "object",
      "properties": {
        "category": {"type": "string"}
      },
      "required": ["category"]
    }
  }
]
```

Why this helps: explicit naming plus “when to use” guidance gives the model a clear decision boundary, reducing ambiguity when the user asks for recommendations or searches.

## 2) Parameter constraints (use enums and clear parameter types)

Constrain parameter values so the model selects supported values only. Add descriptions, enums, and clear typing so your tool contract is unambiguous.

Example: adding an enum to `category` ensures the model provides a supported category:

```json theme={null}
{
  "category": {
    "type": "string",
    "description": "The preference category",
    "enum": [
      "food",
      "travel",
      "schedule",
      "contacts"
    ]
  }
}
```

Benefits:

* Prevents unsupported labels like `dining` or `restaurant` that your backend doesn’t expect
* Produces consistent payloads to your handlers
* Makes validation straightforward on the server side

## 3) Poka-yoke normalization (input normalization in handlers)

Handle small variations in inputs inside your tool implementation so the model doesn’t need to perfectly match your backend’s vocabulary. Normalization is a defensive, internal measure that reduces errors.

Example handler with normalization:

```python theme={null}
def search_restaurants(cuisine, location, price_range=None):
    # Normalize to canonical form
    cuisine = cuisine.lower().strip()
    location = location.lower().strip()
    if price_range:
        price_range = price_range.strip()

    # Now query your database or external API with the normalized values
    # For demonstration, return a structured response
    return {
        "restaurants": [
            {"name": "Trattoria Bella", "cuisine": cuisine, "rating": 4.6, "availability": "open"},
            {"name": "Pasta House", "cuisine": cuisine, "rating": 4.2, "availability": "closing soon"}
        ]
    }
```

Common normalizations:

* Trim whitespace: `" ITALIAN "` → `"italian"`
* Lowercase: `"Italian"` → `"italian"`
* Map synonyms: `"cheap"`, `"$"`, `"1"` → `"cheap"`

Normalization ensures small input differences don’t break downstream logic.

## Example: predictable end-to-end flow

After applying precise descriptions, parameter constraints, and input normalization, the same prompt:

```text theme={null}
find Italian restaurants near me
```

will reliably result in a structured tool call such as:

```python theme={null}
search_restaurants(cuisine="italian", location="near me")
```

and your handler will normalize and return predictable structured results for the assistant to present.

## Summary: Quick reference

| Technique | Purpose | Example outcome |
| -: | - | - |
| Precise descriptions | Tell the model what each tool does and when to use it | Model chooses `search_restaurants` for restaurant searches |
| Parameter constraints | Enforce valid values (enums, types, required) | Model sends `category="food"` instead of `category="dining"` |
| Input normalization | Make backend resilient to small variations | `"Italian"`, `"italian"`, `" ITALIAN "` → `"italian"` |

## Next steps & references

* Implement the exact tool definitions in your agent registration (as shown above).
* Add server-side validation to catch unexpected values and return helpful errors.
* Log model tool calls during testing to confirm the model chooses the intended tool consistently.

References:

* OpenAI API reference: [https://platform.openai.com/docs/api-reference](https://platform.openai.com/docs/api-reference)

<Callout icon="lightbulb" color="#1CB2FE">
  Design tool descriptions to be explicit about purpose, expected inputs, and return shapes. Combine parameter constraints (enums, types, required fields) with internal input normalization to make tool usage reliable and robust.
</Callout>

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/ai-agents-for-beginner-openclaw-case-study/module/b8b38b25-c4eb-425f-a093-cec426365977/lesson/ab3f211c-3378-4614-92d9-780579f35740" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.