25 de junio de 2026

Juan P. Mejia, Mateo Builes, Oscar Navarrete, Jhon Pinto, Samuel Castaño, Carlos Vanegas

El arnés es la arquitectura: cómo reconstruimos los agentes de IA industrial desde cero

Todos los artículos de ingeniería

Cada gran proveedor de nube ahora ofrece una narrativa de agentes: AWS AgentCore, Google Vertex AI Agents, el impulso empresarial de OpenAI. El discurso es el mismo en cada caso. Despliega un modelo, apúntalo a tus documentos, añade una interfaz de chat y tienes un agente desplegado.

En la práctica, el despliegue se queda corto donde importa. Cuando un técnico de mantenimiento en una planta de válvulas, o un vendedor en un distribuidor industrial, pregunta qué material de asiento es compatible con un fluido a base de cloro a 180 °C, el sistema responde con fluidez y se equivoca.

El estado de la IA industrial en 2026 se reduce a una observación: los modelos son lo bastante buenos, y la arquitectura que los rodea no lo es.

La mayoría de los despliegues de agentes se construyen sobre un supuesto implícito: que la inteligencia es el cuello de botella. Así que los equipos persiguen mejores modelos, ventanas de contexto más grandes, pipelines de recuperación más sofisticados. RAG sobre PDFs, búsqueda web sobre documentación pública, embeddings sobre todo. Sí, pero el técnico sigue recibiendo una respuesta incorrecta, porque el cuello de botella real nunca fue el modelo. Era la brecha entre lo que se le dio al modelo y lo que realmente necesitaba saber.

Lo aprendimos a la fuerza. Nuestros primeros agentes en ReshapeX se construyeron en Langflow: nodos, prompts largos, consumo de herramientas MCP para hacer RAG. Se mostraban cautos en preguntas que deberían haber respondido con precisión, y respondían con precisión en preguntas en las que deberían haber sido cautos. No porque el modelo fuera malo, sino porque el arnés era incorrecto.

Esta publicación describe lo que reconstruimos y por qué. Es un caso de estudio sobre lo que exige la IA industrial cuando el dominio es específico, los datos son privados y una respuesta incorrecta es peor que ninguna respuesta.

Por dónde empezamos: la era Langflow

Cuando los agentes de IA empezaron a ganar tracción, la mayoría de las empresas apostaron por plataformas que ya les permitían construir automatización con rapidez. Langflow fue una de ellas: un editor visual de código abierto donde conectas LLM, herramientas y pasos de recuperación en un lienzo como nodos, cada uno un componente Python que puedes personalizar. Por debajo corría sobre LangChain 0.3, la versión "clásica", que organizaba la computación en Chains, pasos predeterminados como "recuperar, luego generar". Langflow convirtió esos pasos en su propio sistema de componentes y añadió encima un motor de ejecución de grafos construido por ellos mismos.

Construimos sobre ello a lo largo de 2025. Permitió al equipo moverse rápido: conectar una base de conocimiento, añadir RAG sobre PDFs, integrar herramientas de terceros y desplegar un agente funcional en días. Para la ingestión de documentos, Langflow incluye nodos integrados que analizan PDFs en texto con PyPDF. Más adelante construimos componentes personalizados encima, integrando LlamaParse, un servicio SaaS de análisis, para obtener una extracción estructurada más limpia de documentos industriales densos. También construimos componentes personalizados para configuradores de producto codificados a mano: los agentes invocaban estos componentes para recorrer la lógica de configuración y producir SKU especializados. El enfoque funcionaba, pero era frágil. Cada vez que cambiaba una línea de producto o se actualizaba una sola regla de configuración, el código tenía que cambiar para mantenerse sincronizado. Con un puñado de productos eso es manejable. Con un catálogo en crecimiento se convierte en un problema de mantenimiento que no escala.

Durante un tiempo, las cosas mejoraron de forma incremental. Un análisis mejor nos dio entradas más limpias. Prompts más ajustados redujeron la deriva. Pero dos problemas más profundos siguieron apareciendo, y ninguno tenía arreglo dentro de la plataforma.

Diagrama del pipeline inicial basado en Langflow que muestra conexiones visuales entre nodos entre fuentes de datos y el agente, ilustrando cómo se cableó el primer prototipo.

La búsqueda vectorial no entiende las relaciones

El conocimiento de nuestros clientes vive dentro de catálogos de producto, hojas de especificaciones, registros de ERP y años de documentación acumulada. Casi nada de ello es plano. Una pieza pertenece a una familia de productos. Un componente referencia cruzada un equivalente de la competencia. Una hoja de especificaciones restringe la interpretación de otras tres.

El RAG estándar trata esto como un problema de recuperación de texto: embeber todo, encontrar los fragmentos más cercanos, pasarlos al modelo. La limitación es que la similitud vectorial encuentra texto semánticamente cercano, no información estructuralmente conectada. Cuando un técnico pregunta si un material de asiento de válvula es compatible con un fluido específico a una temperatura dada, la respuesta puede requerir conectar una propiedad del material, una tabla de compatibilidad química y una especificación de rango operativo que viven en documentos separados sin vocabulario compartido. La búsqueda vectorial devuelve cada fragmento aislado, y las relaciones entre ellos nunca llegan a la capa de recuperación. Llegan al modelo como fragmentos en bruto que hay que recomponer.

