Tu agente IA llama a la tool equivocada. No es el modelo.
Tu agente llama a search_documents cuando debería llamar a get_customer_by_id. O pasa null a un parámetro requerido. O ignora por completo una herramienta cuando la tarea la necesita claramente.
Has probado cambiar el modelo. Has reescrito el system prompt. Has añadido ejemplos. El problema persiste.
La causa real: el schema de tus herramientas es ambiguo. El modelo lee la descripción y no puede determinar con certeza qué tool usar — así que adivina. Y a veces acierta, y a veces no, y esa inconsistencia es exactamente lo que rompe producción.
Por qué el modelo elige herramientas
El modelo no "entiende" tus herramientas. Lee tres cosas:
- El nombre de la herramienta
- La descripción de la herramienta
- Los nombres de los parámetros y sus descripciones
Con esos tres inputs decide qué tool llamar, en qué orden, y qué datos pasarle. Es fundamentalmente un problema de razonamiento sobre texto. Si tus descripciones son vagas, las decisiones del modelo serán inconsistentes.
Ejemplo inmediato: tienes dos herramientas:
get_data— "Gets data"fetch_info— "Fetches information"
Desde la perspectiva del modelo, son idénticas. Elegirá entre ellas según qué contexto del mensaje de usuario active una u otra frase, y eso cambia con cada ejecución.
Este tipo de ambigüedad es el origen del 80% de los fallos de tool selection que vemos en los proyectos de integración de agentes IA en DAILYMP.
Los 4 errores más comunes en schemas de herramientas
1. Nombres y descripciones genéricas
// ❌ El modelo no sabe cuándo usar cada una
const tools = [
{ name: "get_customer", description: "Gets customer information" },
{ name: "find_customer", description: "Finds a customer" },
]
// ✅ Cada tool tiene un propósito inequívoco
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.`,
},
]
La diferencia no es cosmética. "Use ONLY when" y "Do NOT use for" son instrucciones de routing que el modelo sigue literalmente.
2. Parámetros sin descripción de su propósito
// ❌ El modelo adivina el formato y el propósito de cada campo
parameters: {
type: "object",
properties: {
query: { type: "string" },
limit: { type: "number" },
filter: { type: "string" },
}
}
// ✅ Cada parámetro explica cuándo usarlo y qué formato espera
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@empresa.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"],
}
El campo limit es el ejemplo clásico. Sin descripción, el modelo pondrá 10, 100 o lo que parezca razonable dado el contexto. Con descripción, pone el valor correcto para cada caso.
3. No especificar cuándo NO usar una herramienta
Este es el patrón más ignorado. El modelo no conoce las fronteras entre herramientas a menos que las declares explícitamente.
// ✅ Delimita el territorio de cada herramienta
{
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.`,
}
La sección DO NOT call if previene efectos secundarios no deseados que son muy difíciles de debuggear cuando ocurren en producción. Un email enviado de más, una factura duplicada, un estado inconsistente en el CRM — todos tienen su origen en un model que no sabía que no debía llamar a esa tool en ese momento.
4. Herramientas que hacen demasiadas cosas
// ❌ El modelo no puede razonar sobre el estado intermedio
{
name: "process_order",
description: "Processes an order: validates stock, creates invoice, sends confirmation, updates CRM.",
}
// ✅ Separa responsabilidades — el modelo puede razonar sobre cada paso
{
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.`,
},
Cuando una herramienta hace cuatro cosas, el modelo no puede pausar a mitad del proceso ni elegir qué pasos ejecutar. Al separar, el agente puede aplicar lógica condicional — que es exactamente lo que necesitas para flujos de negocio reales.
El patrón del comentario de orquestación
Además de las descripciones de cada tool, añade siempre en el system prompt una sección que establece el orden lógico entre herramientas:
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.
`;
Esto es distinto de las descripciones individuales: es la lógica de orquestación que cruza múltiples herramientas. Los modelos actuales siguen estas reglas numeradas con alta consistencia cuando están en el system prompt — mucho más que cuando intentas codificarlo solo en las descripciones.
Cómo testear tus schemas en 30 segundos
No necesitas un suite de evals completo para esto. Un test heurístico rápido:
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', // modelo rápido para 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}`)
);
}
// Uso
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" },
]);
Ejecuta esto antes y después de cada cambio en el schema. Tarda 30 segundos, cuesta menos de 1 céntimo, y atrapa regresiones de selección de herramientas inmediatamente — sin esperar a que fallen en producción.
Si ya tienes un suite de evals para tu agente, integra este test como una capa separada que se ejecuta en el mismo job de CI.
El impacto económico de un schema malo
Mejorar los schemas no es solo calidad — es latencia y coste directo.
Cuando el modelo llama a la herramienta equivocada, recibe un error o un resultado inesperado, e intenta de nuevo. Eso son dos llamadas a la API en vez de una. Si esto ocurre en el 20% de las interacciones y procesas 10.000 peticiones al día, estás pagando por 2.000 llamadas LLM innecesarias diariamente.
En los proyectos que implementamos en DAILYMP, los schemas bien diseñados reducen los retries de tool selection entre un 60% y un 80%. Eso es una reducción directa de coste, sin contar la mejora de latencia y la eliminación de errores de estado inconsistente.
El tiempo de revisión y mejora de un conjunto de schemas en un agente existente es habitualmente menos de medio día. El retorno se ve desde la primera semana.
Si tienes un agente en producción que presenta comportamiento inconsistente en la selección de herramientas — o simplemente no tienes test alguno sobre qué tool llama el modelo en qué situación — es exactamente el tipo de revisión que hacemos antes de escalar cualquier implementación:
Revisamos el schema de tu agente y te decimos qué está fallando →