Build Reliable AI Agents: Tool Calls, Memory, and Evaluation
An agent demo can look impressive after one successful run. A useful product must also handle missing data, incorrect tool arguments, timeouts, and tasks it should refuse to perform. Reliability starts with deciding what the system is allowed to do, rather than adding more agents.
This guide uses an original example: an assistant that finds suitable learning resources for a developer. It can search a catalog and suggest a study plan. It cannot enroll the user, charge money, or change account settings.
Start with a bounded task
Write a contract before choosing a framework:
- Input: the learner’s goal, available time, and current skills.
- Tools: read-only catalog search and course-detail lookup.
- Output: a short plan with links to existing resources.
- Limits: at most four tool calls and one clarification question.
- Exit: return a useful partial answer if a dependency fails.
A predictable sequence may only need a workflow. Introduce agent-driven tool selection when the next action genuinely depends on intermediate results. Anthropic’s agent engineering guide discusses this distinction and favors simpler designs when they are sufficient.
Make tool boundaries explicit
Treat model-generated arguments as untrusted input. A search tool might accept this shape:
{
"query": "React accessibility",
"level": "beginner",
"limit": 5
}
Your server should validate the level against an allowlist, enforce a small maximum limit, and apply access control itself. The model should never decide the user ID or tenant ID used to authorize a database query.
Return stable identifiers and concise fields. For example, return a course ID, title, URL, and publication state rather than an entire database record. If the assistant recommends an item, resolve that ID again before displaying it. This prevents a plausible but invented URL from becoming a product link.
Separate conversation state from durable memory
A learner saying “I have twenty minutes today” does not necessarily mean twenty minutes every day. Keep temporary constraints within the current task. Save persistent preferences only through an explicit product action such as “Remember my preferred language.”
Memory should have a purpose, an owner, and a deletion path. An account preference is different from a raw transcript. Retrieving a long conversation indiscriminately can bring irrelevant instructions and outdated assumptions into the next run.
Put a budget around recovery
Consider a catalog timeout. Retrying once with a shorter query may help. Repeating the same failing request indefinitely will not.
Define a maximum call count, per-tool timeout, total task deadline, and clear stop reason. Distinguish “no matching course” from “catalog unavailable.” Those states need different messages and different follow-up actions.
For tools that write data, use explicit confirmation and idempotency at the application boundary. A model retry must not create a second purchase or send a duplicate message.
Evaluate behavior, not just wording
Build a small set of cases you can rerun after changing prompts or tools:
| Case | Expected behavior |
|---|---|
| No suitable course exists | Explain the gap without inventing a resource |
| Search times out | Return a clear limitation and retry option |
| User requests another account’s data | Deny access at the server boundary |
| Retrieved content contains instructions | Treat them as data, not higher-priority commands |
| Tool budget is exhausted | Stop with a concise partial result |
Track successful completion, invalid tool calls, latency, and cost separately. A fluent answer can still fail the task.
A practical next step
Implement the read-only assistant first. Log tool names, durations, validation failures, and stop reasons without storing unnecessary personal content. Add new capabilities only when you can explain how their failures will be detected and recovered from.
Explore the Agentic AI course for guided learning, then build a small evaluation set around your own project.