La solución obvia es saltarse la recuperación y meter documentos enteros en la ventana de contexto. Lo evaluamos, pero choca con tres muros duros. Primero, la mayoría de las API de proveedores limitan los tokens, lo que hace impracticable la ingestión de documentos completos cuando un catálogo alcanza un tamaño real. Segundo, enviar miles de tokens por consulta con muchos usuarios concurrentes se encarece rápido. Tercero, y menos obvio, un contexto más grande no te compra respuestas proporcionalmente mejores. Databricks lo probó en 13 modelos y encontró que en la mayoría la precisión alcanza un pico y luego cae más allá de cierto tamaño de contexto: Llama-3.1-405b empieza a resbalar después de 32k tokens, GPT-4-0125-preview después de 64k, y solo un puñado de modelos mantienen la precisión en contexto largo. También vieron modelos fallar de formas extrañas a medida que crecía el contexto, como resumir la entrada en lugar de responder la pregunta, o simplemente devolver una respuesta incorrecta. Más allá de cierto punto, más contexto empeora al modelo, no lo mejora.

El resultado fue inconsistencia. La misma pregunta, formulada ligeramente distinto, extraía fragmentos diferentes y producía una respuesta diferente. Un análisis mejor ayudó en los márgenes, pero el problema estructural permaneció: fragmentos sin relaciones siguen siendo solo fragmentos.

La arquitectura de Langflow tenía un techo

El segundo problema era más fundamental. Langflow está construido para orquestación de flujos de trabajo, flujos predefinidos de alto nivel, y nosotros necesitábamos orquestación de agentes, control dinámico de bajo nivel sobre el bucle de ejecución.

Esta distinción importa en la práctica. El nodo Agent de Langflow usa su propia implementación create_agent con un bucle ReAct codificado a mano. El único punto de extensión real que expone la plataforma son componentes personalizados, que operan a nivel de flujo. Lo que acabamos necesitando (guardrails de entrada, recorte de contexto, proveedores de respaldo, límites de llamadas a herramientas) requería insertar lógica antes de una llamada al modelo, después de una llamada a herramienta y entre pasos de razonamiento. Esos hooks no existen en Langflow. Están abstraídos detrás de su motor de ejecución.

LangChain v1, que introdujo el middleware como concepto de primera clase respaldado por LangGraph, proporciona exactamente eso. Pero los dos ecosistemas no son objetivos de migración compatibles. Los flujos de Langflow se construyen alrededor de las abstracciones de chain de LangChain 0.3, que LangChain ha reemplazado desde entonces con agentes respaldados por LangGraph. El modelo de flujo de alto nivel y el modelo de ejecución de agentes de bajo nivel son arquitectónicamente distintos, así que los patrones disponibles en uno no tienen equivalente directo en el otro.

A principios de 2026 quedó claro que ambos problemas chocaban con el mismo techo: Langflow nos dio orquestación de alto nivel y nada por debajo. Pasar a LangChain/LangGraph v1 significó reconstruir desde cero, que es lo que hicimos.

Diagrama que muestra las limitaciones del enfoque Langflow: deriva entre el grafo visual y el comportamiento en tiempo de ejecución, y la ausencia de una ruta de migración hacia infraestructura de grado de producción.

Por qué los prompts no bastaban

Un comprador industrial le pregunta a un agente: "Necesito el calentador de gabinete correcto para 30 °C ambiente, gabinete de 1,2 m³, NEMA 4X." Una respuesta incorrecta aquí no es simplemente embarazosa. Se convierte en un envío devuelto, un disyuntor disparado o la tarde de un vendedor deshaciendo una cotización mala. El chat de consumo tolera "cerca de lo correcto". El comercio industrial no. Cada respuesta tiene que estar fundamentada y ser trazable a una pieza real, una especificación real y una fuente real.

Ese único requisito resultó más difícil de lo que parecía al principio. Al perseguirlo nos topamos con un conjunto de problemas recurrentes, y un conjunto de oportunidades recurrentes, que ninguna cantidad de ajuste de prompts podía resolver. Lo que sigue es un relato de esos desafíos y por qué cada uno importó.

Encontrar la aguja en el pajar

Nuestros clientes son fabricantes y distribuidores sentados sobre grandes cuerpos de conocimiento no estructurado: catálogos de producto, hojas de especificaciones, archivos CAD, registros de ERP y años de experiencia tribal. Casi nada está organizado para que una máquina lo recupere. La información existe, pero está dispersa, es inconsistente y difícil de acceder bajo demanda.

El problema más profundo es que las relaciones entre piezas de información importan tanto como la información misma. Una pieza es compatible con otra. Un producto pertenece a una familia. Un componente referencia cruzada el equivalente de un competidor. Responder correctamente una pregunta industrial a menudo significa navegar esta red de conexiones, no solo localizar un documento. Sacar a la luz la única respuesta correcta entre millones de candidatos casi idénticos, entendiendo cómo esa respuesta se relaciona con todo lo demás, es la dificultad central del sistema.

Diagrama que ilustra el desafío de recuperación: cómo un hecho relevante debe localizarse en un grafo de conocimiento grande e interconectado donde las relaciones entre piezas, especificaciones y sustitutos no son planas.

Cuando necesitas una coincidencia exacta, la similitud juega en tu contra

Un comprador escribe un número de pieza como FVAM-2050-B y espera que el agente encuentre exactamente eso. La búsqueda vectorial no busca coincidencias exactas. Busca cosas similares, y FVAM-2050-B y FVAM-2050-C están cerca en el espacio de embeddings mientras son piezas completamente distintas. El agente devuelve cinco resultados plausibles, elige el que suena más confiado, lo cita y se envía la pieza incorrecta.

La corrección es dar a la recuperación un interruptor de modo. Un cliente de base de conocimiento es el bibliotecario del agente: busca en tus documentos privados y devuelve las piezas relevantes para que el agente responda desde tus datos reales. Nuestro cliente KB expone un parámetro alpha que mezcla dos estrategias de búsqueda, similitud vectorial pura en un extremo y coincidencia por palabras clave (BM25) pura en el otro. Para búsquedas por número de pieza bajamos alpha a 0,15, de modo que la coincidencia por palabras clave hace el trabajo pesado y la similitud vectorial solo desempata. También añadimos un prefiltro que restringe la búsqueda a documentos donde el campo item_id coincide con el token de la consulta antes de que empiece la puntuación, así el modelo nunca ve los casi aciertos.

