Context Engineering: La habilidad de IA más importante que nadie te enseña
Todos los tutoriales te enseñan prompt engineering. Escribí instrucciones claras. Usá ejemplos few-shot. Agregá un mensaje de sistema. Para demos simples, alcanza.
Pero en cuanto intentás construir algo real — un agente de soporte que recuerde el historial, un asistente de código que entienda todo tu codebase, un pipeline de análisis que procese PDFs de 200 páginas — el prompt engineering se queda corto. No porque tus prompts sean malos, sino porque estás resolviendo el problema equivocado.
El verdadero desafío nunca fue qué decirle al modelo. Es a qué información tiene acceso cuando genera una respuesta. Esa disciplina tiene nombre: context engineering.
Si prompt engineering es elegir las palabras correctas, context engineering es elegir el conocimiento correcto. Es la diferencia entre hacerle una pregunta a un consultor brillante sin darle nada, versus armarle el escritorio con los documentos, historial y herramientas exactas antes de que responda.
Esta guía cubre todo lo que necesitás saber: qué es context engineering, por qué importa más que prompt engineering, y las técnicas concretas que se usan en producción hoy.
¿Qué es exactamente el Context Engineering?
Context engineering es la práctica sistemática de seleccionar, estructurar y gestionar la información que entra en el context window de un LLM para maximizar la calidad del output mientras te mantenés dentro de los límites de tokens y presupuesto.
Pensá en el context window como un escritorio. Prompt engineering es escribir un buen memo para poner en ese escritorio. Context engineering es curar todo lo que hay en ese escritorio — los documentos de referencia, las herramientas, el historial de conversación, los ejemplos — para que la persona sentada ahí pueda darte la mejor respuesta posible.
┌─────────────────────────────────────────┐
│ CONTEXT WINDOW │
│ │
│ ┌─────────────────────────────────┐ │
│ │ Instrucciones del sistema │ │
│ │ (Rol, restricciones, formato) │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Conocimiento recuperado (RAG) │ │
│ │ (Documentos, código, datos) │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Definiciones de herramientas │ │
│ │ (Funciones/APIs disponibles) │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Historial de conversación │ │
│ │ (Mensajes previos + contexto) │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Query actual del usuario │ │
│ │ (Lo que acaba de preguntar) │ │
│ └─────────────────────────────────┘ │
│ │
│ Total: Debe caber en N tokens │
│ (ej: 128K, 200K, 1M, 2M) │
└─────────────────────────────────────────┘
Cada componente compite por el mismo espacio finito. Metés mucho historial y desplazás el conocimiento recuperado. Cargás muchas definiciones de herramientas y no queda lugar para ejemplos. Metés todo y volás el presupuesto de tokens (y la factura de API).
Context engineering es el arte de hacer bien estos trade-offs.
Por qué Prompt Engineering solo no alcanza
Veamos un escenario concreto.
Estás armando un agente de soporte para e-commerce. Un cliente escribe: "El pedido que hice la semana pasada todavía no llegó. Ya es la tercera vez. Quiero un reembolso."
Un sistema con buen prompt engineering maneja bien el tono — empático, profesional. Pero sin context engineering, no tiene idea de:
- A qué pedido se refiere el cliente
- Qué muestra el historial real de pedidos
- Si el cliente ya reclamó antes (y cómo se resolvió)
- Cuál es la política de reembolso para casos repetidos
- Qué dice el tracking de la empresa de envíos
El prompt está bien. El contexto está vacío. El modelo inventa una respuesta genérica y el cliente se enoja más.
Context engineering soluciona esto asegurando que la información correcta esté en el window antes de que el modelo genere:
async function buildSupportContext(customerId: string, message: string) { // 1. Recuperar datos del cliente const customer = await db.customers.findUnique({ where: { id: customerId } }); const recentOrders = await db.orders.findMany({ where: { customerId, createdAt: { gte: thirtyDaysAgo } }, include: { shipments: true }, }); // 2. Recuperar documentos de política relevantes const policies = await vectorStore.search(message, { namespace: 'support-policies', topK: 3, }); // 3. Recuperar interacciones pasadas de soporte const pastTickets = await db.supportTickets.findMany({ where: { customerId }, orderBy: { createdAt: 'desc' }, take: 5, }); // 4. Obtener estado de envío en tiempo real const trackingData = await shippingApi.getStatus( recentOrders[0]?.shipments[0]?.trackingNumber ); // 5. Ensamblar el contexto return { systemPrompt: SUPPORT_AGENT_INSTRUCTIONS, context: [ { role: 'system', content: formatCustomerProfile(customer) }, { role: 'system', content: formatOrderHistory(recentOrders) }, { role: 'system', content: formatPolicies(policies) }, { role: 'system', content: formatPastTickets(pastTickets) }, { role: 'system', content: formatTrackingData(trackingData) }, ...conversationHistory, { role: 'user', content: message }, ], }; }
Ahora el modelo tiene todo. El pedido exacto, el tracking, la política de reembolso para reincidentes, el historial de reclamos. Mismo prompt, resultado radicalmente mejor.
Los cinco pilares del Context Engineering
Un context engineering de producción requiere dominar cinco disciplinas interconectadas:
1. Selección: Qué entra
La primera pregunta siempre es: ¿qué información necesita el modelo para responder bien esta query específica?
Parece obvio, pero la mayoría de los equipos o mete todo (desperdiciando tokens y degradando calidad) o mete muy poco (forzando al modelo a adivinar).
Principio señal-ruido: Cada token en tu context window tiene que ganarse su lugar. La información irrelevante no solo desperdicia tokens — degrada activamente la calidad del output.
// MAL: Meter todo const context = await db.documents.findMany(); // 50,000 tokens de ruido // BIEN: Búsqueda semántica con filtro de relevancia const relevantDocs = await vectorStore.search(query, { topK: 10 }); const filtered = relevantDocs.filter(doc => doc.score > 0.78); // 2,000 tokens de señal
Carga dinámica de herramientas es otra técnica crítica. Si tu agente tiene 50 herramientas pero solo 3-5 son relevantes, cargar las 50 desperdicia miles de tokens y confunde al modelo:
// MAL: Cargar todas las herramientas siempre const tools = getAllTools(); // 50 herramientas, ~8,000 tokens // BIEN: Seleccionar por clasificación de intención const intent = await classifyIntent(userMessage); const tools = getToolsForIntent(intent); // 4 herramientas, ~600 tokens // MEJOR: Enfoque de dos etapas const selectedToolNames = await selectRelevantTools(userMessage, allToolNames); const tools = selectedToolNames.map(name => toolRegistry.get(name));
2. Estructuración: Cómo se organiza
La misma información, estructurada distinto, produce resultados dramáticamente diferentes.
El sesgo posicional es real. Los modelos prestan más atención al principio y al final del context window que al medio. Es el problema de "lost in the middle", demostrado consistentemente en la investigación. Información crítica enterrada en el medio de 100K tokens prácticamente no existe.
function buildContext(systemPrompt, retrievedDocs, history, query) { return [ // INICIO — Zona alta atención { role: 'system', content: systemPrompt }, { role: 'system', content: '## DATOS DE REFERENCIA CRÍTICOS\n' + mostRelevantDoc }, // MEDIO — Zona baja atención ...history.slice(0, -3), ...supplementaryDocs, // FINAL — Zona alta atención ...history.slice(-3), { role: 'user', content: query }, ]; }
Los delimitadores de sección importan. Cuando mezclás tipos distintos de información, marcadores claros ayudan al modelo a distinguir:
const context = ` ## PERFIL DEL CLIENTE Nombre: ${customer.name} Estado de cuenta: ${customer.tier} Pedidos totales: ${customer.orderCount} ## ESTADO DEL PEDIDO ACTUAL Pedido #${order.id} — ${order.date} Estado: ${order.status} Tracking: ${tracking.status} — Última actualización: ${tracking.lastUpdate} ## POLÍTICAS APLICABLES ${policies.map(p => `- ${p.title}: ${p.summary}`).join('\n')} ## AUTORIDAD DE RESOLUCIÓN Podés emitir reembolsos hasta $${agent.refundLimit} sin escalar. Para montos mayores, escalá a un supervisor. `;
3. Memoria: Conectando pasado y presente
Las aplicaciones reales necesitan recordar entre conversaciones. Un usuario que explicó su arquitectura el lunes no debería tener que repetirla el martes.
El modelo de tres niveles de memoria:
┌───────────────────────────────────────┐
│ MEMORIA DE TRABAJO (Context Window) │
│ Conversación actual + datos activos │
│ Capacidad: Límite de tokens │
│ Velocidad: Instantánea │
├───────────────────────────────────────┤
│ MEMORIA A CORTO PLAZO (Sesión) │
│ Resúmenes de conversación recientes │
│ Estado de sesión y variables │
│ Capacidad: 10-50 entradas │
│ Velocidad: Recuperación rápida │
├───────────────────────────────────────┤
│ MEMORIA A LARGO PLAZO (Persistente) │
│ Preferencias e historial │
│ Conocimiento acumulado │
│ Capacidad: Ilimitada │
│ Velocidad: Requiere búsqueda │
└───────────────────────────────────────┘
class MemoryManager { async buildMemoryContext(userId: string, currentQuery: string): Promise<string> { // Nivel 1: Memoria de trabajo — turnos recientes const recentTurns = this.conversationBuffer.slice(-6); // Nivel 2: Corto plazo — resúmenes de sesión const sessionContext = await this.sessionStore.get(userId); // Nivel 3: Largo plazo — conocimiento pasado recuperado semánticamente const longTermMemories = await this.vectorStore.search(currentQuery, { filter: { userId }, topK: 5, }); return this.assembleMemory(recentTurns, sessionContext, longTermMemories); } }
El resumen es esencial. No podés guardar cada turno para siempre. El enfoque estándar es un resumen rotativo:
async function compressConversation(messages: Message[]): Promise<string> { if (messages.length <= 6) return ''; // Sin compresión necesaria // Últimos 6 mensajes: intactos. El resto: resumido const toSummarize = messages.slice(0, -6); const summary = await llm.generate({ messages: [ { role: 'system', content: `Resumí esta conversación en 2-3 oraciones. Enfocá en: decisiones tomadas, hechos establecidos, preferencias. Omití: saludos, charla informal, info repetida.` }, { role: 'user', content: toSummarize.map(m => `${m.role}: ${m.content}`).join('\n') }, ], model: 'gpt-4o-mini', // Modelo rápido y barato para resumen maxTokens: 200, }); return summary; }
4. Compresión: Más señal en menos espacio
Incluso con context windows de 2 millones de tokens, la compresión importa. Contextos más grandes son más lentos, más caros y — paradójicamente — a menudo producen peores resultados.
async function compressDocuments(docs: Document[]): Promise<string> { const deduped = removeSimilarChunks(docs, similarityThreshold: 0.92); const compressed = await llm.generate({ messages: [{ role: 'system', content: `Comprimí los documentos en una referencia densa. Preservar: números específicos, fechas, nombres, código. Eliminar: intros, transiciones, relleno, explicaciones repetidas. Objetivo: 30% de la longitud original.` }, { role: 'user', content: deduped.map(d => d.content).join('\n---\n') }], model: 'claude-3-5-haiku', }); return compressed; }
Asignación adaptativa de presupuesto:
function allocateContextBudget(totalTokens: number) { return { systemPrompt: Math.min(2000, totalTokens * 0.1), // 10% tools: Math.min(3000, totalTokens * 0.15), // 15% knowledge: Math.floor(totalTokens * 0.35), // 35% history: Math.floor(totalTokens * 0.30), // 30% query: 500, // Fijo buffer: Math.floor(totalTokens * 0.10), // 10% margen }; }
5. Evaluación: Midiendo qué funciona
No podés mejorar lo que no medís. Context engineering requiere métricas específicas:
| Métrica | Qué mide | Objetivo |
|---|---|---|
| Relevancia del contexto | % de chunks usados en la respuesta | > 70% |
| Eficiencia de tokens | Tokens de output útiles / tokens de input | > 0.15 |
| Precisión de recuperación | % de docs recuperados que eran relevantes | > 80% |
| Adherencia al presupuesto | % de requests dentro del budget | > 95% |
| Grounding de respuesta | % de afirmaciones rastreables al contexto | > 90% |
| Impacto en latencia | Latencia añadida por el ensamblaje | < 500ms |
Precisión de relevancia — ¿qué porcentaje de tu contexto realmente ayudó a responder?
async function measureContextRelevance( query: string, contextChunks: string[], expectedAnswer: string ): Promise<number> { const evaluations = await Promise.all( contextChunks.map(chunk => llm.generate({ messages: [{ role: 'system', content: `Evaluá si este chunk de contexto es relevante para responder la query. Respondé 0 (irrelevante) o 1 (relevante). Query: "${query}" Respondé solo 0 o 1.` }, { role: 'user', content: chunk }], model: 'gpt-4o-mini', }) ) ); const relevant = evaluations.filter(e => e.trim() === '1').length; return relevant / contextChunks.length; }
Anti-patrones comunes
Anti-patrón 1: El Kitchen Sink
Problema: Meter todo en el context porque "más información siempre es mejor."
Por qué falla: Más allá del 60-70% de la capacidad declarada, el rendimiento se degrada. El modelo empieza a ignorar, confundir o inventar sobre la información que le diste.
Solución: Sé implacable con la relevancia. Si un documento no ayuda directamente con la query actual, no lo incluyas.
Anti-patrón 2: Templates estáticos de contexto
Problema: Usar la misma estructura para toda query sin importar tipo o complejidad.
Solución: Ensamblaje dinámico basado en clasificación:
async function buildDynamicContext(query: string) { const queryType = await classifyQuery(query); switch (queryType) { case 'factual': return buildFactualContext(query); // Mucho conocimiento, poco historial case 'conversational': return buildConversationalContext(query); // Mucho historial, poco conocimiento case 'action': return buildActionContext(query); // Muchas herramientas y estado } }
Anti-patrón 3: Ignorar la ponderación temporal
Problema: Tratar todo el historial igual, sin importar cuándo ocurrió.
Solución: Peso temporal — el contexto reciente tiene más espacio, lo viejo se comprime:
function buildTemporalContext(history: Message[]): Message[] { const result: Message[] = []; // Últimos 4 turnos: intactos (más relevantes) result.push(...history.slice(-4)); // Turnos 5-12: solo mensajes importantes const middleHistory = history.slice(-12, -4); const important = middleHistory.filter( m => m.role === 'user' || containsDecision(m) || containsCodeChange(m) ); result.unshift(...important); // Turno 13+: resumen comprimido if (history.length > 12) { const oldHistory = history.slice(0, -12); const summary = await compressConversation(oldHistory); result.unshift({ role: 'system', content: `Contexto previo: ${summary}` }); } return result; }
Anti-patrón 4: Sin presupuesto de tokens
Problema: Sin límites por componente, uno (generalmente el historial) desplaza a los demás.
Solución: Presupuesto explícito con enforcement:
class ContextBudget { constructor(totalLimit: number) { this.allocations = new Map([ ['system', Math.floor(totalLimit * 0.10)], ['knowledge', Math.floor(totalLimit * 0.35)], ['tools', Math.floor(totalLimit * 0.10)], ['history', Math.floor(totalLimit * 0.30)], ['query', Math.floor(totalLimit * 0.05)], ['buffer', Math.floor(totalLimit * 0.10)], ]); } fit(component: string, content: string): string { const limit = this.allocations.get(component) ?? 0; if (countTokens(content) <= limit) return content; return truncateToTokens(content, limit); } }
Context Engineering para AI Agents
Los sistemas agénticos agregan otra capa. Un agente que ejecuta múltiples pasos necesita gestión de contexto a lo largo de todo su ciclo de ejecución.
El patrón Scratchpad
Dale al agente un espacio dedicado para razonamiento intermedio que no contamine el contexto principal:
class AgentScratchpad { private thoughts: string[] = []; addThought(thought: string) { this.thoughts.push(thought); if (this.thoughts.length > 10) { const toSummarize = this.thoughts.splice(0, 5); this.thoughts.unshift(`[Resumen de razonamiento anterior: ${toSummarize.join('; ')}]`); } } }
Compresión de resultados de herramientas
Las herramientas pueden devolver payloads enormes. Una query de DB puede devolver 500 filas. Inyectar todo eso crudo al contexto es un desperdicio:
async function compressToolResult(toolName: string, result: unknown, query: string) { const rawString = JSON.stringify(result); if (countTokens(rawString) < 500) return rawString; const summary = await llm.generate({ messages: [{ role: 'system', content: `La herramienta "${toolName}" devolvió datos. Resumí en el contexto de esta query: "${query}". Preservá números, IDs y hechos clave.` }, { role: 'user', content: rawString.slice(0, 10000) }], model: 'gpt-4o-mini', maxTokens: 500, }); return `[Resultado de ${toolName} — resumido]\n${summary}`; }
Aislamiento de contexto multi-agente
Cuando múltiples agentes colaboran, cada uno necesita su contexto enfocado. Compartir todo genera contaminación:
class MultiAgentContextManager { async buildAgentContext(agent: Agent, task: Task, sharedState: SharedState) { return [ { role: 'system', content: agent.systemPrompt }, { role: 'system', content: this.filterSharedState(sharedState, agent.role) }, { role: 'system', content: this.formatTask(task) }, ...this.compressUpstreamResults(task.previousResults, agent.role), ]; } }
El problema del "Context Window efectivo"
Lo que la mayoría no sabe: el context window publicitado y el efectivo son muy distintos.
Gemini 2.5 Pro publicita 1 millón de tokens. Claude 3.5 soporta 200K. GPT-4.1 maneja 1M. Pero la investigación muestra que los modelos se vuelven poco confiables más allá del 60-70% de su capacidad declarada.
const EFFECTIVE_CONTEXT_RATIO = 0.65; function getEffectiveLimit(model: string): number { const statedLimits: Record<string, number> = { 'gpt-4.1': 1_000_000, 'claude-3.5-sonnet': 200_000, 'gemini-2.5-pro': 1_000_000, 'gpt-4o': 128_000, }; return Math.floor((statedLimits[model] ?? 128_000) * EFFECTIVE_CONTEXT_RATIO); }
El cambio de mentalidad
Si te llevás una sola cosa de esta guía:
Dejá de pensar en prompts. Empezá a pensar en contexto.
El prompt — instrucciones de sistema, ejemplos few-shot, guías de formato — determina quizás 5-10% de la calidad del output. El 90% restante es si el modelo tuvo acceso a la información correcta, en el momento correcto, estructurada de la manera correcta.
Los mejores ingenieros de IA en 2026 no son los que escriben los prompts más ingeniosos. Son los que construyen los pipelines de contexto más sofisticados — sistemas que ensamblan dinámicamente la información exacta para cada query, la comprimen para que entre en el presupuesto, y miden si el contexto realmente ayudó.
Prompt engineering fue la entrada. Context engineering es el plato principal. Los equipos que lo descubran primero son los que van a lanzar funcionalidades de IA que realmente funcionen en producción.
Conclusión
Context engineering responde una pregunta engañosamente simple: "¿Qué debería saber el modelo cuando genera esta respuesta?"
Pero responderla bien requiere toda una disciplina:
- Seleccionar la información correcta para cada query
- Estructurar con conciencia posicional
- Recordar entre conversaciones con sistemas de memoria jerárquicos
- Comprimir para entrar en el presupuesto sin perder señal
- Medir si el contexto realmente mejoró los resultados
Las herramientas están. Bases vectoriales para recuperación. LLMs para resumen y compresión. Contadores de tokens para gestión de presupuesto. Frameworks de evaluación.
Lo que faltaba era el framework — el modelo mental para ver todas estas piezas como una disciplina coherente. Eso es context engineering. Y es la habilidad que separa los demos de IA de los productos de IA.
Armá tu pipeline de contexto. Medí todo. Iterá sin piedad. Así se lanza IA que funciona.
Explora herramientas relacionadas
Prueba estas herramientas gratuitas de Pockit