Ir al contenido principal
Your AI Agent Calls the Wrong Tool. It's Not the Model.

Your AI Agent Calls the Wrong Tool. It's Not the Model.

AI Integration
7 min readPor Daily Miranda Pardo

Your agent calls search_documents when it should call get_customer_by_id. Or it passes null to a required parameter. Or it completely ignores a tool the task clearly needs.

You've tried switching models. You've rewritten the system prompt. You've added examples. The problem persists.

The real cause: your tool schema is ambiguous. The model reads the description and can't determine which tool to use with confidence — so it guesses. And sometimes it's right, and sometimes it's not, and that inconsistency is exactly what breaks production.

Why the Model Chooses Tools

The model doesn't "understand" your tools. It reads three things:

  1. The tool name
  2. The tool description
  3. The parameter names and their descriptions

From those three inputs it decides which tool to call, in what order, and what to pass. This is fundamentally a text reasoning problem. If your descriptions are vague, the model's choices will be inconsistent.

Quick example: you have two tools:

  • get_data — "Gets data"
  • fetch_info — "Fetches information"

From the model's perspective, these are identical. It will choose between them based on which phrase the user message happens to activate — and that changes on every run.

This type of ambiguity is the root cause of 80% of the tool selection failures we see in AI agent integration projects at DAILYMP.

The 4 Most Common Tool Schema Mistakes

1. Generic Names and Descriptions

// ❌ The model doesn't know when to use each one
const tools = [
  { name: "get_customer", description: "Gets customer information" },
  { name: "find_customer", description: "Finds a customer" },
]

// ✅ Each tool has an unambiguous purpose
const tools = [
  {
    name: "get_customer_by_id",
    description: `Retrieves a customer record by their exact CRM ID.
Use ONLY when you already have the customer ID (a UUID or numeric ID).
Do NOT use for searching by name, email, or any partial information.`,
  },
  {
    name: "search_customers_by_name",
    description: `Searches customers by partial name match.
Use when you have a name but NOT an ID.
Returns a list of matches — always disambiguate before creating records.`,
  },
]

The difference isn't cosmetic. "Use ONLY when" and "Do NOT use for" are routing instructions the model follows literally.

2. Parameters Without Purpose Descriptions

// ❌ The model guesses the format and purpose of each field
parameters: {
  type: "object",
  properties: {
    query: { type: "string" },
    limit: { type: "number" },
    filter: { type: "string" },
  }
}

// ✅ Each parameter explains when to use it and what format it expects
parameters: {
  type: "object",
  properties: {
    query: {
      type: "string",
      description: "Search text to match against customer names and emails. Accepts partial matches. Example: 'García' or 'garcia@company.com'",
    },
    limit: {
      type: "number",
      description: "Max results to return. Default 10. Use 3 when disambiguating, 50 for bulk operations.",
    },
    filter: {
      type: "string",
      description: "Optional status filter: 'active' | 'inactive' | 'prospect'. Omit to return all statuses.",
    },
  },
  required: ["query"],
}

The limit field is the classic example. Without a description, the model will set 10, 100, or whatever seems reasonable given the context. With a description, it sets the correct value for each case.

3. Not Specifying When NOT to Use a Tool

This is the most overlooked pattern. The model doesn't know the boundaries between tools unless you declare them explicitly.

// ✅ Delimit the territory of each tool
{
  name: "create_invoice",
  description: `Creates a new invoice and sends it to the customer.
REQUIRED: customer must already exist (use get_customer_by_id first).
DO NOT call if:
- The customer hasn't confirmed the order yet.
- A draft invoice already exists for this order (use update_invoice instead).
Side effect: triggers an email notification to the customer immediately.`,
}

The DO NOT call if section prevents unintended side effects that are very hard to debug in production. An extra email sent, a duplicate invoice, an inconsistent CRM state — all of these originate from a model that didn't know it shouldn't call that tool at that moment.

4. Tools That Do Too Many Things

// ❌ The model can't reason about intermediate state
{
  name: "process_order",
  description: "Processes an order: validates stock, creates invoice, sends confirmation, updates CRM.",
}

