Presentando el workflow del agente de chatbot mejorado
Introducción
Este artículo continúa lo contado en Por qué el ai-service de Omnilude eligió la estructura Assistant / Thread / Run y Así funcionan los agentes en el ai-service de Omnilude.
En los textos anteriores expliqué la estructura. Esta vez quiero mirar cómo esa estructura corre de verdad dentro del canvas del backoffice. El objeto de esta explicación es Enhanced Chatbot.
Este artículo no parte de un ejemplo inventado. Toma la configuración real del workflow y explica tal cual qué entrada recibe este agente, dónde se bifurca y por qué assistants y tools pasa antes de generar una respuesta.
Qué es el chatbot mejorado en una sola frase
Este agente es un chatbot de ramificación bastante explícita que divide las preguntas en cuatro rutas.
- Si la pregunta es general, responde de inmediato.
- Si necesita información reciente, va por la ruta de búsqueda.
- Si la entrada es una URL de YouTube, va por la ruta de resumen de subtítulos.
- Si la entrada es una URL web normal, va por la ruta de análisis de artículos.
Si simplificamos el workflow real, queda así.
En otras palabras, el punto de este workflow no es exprimir a la fuerza un solo modelo. Primero decide qué tipo de pregunta tiene delante y después elige la tubería de procesamiento adecuada.
Me gustaría abrirlo para que cualquiera lo pruebe, pero el uso de LLM tiene costo y por eso todavía no lo publico de forma abierta. Cuando haya más margen, quiero liberarlo en una forma que realmente se pueda ejecutar.
Todo empieza con una sola entrada de texto
El nodo inicial de este agente es text-input, y su valor de ejemplo es ¿Cuáles son los temas sociales recientes?. Es una frase de demo, pero al mismo tiempo muestra muy bien qué intenta resolver este workflow.
En lugar de enviar la pregunta directamente al LLM, primero la pasa por un nodo router. El assistant que usa ahí es Question Router, y las fuentes de routing configuradas son cuatro.
llm_direct: respuesta directaweb_search: respuesta después de búsqueda webyoutube_summary: resumen de YouTubearticle_analyze: análisis de página web
Este router no es un if simple. En las instructions del assistant está explicado qué source debe elegir en cada situación, y además la respuesta está obligada a volver en JSON. Por ejemplo, las preguntas que necesitan información reciente, información atada a una fecha o fact checking se envían a web_search; las URLs de YouTube van a youtube_summary; las URLs normales a article_analyze; y el resto a llm_direct.
En ese punto, el router no deja solo el source elegido, sino también un reason. Uno de los nodos text-visualize del canvas está puesto precisamente para poder leer ese motivo. Eso significa que esta pantalla no es solo un diagrama. Es una pantalla de trabajo donde se pueden inspeccionar directamente los rastros de ejecución.
La ruta de respuesta directa es la más corta
Cuando la bifurcación cae en llm_direct, la estructura es simple. La salida del router entra en un nodo generate-text que usa el assistant Question Answer, y la salida de ese nodo va directo a finish.
Lo importante es que, aunque sea una ruta corta, no por eso deja de ser estructurada. El nodo generate-text vuelve a cargar el assistant por aiAssistantId en tiempo de ejecución, combina el system prompt y el user prompt, y solo entonces hace la llamada real al modelo. O sea, aunque el canvas tenga metadatos del assistant, la referencia real en runtime sigue siendo el id del assistant.
Los assistants conectados a Enhanced Chatbot son estos.
Router: Question Router (assistantId=1)
Direct answer: Question Answer (assistantId=2)
Search query writer: Web Search Expert (assistantId=12)
Search-based answer: AdaptiveAnswerer (assistantId=3)
YouTube writer: YoutubeSummarizer (assistantId=15)
Article writer: ArticleAnalizer (assistantId=16)
Por eso mismo, el mismo nodo generate-text puede asumir un papel completamente distinto según qué assistant tenga conectado. El nodo es el ejecutor genérico. El assistant es el que carga el rol.
La ruta de búsqueda no tiene dos pasos, sino cuatro
El verdadero centro de este agente está en la ruta web_search. Si miras la implementación actual, la respuesta basada en búsqueda no se genera de una sola vez. Está separada de forma deliberada en varias etapas.
La primera etapa es Web Search Expert. Este assistant toma la pregunta del usuario y la convierte en una línea lista para tirarla al buscador. En vez de buscar con la pregunta cruda, la reescribe como una consulta más apta para búsqueda.
La segunda etapa es web-search-tool. En el workflow guardado este nodo tiene searchTool: "searx", y la implementación real llama a SearXng, junta hasta diez snippets de resultados y usa un timeout de 200 segundos para evitar esperas infinitas.
La tercera etapa es prompt-crafter. Este nodo no pega los resultados de búsqueda directamente en la respuesta final. Inyecta {{results}} dentro de una plantilla ya guardada y construye un system prompt para responder. Esa plantilla ya incluye la hora actual, materiales de referencia, reglas de respuesta y tono de escritura.
La cuarta etapa es AdaptiveAnswerer. Lo interesante es que la pregunta original del usuario se mantiene como prompt, mientras que el contexto armado a partir de la búsqueda entra como system. Es decir, los resultados no se pegan sin más: la pregunta y la evidencia se separan, y luego un assistant orientado a escritura vuelve a redactar la respuesta final.
Si volvemos a dibujar el flujo, queda así.
A mí esta parte me parece importante. No convierte todo de inmediato en una caja opaca con apariencia de RAG solo porque haya búsqueda. Separa el refinamiento de la pregunta, el armado de la evidencia y la escritura final. Eso también hace mucho más fácil ver qué etapa se está moviendo cuando toca operar el sistema.
Las rutas de YouTube y Article son tool-first
Las bifurcaciones basadas en URL son más directas.
Si la entrada es una URL de YouTube, primero corre youtube-summary-tool. Ese nodo usa YoutubeTranscriptTool para obtener un transcript con información temporal. Después el assistant YoutubeSummarizer vuelve a escribir ese transcript como contenido legible.
Si la entrada es una URL web normal, primero corre article-analyzer-tool. Ese nodo rastrea la página con Crawl4Ai y, si es posible, usa Readability4J para extraer solo el cuerpo principal y convertirlo en markdown con título y texto. Luego el assistant ArticleAnalizer lo reorganiza como un artículo más legible.
En otras palabras, ambas rutas de URL siguen la misma lógica.
- Primero el tool trae la materia prima.
- Después el assistant la reorganiza en un resultado que una persona puede leer.
Eso también se parece a la ruta de búsqueda. En el diseño actual de agentes de Omnilude, tool y assistant no compiten entre sí. El tool trae el material. El assistant interpreta y ordena ese material.
Esta pantalla no es solo un diagrama bonito
Si miras el canvas del backoffice, vas a ver nodos text-visualize colocados entre medio. No son imprescindibles para producir la respuesta final. Su función es dejarte ver de inmediato qué valor está circulando por el grafo.
La implementación del frontend también está hecha en esa dirección. Cuando una ejecución de workflow recibe un evento NODE_COMPLETED, el frontend inyecta el valor de salida de ese nodo en el estado del canvas. Un nodo text-visualize muestra ese valor tal cual, y si llegan deltas de streaming, va acumulando el texto.
Esa diferencia importa más de lo que parece. Muchas herramientas de workflow se quedan en dibujar nodos. Pero la pantalla de agentes del backoffice de Omnilude combina dos cosas: ver el grafo guardado y observar los valores intermedios de una ejecución real. Por eso este canvas es diagrama y depurador al mismo tiempo.
Así lo entiende el motor de ejecución
Ahora salgamos del canvas. Este workflow se guarda en la base como ai.ai_agent.workflow JSONB. No está repartido en tablas de nodos independientes. Se conserva como un único grafo en JSON.
Cuando empieza la ejecución, BasicWorkflowEngine primero busca un nodo cuyo type termine en -input y cuyo startNode=true. En este caso ese punto de entrada es text-input. A partir de ahí, NodeExecutorFactory elige el executor que corresponde a cada tipo de nodo y sigue empujando la ejecución.
En Enhanced Chatbot, los executors importantes se pueden resumir así.
TextInputNode: prepara la entrada inicialRouterNode: elige la rama usando un assistantGenerateTextNode: genera texto usando un assistantWebSearchToolNode: ejecuta búsqueda con SearXngYoutubeSummaryToolNode: extrae transcript de YouTubeArticleAnalyzerToolNode: limpia el cuerpo de una página webPromptCrafterNode: construye un system prompt basado en plantillaFinishNode: reúne los resultados de las ramas y termina la ejecución
El detalle más importante aquí es que tanto generate-text como router vuelven a cargar el assistant en el momento de ejecutar. El agente no contiene inteligencia de forma directa. Cada nodo trae un assistant solo cuando lo necesita. Al final, el agent es orchestration y la inferencia real sigue ocurriendo en assistants.
El nodo finish es menos trivial de lo que parece
El último nodo finish no es solo un botón de cierre. Revisa las edges entrantes, espera si todavía hay handles de entrada en progreso y solo envía como resultado final las salidas de los nodos fuente que realmente ya están listas.
Esa estructura es necesaria por las bifurcaciones. Que el router abra cuatro caminos no significa que esos cuatro caminos terminen siempre igual. Algunas preguntas cierran con respuesta directa y otras pasan por búsqueda y resumen. finish funciona como el recolector final que absorbe esas diferencias.
Eso significa que este agente no es una simple función que recibe una pregunta y devuelve una respuesta. Corre encima de un ejecutor de grafos con bifurcación, espera y convergencia.
La intención del agente
Yo creo que incluso este agente relativamente simple muestra bastante bien la dirección actual de Omnilude.
Primero, no exagera la idea de un agent general. Antes clasifica el tipo de pregunta y luego conecta el tool y el writer adecuados de una forma bastante práctica.
Segundo, no aplasta la ruta de búsqueda dentro de un único bloque opaco. Refinamiento de la pregunta, búsqueda, armado del contexto y redacción final están separados.
Tercero, el canvas del backoffice no es solo un editor. También muestra valores intermedios de ejecución, de modo que el experimento de prompts y la depuración ocurren en la misma pantalla.
Cuarto, los papeles de assistant y agent están claros. El assistant es el componente de inferencia. El agent es el workflow que conecta esos componentes.
Por eso, esta implementación me parece más cercana a decir "hicimos una tubería de procesamiento de preguntas visualmente operable" que a decir "hicimos un AI agent".
Cierre
Si los textos anteriores explicaban la estructura de assistant, thread, run y agent, este artículo es el caso concreto que muestra cómo esa explicación aparece convertida en un canvas real y en un grafo real de nodos. Enhanced Chatbot ya funciona como un chatbot bastante práctico al combinar un router, varios writer assistants y tools de búsqueda, YouTube y artículos.
Cuando haya más margen, me gustaría volver sobre la composición e implementación del agente con una explicación más fácil de seguir, apoyada en imágenes y en la pantalla real en funcionamiento.