Generics para wrappers de IA en TypeScript
Tiempo estimado de lectura: 4 min
- Evita desincronización: usa un wrapper genérico
withAI<T>()para enlazar firma TypeScript y validación Zod. - Zod‑first: Zod en runtime + z.infer en TypeScript ofrece validación práctica frente al type erasure.
- Autodocumentación y registros: genera descripciones básicas y registra prompt, rawResponse y resultado de Zod.
- Operación segura: define límites de reintentos y métricas; en sistemas críticos separa intención (LLM) de efecto (máquina de estado).
Generics para wrappers de IA en TypeScript: si vas a exponer funciones de negocio a agentes, necesitas una forma segura y mantenible de hacerlo. En las primeras líneas: usar generics y Zod evita duplicar contratos y convierte la exposición de funciones en un proceso reproducible y tipado. Aquí explico por qué funciona, cómo implementarlo y qué decisiones arquitectónicas debes tomar.
Resumen rápido (lectores con prisa)
Patrón Zod‑first: pasa un esquema Zod al wrapper y usa z.infer<…> para que TypeScript infiera tipos. El wrapper genérico withAI<T> enlaza la firma de la función con el esquema, validando en runtime y detectando incompatibilidades en compilación.
Úsalo cuando expongas funciones a LLMs o agentes; mejora seguridad estática, validación runtime y trazabilidad.
Por qué necesitas Generics para wrappers de IA en TypeScript
Exponer una función como herramienta para un LLM suele generar cuatro elementos repetitivos: descripción, esquema de validación, bindings del SDK y la ejecución. Ese boilerplate se desincroniza con el tiempo: la firma cambia, el esquema no, y el error aparece en producción, no en el IDE.
La solución es un wrapper genérico —withAI<T>()— que capture la firma de la función mediante tipos TypeScript y reciba un esquema Zod en runtime. Zod vive en ejecución; TypeScript no. Esta combinación (TypeScript + Zod) te da lo mejor de ambos mundos: seguridad estática y validación runtime.
Limitación real: type erasure y la decisión Zod‑first
TypeScript suprime tipos en runtime (type erasure). No puedes inspeccionar en ejecución que un parámetro se llama userId y es string. Por eso hay dos rutas:
- Extraer metadatos en build time (AST/JSDoc) — viable pero compleja.
- Patrón Zod‑first — práctico y fiable: pasas un esquema Zod al wrapper, Zod valida en runtime y TypeScript infiere tipos con
z.infer<…>.
Recomiendo Zod‑first. Es simple, robusto y encaja con flujos CI/CD.
Implementación: withAI<T> paso a paso
Idea: recibir la función original, su esquema Zod y devolver una herramienta lista para el SDK de IA (p. ej. Vercel AI SDK https://sdk.vercel.ai/docs). El genérico obliga a coherencia entre firma y esquema.
Ejemplo reducido
import { z } from 'zod';
import { tool } from 'ai'; // Vercel AI SDK
export function withAI>(
fn: T,
schema: z.ZodType<Parameters<T>[0]>,
description?: string
) {
const autoDesc = description ?? generateDescription(fn.name, schema);
return tool({
description: autoDesc,
parameters: schema,
execute: async (args) => {
// args ya validado por Zod cuando el SDK integra la validación
return await fn(args as Parameters<T>[0]);
},
});
}
Claves:
Parameters<T>[0]enlaza el tipo esperado del primer argumento defncon el esquema.- Si la firma de
fncambia y el esquema no, TypeScript marcará el error en compilación. tool()es una abstracción; adapta al SDK que uses (Vercel, OpenAI, etc.).
Autodocumentación práctica
El wrapper puede generar una descripción básica a partir del nombre de la función y las claves del esquema. No es NLP mágico, pero reduce trabajo manual y mejora la señal hacia el modelo.
function generateDescription(name: string, schema: z.ZodTypeAny) {
const readable = name.replace(/([A-Z])/g, ' $1').trim().toLowerCase();
const params = schema instanceof z.ZodObject ? Object.keys(schema.shape).join(', ') : 'input object';
return `Use this tool to ${readable}. Parameters: ${params}.`;
}
Para funciones críticas, proporciona siempre una descripción manual y ejemplos de uso. Puedes enriquecer la doc con ejemplos JSON y constraints — los modelos modernos respetan instrucciones claras (ver Structured Outputs de OpenAI: https://platform.openai.com/docs/guides/structured-outputs).
Buenas prácticas operativas
- Valida con
.safeParse()en agentes que puedan autocorregirse; usa.parse()para endpoints que deban fallar rápido. - Registra siempre: prompt, rawResponse, resultado de Zod (
error.flatten()), la herramienta invocada y contexto. Sin esto, los postmortems son inútiles. - Mide: tasa de validación fallida, latencia de autocorrección, reintentos por prompt y degradaciones a humano.
- Define límites: si tras N reintentos no hay corrección, encola para revisión humana. Evita loops que consuman tokens/requests.
Trade‑offs y decisiones arquitectónicas
- Autogeneración vs. precisión: la descripción automática agiliza pero no sustituye documentación humana para casos sensibles.
- Structured Outputs + generateObject (OpenAI) reducen errores de formato, pero no reemplazan validaciones semánticas (p. ej. rangos, signos). Zod sigue siendo necesario.
- En sistemas críticos, deja que el LLM decida la herramienta, pero que una máquina de estado (n8n, XState) controle la ejecución final; así separas intención y efecto.
Ejemplo completo: patrón en producción
1. Define la función pura:
async function getOrder(args: { orderId: string }) { /* ... */ }
2. Define esquema Zod:
const OrderSchema = z.object({ orderId: z.string().uuid() });
3. Envuelve:
const getOrderTool = withAI(getOrder, OrderSchema, 'Obtiene estado de un pedido por ID');
4. Registra y mide cada llamada. Si Zod falla, serializa error.flatten() y envíalo al LLM para autocorrección o al equipo de soporte.
Conclusión
Generics para wrappers de IA en TypeScript no es un truco académico: es una medida práctica para escalar agentes sin introducir deuda técnica. El patrón Zod‑first con withAI<T> convierte la exposición de funciones en una operación segura, rastreable y testeable. Si tu agente escribe en bases de datos, llama APIs facturadas o ejecuta efectos críticos, aplica este patrón hoy: te evitará errores que sólo descubres en producción.
Para equipos que diseñan flujos de agentes y workflows relacionados con automatización e IA aplicada, puede ser útil revisar trabajos y herramientas experimentales. Más recursos y experimentos están disponibles en Dominicode Labs.
FAQ
- ¿Qué es exactamente el patrón Zod‑first?
- ¿Cuándo debo usar
safeParse()vsparse()? - ¿Cómo detecta TypeScript desalineaciones entre firma y esquema?
- ¿Qué hacer si Zod falla de forma recurrente?
- ¿Puedo usar este patrón con otros SDKs además de Vercel?
- ¿Cómo debo registrar errores y métricas?
¿Qué es exactamente el patrón Zod‑first?
Es la práctica de definir esquemas de validación con Zod en runtime y usar z.infer<…> para que TypeScript derive los tipos, evitando depender de metadatos de tipos en ejecución.
¿Cuándo debo usar safeParse() vs parse()?
Usa safeParse() cuando el agente pueda autocorregirse o cuando quieras manejar errores sin lanzar. Usa parse() en endpoints que deban fallar rápido y propagar excepciones.
¿Cómo detecta TypeScript desalineaciones entre firma y esquema?
El wrapper genérico usa tipos como Parameters<T>[0]. Si la firma de la función cambia y el esquema suministrado no coincide, TypeScript emitirá un error en compilación por incompatibilidad de tipos.
¿Qué hacer si Zod falla de forma recurrente?
Registra el resultado de error.flatten(), envía el fallo al LLM para autocorrección o encola el caso para revisión humana si supera N reintentos. Mide la tasa de validación fallida para priorizar correcciones.
¿Puedo usar este patrón con otros SDKs además de Vercel?
Sí. tool() en el ejemplo es una abstracción; adapta la forma de registrar parámetros, validar y ejecutar según el SDK (Vercel, OpenAI u otros).
¿Cómo debo registrar errores y métricas?
Registra prompt, rawResponse, resultado de Zod (error.flatten()), herramienta invocada, contexto y métricas como latencia y reintentos. Estos datos son esenciales para postmortems y mejoras iterativas.

Leave a Reply