Saltar al contenido
Tool use con la API de Claude8:11
Cuadro de Agente que consulta inventario en Postgres

No hay slides. Hay un proyecto.

Agente que consulta inventario en Postgres

NovaTool use con la API de ClaudeIntermedio8:11

Capítulos

Sales con esto

  • Tool 'query_inventory' que ejecuta SQL parametrizado
  • Loop de tool_use hasta respuesta final del modelo
  • Manejo de errores de conexión y de datos vacíos
  • Endpoint HTTP /api/chat para consumir el agente
  • UI mínima en HTML para probar en el navegador

NecesitasNode.js 18+ · PostgreSQL corriendo localmente (o Docker) · psql CLI instalado · Cuenta y API key de Anthropic

4clases31min de clase56capítulospróxima: Supabase Edge Functions, 6 en render

03LO QUE VAS A CONSTRUIR

6 pasos, un proyecto que corre

Cuadro de Agente que consulta inventario en Postgres

8:11204 líneas

Ver la clase
  1. 01

    Crear la tabla y datos de prueba

    Tener una tabla real de productos con stock y precio para que el agente consulte datos verdaderos

    schema.sql

    schema.sql
    1CREATE TABLE IF NOT EXISTS products (
    2 id SERIAL PRIMARY KEY,
    3 name TEXT NOT NULL,
    4 category TEXT NOT NULL,
    5 stock INTEGER NOT NULL DEFAULT 0,
    6 price NUMERIC(10,2) NOT NULL
    7);
    8
    9INSERT INTO products
    10 (name, category, stock, price)
    11VALUES
    12 ('Teclado mecanico', 'Perifericos',
    13 34, 89.90),
    14 ('Monitor 27 pulgadas 4K', 'Monitores',
    15 12, 349.00),
    16 ('Mouse inalambrico', 'Perifericos',
    17 58, 29.50),
    18 ('Silla ergonomica', 'Muebles',
    19 7, 219.00)
    20ON CONFLICT DO NOTHING;
    psql -U postgres -d inventory -f schema.sql

    psql -U postgres -d inventory -f schema.sql

    salida

    CREATE TABLE

    INSERT 0 4

  2. 02

    Conectar Node a Postgres

    Crear un pool de conexiones reutilizable con manejo de errores de conexión

    src/db.ts

    src/db.ts
    1import { Pool } from 'pg';
    2import 'dotenv/config';
    3
    4export const pool = new Pool({
    5 connectionString:
    6 process.env.DATABASE_URL,
    7});
    8
    9pool.on('error', (err) => {
    10 console.error(
    11 'Error en el pool de PG:',
    12 err
    13 );
    14});
  3. 03

    Definir el tool y su ejecutor

    Declarar el schema del tool que Claude puede llamar y la función que corre el SQL real con try/catch

    src/tools.ts

    src/tools.ts
    1import { pool } from './db';
    2
    3export const inventoryTool = {
    4 name: 'query_inventory',
    5 description:
    6 'Consulta el inventario. Recibe una ' +
    7 'categoria opcional y devuelve nombre, ' +
    8 'stock y precio de los productos.',
    9 input_schema: {
    10 type: 'object' as const,
    11 properties: {
    12 category: {
    13 type: 'string',
    14 description:
    15 'Categoria a filtrar',
    16 },
    17 },
    18 },
    19};
    20
    21type Input = { category?: string };
    22
    23export async function runInventoryQuery(
    24 input: Input
    25) {
    26 try {
    27 const { category } = input;
    28 const base =
    29 'SELECT name, stock, price ' +
    30 'FROM products';
    31 const sql = category
    32 ? `${base} WHERE category ILIKE $1`
    33 : base;
    34 const values = category
    35 ? [category]
    36 : [];
    37
    38 const { rows } = await pool.query(
    39 sql,
    40 values
    41 );
    42
    43 if (rows.length === 0) {
    44 return {
    45 ok: false,
    46 error:
    47 'No hay productos en esa ' +
    48 'categoria.',
    49 };
    50 }
    51 return { ok: true, data: rows };
    52 } catch (err) {
    53 console.error('Error en PG:', err);
    54 return {
    55 ok: false,
    56 error: 'La base de datos no respondio.',
    57 };
    58 }
    59}
  4. 04

    Armar el loop de tool use

    Entender cómo Claude pide ejecutar el tool, cómo se le devuelve el resultado y cómo se sigue el loop hasta la respuesta final

    src/agent.ts

    src/agent.ts
    1import Anthropic from '@anthropic-ai/sdk';
    2import {
    3 inventoryTool,
    4 runInventoryQuery,
    5} from './tools';
    6
    7const anthropic = new Anthropic({
    8 apiKey: process.env.ANTHROPIC_API_KEY,
    9});
    10
    11export async function askAgent(
    12 question: string
    13) {
    14 const messages: any[] = [
    15 { role: 'user', content: question },
    16 ];
    17
    18 let response = await anthropic.messages.create({
    19 model: 'claude-sonnet-4-5',
    20 max_tokens: 1024,
    21 tools: [inventoryTool],
    22 messages,
    23 });
    24
    25 while (response.stop_reason === 'tool_use') {
    26 const toolUse = response.content.find(
    27 (b: any) => b.type === 'tool_use'
    28 );
    29 if (!toolUse) break;
    30
    31 const result = await runInventoryQuery(
    32 toolUse.input as { category?: string }
    33 );
    34
    35 messages.push({
    36 role: 'assistant',
    37 content: response.content,
    38 });
    39 messages.push({
    40 role: 'user',
    41 content: [
    42 {
    43 type: 'tool_result',
    44 tool_use_id: toolUse.id,
    45 content: JSON.stringify(result),
    46 },
    47 ],
    48 });
    49
    50 response = await anthropic.messages.create({
    51 model: 'claude-sonnet-4-5',
    52 max_tokens: 1024,
    53 tools: [inventoryTool],
    54 messages,
    55 });
    56 }
    57
    58 const finalText = response.content.find(
    59 (b: any) => b.type === 'text'
    60 );
    61 return finalText ? finalText.text : '';
    62}
  5. 05

    Exponer el agente por HTTP

    Crear un endpoint Express que recibe la pregunta, llama al agente y devuelve un error controlado si algo falla

    src/server.ts

    src/server.ts
    1import express from 'express';
    2import 'dotenv/config';
    3import { askAgent } from './agent';
    4
    5const app = express();
    6app.use(express.json());
    7app.use(express.static('public'));
    8
    9app.post('/api/chat', async (req, res) => {
    10 const { question } = req.body;
    11 if (!question) {
    12 return res.status(400).json({
    13 error: 'Falta la pregunta',
    14 });
    15 }
    16 try {
    17 const answer = await askAgent(question);
    18 res.json({ answer });
    19 } catch (err) {
    20 console.error('Error del agente:', err);
    21 res.status(500).json({
    22 error: 'El agente no pudo responder.',
    23 });
    24 }
    25});
    26
    27const PORT = process.env.PORT || 3000;
    28app.listen(PORT, () => {
    29 console.log(`Server en puerto ${PORT}`);
    30});
    npx ts-node src/server.ts

    npx ts-node src/server.ts

    salida

    Server en puerto 3000

  6. 06

    Probar el agente en el navegador

    Ver el agente respondiendo con datos reales y comprobar que un caso sin resultados o con error de conexión se explica en lenguaje natural en vez de romper la app

    public/index.html

    public/index.html
    1<!DOCTYPE html>
    2<html lang="es">
    3<head>
    4 <meta charset="UTF-8" />
    5 <title>Agente de Inventario</title>
    6</head>
    7<body>
    8 <h1>Agente de Inventario</h1>
    9 <input
    10 id="q"
    11 placeholder="Pregunta por el stock..."
    12 />
    13 <button id="send">Enviar</button>
    14 <p id="answer"></p>
    15
    16 <script>
    17 const send =
    18 document.getElementById('send');
    19 send.onclick = async () => {
    20 const q =
    21 document.getElementById('q').value;
    22 const res = await fetch('/api/chat', {
    23 method: 'POST',
    24 headers: {
    25 'Content-Type':
    26 'application/json',
    27 },
    28 body: JSON.stringify({
    29 question: q,
    30 }),
    31 });
    32 const data = await res.json();
    33 document.getElementById(
    34 'answer'
    35 ).innerText = data.answer || data.error;
    36 };
    37 </script>
    38</body>
    39</html>
    npx ts-node src/server.ts

    npx ts-node src/server.ts

    salida

    Server en puerto 3000

    Agente de Inventario

    Pregunta por el stock...

    Enviar

    Monitor 27 pulgadas 4K: 12 unidades a $349.00

    No hay productos en esa categoria.

