Skip to main content
📖 You are viewing: Conceptual Guide - Learn how tools work and create your ownSee also: API Specification
What You’ll Learn: How the tool system works under the hood, how LLMs “see” and choose which tools to use, how to write tools that LLMs understand correctly, best practices for parameters, return values, and error handling, and common pitfalls and how to avoid them.

What Are Tools?

LLMs are brilliant at language but can’t interact with the real world. Tools are the bridge: Tools give your agent hands to interact with the world.

The Tool Contract: What the LLM Actually Sees

Here’s the key insight: The LLM never sees your Python code. It only sees a “contract” describing the tool. When you write this:
The decorator registers an entry in the _TOOL_REGISTRY dictionary. Today the registry entry looks like this (source: src/gaia/agents/base/tools.py:79-87):
What’s actually captured: The function name, the docstring without its Args: block (as description), and a {type, required, description} entry per parameter. The type comes from the annotation (str, int, float, bool, tuple, set, dict, plus generics like List[str] and Optional[...]); an unannotated or unrecognised parameter falls back to "unknown". The per-parameter description is parsed from the docstring’s Google-style Args: block, so the model reads the constraint at the slot it is filling — write one line per argument, and don’t repeat it in the prose. Parameter default values are still not stored; required is derived from whether a default exists.

The description budget

Every model call re-sends the schema of every offered tool, so this text is billed on each step of each turn. Two caps, defined in src/gaia/agents/base/tools.py: python util/lint.py --tool-descriptions fails the build when a tool exceeds either, naming the tool and its length; python util/tool_schema_tokens.py shows where the characters go. Keep what the model needs to call the tool correctly — what it does, when to use it instead of a neighbouring tool, what it refuses, what it returns. Rationale, history, and worked examples belong in a code comment, not the schema.
What the check does not cover. It measures the flagship agent’s tool surface only: MCP tools are excluded, and a hub agent outside the flagship is unchecked — so a packaged agent can still ship a multi-thousand-character description without failing lint. The flagship skeleton also picks up whatever the host environment offers, so the tool count it sees can differ between a dev box and CI. Treat a green run as “the flagship is in budget”, not “every tool in the repo is”.

How LLMs Choose Tools

When a user asks a question, the LLM goes through a decision process: This is why clear docstrings matter! If your description doesn’t match user intent, the LLM won’t choose your tool.

Building Effective Tools

Step 1: Start Simple

Begin with the most basic tool structure:
What makes this effective:
  • Clear function name (calculate) matches what it does
  • Type hint (str) tells LLM what to pass
  • Return type (float) sets expectations
  • Docstring explains when to use it (“when the user asks to calculate”)
  • Args section describes the expected format

Step 2: Add Parameters with Defaults

Make tools flexible with optional parameters:
How defaults work with LLMs:

Step 3: Handle Complex Types

For structured data, use type hints to guide the LLM:
The LLM understands:
  • List[str] → needs to pass a list of strings
  • Optional[List[str]] → can be omitted or set to null
  • The Args descriptions show example formats

Return Value Patterns

What you return matters—the LLM uses it to form responses.

Pattern 1: Success with Data

LLM receives: {"status": "success", "user": {"id": 123, "name": "Alice", ...}} LLM responds: “Alice ([email protected]) has the Admin role.”

Pattern 2: Error with Guidance

LLM receives: {"status": "error", "error": "No user found...", "suggestion": "..."} LLM responds: “I couldn’t find a user with ID 999. Would you like to search by name instead?”

Pattern 3: Partial Results

Why this matters: Prevents context overflow while telling the LLM there’s more.

Standard Response Format

All GAIA tools should return responses in a standardized format that helps the LLM understand the result and how to use it. This is especially important for tools that return structured JSON data.

The GAIA Response Pattern

Why This Format Matters

When tools return raw JSON without context, the LLM may:
  • Echo the JSON directly instead of summarizing it
  • Return structured data in its answer instead of human-readable text
  • Misunderstand how to interpret the data
The instruction field explicitly tells the LLM what to do with the data.

Field Reference

Complete Example

With this format, the LLM responds:
“Your system is running well. CPU usage is at 23%, memory is using 8.2 GB of 16 GB (51%), and you have 245 GB of free disk space.”
Instead of echoing the JSON:
{"cpu_percent": 23, "memory_gb_used": 8.2, ...}
MCP Tools: When using MCPClientMixin, external MCP tool responses are automatically wrapped in this GAIA format with status, message, data, and instruction fields.

The Power of Good Docstrings

Your docstring teaches the LLM when and how to use your tool. Compare:
Problems: LLM doesn’t know what kind of search, q parameter name is unclear, no guidance on when to use it, might conflict with other search tools.