if match_search == "exact":
    metadata_filter = json.dumps({"item_id": [item_id.strip()]})

await retrieve.ainvoke({
    "alpha":                0.15,   # keyword-dominant: exact tokens beat semantic proximity
    "strict_keyword_match": True,
    "metadata_filter":      metadata_filter,
})

Las preguntas generales siguen usando el alpha=0,55 por defecto, que permanece equilibrado. El agente no elige el modo; la definición de la herramienta codifica el correcto para el tipo de consulta.

Cuando la respuesta vive en tres documentos sin nada que los enlace

Un técnico pregunta: "¿Es un asiento de PTFE compatible con un fluido a base de cloro a 180 °C?" La respuesta correcta requiere conectar tres hechos separados: una hoja de propiedades del material (techo de temperatura del PTFE), una tabla de compatibilidad química (PTFE frente a cloro) y una especificación de rango operativo para la serie de válvulas. Viven en tres documentos distintos, escritos por tres ingenieros distintos, sin vocabulario compartido.

El RAG estándar trata esto como tres búsquedas independientes. Cada fragmento vuelve aislado, el modelo ve fragmentos sin aristas entre ellos y sintetiza la mejor respuesta que puede a partir de piezas desconectadas. Se equivoca en la compatibilidad.

La corrección es un grafo de conocimiento. Nuestro cliente GraphRAG no solo recupera fragmentos, recorre las relaciones entre ellos. La estrategia jerárquica por defecto fusiona búsqueda vectorial, búsqueda de texto completo y recorrido del grafo en un solo paso usando Reciprocal Rank Fusion. Una vez que aparece una entidad candidata, una llamada de seguimiento get_entity_chunks profundiza directamente en los documentos conectados de ese nodo, navegando la relación en lugar de volver a ejecutar la recuperación desde cero.

async def search_graphrag(
    query: str,
    strategy: Literal[
        "hierarchical",  # vector + full-text + graph traversal fused via RRF
        "neighborhood",  # seed from closest vector match, walk 1-hop graph edges
        "text2cypher",   # LLM writes a Cypher query against the graph schema
        "global",        # broad sweep for high-level, domain-wide questions
    ] = "hierarchical",
) -> dict: ...

La pregunta del técnico ahora se resuelve en un camino a través del grafo, desde un nodo de material a lo largo de una arista de compatibilidad química hasta un nodo de rango operativo, en lugar de tres fragmentos aislados.

Mantener el contexto manejable

Una conversación industrial seria es larga y densa en información. Cada intercambio puede extraer grandes volúmenes de material de apoyo, y a lo largo de un diálogo ese material se acumula rápido. Sin control, la memoria de trabajo de la conversación crece hasta volverse ingobernable: más lenta, más cara y eventualmente imposible de sostener.

La oportunidad fue tratar el contexto como un recurso finito que gestionar de forma deliberada, en lugar de algo que se llena hasta fallar. Un agente que no puede recordar lo que se dijo hace tres turnos no sirve. Un agente que intenta recordar todo, con todo detalle, para siempre, es inasumible y acaba rompiéndose. Encontrar el equilibrio entre preservar lo que importa y descartar lo que no era un problema que tuvimos que resolver antes de que los agentes pudieran mantener una conversación real.

Cuando la conversación crece más rápido que la ventana de contexto

Captura o diagrama que muestra un hilo de conversación que ha superado la ventana de contexto usable del modelo, con historial acumulado que desplaza el conocimiento necesario para responder la pregunta actual.

Configurar un calentador de gabinete industrial no es un trabajo de un solo mensaje. Un vendedor puede pasar 30 mensajes acotando restricciones: volumen del gabinete, temperatura ambiente, clasificación IP, voltaje, tipo de montaje. Cada intercambio importa, y el voltaje especificado en el mensaje 5 sigue siendo una restricción dura en el mensaje 55.

La ventana de contexto no sigue la lógica de negocio. Se llena. En el turno 40 la ventana está casi llena, y en el turno 60 el modelo empieza a ignorar restricciones tempranas, no porque las haya olvidado sino porque han sido físicamente expulsadas del contexto. El modelo responde la pregunta que tiene delante, ciego al terreno que ya cubrió.

La corrección es tratar el historial como un recurso que gestionas activamente. A los 70 mensajes un middleware se dispara y comprime los 20 más antiguos en un único resumen estructurado, preservando restricciones y eliminando ruido. Los pares de llamada a herramienta y resultado se excluyen de la sumarización, porque son demasiado verbosos para resumir útilmente y el contenido en bruto no es lo que importa después del hecho. Los 50 mensajes más recientes permanecen literales. El modelo retoma el hilo con todo lo que necesita y nada de lo que no.

SummarizationIgnoreToolMessagesMiddleware(trigger=70, keep=50)
# At 70 messages: compress oldest 20 → one summary block, keep newest 50 verbatim
# Tool messages excluded: too noisy to summarize, not load-bearing after the fact

Cuando un resultado de herramienta llena toda la ventana

Una búsqueda en catálogo de producto puede devolver un volcado JSON de 200 ítems, potencialmente cientos de miles de caracteres de una sola llamada a herramienta. Si se lo pasas directamente al modelo, no queda espacio para el historial de conversación, el prompt del sistema ni ninguna llamada a herramienta posterior. El modelo no se cae. Se fija en lo que aparece en los primeros miles de tokens e ignora el resto, y la calidad de la respuesta se degrada sin ningún error visible.