04LA MESA

Clases listas para ver

Cuadro de Registro con validación en vivo

Registro con validación en vivo

14 capítulos375 líneas

Intermedio
Next.js 15

05MOMENTOS

Entra por el minuto que te sirve

06POR DÓNDE ENTRAR

Tres maneras de encontrar tu clase

07QUIÉN DICTA

6 presentadores, 6 especialidades

Claude API / Anthropic

Nova

Tu guía para APIs de lenguaje

1 clase

Ver su última clase
Cuadro de Agente que consulta inventario en Postgres

Agente que consulta inventario en Postgres

Presentador generado por AI Talks

08APUNTES

Apuntes de la clase

En 4 líneas

  • El tool_use no es magia: es un loop explícito donde tú ejecutas la función y devuelves el resultado como 'tool_result'
  • El error de la base de datos hay que atraparlo en el ejecutor del tool, no dejar que reviente el server
  • El input_schema del tool es lo único que Claude 've' de tu base: si está mal descrito, va a pedir parámetros que no existen
  • Separar db.ts, tools.ts y agent.ts hace que el mismo tool se pueda testear sin llamar a la API de Claude

Lo que se rompe

¿Qué problema resuelve Agente que consulta inventario en Postgres?
Los chatbots que solo 'alucinan' respuestas no sirven para soporte real: hace falta que el modelo consulte datos verdaderos y sepa explicar cuando algo no existe o la base falla. Un agente mínimo en Node.js que usa tool use de Claude para consultar una tabla real de productos en Postgres y responder en lenguaje natural, incluso cuando la consulta falla.
¿Qué necesito saber antes de seguir esta clase?
Necesitas Node.js 18+, PostgreSQL corriendo localmente (o Docker), psql CLI instalado, Cuenta y API key de Anthropic, Conocimientos básicos de SQL y Express. La clase es de nivel intermedio y dura 8 minutos.
¿Qué stack se usa para Tool use con la API de Claude?
El proyecto usa Node.js, TypeScript, Express, PostgreSQL, @anthropic-ai/sdk, pg. Todo el código se escribe en pantalla durante la clase.
¿Por qué falla al olvidar el while(stop_reason === 'tool_use') y devolver solo la primera respuesta, que suele venir vacía o…?
Olvidar el while(stop_reason === 'tool_use') y devolver solo la primera respuesta, que suele venir vacía o incompleta
¿Por qué falla al dejar que una excepción de pg tumbe el proceso en vez de capturarla y devolver un objeto { ok: false, error }?
Dejar que una excepción de pg tumbe el proceso en vez de capturarla y devolver un objeto { ok: false, error }
¿Por qué falla al usar SELECT * en el tool, exponiendo columnas sensibles que no querías que el modelo viera ni repitiera?
Usar SELECT * en el tool, exponiendo columnas sensibles que no querías que el modelo viera ni repitiera

El truco

Serializa SIEMPRE el resultado del tool con JSON.stringify, incluso cuando es un error ({ ok: false, error: '...' }). Claude interpreta mejor un string estructurado y consistente que un objeto crudo o un mensaje de error suelto, y eso reduce muchísimo los casos donde el modelo se queda repitiendo la misma pregunta o inventa una respuesta genérica en vez de explicar la falla real.

Transcripción

10LA PRÓXIMA CLASE

¿Qué construimos la próxima semana?

  • Supabase Edge Functions0 votos
  • Búsqueda semántica con pgvector0 votos
  • AWS Lambda con CDK en TypeScript0 votos
  • n8n0 votos
  • React Server Components en Next.js 150 votos
  • Prompt caching en la API de Claude0 votos

Vota o propón