Skip to content
Ingeniería

Diseño de un motor de flujos de trabajo con condiciones, bucles, ejecución paralela y puertas de aprobación

Dentro del ejecutor de flujos de trabajo de JieGou — 8 tipos de pasos, ejecución recursiva, flujo de datos entre pasos, pausa/reanudación de aprobaciones, reintentos con backoff y observabilidad en cada capa.

JT
JieGou Team
· · 8 min de lectura

Construir una operación de IA de un solo paso es sencillo: tomar entrada, llamar a un LLM, devolver salida. Construir un motor de flujos de trabajo multi-paso que soporte ramificación, bucles, ejecución paralela, puertas de aprobación humana y reintentos automáticos es un tipo de problema completamente diferente.

Este artículo cubre la arquitectura del motor de flujos de trabajo de JieGou — el modelo de ejecución, los 8 tipos de pasos, cómo fluyen los datos entre pasos, cómo las aprobaciones pausan y reanudan la ejecución, y los guardarraíles que mantienen todo confiable.

Modelo de ejecución

Un flujo de trabajo es un grafo dirigido de pasos. La ejecución comienza con executeWorkflow(), que crea un registro WorkflowRun, construye un StepExecutionContext compartido y llama a executeStepList() para procesar pasos secuencialmente.

El contexto lleva un Map previousStepOutputs — un almacén clave-valor donde cada paso completado deposita su salida para que los pasos posteriores la referencien. Esta es la columna vertebral del flujo de datos. El Paso B puede referenciar la salida del Paso A usando sintaxis de plantilla como {{step.stepA.fieldName}}.

Cada flujo de trabajo tiene un timeout configurable (por defecto 5 minutos), aplicado vía un campo deadlineMs en el contexto. Los pasos individuales también tienen su propio timeout (por defecto 60 segundos) aplicado por un wrapper withStepTimeout().

Los 8 tipos de pasos

Paso de receta

El caballo de batalla. Ejecuta una plantilla de prompt reutilizable vía executeRecipe() y almacena la salida parseada en previousStepOutputs. El mapeo de entrada resuelve referencias a entradas del flujo de trabajo, salidas de pasos anteriores, valores estáticos o elementos de bucle.

Paso de condición

Evalúa una expresión booleana y ejecuta thenSteps o elseSteps recursivamente. Esta es una ramificación verdadera — ambos caminos pueden contener cualquier tipo de paso, incluyendo condiciones anidadas. El motor llama a executeStepList() recursivamente en la rama elegida.

Paso de bucle

Itera sobre una colección y ejecuta una lista de sub-pasos para cada elemento. La colección puede provenir de 4 fuentes: un array estático definido en el flujo de trabajo, la salida de un paso anterior, un campo de entrada del flujo de trabajo o elementos del bucle padre.

Cada iteración obtiene su propio Map loopContext, para que los sub-pasos puedan referenciar el elemento actual vía {{loop_item.fieldName}}. Los resultados de iteración se almacenan como un array con stepRuns anidados para observabilidad.

Paso paralelo

Ejecuta múltiples ramas concurrentemente vía Promise.allSettled(). Cada rama es una lista independiente de pasos con su propio array de stepRuns. Esto es útil cuando múltiples operaciones independientes pueden ejecutarse simultáneamente — por ejemplo, enriquecer un lead desde tres fuentes de datos diferentes a la vez.

Paso de aprobación

El tipo de paso más interesante arquitectónicamente. Cuando la ejecución llega a un paso de aprobación, lanza un ApprovalPauseError. Esta es una excepción controlada — no un fallo.

El error se captura en el nivel superior, el WorkflowRun se persiste con estado pending_approval y los aprobadores elegibles son notificados por email. La ejecución se detiene completamente. No se mantienen recursos.

Cuando un aprobador actúa (aprobar o rechazar vía la API), resumeWorkflowFromApproval() carga la ejecución persistida, llama a reconstructPreviousOutputs() para reconstruir el Map previousStepOutputs desde los stepRuns guardados, y reanuda la ejecución desde el siguiente paso después de la puerta de aprobación.

La reconstrucción es recursiva — recorre thenStepRuns, elseStepRuns, iterations[] y branchStepRuns[] para restaurar cada salida anidada. Esto significa que las aprobaciones funcionan correctamente incluso cuando están dentro de una rama condicional o una iteración de bucle.

Paso de escritura a KB

Captura la salida de un paso y la escribe en un documento de base de conocimiento. Esto habilita flujos de trabajo que construyen conocimiento institucional — un flujo de triaje de soporte podría escribir resúmenes de resolución a una KB que futuras ejecuciones referencien vía RAG.

Paso de transferencia

Notifica a usuarios en un departamento objetivo vía email y notificación en la aplicación. Es un no-op para la ejecución — no produce salida ni bloquea el pipeline. Útil para flujos de escalación donde un humano necesita ser alertado pero el flujo de trabajo debería continuar.

Paso de acción de navegador

Ejecuta una llamada a herramienta MCP vía la extensión de navegador. Adquiere un cliente del pool de conexiones MCP, resuelve argumentos de plantilla (campos de entrada del flujo de trabajo y salidas de pasos anteriores) y devuelve el resultado de la herramienta.