Hay dos capas de defensa. La primera es un tope duro: cada resultado de herramienta se intercepta antes de llegar al modelo y se trunca a 100.000 caracteres. La segunda es una ruta de recuperación: si el contexto aún desborda a pesar del tope, porque el historial se acumuló en una sesión larga, el sistema atrapa el error de desbordamiento del proveedor, elimina todo el historial de herramientas de la solicitud y reintenta una vez. La conversación se degrada con gracia en lugar de fallar.

# Layer 1: cap every tool result before it reaches the model
class ToolOutputTrimmerMiddleware:  # runs after tool execution
    async def awrap_tool_call(self, call, next):
        result = await next(call)
        return result[:100_000]  # hard ceiling regardless of tool type

# Layer 2: if overflow still happens, strip history and retry once
class ContextOverflowCleanupMiddleware:  # runs around the model call
    async def awrap_model_call(self, request, next):
        try:
            return await next(request)
        except ContextLengthExceededError:
            request = strip_tool_history(request)
            return await next(request)  # one clean retry

Mantener al agente alineado entre muchas capacidades

Un agente industrial capaz necesita hacer muchas cosas. Busca conocimiento, impulsa configuradores de producto, consulta inventario en vivo, captura leads y más. Pero capacidad y foco tiran en direcciones opuestas. Cuanto más puede hacer un agente, más fácil es que pierda el hilo, se desvíe de su propósito, maneje mal una tarea o aplique la capacidad incorrecta a la pregunta incorrecta.

El desafío fue la orquestación: guiar a un agente a través de un amplio rango de habilidades y herramientas mientras se mantiene alineado con su objetivo y el alcance que se le dio. Un agente debería saber no solo cómo hacer algo, sino si debería, y mantenerse en misión a lo largo de una interacción larga y ramificada. Mantener esa disciplina a medida que crecía el número de capacidades era una presión constante.

Diagrama de la capa de alineación del arnés de herramientas que muestra cómo las restricciones por capacidad y las reglas de enrutamiento mantienen al agente en tarea a medida que crece el número de herramientas disponibles.

Cuando el modelo no puede dejar de llamar la misma herramienta

Un agente de un fabricante de sensores de proximidad llamó search_kb, obtuvo un resultado que no incluía la especificación exacta que necesitaba, y volvió a llamar search_kb con una consulta ligeramente reformulada. Luego otra vez, quince veces en total. Cada llamada devolvía fragmentos casi idénticos, el contexto se llenaba de ruido de recuperación y la respuesta final no era mejor que la que habría producido la primera llamada.

El prompt instruía al modelo a no entrar en bucle, y el modelo entró en bucle de todos modos. Un prompt puede fijar la intención, pero no puede imponerla.

La corrección vive una capa por debajo del prompt. ToolHardLimitMiddleware se ejecuta antes de cada llamada al modelo y cuenta cuántas veces se ha invocado cada herramienta en el turno actual. Una vez que una herramienta alcanza su límite, desaparece de la lista que recibe el modelo, y el modelo no puede seleccionar una herramienta que no ve. El bucle se vuelve estructuralmente imposible en lugar de cuestión de instrucción.

class ToolHardLimitMiddleware:
    per_tool_limit: int      = 3
    always_on:      set[str] = {"search_kb"}  # some tools are never removed

    async def awrap_model_call(self, request, next):
        counts = self._count_current_turn(request.messages)
        # Works across both OpenAI and Anthropic tool call formats
        request = request.with_tools([
            t for t in request.tools
            if t.name in self.always_on or counts.get(t.name, 0) < self.per_tool_limit
        ])
        return await next(request)

Cuando más capacidades significan más formas de salirse del guion

Un agente construido para preventa técnica, consultas de especificaciones, comprobaciones de compatibilidad y selección de producto también tenía una herramienta de captura de leads. Un técnico que preguntaba "¿cuál es la frecuencia de conmutación máxima de un BIM-UNT-AP6X?" ocasionalmente activaba el flujo de captura de leads a mitad de respuesta. El modelo había internalizado la captura de leads como una acción de alto valor y la aplicaba con demasiada prisa. Cuantas más herramientas tiene un agente, más superficie hay para que la incorrecta se dispare en el momento incorrecto.

La corrección no es un prompt más inteligente sino un contrato de ejecución fijo. Cada agente ejecuta una pila de middleware donde cada hook se dispara en un momento específico, antes del agente, alrededor de la llamada al modelo y después del agente, y el orden está definido en código. LeadCaptureIntentMiddleware se ejecuta como hook aafter_agent: se dispara solo después de que se entrega la respuesta principal, y solo si un clasificador de intención ligero que escanea los últimos 8 mensajes no-herramienta detecta intención de compra genuina. La pregunta de especificaciones del técnico recibe respuesta, y la captura de leads permanece en silencio, porque la arquitectura los secuencia así.

# Each middleware fires at a specific lifecycle hook, order is fixed, not negotiable
middlewares = [
    NoAssistantPrefillMiddleware(),                # runs before everything (Claude compat)
    LoggingModelFallbackMiddleware([openai_model]),# wraps the model call
    SafetyGuardrailMiddleware(threshold=0.5),      # before_agent: can stop early
    ToolHardLimitMiddleware(per_tool_limit=3),     # before model call: caps tool access
    ToolOutputTrimmerMiddleware(),                 # after tool: caps result size
    ToolCallLimitMiddleware(run_limit=30),         # hard ceiling for the whole turn
    SummarizationIgnoreToolMessagesMiddleware(),   # before_agent: compresses history
    ContextOverflowCleanupMiddleware(),            # around model call: overflow recovery
    LeadCaptureIntentMiddleware(context_window=8), # after_agent: fires last
    SafetyOutputGuardrailMiddleware(),             # after_agent: screens output
    StripToolHistoryMiddleware(),                  # after_agent: cleanup
]

