Hoy en día, cuando hablamos de ingeniería de contexto, generalmente se refieren a recuperar el contexto correcto de un documento: selección de fragmentos, búsqueda híbrida, reclasificación, recuperación basada en TOC. Esa es la mitad de la historia. La otra mitad es que la pregunta en sí es el contexto que verá el LLM y merece el mismo tratamiento que el pasaje recuperado.
Tomemos como ejemplo a un analista de seguros que pregunta: "¿Cuál es el monto máximo de cobertura? No lo confunda con el deducible, a menudo aparecen juntos". Una cadena, cuatro señales (un tema, una señal negativa, una forma esperada, una sugerencia estructural), cada una destinada a un paso posterior diferente. Entregados palabra por palabra al coseno top-k, ninguno de ellos va a donde debería: la incrustación atrae líneas deducibles, la generación elige el primer número plausible, el rastro no muestra nada sobre por qué. Esto no es un fracaso de recuperación ni un fracaso de generación. Es una falla de ingeniería contextual en el lado de la cuestión: para empezar, las piezas correctas nunca fueron ensambladas. Lo que sigue nombra esas piezas, una estrategia a la vez, y muestra para qué sirve cada una en el lado receptor.
Enterprise Document Intelligence construye RAG empresarial sobre cuatro ladrillos (análisis de documentos, análisis de preguntas, recuperación, generación). Este artículo relee el ladrillo 2 a través de la lente de ingeniería contextual nombrada por Tobi Lütke y Andrej Karpathy a mediados de 2025 y luego estructurada por LangChain en cuatro estrategias canónicas (escribir, seleccionar, comprimir, aislar). No se envía nada nuevo aquí. Los artículos 6A (tesis), 6B (extracción) y 6C (despacho) ya codificaban el ladrillo; éste nombra para qué sirve cada pieza mecanografiada en el lado receptor.
📓 Las cinco preguntas de la Sección 3 se ejecutan en el cuaderno enviado: analícelas usted mismo, imprima el JSON de ParsedQuestion, observe el resumen de recuperación y el resumen de generación. Repositorio → doc-intel/notebooks-vol1.
1. El analizador lee el contexto y escribe el contexto.
Cada elemento del proceso se puede leer como un escritor de contexto mecanografiado.
El análisis escribe line_df / page_df. La recuperación escribe un subconjunto filtrado más una auditoría. Generation los lee y produce la respuesta.
El análisis de preguntas es el caso interesante. Es el único bloque que es a la vez consumidor de contexto (realiza una llamada LLM por sí mismo) y escritor de contexto (su fila escrita de salida alimenta tres llamadas posteriores).
Leída como consumidor, la propia llamada LLM del analizador está diseñada en contexto según las mismas reglas que sigue cualquier otra llamada LLM escrita en la canalización. Su ventana de contexto se compone de cuatro espacios escritos:
Un PARSE_QUESTION_SYSTEM_PROMPT fijo a nivel de módulo, almacenado en caché en cada pregunta de una sesión, nunca reescrito por llamada. El JSON compacto de DocContext: cien tokens de datos a nivel de documento (tipo_doc, n_páginas, campos_típicos) para que el analizador sepa si está mirando una hoja de producto de dos páginas o una política de doscientas páginas. Un bloque estable de pocos disparos de pares (pregunta, ParsedQuestion) (puesta en marcha, un cuenco por forma). La pregunta cruda del usuario: la única carga útil por llamada.
No entra nada más por esa ventana. Sin texto de documento textual, sin resultados de recuperación, sin memoria.
Esa restricción es deliberada. El analizador se encuentra antes de la recuperación, por lo que el resultado de la recuperación aún no existe. Ninguna memoria pertenece a una llamada que tiene que ser reproducible. La ingeniería de contexto aquí es mínima por diseño.
Leído como escritor, el mismo ladrillo escribe una fila de question_df más tres piezas derivadas que la etapa de ensamblaje conectará en tres llamadas posteriores separadas. Cada receptor descendente ya conoce los nombres de los campos y los lee directamente. No hay necesidad de volver a analizar el formato libre al ingresar.
La vista del lado del receptor es lo que cambia cuando lees el ladrillo como escritor de contexto. Cada campo está escrito para alguien:
palabras clave para el detector de coincidencia de palabras clave dentro de la recuperación. intención para el despachador, impulsando el nivel del modelo y la estrategia de fragmentos que consumirá el bloque de generación. pages_hint para el detector de anclaje, de modo que pueda fijar su búsqueda en un rango de páginas cuando el usuario nombra una explícitamente.
No entra nada en la fila que nadie más abajo pida.
2. Cuatro estrategias, cuatro piezas mecanografiadas
La taxonomía de ingeniería contextual de LangChain enumera cuatro estrategias canónicas: escribir, seleccionar, comprimir y aislar. Leído como escritor, el ladrillo de análisis de preguntas encarna cada una de ellas en una pieza diferente. Ese es el mapeo que vale la pena recordar:
Las cuatro subsecciones siguientes toman cada estrategia y analizan lo que el analizador escribe para ella.
2.1 Escribir: ParsedQuestion como contrato
La mayoría de los pipelines RAG resumen la pregunta del usuario en una cadena reescrita que mantiene el sabor y pierde la estructura. El equivalente en ingeniería de contexto es lo opuesto: escriba una fila escrita con campos con nombre, de modo que cada llamada posterior tenga la misma forma y pueda abordar los campos por nombre.
ParsedQuestion es esa fila. Lo que importa aquí no es la lista de campos (consulte el Artículo 6B), sino que los nombres de los campos sean el contrato. Una prueba posterior puede afirmar "si hay páginas_hint, el informe de recuperación debe incluirlo". Un linter de esquema puede marcar un campo que ninguna llamada posterior lee. Un experto en el dominio puede auditar la fila sin abrir ningún código.
Receptor: los dos constructores breves (build_retrieval_brief y build_generación_brief), cada uno de los cuales genera columnas diferentes. Perfil de caché: determinista por cadena sin formato; una llamada de LLM para compilar, almacenar en caché y nunca volver a analizarse. Columna de auditoría: la fila misma.
2.2 Comprimir: el resumen de recuperación
El bloque de recuperación no tiene por qué conocer la forma de la respuesta, el nivel de modelo sugerido o la aclaración que se le pidió al usuario. Esos campos existen en la fila analizada; el informe de recuperación los descarta.
Se trata de compresión como ingeniería de contexto, no compresión como ahorro de tokens. El mandato de recuperación es más pequeño que ParsedQuestion, pero la razón es arquitectónica, no económica: los detectores de anclaje que nunca ven el campo de forma no pueden comenzar a ramificarse accidentalmente en él. Cada campo que el informe omite es un acoplamiento que nunca se formará.
Receptor: los detectores de anclaje (comparador de palabras clave, árbitro TOC, anotador de incorporación) en el Artículo 7B. Perfil de caché: proyección determinista de ParsedQuestion; al desalojar la fila analizada se desaloja el escrito. Columna de auditoría: el bloque de recuperación registra el informe que recibió junto con las anclas que eligió.
2.3 Seleccionar: el resumen de generación
La selección en una canalización diseñada por contexto significa elegir la plantilla correcta para crear una instancia para esta llamada, no dejar que el LLM decida la plantilla sobre la marcha. El artículo 6C desarrolla el despachador que hace eso: intención, sección_hint, diseño_hint y páginas_hint impulsan chunk_strategy, sugerido_modelo y los tres indicadores de activación (pages_hint_active, sección_filtro_activo, diseño_pattern_activo).
Leído de esta manera, el mandato generacional no es “los campos que la generación necesita”; son “las decisiones que el despachador ya tomó, por lo que el LLM no tiene nada que inventar”. La selección ocurre de forma determinista en Python. El LLM hereda la elección y la ejecuta.
Receptor: la búsqueda del esquema de generación y la llamada LLM en el Artículo 8A. Perfil de caché: enviar salida codificada por (intención, sección_sugerencia, diseño_sugerencia, páginas_sugerencia); reutilizado entre usuarios y sesiones con la misma forma. Columna de auditoría: el despachador registra un motivo por activación.
2.4 Aislar: la petición de aclaración
La estrategia de aislamiento en la taxonomía de LangChain evita que el contexto de baja calidad se traslade a una llamada posterior. En el análisis de preguntas, el bucle de clarificación es exactamente ese patrón aplicado a la propia salida del analizador.
Cuando el analizador no puede resolver la intención, la forma o el alcance con confianza, no elige la mejor suposición ni la reenvía. Eso envenenaría el informe de recuperación con un filtro de alcance que podría estar equivocado. Cada ancla y cada respuesta posterior heredarían el sesgo.
En cambio, escribe una Solicitud de aclaración, mantiene la canalización y permite al usuario decir lo que quiso decir.
El artículo 6bis desarrolla el bucle que sigue, incluyendo cómo la respuesta se almacena en caché de forma predeterminada para que la segunda aparición sea silenciosa.
Receptor: la interfaz de usuario, luego el analizador nuevamente en la cadena enriquecida. Perfil de caché: filas ClarificationDefault ingresadas (ambigüedad_reason, usuario); Golpea después de la primera pregunta. Columna de auditoría: una línea por aclaración con la ambigüedad, la pregunta, la respuesta, el incumplimiento resultante.
3. Cinco ejemplos caminados
Las abstracciones se leen mejor en cuerdas concretas. Cada una de las cinco preguntas a continuación ilustra un caso técnico diferente: sin sugerencias, solo sección_sugerencia, sección_sugerencia más diseño_sugerencia, una Solicitud de aclaración en lugar de una Pregunta analizada y la distinción sección_sugerencia-vs-páginas_sugerencia. Para cada uno, parse_question(…) en docintel.question escribe una fila ParsedQuestion, luego despacho(parsed) en docintel.question.dispatcher produce las decisiones de enrutamiento que lee el bloque de generación. Los bloques JSON a continuación utilizan los campos reales de src/docintel/question/: no se inventa nada encima.
3.1 Línea de base: solo palabras clave, sin sugerencias de alcance
Pregunta de seguro.
"¿Cuál es el monto máximo de cobertura? No lo confunda con el deducible, a menudo aparecen juntos".
El extractor de palabras clave extrae las dos frases nominales de la pregunta. La intención se basa en hechos porque la respuesta es un valor acotado. No se fija ninguna página, no se nombra ninguna sección, no se establece ningún objetivo de diseño.
Pregunta analizada:
{ "original_question": "¿Cuál es el monto máximo de cobertura? No lo confunda con el deducible, a menudo se enumeran juntos.", "keywords": ["monto máximo de cobertura", "deducible"], "intent": "factual", "retrieval": { "main_query": "monto máximo de cobertura", "rewrites": ["límite de póliza", "límite de cobertura"], "anchor_keywords": ["cobertura máxima" cantidad"], "section_hint": nulo, "layout_hint": nulo }, "estructural_hints": nulo }
envío (analizado):
{ "chunk_strategy": "combined", "suggested_model": "gpt-4.1-mini", "activations": [ {"flag": "pages_hint_active", "on": false, "reason": "no pages_hint -> la recuperación busca el documento completo."}, {"flag": "section_filter_active", "on": false, "reason": "no section_hint -> la recuperación escanea cada sección."}, {"flag": "layout_pattern_active", "on": false, "reason": "sin layout_hint -> recuperación de texto predeterminada, sin diseño de dos saltos."} ] }
Lo que el esquema actual no captura. La señal negativa (“no confundir con deducible”) no tiene un campo dedicado. Ambos términos terminan uno al lado del otro en palabras clave, y la desambiguación depende de que los detectores de recuperación elijan el ancla correcta más el LLM de generación que lea el contexto circundante. Agregar un campo de palabras clave negativas en RetrievalQuery es una pregunta en vivo sobre la evolución del esquema, no una afirmación sobre lo que el analizador escribe hoy. El artículo documenta qué aterriza; no inventa campos que aún no existen.
3.2 sección_hint activa sección_filtro_activo
Cuestión jurídica.
“¿La cláusula de indemnización sobrevive a la rescisión y, de ser así, por cuánto tiempo?”
La pregunta nombra una sección por título. La intención aterriza en section_retrieval porque la carga útil es una parte limitada del documento.
Pregunta analizada:
{ "original_question": "¿La cláusula de indemnización sobrevive a la terminación y, de ser así, por cuánto tiempo?", "keywords": ["cláusula de indemnización", "terminación", "supervivencia"], "intent": "section_retrieval", "retrieval": { "main_query": "indemnización de supervivencia después de la terminación", "rewrites": ["cláusula de supervivencia de indemnización"], "anchor_keywords": ["indemnización", "supervivencia"], "section_hint": "Indemnización", "layout_hint": null }, "structural_hints": null }
envío (analizado):
{ "chunk_strategy": "combined", "suggested_model": "gpt-4.1", "activations": [ {"flag": "pages_hint_active", "on": false, "reason": "no pages_hint -> la recuperación busca el documento completo."}, {"flag": "section_filter_active", "on": true, "reason": "retrieval.section_hint='Indemnification' -> filtrar toc_df, restringir a esa sección."}, {"flag": "layout_pattern_active", "on": false, "reason": "sin layout_hint -> recuperación de texto predeterminada, sin diseño de dos saltos."} ] }
sección_filter_active: verdadero es el efecto concreto de escribir sección_hint. La recuperación descarta el escaneo del documento completo y lee solo las páginas que se encuentran dentro de la sección Indemnización de toc_df. La respuesta de dos partes (sobrevive + por cuánto tiempo) no se incluye en la fila analizada: permanece en el texto de la sección que lee la generación LLM.
3.3 layout_hint = "table" activa el patrón de dos saltos
Cuestión de finanzas.
"¿Cuáles fueron los ingresos totales del tercer trimestre de 2024, desglosados por región?"
La intención llega a la lista porque la respuesta esperada es una fila por región. layout_hint: "table" activa el patrón de dos saltos.
Pregunta analizada:
{ "original_question": "¿Cuáles fueron los ingresos totales del tercer trimestre de 2024, desglosados por región?", "keywords": ["ingresos totales", "Tercer trimestre de 2024", "región"], "intent": "listing", "retrieval": { "main_query": "ingresos totales del tercer trimestre de 2024 por región", "rewrites": ["ingresos del segmento", "ingresos por geografía"], "anchor_keywords": ["ingresos", "T3 2024"], "section_hint": "Ingresos del segmento", "layout_hint": "table" }, "structural_hints": null }
envío (analizado):
{ "chunk_strategy": "combined", "suggested_model": "gpt-4.1-mini", "activations": [ {"flag": "pages_hint_active", "on": false, "reason": "no pages_hint -> la recuperación busca el documento completo."}, {"flag": "section_filter_active", "on": true, "reason": "retrieval.section_hint='Ingresos del segmento' -> filtrar toc_df, limitarse a esa sección."}, {"flag": "layout_pattern_active", "on": true, "reason": "retrieval.layout_hint='table' -> patrón de dos saltos: busque el elemento, luego lea el título/fila circundante."} ] }
layout_pattern_active: verdadero es el efecto concreto de escribir layout_hint. Aguas abajo, el detector de anclaje cambia a un paso de dos saltos: busca la región de la tabla, luego lee la fila y el encabezado que le pertenecen.
3.4 Solicitud de aclaración en lugar de Pregunta analizada
Pregunta médica.
“¿Está contraindicada la warfarina con la lista de medicamentos actual?”
La pregunta hace referencia a una lista de medicamentos que no está en el alcance del proyecto actual. La mejor ParsedQuestion que el analizador puede crear no tendría un ancla en la que pueda confiar una llamada posterior. En lugar de enviar una suposición de baja confianza, el bucle de clarificación (Artículo 6bis) escribe una Solicitud de Aclaración y mantiene el proceso hasta que el usuario elige una fuente.
Solicitud de aclaración:
{ "target_field": "medication_list_source", "question_to_user": "¿Qué documento enumera los medicamentos actuales del paciente?", "candidate_values": [ "current_prescription.pdf (en este proyecto)", "post_op_orders.pdf (en este proyecto)", "pegue la lista aquí" ], "proposed_default": "current_prescription.pdf (en este proyecto)", "proposed_default_reason": "El único documento marcado como 'receta activa' en el índice del proyecto.", "audit": { "request_id": "clar_20260710_007", "model": "gpt-4.1-mini", "prompt_version": "v3", "doctype": "medical", "sub_conditions": ["missing_scope_reference"] } }
despacho() nunca se llama aquí: sin un alcance resuelto, sin estrategia de fragmentos, sin nivel de modelo, no existen indicadores de activación todavía. Aislar como ingeniería de contexto, vivir en código. Una vez que el usuario elige un documento, parse_question se vuelve a ejecutar en la cadena enriquecida y la canalización obtiene tanto una salida ParsedQuestion como su despacho().
3.5 conjunto de sugerencias de sección, sugerencias de páginas deliberadamente nulas
Pregunta de trabajo académico.
"¿Cuál es la dimensión de cada cabeza de atención en el Transformer?"
section_hint llega a "3.2" porque la pregunta es sobre una sección específica de Atención es todo lo que necesitas. pages_hint permanece nulo porque el usuario no fijó una página. La búsqueda de sección a página se realiza en sentido descendente a través de section_filter_active, de manera determinista, a través de toc_df.
Pregunta analizada:
{ "original_question": "¿Cuál es la dimensión de cada cabezal de atención en el Transformer?", "keywords": ["d_k", "attention head", "Transformer"], "intent": "factual", "retrieval": { "main_query": "dimensión del cabezal de atención d_k", "rewrites": ["d_k atención", "dimensión del cabezal"], "anchor_keywords": ["d_k", "attention head"], "section_hint": "3.2", "layout_hint": nulo }, "structural_hints": nulo }
envío (analizado):
{ "chunk_strategy": "combined", "suggested_model": "gpt-4.1-mini", "activations": [ {"flag": "pages_hint_active", "on": false, "reason": "no pages_hint -> la recuperación busca el documento completo."}, {"flag": "section_filter_active", "on": true, "reason": "retrieval.section_hint='3.2' -> filtrar toc_df, limitarse a esa sección."}, {"flag": "layout_pattern_active", "on": false, "reason": "sin layout_hint -> recuperación de texto predeterminada, sin diseño de dos saltos."} ] }
páginas_hint vs sección_hint son dos señales distintas con diferente procedencia. pages_hint solo se escribe cuando el usuario dice explícitamente "en la página 3"; el bloque de recuperación luego filtra line_df con .isin(pages_hint). sección_hint se escribe cuando el usuario nombra una sección (por título o número); la búsqueda de sección a página se realiza de forma determinista a través de toc_df, impulsada por sección_filter_active. Nunca combine los dos: el analizador no deriva una sugerencia de páginas de una búsqueda de TOC.
Lo que los cinco tienen en común. Cada campo en cada fila analizada es un campo real en ParsedQuestion (src/docintel/question/parse_question.py). Cada indicador de activación es una entrada real de build_execution_plan(…) (src/docintel/question/dispatcher.py). Las dos funciones son toda la ingeniería de contexto que realiza el lado de la pregunta hoy. Agregar una estrategia significa agregar un campo a ParsedQuestion o una activación al despachador y luego documentar qué receptor lo lee. Nada en el artículo es un esquema ficticio.
4. ¿Por qué no fusionar las piezas?
Un ingeniero nuevo en el código base pregunta razonablemente: ¿por qué cuatro piezas? ¿Por qué no una carga útil con cada campo y dejar que cada receptor elija lo que necesita?
La respuesta de la ingeniería contextual no es estética. Está operativo.
Una carga útil fusionada rompe la separación de estrategias. Comprimir, seleccionar y aislar solo tienen sentido entre receptores.
Si la recuperación y la generación comparten la misma carga útil, la recuperación puede comenzar a leer la forma de respuesta "por si acaso" y las formas de acoplamiento. Seis meses después, un linter de esquema no puede determinar si un campo se utiliza por diseño o por accidente.
Las cuatro piezas mecanografiadas hacen que el accidente sea imposible: el resumen de recuperación no incluye la forma, por lo que el código que podría derivarse de ella no se compila.
Una carga útil fusionada colapsa los límites de la caché. Cada pieza tiene su propia clave de caché: fila analizada por cadena sin formato, salida de envío por (intención, sugerencia_sección, sugerencia_diseño, sugerencia_páginas), resumen de recuperación y resumen de generación son proyecciones deterministas de ambos. Al fusionarlos, se colapsan todas las claves a "por cadena sin formato": un cambio en la enumeración de intención expulsa cada análisis almacenado, aunque no se haya movido ningún campo de ParsedQuestion. La separación escrita es lo que mantiene pequeño el radio de explosión de un cambio de esquema.
Una carga útil fusionada deja escapar la ambigüedad. Con cuatro piezas, el analizador puede escribir sólo una Solicitud de Clarificación y retener el resto. Un monolito obliga al analizador a enviar algo a cada receptor cada vez, por lo que un análisis parcial se reenvía como uno real y el bloque de recuperación se ejecuta en un alcance con el que el analizador no quería comprometerse. El aislamiento deja de funcionar en el momento en que la carga útil es única.
Nada de esto va en contra de los ayudantes de conveniencia que ensamblan las piezas cuando una persona que llama las quiere juntas. Sostiene que la forma a nivel de almacenamiento y a nivel de receptor es de cuatro piezas mecanografiadas, no de una gota.
5. Fuera de alcance
Tres cosas que este artículo deliberadamente no cubre.
Contexto a nivel de corpus. Cuando la recuperación abarca un corpus, el analizador de preguntas también necesita saber sobre qué documento está preguntando el usuario o qué subconjunto. Esa es una ranura escrita en CorpusContext que desarrolla el Volumen 2; aquí nos quedamos en un solo documento. Historial de conversaciones. Una sesión de varios turnos donde “¿y la próxima?” se refiere a una pregunta analizada anteriormente que necesita una ranura de memoria en la etapa de ensamblaje. El volumen 3 cubre eso; aquí cada pregunta es nueva. Cargas útiles de llamadas de herramientas. Cuando el analizador puede descargar un trabajo a una herramienta externa (por ejemplo, un conversor de moneda para "en dólares"), el contrato de la herramienta se convierte en otra ranura escrita. El volumen 4 cubre ladrillos agentes con herramientas; aquí el analizador sigue siendo determinista.
Cada uno de estos añade una pieza mecanografiada más a la etapa de montaje. El vocabulario es el mismo. Las piezas son cada vez más numerosas.
6. Fuentes y lecturas adicionales
El marco de la ingeniería contextual provino de dos fuentes. El tuit de Tobi Lütke propuso el término en junio de 2025 como sustituto de "ingeniería rápida". Andrej Karpathy lo respaldó una semana después. Posteriormente, LangChain estructuró la práctica en cuatro estrategias canónicas (escribir, seleccionar, comprimir, aislar) que se asignan claramente al bloque de análisis de preguntas, como muestra este artículo.
La serie de artículos que construyen las piezas que éste nombra:
Analice la pregunta antes de realizar la búsqueda (Artículo 6A). La tesis publicada sobre el análisis de preguntas y la división en dos resúmenes. El GAR debe extraer cinco campos de cualquier pregunta (artículo 6B). La extracción columna por columna en código. Envío de la pregunta RAG analizada (artículo 6C). El patrón del despachador que convierte las columnas analizadas en decisiones de enrutamiento. Cuando los usuarios del RAG hagan preguntas vagas (artículo 6bis). El bucle de aclaración que escribe la cuarta pieza mecanografiada. Las lecciones no enseñadas del análisis de preguntas del RAG (artículo 6ter). Las seis posiciones que toma el ladrillo frente al libro de jugadas convencional de RAG.
Las fuentes de la industria que nombraron la práctica:
Tobi Lütke, “ingeniería de contexto sobre ingeniería rápida”: tweet, junio de 2025. Andrej Karpathy, “ingeniería de contexto como el delicado arte de llenar la ventana”: tweet, junio de 2025. LangChain, “Ingeniería de contexto para agentes”: las cuatro estrategias canónicas (escribir, seleccionar, comprimir, aislar).