Patrones de Integracion con LLMs¶
Proposito¶
Patrones arquitectonicos concretos para integrar LLMs en aplicaciones de produccion. No es teoria sobre LLMs: es como conectarlos a tu sistema de forma confiable, segura, y eficiente.
1. Patron: Structured Output¶
Problema¶
Los LLMs retornan texto libre. Tu aplicacion necesita datos estructurados.
Solucion¶
Forzar al LLM a retornar JSON con un schema definido.
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
messages: [
{
role: 'user',
content: `Analiza este mensaje del paciente y extrae la informacion.
Mensaje: "Necesito un turno de cardiologia para el proximo martes en la manana"
Responde SOLO con JSON valido en este formato:
{
"specialty": string,
"preferredDate": string (YYYY-MM-DD o "next_tuesday"),
"preferredTimeOfDay": "morning" | "afternoon" | "evening" | "any",
"urgency": "low" | "medium" | "high"
}`
}
]
});
// Parsear y validar
const parsed = JSON.parse(response.content[0].text);
const validated = BookingIntentSchema.parse(parsed); // zod validation
Cuando usarlo¶
Siempre que necesites datos del LLM para procesamiento programatico.
2. Patron: Tool Use / Function Calling¶
Problema¶
El LLM necesita acceder a datos de tu sistema (BD, APIs) para responder.
Solucion¶
Definir "tools" que el LLM puede invocar.
const tools = [
{
name: 'search_doctors',
description: 'Busca medicos por especialidad y disponibilidad',
input_schema: {
type: 'object',
properties: {
specialty: { type: 'string', description: 'Nombre de la especialidad' },
dateFrom: { type: 'string', description: 'Fecha inicio (YYYY-MM-DD)' },
dateTo: { type: 'string', description: 'Fecha fin (YYYY-MM-DD)' }
},
required: ['specialty']
}
},
{
name: 'create_booking',
description: 'Crea una reserva de turno',
input_schema: {
type: 'object',
properties: {
doctorId: { type: 'string' },
date: { type: 'string' },
startTime: { type: 'string' }
},
required: ['doctorId', 'date', 'startTime']
}
}
];
// El LLM decide que tool llamar basado en el mensaje del usuario
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-6',
tools,
messages: [{ role: 'user', content: 'Quiero un turno de cardiologia' }]
});
Consideraciones de seguridad¶
- Valida los parametros que el LLM pasa a las tools (no confies ciegamente).
- Usa autorizacion: la tool
create_bookingdebe verificar que el paciente esta autenticado. - Limita las tools disponibles segun el contexto (un paciente no deberia tener tools de admin).
3. Patron: RAG (Retrieval-Augmented Generation)¶
Problema¶
El LLM no conoce los datos especificos de tu sistema.
Solucion¶
Buscar informacion relevante y pasarla como contexto.
Paso 1: El usuario pregunta algo.
Paso 2: Buscas en tu BD/documentos la informacion relevante.
Paso 3: Pasas la pregunta + la informacion al LLM.
Paso 4: El LLM genera una respuesta basada en tus datos.
Implementacion simple (sin embeddings)¶
async function answerQuestion(question: string, patientId: string) {
// Buscar contexto relevante
const upcomingBookings = await bookingRepo.findByPatient(patientId, {
status: 'CONFIRMED',
dateFrom: new Date()
});
const context = upcomingBookings.map(b =>
`Turno: ${b.date} ${b.startTime} con ${b.doctor.name} (${b.specialty.name}) en ${b.clinic.name}`
).join('\n');
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-6',
messages: [{
role: 'user',
content: `Eres un asistente del sistema de turnos medicos.
Responde la pregunta del paciente usando SOLO la informacion proporcionada.
Turnos del paciente:
${context}
Pregunta: ${question}`
}]
});
return response.content[0].text;
}
Implementacion con embeddings (para datos grandes)¶
1. Indexar documentos/datos en vector DB (Pinecone, pgvector).
2. Convertir la pregunta en embedding.
3. Buscar los documentos mas similares (cosine similarity).
4. Pasar los documentos relevantes como contexto al LLM.
4. Patron: Guardrails¶
Problema¶
El LLM puede generar respuestas incorrectas, inseguras, o fuera de scope.
Solucion¶
Capas de validacion antes y despues del LLM.
Input -> [Input Guardrail] -> LLM -> [Output Guardrail] -> Response
| |
- Sanitizar - Validar formato
- Detectar injection - Verificar facts
- Verificar permisos - Filtrar PII
- Verificar seguridad
// Input guardrail
function sanitizeInput(userMessage: string): string {
// Detectar intentos de prompt injection
if (containsInjectionAttempt(userMessage)) {
throw new AppError('INVALID_INPUT', 400);
}
// Limitar largo
return userMessage.slice(0, 2000);
}
// Output guardrail
function validateOutput(llmResponse: string, context: any): string {
// Verificar que no expone datos sensibles
if (containsPII(llmResponse, context.allowedData)) {
return 'No puedo compartir esa informacion. Contacta a la clinica directamente.';
}
// Verificar que no inventa datos
if (!factCheck(llmResponse, context.knownFacts)) {
return 'No tengo informacion suficiente para responder. Contacta a la clinica.';
}
return llmResponse;
}
5. Patron: Caching de Respuestas LLM¶
Problema¶
Las llamadas a LLMs son lentas (1-10s) y caras.
Solucion¶
Cache para queries repetitivas o similares.
| Estrategia | Cuando | Ejemplo |
|---|---|---|
| Cache exacto | Mismo input = mismo output | "Que especialidades tienen?" (siempre igual) |
| Cache semantico | Inputs similares = mismo output | "horarios de cardiologia" ~ "disponibilidad cardiologo" |
| Pre-computar | Respuestas predecibles | FAQs, descripciones de especialidades |
6. Patron: Streaming¶
Problema¶
La respuesta del LLM tarda varios segundos. El usuario ve una pantalla en blanco.
Solucion¶
Streaming token por token al frontend.
// Backend (NestJS)
@Get('chat')
async chat(@Query('message') message: string, @Res() res: Response) {
res.setHeader('Content-Type', 'text/event-stream');
const stream = await anthropic.messages.stream({
model: 'claude-sonnet-4-6',
messages: [{ role: 'user', content: message }]
});
for await (const event of stream) {
if (event.type === 'content_block_delta') {
res.write(`data: ${JSON.stringify({ text: event.delta.text })}\n\n`);
}
}
res.end();
}
7. Resumen de Patrones¶
| Patron | Problema que resuelve | Complejidad |
|---|---|---|
| Structured Output | LLM retorna texto, necesito JSON | Baja |
| Tool Use | LLM necesita datos de mi sistema | Media |
| RAG | LLM no conoce mis datos | Media-Alta |
| Guardrails | LLM puede dar respuestas incorrectas/inseguras | Media |
| Caching | LLM es lento y caro | Baja-Media |
| Streaming | El usuario espera mucho | Baja |
Checklist de Completitud¶
- 6 patrones documentados con codigo
- Consideraciones de seguridad
- Ejemplos del dominio de turnos medicos
- Complejidad relativa por patron
- Revisada por otro ingeniero
Archivos relacionados¶
- ai-native-development.md - Desarrollo AI-native
- autonomous-agents.md - Agentes autonomos
- emerging-tech-radar.md - Radar tecnologico
- ../06-ai-assisted-dev/prompt-engineering-guide.md - Prompt engineering