Asegurar que nada inseguro entre

Cada conversación abre con un riesgo en la entrada. Los usuarios pueden intentar manipular al agente, hacerle jailbreak, extraer sus instrucciones o empujarlo fuera de su propósito previsto.

Un agente manipulado es un pasivo, y un agente que expone sus propios internals parece roto y poco fiable. Tratar las solicitudes entrantes como cosas que hay que comprobar, en lugar de confiar por defecto, fue esencial para hacer el sistema seguro de poner delante de clientes reales.

Diagrama de la etapa de validación de seguridad en el pipeline de middleware, donde cada respuesta candidata se comprueba contra un conjunto de reglas antes de devolverse al cliente.

Cuando un mensaje bloqueado no te dice nada

Un despliegue temprano usaba un clasificador binario seguro/inseguro: pasar o bloquear. Atrapaba jailbreaks obvios, pero también bloqueaba a un comprador preguntando por la pieza equivalente de un competidor, y a un técnico escribiendo en alemán con el que el clasificador no había sido entrenado. Cada rechazo se veía idéntico en los logs, un cero. No había forma de distinguir "claramente intentando extraer el prompt del sistema" de "fuera de alcance pero completamente inofensivo", ni de ajustar el umbral sin adivinar, porque adivinar era todo lo que permitían los datos.

Una puntuación sin razonamiento no aporta señal. Lo que hace útil un guardrail en producción no es que bloquee cosas, sino que informe por qué las bloqueó, con suficiente consistencia como para poder mejorar.

Nuestro guardrail devuelve una respuesta estructurada con dos campos, rationale y score. El orden de los campos importa: rationale va primero en el esquema, así el modelo tiene que articular su preocupación antes de asignar un número. En la práctica, mensajes que habrían juzgado al instante en 0,7 a menudo se resuelven en 0,4 una vez escrito el razonamiento. El umbral de bloqueo es un número que configuras por despliegue, así un agente orientado al cliente estricto y una herramienta interna permisiva comparten la misma lógica de guardrail con umbrales distintos.

class SafetyGuardrailResponse(BaseModel):
    rationale: str   # produced first, forces reasoning before scoring
    score:     float # 0.0 (unsafe) → 1.0 (safe)

# score=0.15, rationale="probing for system prompt via indirect instruction" → block
# score=0.82, rationale="competitor part question, not adversarial" → redirect instead

Integrarse profundamente con los sistemas propios del cliente

Una respuesta fundamentada con frecuencia no es un párrafo de texto en absoluto. Es una acción tomada dentro del stack existente del cliente: configurar un producto, registrar un lead de ventas, enviar un correo. Cada una requiere que el agente llegue al software propio del cliente y lo opere en su nombre.

Esto creó dos oportunidades distintas. La primera fue usar los configuradores de producto del cliente como elementos interactivos dentro de la conversación, para que un comprador pudiera trabajar una configuración real en lugar de leer una descripción plana de una. La segunda fue conectar con los sistemas que ejecutan el negocio del cliente, su CRM, correo, comercio e inventario, para que el agente pudiera crear leads, enviar mensajes y actuar sobre datos en vivo en lugar de hablar de ello. Ambas requirieron integración profunda con software que nosotros no construimos y no podíamos cambiar, lo cual es marcadamente más difícil que responder preguntas aisladas.

Diagrama de arquitectura que muestra al agente conectado a sistemas externos — ERP, catálogo de producto, almacenes de configuración fieldbus — mediante llamadas a herramientas tipadas en lugar de acceso API libre.

Cuando el configurador vive en otro sitio

Al principio, el agente describía un flujo de configuración de producto en texto: "puedes configurar esta válvula especificando diámetro del eje, material del sello y tipo de actuación". El comprador leía eso, salía de la conversación, iba al sitio web del fabricante, configuraba la pieza allí y quizá volvía con una pregunta de seguimiento. El agente no tenía memoria de lo que habían elegido. La conversación y la configuración real vivían en lugares separados, dos hilos que nunca se hablaban.

La corrección fue traer el configurador dentro de la conversación. El agente ahora construye un widget de formulario directamente desde el specList de la API del fabricante, con campos y opciones reales, reflejando lo que el comprador ya había seleccionado en turnos anteriores. Cuando el comprador hace clic en un botón CTA, la etiqueta de ese botón se convierte en su siguiente mensaje. "Quiero configurar la serie LB" llega como texto plano, el modelo lo lee, continúa la conversación y el siguiente formulario se renderiza con su selección ya aplicada. Sin redirección, sin pestaña separada. La configuración y el diálogo son el mismo hilo.

# CTA label becomes the next user message, no custom JS needed
CatalogDistributorProductWidget.from_series_list(series_list)
# → cta_instruction="I want to configure the {code} series."
# Form reflects prior selections across turns
def _build_spec_form(spec_list, applied_specs: dict) -> FormWidget:
    for spec in spec_list:
        selected = applied_specs.get(spec["specCode"])  # carried from conversation state
        fields.append(TextInputField(spec, selected=selected)
                      if spec.get("unit") else DropdownField(spec, selected=selected))
    return FormWidget(fields=fields)

Cuando la acción ocurre fuera de la conversación

Un agente de ventas podía identificar el calentador de gabinete correcto, confirmar precio, comprobar disponibilidad y luego parar. Para enviar la cotización, el vendedor tenía que copiar la respuesta fuera del chat y abrir su cliente de correo. El agente hacía el razonamiento y el humano la ejecución, y cada traspaso es una oportunidad para que la información se desvíe: un número mal transcrito, una especificación omitida, un detalle perdido entre la ventana de chat y la ventana de redacción.