Docstring Anatomy


Error Handling: Return, Don’t Raise

Critical Rule: Tools should never raise exceptions. Always return error information as data.

Why?

When a tool raises an exception:
  1. The agent’s reasoning loop may crash
  2. The LLM doesn’t get useful error information
  3. The user sees a technical error instead of helpful guidance

The Pattern

Now the LLM can respond helpfully:

Common Pitfalls and Solutions

Symptom: User asks to search code, but LLM calls search_web.Cause: Tool descriptions are too similar or vague.Solution: Add explicit differentiation:
Symptom: Tool expects integer, receives string like “5”.Cause: Missing or unclear type hints.Solution: Always use type hints and validate:
Symptom: Agent becomes slow or gives inconsistent answers.Cause: Tool returns massive amounts of data that overflow context.Solution: Limit and summarize output:
Symptom: LLM says “I don’t have a tool for that” when you do.Cause: Tool is defined but not registered with the agent.Solution: Make sure tool is inside _register_tools():
Symptom: Emails sent, files deleted, or data modified when user was just asking a question.Cause: Destructive tools need safeguards.Solution: Add confirmation or dry-run modes:
Now the LLM will preview first:

Practice Challenge

Build a Database Query Tool

Create a tool that: (1) Accepts a natural language query about users, (2) Translates it to a database operation, (3) Returns structured results, (4) Handles errors gracefully.Requirements: Clear docstring with usage examples, type hints on all parameters, graceful error handling, reasonable result limits.
Use a dict to simulate database records. Include examples in the docstring to guide the LLM. Return both data and metadata (count, any filtering applied).
Why this solution works:
  1. Clear docstring with examples: LLM knows exactly how to map user requests to parameters
  2. Optional parameters: LLM can use any combination of filters
  3. Input validation: Invalid values return helpful error messages
  4. Graceful limits: Prevents returning too much data
  5. Rich metadata: LLM knows total count, filters used, and has suggestions
  6. Error handling: Catches unexpected errors with helpful messages

Deep Dive: Tool Schema Generation

When you decorate a function with @tool, Python introspection extracts the schema (see src/gaia/agents/base/tools.py for the authoritative implementation):
Notable implementation details:
  • description is the docstring with its leading indentation and its Args: block removed — each argument’s line is stored on that argument’s entry instead, so the same text is not billed twice per call. Wrapped continuation lines are joined into one string. An argument with no Args: entry simply has no description key. Sections after Args: (Returns:, Raises:, …) stay in description.
  • Default values are not stored on the parameter entries. required is a boolean derived from whether the parameter has a default.
  • Generic annotations are specialised: list[str] / List[str] → array, Dict[str, Any] → object, and Optional[X] / X | None resolves to X’s type. An unannotated parameter (or a union of two real types) stays "unknown".
  • @tool(atomic=True) marks a tool as non-decomposable so the planner will not try to split it further.
  • @tool(display_label=...) sets a user-facing label for UI progress strips, and @tool(timeout=...) overrides the global per-tool execution limit (seconds). Both default to None and are stored on the registry entry.
  • @tool(timeout_arg="timeout") names the tool’s own parameter that sets how long a call may run. The agent then waits at least that long plus 30 seconds before abandoning the call (capped at 3600 seconds), so a call given 15 minutes is not cut off at the default. A name that is not a parameter of the tool raises TypeError.
  • @tool(preflight=...) takes a function of the call’s arguments that returns the refusal the call has already earned, or None. The agent runs it before the confirmation prompt, so nobody is asked to approve a call that is going to be refused. The tool body must enforce the same rule itself; the file-editing tools use this for their read-before-edit check.
  • Unsupported decorator keywords raise TypeError at decoration time and identify the invalid argument, tool name, and accepted keyword set.

Key Takeaways

LLM Sees the Contract

Function name, type hints, and docstring are all the LLM knows. Write them for the LLM, not just humans.

Docstrings Drive Selection

“Use this tool when…” phrases directly influence when the LLM chooses your tool.

Return Errors as Data

Never raise exceptions. Return structured error info so the LLM can respond helpfully.

Limit Output Size

Large returns overflow context. Truncate, summarize, and indicate when there’s more.

Next Steps

Agent System

Understand how agents use tools in the reasoning loop

Tool Mixins

Use pre-built tool collections for common tasks

Best Practices

Advanced patterns for production tools

Instance-local tool registration

Capability-gated tools can pass registry=local_tools to @tool(...). The decorator writes only to that mapping, including when it is empty. Merge it into the owning agent’s instance registry after normal registration. This prevents concurrently constructed agents from snapshotting another instance’s gated tools. Existing callers that omit registry keep the normal global registration behavior.