Engineering Journal
Building Reliable Tool Use into an AI Agent
How tool calling turns an AI agent from a text generator into an application capable of interacting with real systems.
Building Reliable Tool Use into an AI Agent
The moment an AI agent needs to interact with something outside the language model, tool use becomes central to its design.
A model can explain how to query a database.
It cannot query the database unless the surrounding application gives it a mechanism to do so.
That mechanism is the tool.
Why Tools Matter
A language model operates primarily through generated information.
Tools give the agent the ability to perform actions.
The difference looks like this:
Without Tools
User
│
▼
LLM
│
▼
Text
With Tools
User
│
▼
LLM
│
▼
Tool Selection
│
▼
Tool Execution
│
▼
Result
│
▼
LLM
│
▼
Final Response
This changes the capabilities of the system dramatically.
An agent can now retrieve information, manipulate data, interact with APIs, inspect files, or perform other controlled operations.
Tools Should Have Contracts
A tool should behave like an API.
Its interface should be explicit.
For example:
def search_repository(query: str, limit: int = 10) -> list[str]:
...
The agent should know:
- the tool name;
- its purpose;
- its arguments;
- argument types;
- expected return format;
- possible failures.
Ambiguous tools create unreliable agents.
Clear contracts reduce that ambiguity.
The Tool Registry
As the number of tools increases, they should be managed through a registry.
Conceptually:
Tool Registry
├── search
├── filesystem
├── HTTP client
├── database
└── code execution
The registry becomes the boundary between the agent and the capabilities available to it.
This also makes permissions easier to manage.
An agent working in a read-only environment should not receive write-capable tools.
An agent operating against production infrastructure should have an even stricter capability set.
Tool Selection
The model can determine which tool appears appropriate for a particular task.
For example:
User:
"Find the latest deployment configuration."
│
▼
Agent
│
▼
Select filesystem tool
│
▼
Execute read operation
│
▼
Result
The important engineering principle is that the model chooses the action, but the application executes it.
The model does not directly gain arbitrary operating-system access.
Validate Arguments Before Execution
Never assume that model-generated arguments are correct.
A tool should validate its inputs before doing anything consequential.
For example:
if limit < 1 or limit > 100:
raise ValueError("limit must be between 1 and 100")
Validation should happen at the application boundary.
This protects the system from:
- malformed arguments;
- unexpected values;
- accidental destructive operations;
- resource exhaustion.
Tool Results Are Untrusted Input
The result returned by a tool should also be treated carefully.
A tool might return:
{
"status": "success",
"data": []
}
But it could also return:
- malformed JSON;
- an error message;
- partial data;
- an unexpected schema;
- an excessively large response.
The agent should validate and normalize tool results before feeding them into subsequent reasoning.
Timeouts Are Essential
External operations can hang.
A network request might never return.
A shell command might block.
A database might become unavailable.
Every tool capable of waiting on an external system should have a bounded execution time.
Conceptually:
Tool Call
│
├── Success ──► Result
│
├── Failure ──► Error
│
└── Timeout ──► Recovery
An agent that cannot distinguish between a slow operation and a failed operation becomes difficult to operate safely.
Tool Permissions
Tool access should be treated as a capability system.
Instead of giving every agent every available capability:
Agent
├── read files
├── write files
├── execute commands
├── access network
└── modify infrastructure
define the smallest useful capability set:
Research Agent
├── read files
└── search
Documentation Agent
├── read files
└── write documents
This principle reduces the blast radius of incorrect decisions.
Designing Idempotent Tools
Whenever possible, tools should be idempotent.
An operation is easier to reason about when executing it multiple times produces the same effective state.
This becomes particularly important when an agent retries an operation after a timeout.
For example, a tool that creates infrastructure without checking whether it already exists can produce unexpected side effects when retried.
Designing operations carefully makes automated recovery much safer.
Observability
Tool execution should be observable.
Useful information includes:
- tool name;
- execution duration;
- success or failure;
- arguments after safe redaction;
- result size;
- retry count.
A useful execution log might look like:
[agent] selecting tool: repository_search
[tool] repository_search started
[tool] repository_search completed in 42ms
[agent] received 8 results
This becomes invaluable when debugging agent behavior.
The Agent Should Not Trust Every Tool
Tools can fail.
Tools can return unexpected data.
External systems can be wrong.
Therefore, tool use should be followed by observation and validation rather than blind continuation.
The agent loop should effectively become:
Decide
↓
Execute
↓
Validate
↓
Observe
↓
Decide Again
That feedback loop is what makes tool use useful rather than merely decorative.
Final Thoughts
Tool calling is one of the defining characteristics of an AI agent.
But exposing tools is easy.
Building reliable tool execution is the real engineering challenge.
A production-minded agent needs explicit contracts, argument validation, bounded execution, permissions, observability, and careful handling of results.
The model provides the decision-making capability.
The application provides the safety and reliability around that capability.