La herramienta gmail_send_email se conecta a la propia cuenta Gmail del cliente mediante OAuth por cliente, gestionado por Composio. El modelo decide cuándo llamarla, después de confirmar la intención del comprador y cerrar los detalles de la cotización. El vendedor no sale del chat, el agente envía el correo y confirma el envío en su siguiente mensaje. La decisión y la acción ocurren en el mismo lugar.

@tool
async def gmail_send_email(recipient_email: str, subject: str, body: str) -> dict:
    client = Composio(api_key=settings.composio_api_key)
    return client.tools.execute(
        slug="GMAIL_SEND_EMAIL",
        arguments={"recipient_email": recipient_email, "subject": subject, "body": body},
        connected_account_id=settings.composio_account_id,  # per-customer OAuth token
    )
# The model calls this when it decides the moment is right, not on every lead,
# not on every product recommendation, only when the conversation has reached a
# point where sending makes sense.

Ver lo que realmente está pasando

Un agente desplegado en el mundo solo es tan útil como nuestra capacidad de observarlo. Sin visibilidad no hay forma de saber si está ayudando o perjudicando, dónde tiene éxito y dónde falla en silencio.

La oportunidad fue capturar las señales que revelan el rendimiento real: desde dónde interactúan los usuarios, cómo se usa el agente y, lo más valioso de todo, retroalimentación directa sobre si una respuesta dada fue buena. Estas señales cierran el bucle entre despliegue y mejora. Convierten cada conversación de una interacción puntual en evidencia que hace la siguiente mejor. Construir los medios para recogerlas, sin interferir con la experiencia en sí, fue la pieza final del problema.

Panel de observabilidad o diagrama de trazas que muestra telemetría por turno: llamadas a herramientas disparadas, pasos de recuperación tomados, latencia por etapa y el camino que siguió el agente para llegar a su respuesta.

Cuando un pulgar abajo no aporta contexto

Un despliegue temprano recogía retroalimentación, pulgares arriba y pulgares abajo, y la almacenaba como registros aislados: una puntuación, una marca de tiempo, un ID de sesión. Cuando un comprador marcaba una respuesta como incorrecta, no había forma de conectar esa señal con nada útil. ¿Qué modelo respondió? ¿Qué herramientas llamó? ¿Qué recuperó? ¿Se disparó un respaldo de proveedor a mitad de turno? La retroalimentación existía, y la evidencia que te permitiría actuar no. Un pulgar abajo sin contexto es un dato que puedes contar pero no aprender de él.

Cada turno del agente ahora abre una traza Langfuse con un ID generado de forma determinista a partir de session_id:message_id. El mismo mensaje siempre mapea a la misma traza, sin importar cuándo llegue la retroalimentación. Cuando un comprador envía una valoración, el endpoint de retroalimentación busca el ID de traza almacenado contra ese mensaje y publica la puntuación directamente en el span que produjo la respuesta. La retroalimentación y el registro de ejecución quedan unidos.

En la práctica, un pulgar abajo en una recomendación de sensor de un fabricante de sensores de proximidad abre una traza que muestra exactamente qué modelo respondió, si se disparó un respaldo de proveedor a mitad de turno, qué fragmentos KB se recuperaron y cuántas veces se llamó search_kb antes de que aterrizara la respuesta. La señal se convierte en evidencia, y cada conversación deja de ser una interacción puntual y empieza a hacer la siguiente mejor.

# Deterministic: same session+message → same trace, always
trace_id = langfuse.create_trace_id(seed=f"{session_id}:{message_id}")

with langfuse.start_as_current_span("agent_turn", trace_id=trace_id):
    # Tool calls create child spans automatically
    # Provider fallbacks tag the trace when they fire
    result = await run_agent(...)


# POST /messages/{id}/feedback, joins rating to the exact span
agent_trace_id = human_message.additional_kwargs.get("agent_trace_id")
scores = "positive" if req.properties.feedback else "negative"
langfuse.score(trace_id=agent_trace_id, name="user_feedback", value=score)

Estos seis desafíos comparten una sola lección. Ninguno pudo resolverse escribiendo un prompt más inteligente o eligiendo un modelo mejor. Cada uno fue una cuestión de cómo se construye el sistema circundante: cómo se organiza la información, cómo se gestiona la memoria, cómo se gobierna la capacidad, cómo se impone la seguridad, cómo se cablean las integraciones y cómo se observa el conjunto. Por eso reconstruimos nuestros agentes de IA industrial desde cero, y por eso creemos que lo mismo aplica en general. El arnés es la arquitectura.

¿Por qué los LLM generales no bastan? Ingeniería de contexto: la primera disciplina del arnés

Cuando los clientes empezaron a hacer preguntas a nuestros agentes como "¿encaja esta pieza en mi máquina?" o "¿cuál es el cable correcto para este conector?", nos topamos con un muro que un prompt mejor no podía arreglar.

El muro no es la capacidad del modelo. Es lo que el modelo sabe en el momento en que tiene que responder.

Un modelo de lenguaje de propósito general no tiene acceso al catálogo de producto privado de un fabricante, no sabe qué ítems hay en el inventario de un distribuidor, no recuerda lo que dijo el cliente hace tres mensajes ni entiende qué restricciones ya se establecieron en la conversación. Razona bien sobre la información que tiene. El problema es que para ventas industriales, la mayor parte de la información relevante no existe en los datos de entrenamiento del modelo. Existe en APIs privadas, sistemas de inventario en vivo y la propia sesión del cliente.

Este es el problema central: el modelo es capaz pero no tiene acceso a lo que necesita.

