Inicio / Inteligencia Artificial / AI-First Full Stack: Construye Apps con IA / Function Calling y Tool Use

Function Calling y Tool Use

Definición de tools, schemas JSON, ejecución de funciones, manejo de errores y tool use multi-step.

Intermedio Funciones
🔒 Solo lectura
📖

Estás en modo lectura

Puedes leer toda la lección, pero para marcar progreso, hacer ejercicios y ganar XP necesitas una cuenta Pro.

Desbloquear por $9/mes

Function Calling y Tool Use

Function calling (o tool use) permite que un LLM invoque funciones externas para obtener datos en tiempo real, ejecutar acciones o interactuar con APIs. Es la base para construir agentes y aplicaciones AI que van más allá de generar texto.


¿Cómo funciona?

1. Defines las herramientas disponibles (schema JSON)
2. Envías el mensaje del usuario + definición de tools
3. El LLM decide SI llamar una tool y con qué parámetros
4. Tú ejecutas la función y retornas el resultado
5. El LLM genera la respuesta final con el resultado
Usuario: "¿Qué clima hace en Madrid?"
    │
    ▼
LLM analiza → Decide llamar: get_weather({ city: "Madrid" })
    │
    ▼
Tu código ejecuta get_weather("Madrid") → { temp: 22, condition: "sunny" }
    │
    ▼
LLM recibe resultado → "En Madrid hace 22°C y está soleado."

Function Calling con OpenAI

Definir herramientas

import OpenAI from 'openai';

const openai = new OpenAI();

