Sesión 4 · Tool Use

Calcula montos con calculateTotal

Aplica un descuento a IDs reales del catálogo y trata cursos ausentes antes de presentar un total.

30 min aprox.5 pasosPráctica + comprobación
Qué aprenderás
  • Validar IDs y un porcentaje entre 0 y 100.
  • Calcular dinero con una regla explícita de redondeo.
  • Integrar calculateTotal y probar errores sin llamadas al modelo.

Referencias de código

Índice de esta lección

Paso 1

Define la entrada del cálculo

calculateTotal recibe IDs de cursos, no precios elegidos por el modelo. discountPercent admite 0 a 100 y usa cero cuando se omite.

La lista no puede estar vacía. El esquema valida la forma y el rango; después la función comprueba si los IDs existen. El rango permitido es una regla de este laboratorio, no una autorización universal de descuentos comerciales.

src/patterns/01-tool-use/tools.ts
Paso 1 · typescript
export const totalSchema = z.object({  ids: z.array(z.string().min(1)).min(1)    .describe("IDs del catálogo, por ejemplo ts-01 y dkr-05"),  discountPercent: z.number().min(0).max(100).default(0)    .describe("Descuento porcentual solicitado, entre 0 y 100"),});

Qué deberías observar

Las entradas tienen una forma explícita antes de consultar el catálogo.

Paso 2

Resuelve IDs y calcula en centavos

El material filtra cursos y devuelve notFound junto al cálculo. Esta adaptación detiene el cálculo si falta algún ID: así no muestra un total parcial como si la cotización estuviera completa.

Deduplicamos IDs porque la práctica cotiza cada curso una vez. Trabajamos en centavos enteros y redondeamos el descuento al centavo más cercano; el total resta ese descuento redondeado del subtotal.

Un porcentaje del 100 % deja el total en cero. Los IDs ausentes producen ok: false y total: null para que el asistente pueda pedir la corrección.

src/patterns/01-tool-use/tools.ts
Paso 2 · typescript
export function calculateTotalData(input: z.input<typeof totalSchema>) {  const { ids, discountPercent } = totalSchema.parse(input);  const uniqueIds = [...new Set(ids)];  const notFound = uniqueIds.filter(    (id) => !COURSE_CATALOG.some((course) => course.id === id),  );  if (notFound.length) {    return { ok: false, notFound, subtotal: null, discount: null, total: null };  }  const courses = COURSE_CATALOG.filter((course) => uniqueIds.includes(course.id));  const subtotalCents = courses.reduce(    (sum, course) => sum + Math.round(course.priceUSD * 100), 0,  );  const discountCents = Math.round(subtotalCents * discountPercent / 100);  return {    ok: true,    currency: "USD",    ids: uniqueIds,    notFound: [],    subtotal: subtotalCents / 100,    discount: discountCents / 100,    total: (subtotalCents - discountCents) / 100,    totalHours: courses.reduce((sum, course) => sum + course.hours, 0),  };}

Qué deberías observar

ts-01 y dkr-05 al 20 % producen 37.98, 7.60 y 30.38 USD.

Paso 3

Expón el cálculo como herramienta

La descripción indica cuándo utilizar calculateTotal. execute invoca la misma regla probada localmente; no sustituye los precios del catálogo por números del prompt.

Devuelve un objeto con moneda, IDs, subtotal, descuento, total y horas, o un resultado de error con notFound. No sumes alumnos como personas únicas: son inscripciones por curso.

src/patterns/01-tool-use/tools.ts
Paso 3 · typescript
export const calculateTotal = tool({  description: "Calcula el subtotal, descuento, total y horas de los IDs consultados. Utilízala para el cálculo; no inventes IDs ni calcules mentalmente. Si falta un ID, devuelve el error sin total parcial.",  inputSchema: totalSchema,  execute: async (input) => calculateTotalData(input),});

Qué deberías observar

El modelo recibe una operación de cálculo reproducible.

Paso 4

Registra ambas herramientas e instala el laboratorio

Añade calculateTotal a tools y exige usarlo para los montos. Si la consulta de TypeScript devuelve varios cursos, el asistente debe pedir que se precise el ID.

Los tres archivos del laboratorio se copian al directorio del patrón de la práctica existente. Conservan los helpers y el proveedor que ya configuraste. Cambia la entrada de main.ts como se indica abajo.

El laboratorio final ejecuta solo la versión con herramientas por defecto. Activa compare: true únicamente cuando quieras repetir la comparación y su consumo adicional.

src/patterns/01-tool-use/tool-use.ts
Paso 4 · typescript
tools: { findCourses, calculateTotal }, // En src/main.ts:// import { toolUseMain } from "./patterns/01-tool-use/tool-use.js";// await toolUseMain();// Comparación opcional: await toolUseMain({ compare: true });

Qué deberías observar

La respuesta puede consultar cursos y delegar el cálculo en una función.

Paso 5

Prueba éxito y fallos antes de la inferencia

Con ts-01 y dkr-05 al 20 %, el subtotal es 37.98 USD, el descuento redondeado 7.60 y el total 30.38, con 36 horas. Son valores del ejercicio.

Prueba un ID ausente, duplicados y los extremos de porcentaje. Con un ID ausente no hay un total completo; un duplicado no cuenta el curso dos veces.

Los logs pueden servir para depurar la función, pero la evidencia decisiva es que sus resultados cumplen la regla. Para usarla con el modelo precisa los IDs: quiero ts-01 y dkr-05, 20 % de descuento y horas totales.

Pruebas locales de tools.ts
Paso 5 · typescript
calculateTotalData({ ids: ["ts-01", "dkr-05"], discountPercent: 20 });// ok: true, subtotal: 37.98, discount: 7.6, total: 30.38, totalHours: 36calculateTotalData({ ids: ["ts-01", "ausente"] });// ok: false, notFound: ["ausente"], total: nullcalculateTotalData({ ids: ["ts-01", "ts-01"] });// subtotal: 19.99, discount: 0, total: 19.99

Qué deberías observar

La operación se verifica sin cuota y el modelo puede comunicar los errores.

Ahora te toca

Prueba la idea por tu cuenta

Calcula ts-01 y dkr-05 con descuento del 20 %. Repite con un ID ausente, un ID duplicado y descuentos de 0 y 100. Define cómo debe responder el asistente en cada caso.

Necesito una pista

Usa centavos y devuelve un error sin total cuando falte un ID.

Comparar con una solución

Para ambos IDs: subtotal 37.98, descuento 7.60, total 30.38 USD y 36 horas. Al 0 % el total es 37.98; al 100 % es cero. Un ID ausente da ok: false y total: null. Un duplicado se cuenta una vez.

Antes de seguir

Comprueba lo que aprendiste

No es una nota. Es una pausa para comprobar que la idea quedó clara.

1. ¿De dónde obtiene calculateTotal los precios?
2. ¿Qué ocurre si falta un ID en esta adaptación?
3. ¿Cuál es el total de 19.99 + 17.99 USD al 20 %?

¿Terminaste la práctica?

Haz el ejercicio y comprueba tus respuestas antes de marcar la lección como completada.

Apuntes y código de esta lección