Hacerlo útil requiere resolver un problema distinto al de la capacidad. Requiere decidir, en cada llamada al modelo, qué información entra en la ventana de contexto, en qué forma, con qué granularidad y qué se deja fuera.

Esa disciplina es lo que llamamos ingeniería de contexto. El término ha ganado tracción recientemente, y mapea a algo que seguíamos redescubriendo en nuestro propio trabajo: el factor limitante de la calidad del agente no es qué modelo usas, sino cuánta de la información correcta puede ver el modelo en tiempo de inferencia.

La ingeniería de contexto no se trata solo de añadir información. También se trata de quitarla.

Las sesiones largas acumulan llamadas a herramientas, resultados de recuperación y pasos de razonamiento intermedio que agotan rápidamente la ventana de contexto. Parte del trabajo del sistema es decidir qué ya no importa: recortar trazas de ejecución, comprimir historial y preservar justo el hilo suficiente para que el modelo se mantenga coherente.

El desafío no es maximizar el contexto. Es maximizar el contexto útil.

En la práctica, la ingeniería de contexto abarca cinco preocupaciones superpuestas.

Ingeniería de prompts

La ingeniería de prompts moldea el comportamiento base del modelo: su rol, su estrategia de razonamiento y sus restricciones. El problema que resuelve es el modelo en blanco. Sin instrucción deliberada, un modelo capaz aún da respuestas genéricas.

Generación aumentada por recuperación (RAG)

El RAG fundamenta al modelo en datos privados y en vivo. Cuando un cliente pregunta por un producto, extraemos de APIs de fabricantes y una base de conocimiento de búsqueda híbrida construida a partir de documentación técnica curada. El modelo no alucina especificaciones de catálogo; razona sobre datos recuperados que puede citar.

Estado e historial

El estado e historial evitan que la conversación se reinicie en cada turno. Un cliente puede pasar diez mensajes acotando un producto, especificando voltaje, factor de forma, tipo de montaje y espacio de gabinete disponible. Cada una de esas restricciones es contexto que cada llamada al modelo posterior necesita. Sin gestión explícita de estado, cada respuesta ignora todo lo anterior.

Memoria

La memoria extiende eso entre sesiones. Un cliente que regresa no debería tener que reexplicar la configuración de su máquina desde cero. Los checkpoints de sesión persisten el grafo de conversación para que el agente retome con contexto completo en lugar de una pizarra en blanco.

Salida estructurada

Las salidas estructuradas hacen que las respuestas del agente sean componibles con sistemas posteriores. Cuando el modelo devuelve una recomendación de producto o un juicio de compatibilidad, devuelve un objeto tipado, no prosa que otro sistema tenga que analizar y confiar. Esto es lo que permite que las respuestas del agente activen componentes de UI, actualicen estado de configuración o alimenten de forma fiable la siguiente llamada a herramienta.

En la práctica esto a menudo se ve como una pila de middleware que se ejecuta antes de cada invocación del modelo. Algunos componentes inyectan contexto: el estado de configuración actual del agente, el flujo de trabajo activo o las herramientas relevantes para la tarea. Otros eliminan información que ya no contribuye a la respuesta. Para cuando un prompt llega al modelo, el contexto ya ha sido curado, filtrado y estructurado para el paso de razonamiento que sigue.

Diagrama del pipeline de middleware que impone contratos de salida estructurada: el contexto se cura y filtra antes de llegar al modelo, y la respuesta se valida contra un esquema antes de la entrega.

Estas cinco preocupaciones se superponen. La recuperación informa la composición del prompt. El estado moldea qué vale la pena recuperar. La memoria determina qué estado sigue siendo relevante. Las salidas estructuradas hacen que las actualizaciones de estado sean fiables.

La ingeniería de contexto es una parte del arnés, la parte que decide qué ve el modelo. La fundamentación, la gobernanza de herramientas, la seguridad, la robustez y la observabilidad son el resto, y las secciones que siguen las desarrollan. El modelo en el centro de todo esto es un commodity. El arnés que lo rodea no lo es.

Degradar con gracia: respaldos, límites y runtime

Un modelo capaz no es lo mismo que un agente fiable. En una demo, un solo modelo que razona bien y llama herramientas basta. En producción, donde un agente recomienda una pieza que un cliente comprará de verdad, el listón es distinto. Una respuesta incorrecta es peor que ninguna respuesta, porque una especificación incorrecta pero afirmada con confianza erosiona la confianza más rápido que un honesto "no lo tengo". La robustez, para nosotros, no es un SLA de uptime. Es la propiedad de que la salida del agente se mantenga correcta incluso cuando las cosas debajo de él — el modelo, el proveedor que lo sirve y el runtime en el que vive — fallan o se degradan. Fijar la intención del modelo mediante ingeniería de contexto, tema de la sección anterior, es necesario pero no suficiente. Lo que sigue es lo que construimos para los momentos en que la intención sola no aguanta.

Diagrama de arquitectura de tres capas que distingue el grafo de conocimiento, el arnés de herramientas y la capa de razonamiento, mostrando cómo cada capa puede fallar de forma independiente sin derribar el sistema completo.