// ✅ Separate responsibilities — the model can reason about each step
{
  name: "validate_order_stock",
  description: `Checks if all items in the order are available in stock.
Returns availability per item. ALWAYS call this FIRST before creating an invoice.
If any item returns available: false, STOP and inform the user before proceeding.`,
},
{
  name: "create_order_invoice",
  description: `Creates an invoice for a validated order.
Call ONLY after validate_order_stock confirms all items are available.
Do NOT call if validation returned any unavailable items.`,
},

When a tool does four things at once, the agent can't pause midway through or choose which steps to execute. By separating them, the agent can apply conditional logic — which is exactly what you need for real business workflows.

The Orchestration Comment Pattern

Beyond individual tool descriptions, always add a section to your system prompt that establishes the logical order between tools:

const systemPrompt = `
You are an order processing agent for a B2B company.

TOOL USAGE RULES — follow these strictly:
1. Always call validate_order_stock BEFORE create_order_invoice.
2. Use get_customer_by_id when you have an ID; search_customers_by_name when you only have a name.
3. Never call send_invoice without a confirmed invoice_id from create_order_invoice.
4. If validate_order_stock returns any item as unavailable, stop and ask the user before proceeding.
5. Prefer get_customer_by_id over search when both would work — it's faster and deterministic.
`;

This is different from individual descriptions: it's the orchestration logic that spans multiple tools. Current models follow these numbered rules with high consistency when they're in the system prompt — much more reliably than when you try to encode it only in the descriptions.

How to Test Your Schemas in 30 Seconds

You don't need a full eval suite for this. A quick heuristic test:

async function testToolSelection(
  tools: Anthropic.Tool[],
  testCases: { input: string; expectedTool: string }[]
) {
  const results = await Promise.all(
    testCases.map(async ({ input, expectedTool }) => {
      const response = await anthropic.messages.create({
        model: 'claude-haiku-4-5-20251001', // fast model for tests
        max_tokens: 200,
        tools,
        tool_choice: { type: 'auto' },
        messages: [{ role: 'user', content: input }],
      });

      const toolCall = response.content.find(b => b.type === 'tool_use');
      return {
        input,
        expected: expectedTool,
        actual: toolCall?.name ?? 'none',
        correct: toolCall?.name === expectedTool,
      };
    })
  );

  const accuracy = results.filter(r => r.correct).length / results.length;
  console.log(`Accuracy: ${(accuracy * 100).toFixed(0)}%`);
  results
    .filter(r => !r.correct)
    .forEach(r =>
      console.log(`FAIL: "${r.input}" → expected ${r.expected}, got ${r.actual}`)
    );
}

// Usage
await testToolSelection(myTools, [
  { input: "Get info for customer ID cus_abc123", expectedTool: "get_customer_by_id" },
  { input: "Find customers named García", expectedTool: "search_customers_by_name" },
  { input: "Process this order for customer García", expectedTool: "search_customers_by_name" },
]);

Run this before and after every schema change. It takes 30 seconds, costs less than a cent, and catches tool selection regressions immediately — without waiting for production failures.

If you already have an eval suite for your agent, integrate this as a separate layer that runs in the same CI job.

The Economic Impact of a Bad Schema

Improving schemas isn't just about quality — it's about direct latency and cost reduction.

When the model calls the wrong tool, gets an error or unexpected result, and tries again, that's two API calls instead of one. If this happens on 20% of interactions and you process 10,000 requests per day, you're paying for 2,000 unnecessary LLM calls daily.

In projects we implement at DAILYMP, well-designed schemas reduce tool selection retries by 60–80%. That's a direct cost reduction, not counting the latency improvement and the elimination of inconsistent state errors.

The time to review and improve a set of schemas in an existing agent is typically less than half a day. The return is visible from the first week.

If you have an agent in production showing inconsistent tool selection behavior — or simply have no test coverage on which tool the model calls in which situation — that's exactly the kind of review we run before scaling any implementation:

We review your agent's schema and tell you what's failing →

Compartir artículo

LinkedInXWhatsApp

¿Procesos repetitivos en tu empresa?

Descarga gratis el Mapa de Automatización IA — los 5 procesos que más tiempo roban y cómo resolverlos.

Sin spam. Solo el PDF. Puedes darte de baja cuando quieras.

Escrito por Daily Miranda Pardo

Ayudo a empresas a automatizar procesos, crear agentes IA y conectar sistemas inteligentes.