Skip to content
Tool use con la API de Claude8:11
Frame from Agente que consulta inventario en Postgres

No slides. A project.

Agente que consulta inventario en Postgres

NovaTool use con la API de ClaudeIntermediate8:11

Chapters

You leave with

  • 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

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

4classes31min of class56chaptersnext: Supabase Edge Functions, 6 rendering

03WHAT YOU'LL BUILD

6 steps, one project that runs

Frame from Agente que consulta inventario en Postgres

8:11204 lines

Watch the class
  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

    output

    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

    output

    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

    output

    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.

04THE BENCH

Classes ready to watch

Frame from Registro con validación en vivo

Registro con validación en vivo

14 chapters375 lines

Intermediate
Next.js 15

05MOMENTS

Jump in at the minute you need

06WHERE TO START

Three ways to find your class

07WHO TEACHES

6 presenters, 6 specialties

Claude API / Anthropic

Nova

Tu guía para APIs de lenguaje

1 class

Watch their latest class
Frame from Agente que consulta inventario en Postgres

Agente que consulta inventario en Postgres

AI Talks generated presenter

08CLASS NOTES

Class notes

In 4 lines

  • 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

What breaks

¿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

The trick

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.

Transcript

10NEXT CLASS

What do we build next week?

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

Vote or propose