Flujo de datos: resolución de plantillas

Los pasos se referencian entre sí a través de una sintaxis de plantilla resuelta en tiempo de ejecución:

  • {{workflow_input.fieldName}} — Referencia los datos de entrada del flujo de trabajo
  • {{step.stepId.path.to.value}} — Referencia la salida de un paso anterior usando notación de punto
  • {{loop_item.fieldName}} — Referencia el elemento actual en una iteración de bucle

El resolvedor usa getNestedValue() para el recorrido de ruta con notación de punto, manejando arrays y objetos anidados. Los mapeos de entrada declaran su fuente explícitamente: workflowInput, previousStep, static o loopItem.

Reintentos y manejo de errores

No todos los fallos son permanentes. Los límites de tasa, errores transitorios 5xx, timeouts y fallos de conexión son reintentables. Los errores de validación del lado del cliente (4xx) no lo son.

La estrategia de reintento usa backoff exponencial con jitter: Math.min(30000, 2000 * 2^attempt + jitter aleatorio). Eso es aproximadamente 2 segundos, 4 segundos, 8 segundos, 16 segundos, limitado a 30 segundos, con jitter aleatorio para evitar problemas de estampida. El máximo predeterminado de intentos es 3, configurable por paso.

Cada reintento se registra con el índice de intento y los detalles del error. Si todos los intentos fallan, el paso se marca como fallido y el flujo de trabajo termina (a menos que el manejo de errores a nivel de flujo de trabajo especifique lo contrario).

Control de concurrencia

Cada cuenta está limitada a 10 llamadas LLM concurrentes vía un semáforo Redis. Esto previene que un flujo de trabajo por lotes monopolice las conexiones del proveedor.

Las conexiones MCP usan un cliente con pool con desalojo LRU (máximo 20 conexiones, 60 segundos de timeout por inactividad). El pool maneja la configuración de conexión, keepalive y limpieza.

Ambos sistemas usan semántica de fallo abierto en errores de Redis — una capa de caché degradada nunca debería prevenir la ejecución del flujo de trabajo.

Observabilidad

Tres capas de observabilidad se ejecutan concurrentemente:

Métricas Prometheus rastrean workflowExecutionsTotal y workflowDuration por estado. Esto da paneles operacionales para tasas de éxito y percentiles de latencia.

Spans OpenTelemetry crean una jerarquía de trazas con spans a nivel de flujo de trabajo y paso, llevando ID del flujo de trabajo, nombre y metadatos de paso. Estos se integran con cualquier backend compatible con OTel.

Trazas de ejecución son un árbol jerárquico personalizado de spans persistido en Firestore vía disparar-y-olvidar. Estos potencian el inspector detallado de ejecuciones en la UI, mostrando exactamente qué pasó en cada paso incluyendo entradas, salidas, tiempos e intentos de reintento.

Verificaciones pre-vuelo

Antes de que comience la ejecución, el motor ejecuta validación:

  1. Validación de esquema de entrada — Las entradas del flujo de trabajo se verifican contra el esquema declarado
  2. Validación de mapeo — Todos los mapeos de entrada de pasos se verifican (los pasos referenciados existen, las rutas son plausibles)
  3. Verificación MCP — Si algún paso requiere acciones de navegador, el motor verifica la conectividad del cliente MCP por adelantado en lugar de fallar a mitad de ejecución
  4. Resolución de auto-contexto — Los documentos de base de conocimiento se resuelven desde fuentes de flujo de trabajo, departamento e ID explícito, para que el contexto RAG esté listo antes de que se ejecute el primer paso

Lecciones aprendidas

Las puertas de aprobación como excepciones simplifican la arquitectura. Nuestro primer diseño usaba una máquina de estados con estados explícitos de pausa/reanudación. El enfoque basado en excepciones es más limpio — el flujo de trabajo se ejecuta hacia adelante hasta que encuentra una aprobación, lanza, persiste y se detiene. La reanudación reconstruye el estado y continúa. Sin transiciones de estado complejas que gestionar.

La reconstrucción recursiva de salidas vale la complejidad. Reconstruir previousStepOutputs desde las ejecuciones de pasos persistidos requiere recorrer cada nivel de anidamiento — condiciones, bucles, ramas paralelas. Es código intrincado, pero significa que las aprobaciones funcionan correctamente a cualquier profundidad de anidamiento sin casos especiales.

Los timeouts por paso previenen retrasos en cascada. Una sola llamada LLM lenta no debería consumir todo el timeout del flujo de trabajo. El timeout predeterminado de 60 segundos por paso detecta solicitudes colgadas temprano, y el timeout a nivel de flujo de trabajo actúa como respaldo.

La observabilidad disparar-y-olvidar mantiene la ruta crítica rápida. Las trazas de ejecución, entregas de webhooks y salidas de destino se despachan asincrónicamente después de que la ejecución principal se completa. El usuario obtiene su resultado sin esperar a que las escrituras de observabilidad terminen.

workflow-engine architecture orchestration distributed-systems
Compartir este artículo

¿Le gustó este artículo?

Reciba consejos sobre flujos de trabajo, actualizaciones de producto y guías de automatización en su bandeja de entrada.

No spam. Unsubscribe anytime.