How to Design Effective Tool Descriptions for LLM Tool Calling in AI Agents

Effective tool descriptions use purpose-first phrasing, explicit JSON-Schema parameters, negative examples, and performance hints to enable LLMs to invoke external tools reliably while minimizing token overhead and security risks.

The quality of your tool descriptions directly determines whether an AI agent can successfully bridge internal reasoning with external capabilities. In the bojieli/ai-agent-book repository, the authors demonstrate that well-crafted descriptions are the single most important factor for reliable LLM tool calling. This guide synthesizes the architectural patterns found in book-en/chapter4.md and the reference implementations in extras/agent-lab/SCHEMA.md to provide a concrete framework for designing tool descriptions that maximize accuracy and minimize failures.

Core Design Principles for LLM Tool Descriptions

Purpose-First Phrasing

Start every description with a concise "use this tool when..." trigger. This creates a clear decision boundary that prevents both over-use and mis-use. According to the source code in book-en/chapter4.md, phrasing the description as a conditional statement helps the LLM classify whether the current task state warrants an external call.

Bad: "A tool that searches the web."

Good: "Use when you need up-to-date factual information that is not in the model's knowledge cutoff."

Explicit Parameter Schemas with JSON-Schema

Define every argument using strict JSON-Schema with types, constraints, and examples. The extras/agent-lab/SCHEMA.md file mandates that each parameter include description, type, and optionally example fields. This guarantees syntactically valid calls and enables deterministic pre-validation.

{
  "query": {
    "type": "string",
    "description": "The search query; keep it short (≤100 chars).",
    "example": "latest GPT-4 release notes"
  },
  "max_results": {
    "type": "integer",
    "description": "Maximum number of search results to return (1–5).",
    "minimum": 1,
    "maximum": 5,
    "default": 3
  }
}

Negative Examples and Usage Boundaries

Explicitly state what the tool should not be used for. As implemented in book-en/chapter4.md, negative examples reduce false-positive calls for ambiguous tasks. For instance, instruct the model: "Do NOT use the web-search tool for private user data or personally identifiable information."

Performance and Rate Limit Hints

Include latency estimates, rate limits, and batch-ability flags. The repository's Chapter 4 "Performance Tips" section shows that noting constraints like "≤5 calls/minute" helps the LLM decide whether to call immediately or defer, and enables efficient call grouping.

Security-First Documentation

Never embed secret values or credentials in descriptions. The book-en/chapter4.md "Security Risks" section warns against "Tool Description Poisoning"—where attackers inject instructions via compromised descriptions. Explicitly forbid passing credentials through parameters:

{
  "security_notice": "Never pass API keys or passwords through this tool. Use environment variables only."
}

Checklist-Style Optional Parameters

Add expected_* fields that force the model to reason before calling. As documented in book-en/chapter5.md, requiring the model to fill out checklist parameters (e.g., expected_output_format) empirically improves call validity by approximately 50% in ablation studies.

Hierarchical Organization to Reduce Token Overhead

The book-en/chapter4.md token-budget analysis demonstrates that exposing only tool names in the system prompt—while deferring full descriptions—reduces context size by approximately 46%. Implement a progressive disclosure architecture:

  1. Context Construction: The system prompt contains only tool names and minimal metadata.
  2. Deferred Description Lookup: When the model emits a tool_call intent, the runtime fetches the full JSON definition from extras/agent-lab/SCHEMA.md specifications.
  3. Dynamic Loading: Full schemas are retrieved on-demand rather than loaded into every context window.

Implementation Architecture

Deferred Description Lookup

Instead of embedding full tool definitions in the system prompt, store standardized descriptions in a shared folder. The agent runtime performs lookups only when the LLM signals intent to use a specific tool, as shown in the chapter2/local_llm_serving/README.md reference implementation.

Schema Validation Layer

Before executing any external call, validate arguments against the JSON-Schema. Invalid calls are rejected early with deterministic error messages, preventing runtime failures. The validation layer in chapter4/perception-tools/ demonstrates this pattern using jsonschema validation.

Security Gate and Approval Models

