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.
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.
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.
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.
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.
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.
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.99Qué 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.
¿Terminaste la práctica?
Haz el ejercicio y comprueba tus respuestas antes de marcar la lección como completada.