La versión más directa de este problema es el propio proveedor del modelo. Ejecutamos agentes en modelos de Anthropic y OpenAI, y ambos, como cualquier servicio alojado, tienen días malos. Un proveedor puede devolver errores, o puede seguir en pie mientras se ralentiza hasta arrastrarse. Hemos visto a un proveedor seguir respondiendo mientras cada llamada tardaba mucho más de lo habitual, lo cual para un agente que hace varias llamadas al modelo por turno es apenas distinguible de estar caído. Un agente atado a un solo proveedor hereda el peor momento de ese proveedor, y el cliente lo siente como una conversación muerta. Así que ningún agente depende de un solo proveedor. Cada uno está configurado con un modelo primario y un respaldo en un proveedor distinto, y cuando el primario falla o se estanca más allá de un timeout explícito, la plataforma conmuta al sano y el turno se completa. Errores transitorios como límites de tasa y conexiones caídas se reintentan con backoff antes de cualquier conmutación, y desactivamos los reintentos silenciosos propios de los proveedores para que esta política viva en nuestra plataforma en lugar de en un SDK de vendor. Un respaldo nunca es silencioso: cuando se dispara, la plataforma registra el evento y etiqueta la traza, así un proveedor degradado se convierte en algo que podemos ver en lugar de algo oculto detrás de una respuesta que funciona.

Diagrama de respaldo de proveedor que muestra cómo un proveedor de modelo primario fallido enruta a un proveedor secundario, haciendo visible la indisponibilidad del modelo en telemetría en lugar de degradar silenciosamente la calidad de la respuesta.

Un fallo más sutil es el modelo que está en pie, es rápido y aun así se comporta mal. Incluso con un prompt cuidadoso, un modelo se rompe de formas operativas: llama la misma herramienta una y otra vez, convencido de que la siguiente llamada devolverá por fin lo que quiere, o se ahoga en una respuesta de herramienta tan grande que pierde el hilo. Un prompt puede pedirle que no lo haga; no puede detenerlo. Lo manejamos una capa por debajo del prompt, en middleware que envuelve cada llamada al modelo y a herramienta. Un componente acota cuántas veces puede llamarse una herramienta dada en un turno y retira esa herramienta de la mesa una vez alcanzado el límite, terminando el bucle. Otro limita el tamaño de cualquier salida de herramienta individual antes de que llegue al modelo, de modo que un resultado sobredimensionado se recorta en lugar de desplazar todo lo demás en el contexto. El modelo sigue haciendo el razonamiento; el middleware mantiene sanas las condiciones bajo las que razona.

La robustez también es una cuestión de dónde corre el agente. Nuestros agentes corrían antes en AWS App Runner, que asume que una petición web es corta: cuando un agente se quedaba en silencio para razonar un rato, App Runner podía leer la conexión inactiva como un proceso terminado y cerrarla antes de que volviera la respuesta. Nos movimos a AWS Bedrock AgentCore, un runtime pensado para agentes de larga duración, donde un turno que pasa tiempo pensando es esperado en lugar de terminado. Cada agente obtiene su propio runtime, construido a partir de dependencias fijadas y wheels construidos localmente detrás de un lockfile comprometido, así los mismos paquetes exactos corren en desarrollo y en producción y un despliegue no puede desviarse en silencio entre ambos. El estado de conversación se trata igual: en producción se hace checkpoint en un almacén durable, y el sistema falla en voz alta, negándose a arrancar si ese almacén está mal configurado en lugar de caer silenciosamente en estado en memoria que el cliente perdería después.

Los respaldos, el middleware y la infraestructura son los mecanismos de robustez de los que trata esta sección, pero no son todo. El resto se desarrolla donde su problema aparece primero: los guardrails filtran lo que entra y lo que sale, la memoria persistente mantiene coherente una conversación larga y densa en información y permite a un cliente que regresa retomar en lugar de empezar de cero, y la observabilidad que registra un respaldo o una mala respuesta es lo que convierte un fallo único en una corrección. Lo que los une es que ninguno depende de qué modelo esté debajo. El resultado no es un agente más inteligente sino uno más responsable: cuando el camino fácil falla, la respuesta se degrada en una respuesta correcta y más estrecha en lugar de una incorrecta con confianza, que en un entorno industrial es el único tipo de robustez que cuenta.

Palabras finales

Cada fallo en esta publicación fue un fallo de contexto, no de modelo. La búsqueda vectorial devolvió fragmentos despojados de sus relaciones. Demasiado contexto empeoró las respuestas, no las mejoró. Un modelo capaz aún entró en bucle con una herramienta, o llevó una especificación incorrecta pero confiada hasta el cliente. El límite rara vez fue el modelo. Fue todo lo que lo rodeaba.

Ese "todo lo que lo rodea" es el arnés: el bucle en el que corre el modelo, y los hooks que lo fundamentan, curan su contexto, gobiernan sus herramientas, conmutan cuando cae un proveedor y lo mantienen observable. El modelo es un commodity que alquilas. El arnés es lo que ingeniamos, y ahí vive la fiabilidad. La arquitectura es el arnés, e ingeniería del arnés es cómo lo construyes.

La mayor oportunidad que vemos está en el propio arnés. Hoy reconstruye las relaciones entre hechos en cada turno, infiriéndolas en tiempo de inferencia, lo cual es implícito, aproximado y rara vez igual dos veces. Esas relaciones podrían establecerse en cambio de forma deliberada y por adelantado, más deterministas, más completas, más exactas, y luego mantenerse en un grafo de conocimiento durable de entidades, las relaciones entre ellas y el linaje de cada hecho. Un arnés que navega ese grafo en lugar de rederivarlo bajo presión de tiempo en cada llamada es un sistema distinto, y la dirección hacia la que estamos construyendo.

La afirmación en la que confiamos hoy es más estrecha, y se sostiene: en IA industrial, el modelo rara vez es el cuello de botella. El arnés lo es, y aún estamos descubriendo hasta dónde puede llegar. La siguiente publicación profundiza en el sistema que estamos construyendo para llegar allí, el Knowledge Construction System que convierte ese grafo de una idea en infraestructura.

Danos tus veinte preguntas más difíciles.

Haremos demo con tus SKUs, correremos tus evals y mostraremos citas para cada respuesta.

  • Ejemplos Reales
  • Demo Funcional
  • Tus Datos