const tools: OpenAI.ChatCompletionTool[] = [
  {
    type: 'function',
    function: {
      name: 'get_weather',
      description: 'Obtiene el clima actual de una ciudad',
      parameters: {
        type: 'object',
        properties: {
          city: {
            type: 'string',
            description: 'Nombre de la ciudad (ej: "Madrid", "Buenos Aires")',
          },
          units: {
            type: 'string',
            enum: ['celsius', 'fahrenheit'],
            description: 'Unidad de temperatura',
          },
        },
        required: ['city'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'search_database',
      description: 'Busca información en la base de datos de la empresa',
      parameters: {
        type: 'object',
        properties: {
          query: { type: 'string', description: 'Término de búsqueda' },
          category: {
            type: 'string',
            enum: ['products', 'users', 'orders'],
          },
          limit: { type: 'number', description: 'Máximo de resultados' },
        },
        required: ['query'],
      },
    },
  },
];

Loop de ejecución

// Las funciones reales que el LLM puede invocar
const toolFunctions: Record<string, Function> = {
  get_weather: async ({ city, units = 'celsius' }: any) => {
    const response = await fetch(
      `https://api.weather.com/v1/current?city=${city}&units=${units}`
    );
    return response.json();
  },
  search_database: async ({ query, category, limit = 5 }: any) => {
    return prisma[category || 'products'].findMany({
      where: { name: { contains: query, mode: 'insensitive' } },
      take: limit,
    });
  },
};

async function chatWithTools(userMessage: string): Promise<string> {
  const messages: OpenAI.ChatCompletionMessageParam[] = [
    { role: 'system', content: 'Eres un asistente útil con acceso a herramientas.' },
    { role: 'user', content: userMessage },
  ];

  // Loop: el LLM puede hacer múltiples llamadas a tools
  while (true) {
    const response = await openai.chat.completions.create({
      model: 'gpt-4o',
      messages,
      tools,
      tool_choice: 'auto', // 'auto' | 'none' | 'required' | { function: { name: '...' } }
    });

    const choice = response.choices[0];

    // Si no hay tool calls, retornar la respuesta final
    if (choice.finish_reason === 'stop') {
      return choice.message.content!;
    }

    // Procesar tool calls
    if (choice.message.tool_calls) {
      messages.push(choice.message); // Agregar la decisión del LLM

      for (const toolCall of choice.message.tool_calls) {
        const functionName = toolCall.function.name;
        const args = JSON.parse(toolCall.function.arguments);

        console.log(`🔧 Calling: ${functionName}(${JSON.stringify(args)})`);

        try {
          const result = await toolFunctions[functionName](args);
          messages.push({
            role: 'tool',
            tool_call_id: toolCall.id,
            content: JSON.stringify(result),
          });
        } catch (error) {
          messages.push({
            role: 'tool',
            tool_call_id: toolCall.id,
            content: JSON.stringify({ error: (error as Error).message }),
          });
        }
      }
    }
  }
}

Tool Use con Anthropic (Claude)

import Anthropic from '@anthropic-ai/sdk';

const anthropic = new Anthropic();

const tools: Anthropic.Tool[] = [
  {
    name: 'get_weather',
    description: 'Obtiene el clima actual de una ciudad',
    input_schema: {
      type: 'object' as const,
      properties: {
        city: { type: 'string', description: 'Nombre de la ciudad' },
      },
      required: ['city'],
    },
  },
];

async function claudeWithTools(userMessage: string): Promise<string> {
  const messages: Anthropic.MessageParam[] = [
    { role: 'user', content: userMessage },
  ];

  while (true) {
    const response = await anthropic.messages.create({
      model: 'claude-sonnet-4-20250514',
      max_tokens: 1024,
      tools,
      messages,
    });

    // Si termina sin tool use, retornar texto
    if (response.stop_reason === 'end_turn') {
      return response.content
        .filter(b => b.type === 'text')
        .map(b => b.text)
        .join('');
    }

    // Procesar tool use
    if (response.stop_reason === 'tool_use') {
      messages.push({ role: 'assistant', content: response.content });

      const toolResults: Anthropic.ToolResultBlockParam[] = [];

      for (const block of response.content) {
        if (block.type === 'tool_use') {
          const result = await toolFunctions[block.name](block.input);
          toolResults.push({
            type: 'tool_result',
            tool_use_id: block.id,
            content: JSON.stringify(result),
          });
        }
      }

      messages.push({ role: 'user', content: toolResults });
    }
  }
}

Herramientas prácticas comunes

const practicalTools: OpenAI.ChatCompletionTool[] = [
  // Ejecutar SQL (con permisos restringidos)
  {
    type: 'function',
    function: {
      name: 'run_sql_query',
      description: 'Ejecuta una consulta SQL SELECT de solo lectura contra la base de datos',
      parameters: {
        type: 'object',
        properties: {
          query: { type: 'string', description: 'Consulta SQL SELECT' },
        },
        required: ['query'],
      },
    },
  },
  // Enviar email
  {
    type: 'function',
    function: {
      name: 'send_email',
      description: 'Envía un email a un destinatario',
      parameters: {
        type: 'object',
        properties: {
          to: { type: 'string', description: 'Email del destinatario' },
          subject: { type: 'string' },
          body: { type: 'string', description: 'Contenido del email en HTML' },
        },
        required: ['to', 'subject', 'body'],
      },
    },
  },
  // Crear ticket
  {
    type: 'function',
    function: {
      name: 'create_ticket',
      description: 'Crea un ticket de soporte en el sistema',
      parameters: {
        type: 'object',
        properties: {
          title: { type: 'string' },
          description: { type: 'string' },
          priority: { type: 'string', enum: ['low', 'medium', 'high', 'critical'] },
          assignee: { type: 'string', description: 'Email del asignado' },
        },
        required: ['title', 'description', 'priority'],
      },
    },
  },
];

Seguridad en Function Calling

// 1. Validar argumentos con Zod
const weatherArgs = z.object({
  city: z.string().min(1).max(100),
  units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
});

// 2. Whitelist de funciones permitidas
const ALLOWED_FUNCTIONS = new Set(['get_weather', 'search_database']);

function executeToolCall(name: string, args: unknown) {
  if (!ALLOWED_FUNCTIONS.has(name)) {
    throw new Error(`Function not allowed: ${name}`);
  }

  // Validar args según la función
  const validated = schemas[name].parse(args);
  return toolFunctions[name](validated);
}

// 3. SQL injection prevention
async function runSqlQuery({ query }: { query: string }) {
  // Solo permitir SELECT
  if (!query.trim().toUpperCase().startsWith('SELECT')) {
    throw new Error('Solo se permiten consultas SELECT');
  }

  // Bloquear operaciones peligrosas
  const forbidden = ['DROP', 'DELETE', 'UPDATE', 'INSERT', 'ALTER', 'TRUNCATE'];
  if (forbidden.some(f => query.toUpperCase().includes(f))) {
    throw new Error('Operación no permitida');
  }

  return db.query(query);
}

// 4. Confirmación humana para acciones destructivas
async function sendEmail(args: any) {
  if (args.to.includes('@external.com')) {
    // Requiere confirmación del usuario
    return {
      requiresConfirmation: true,
      message: `¿Confirmas enviar email a ${args.to}?`,
      action: args,
    };
  }
  return mailer.send(args);
}

Parallel tool calls

OpenAI puede hacer múltiples llamadas en paralelo:

// El LLM puede decidir llamar varias funciones a la vez:
// "¿Qué clima hace en Madrid y Barcelona?"
// → tool_calls: [
//     { function: 'get_weather', arguments: '{"city":"Madrid"}' },
//     { function: 'get_weather', arguments: '{"city":"Barcelona"}' }
//   ]

// Ejecutar en paralelo
const results = await Promise.all(
  toolCalls.map(async (call) => {
    const result = await toolFunctions[call.function.name](
      JSON.parse(call.function.arguments)
    );
    return {
      role: 'tool' as const,
      tool_call_id: call.id,
      content: JSON.stringify(result),
    };
  })
);

messages.push(...results);
🔒

Ejercicio práctico disponible

Registry de tools y ejecución

Desbloquear ejercicios
// Registry de tools y ejecución
// Desbloquea Pro para acceder a este ejercicio
// y ganar +50 XP al completarlo

function ejemplo() {
    // Tu código aquí...
}

¿Te gustó esta lección?

Con Pro puedes marcar progreso, hacer ejercicios, tomar quizzes, ganar XP y obtener tu constancia.

Ver planes desde $9/mes