Implement a lightweight side-car "approval model" that reviews raw tool call JSON before execution. As detailed in book-en/chapter4.md, this catches hidden instruction injection attempts that bypass static description filtering.

Practical Implementation in Python

The following examples follow the structure used in chapter2/local_llm_serving/README.md and chapter5/coding-agent/README.md.

1. Declaring a Tool with Pydantic


# tools/search_web.py

from pydantic import BaseModel, Field

class SearchWebParams(BaseModel):
    query: str = Field(
        ..., 
        description="Search query (≤100 chars)."
    )
    max_results: int = Field(
        3,
        ge=1,
        le=5,
        description="Maximum number of results to return."
    )

TOOL_DESCRIPTION = {
    "name": "search_web",
    "description": "Use when you need up-to-date factual information not in training data.",
    "parameters": SearchWebParams.schema(),
    "usage_hint": "Call only for facts newer than 2023-01-01; do NOT request private data.",
    "rate_limit": "≤5 calls/minute",
    "batchable": False
}

2. Validation Before Invocation

import json
from jsonschema import validate, ValidationError
from tools.search_web import TOOL_DESCRIPTION

def validate_tool_call(name: str, args: dict) -> dict:
    """Raise if args do not match the schema."""
    if name != TOOL_DESCRIPTION["name"]:
        raise ValueError(f"Unknown tool: {name}")
    
    schema = TOOL_DESCRIPTION["parameters"]
    try:
        validate(instance=args, schema=schema)
    except ValidationError as exc:
        raise ValueError(f"Invalid arguments for {name}: {exc.message}")
    return args

3. Execution and Observation

import httpx

def call_search_web(args: dict) -> dict:
    """Execute the tool and return structured observation."""
    resp = httpx.get(
        "http://localhost:8000/search",
        params=args,
        timeout=10.0
    )
    resp.raise_for_status()
    return {"results": resp.json()}

4. Agent Loop Integration

def agent_step(message: str, trajectory: list):
    # LLM generates tool_call (simplified)

    tool_call = llm_generate_tool_call(message)
    
    # Validation layer

    args = validate_tool_call(tool_call["name"], tool_call["args"])
    
    # Execution

    observation = call_search_web(args)
    
    # Append to trajectory for next reasoning step

    trajectory.append({
        "role": "tool", 
        "name": tool_call["name"], 
        "result": observation
    })

Summary

  • Purpose-first phrasing establishes clear decision boundaries for when to invoke tools, reducing mis-use.
  • Explicit JSON-Schema definitions with constraints and examples ensure syntactically valid arguments.
  • Negative examples explicitly document prohibited use cases, preventing false-positive calls.
  • Hierarchical organization with deferred loading reduces token overhead by 46% compared to full inline descriptions.
  • Checklist parameters can reduce invalid tool calls by 50% by forcing reasoning before execution.
  • Security gates using approval models prevent prompt injection via compromised tool descriptions.

Frequently Asked Questions

What makes a tool description "effective" for LLM calling?

An effective description provides a clear conditional trigger ("use when..."), explicit input schemas with examples, negative constraints outlining prohibited uses, and operational metadata like rate limits. According to book-en/chapter4.md, descriptions lacking these elements result in ambiguous decision boundaries and higher failure rates.

How do I prevent LLMs from calling tools with invalid arguments?

Define strict JSON-Schema constraints in the parameters field, including type, minimum, maximum, and enum restrictions. Implement a validation layer—like the one in chapter2/local_llm_serving/README.md—that validates arguments against the schema before execution. This deterministic validation catches errors before they reach external APIs.

Should I include all tool descriptions in the system prompt?

No. The bojieli/ai-agent-book repository recommends deferred description lookup: include only tool names in the system prompt, then fetch full descriptions on-demand when the model signals intent. This approach reduces context token usage by approximately 46% while maintaining full semantic fidelity.

How do I protect against prompt injection through tool descriptions?

Never embed secrets or credentials in tool descriptions. Explicitly forbid passing sensitive data through parameters. Implement a security gate using a secondary "approval model" that reviews raw tool call JSON for hidden instructions before execution, as detailed in book-en/chapter4.md under "Tool Description Poisoning" mitigation strategies.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →