Creación de flujos de trabajo agentes humanos en el circuito | Hacia la ciencia de datos

como GPT-5.4 de OpenAI y Opus 4.6 de Anthropic han demostrado capacidades sobresalientes en la ejecución de tareas agentes de larga duración.

Como resultado, vemos un mayor uso de agentes LLM en entornos individuales y empresariales para realizar tareas complejas, como ejecutar análisis financieros, crear aplicaciones y realizar investigaciones exhaustivas.

Estos agentes, ya sean parte de una configuración altamente autónoma o de un flujo de trabajo predefinido, pueden ejecutar tareas de varios pasos utilizando herramientas para lograr objetivos con una mínima supervisión humana.

Sin embargo, "mínima" no significa cero supervisión humana.

Por el contrario, la revisión humana sigue siendo importante debido a la naturaleza probabilística inherente de los LLM y la posibilidad de errores.

Estos errores pueden propagarse y agravarse a lo largo del flujo de trabajo, especialmente cuando unimos numerosos componentes agentes.

Habrás notado el impresionante progreso que han logrado los agentes en el ámbito de la codificación. La razón es que el código es relativamente fácil de verificar (es decir, se ejecuta o falla y la retroalimentación es visible de inmediato).

Pero en áreas como la creación de contenidos, la investigación o la toma de decisiones, la corrección suele ser subjetiva y más difícil de evaluar automáticamente.

Es por eso que el diseño human-in-the-loop (HITL) sigue siendo fundamental.

En este artículo, explicaremos cómo utilizar LangGraph para configurar un flujo de trabajo agente humano en el circuito para la generación y publicación de contenido en Bluesky.

Contenido

(1) Introducción a LangGraph
(2) Ejemplo de flujo de trabajo
(3) Conceptos clave
(4) Tutorial del código
(5) Mejores prácticas de interrupciones

Puede encontrar el repositorio de GitHub adjunto aquí.

(1) Introducción a LangGraph

LangGraph (parte del ecosistema LangChain) es un marco de orquestación de agentes de bajo nivel y un tiempo de ejecución para crear flujos de trabajo agentes.

Es mi marco de referencia dado su alto grado de control y personalización, lo cual es vital para soluciones de nivel de producción.

Si bien LangChain ofrece un objeto de middleware (HumanInTheLoopMiddleware) para comenzar fácilmente con la supervisión humana en las llamadas de los agentes, se realiza con un alto nivel de abstracción que enmascara la mecánica subyacente.

LangGraph, por el contrario, no abstrae las indicaciones ni la arquitectura, lo que nos brinda el grado más preciso de control que necesitamos. Nos permite definir explícitamente:

Cómo fluyen los datos entre pasos Dónde ocurren las decisiones y ejecuciones de código Dónde se requiere intervención humana

Por lo tanto, utilizaremos LangGraph para demostrar el concepto HITL dentro de un flujo de trabajo agente.

También es útil distinguir entre flujos de trabajo de agentes y agentes de IA autónomos.

Los flujos de trabajo agentes tienen rutas predeterminadas y están diseñados para ejecutarse en un orden definido, con LLM y/o agentes integrados en uno o más componentes. Por otro lado, los agentes de IA planifican, ejecutan e iteran de forma autónoma hacia un objetivo.

En este artículo, nos centramos en los flujos de trabajo agentes, en los que insertamos deliberadamente puntos de control humanos en un flujo predefinido.

Comparación de flujos de trabajo agentes y agentes LLM | Imagen utilizada bajo licencia

(2) Ejemplo de flujo de trabajo

Para nuestro ejemplo, construiremos un flujo de trabajo de generación de contenido de redes sociales de la siguiente manera:

Flujo de trabajo de generación de contenido | Imagen del autor

El usuario ingresa un tema de interés (por ejemplo, “últimas noticias sobre Anthropic”). El nodo de búsqueda web utiliza la herramienta Tavily para buscar en línea artículos que coincidan con los primeros. El resultado de búsqueda superior se selecciona y se introduce en un LLM en el nodo de creación de contenido para generar una publicación en las redes sociales. En el nodo de revisión, hay dos puntos de control de revisión humana:
(i) Presentar contenido generado para que los humanos lo aprueben, rechacen o editen;
(ii) Tras la aprobación, el flujo de trabajo activa la herramienta API de Bluesky y solicita la confirmación final antes de publicarla en línea.

Así es como se ve cuando se ejecuta desde la terminal:

Ejecución del flujo de trabajo en la terminal | Imagen del autor

Y aquí está la publicación en vivo en mi perfil de Bluesky:

Publicación de Bluesky en las redes sociales generada a partir del flujo de trabajo | Imagen del autor

Bluesky es una plataforma social similar a Twitter (X) y se elige en esta demostración porque su API es mucho más fácil de acceder y utilizar.

(3) Conceptos clave

El mecanismo central detrás de la configuración HITL en LangGraph es el concepto de interrupciones.

Las interrupciones (usando interrupción() y Comando en LangGraph) nos permiten pausar la ejecución del gráfico en puntos específicos, mostrar cierta información al humano y esperar su entrada antes de reanudar el flujo de trabajo.

El comando es un objeto versátil que nos permite actualizar el estado del gráfico (actualizar), especificar el siguiente nodo a ejecutar (ir a) o capturar el valor para reanudar la ejecución del gráfico (reanudar).

Así es como se ve el flujo:

(1) Al llegar a la función de interrupción(), la ejecución se detiene y la carga útil que se le pasa se muestra al usuario. La carga útil pasada en la interrupción normalmente debe tener formato JSON o cadena, por ejemplo,

decisión = interrupción ("¿Deberíamos ir a almorzar KFC?") # Cadena mostrada al usuario

(2) Después de que el usuario responde, pasamos los valores de respuesta al gráfico para reanudar la ejecución. Implica usar Command y su parámetro resume como parte de volver a invocar el gráfico:

si respuesta_humana == "sí": devuelve graph.invoke(Command(resume="KFC")) de lo contrario: devuelve graph.invoke(Command(resume="McDonalds"))

(3) El valor de respuesta en el resumen se devuelve en la variable de decisión, que el nodo utilizará para el resto de la ejecución del nodo y el flujo del gráfico posterior:

si decisión == "KFC": devolver Comando(goto="kfc_order_node", actualizar={"lunch_choice": "KFC") más: devolver Comando(goto="mcd_order_node", actualizar={"lunch_choice": "McDonalds")

Las interrupciones son dinámicas y se pueden colocar en cualquier parte del código, a diferencia de los puntos de interrupción estáticos, que se fijan antes o después de nodos específicos.

Dicho esto, normalmente colocamos interrupciones dentro de los nodos o dentro de las herramientas llamadas durante la ejecución del gráfico.

Finalmente, hablemos de los puntos de control. Cuando un flujo de trabajo se detiene ante una interrupción, necesitamos una forma de guardar su estado actual para poder reanudarlo más tarde.

Por lo tanto, necesitamos un punto de control para conservar el estado de modo que no se pierda durante la pausa de interrupción. Piense en un punto de control como una instantánea del estado del gráfico en un momento determinado.

Para el desarrollo, es aceptable guardar el estado en la memoria con el puntero de control InMemorySaver.

Para la producción, es mejor utilizar tiendas como Postgres o Redis. Con eso en mente, usaremos el punto de control SQLite en este ejemplo en lugar de un almacén en memoria.

Para garantizar que el gráfico se reanude exactamente en el punto donde ocurrió la interrupción, debemos pasar y usar el mismo ID de hilo.

Piense en un hilo como una sesión de ejecución única (como una conversación individual separada) donde cada uno tiene una identificación única y mantiene su propio estado e historial.

El ID del hilo se pasa a la configuración en cada invocación del gráfico para que LangGraph sepa desde qué estado reanudar después de la interrupción.

Ahora que hemos cubierto los conceptos de interrupciones, comandos, puntos de control y subprocesos, entremos en el tutorial del código.

Como la atención se centrará en la mecánica del ser humano en el circuito, no cubriremos la configuración completa del código. Visite el repositorio de GitHub para ver la implementación completa.

(4) Tutorial del código

(4.1) Configuración inicial

Comenzamos instalando las dependencias necesarias y generando claves API para Bluesky, OpenAI, LangChain, LangGraph y Tavily.

# requisitos.txt langchain-openai>=1.1.9 langgraph>=1.0.8 langgraph-checkpoint-sqlite>=3.0.3 openai>=2.20.0 tavily-python>=0.7.21 # env.example export OPENAI_API_KEY=your_openai_api_key export TAVILY_API_KEY=your_tavily_api_key exportar BLUESKY_HANDLE=tu nombre.bsky.social exportar BLUESKY_APP_PASSWORD=tu_contraseña_de_aplicación_bluesky

(4.2) Definir estado

Configuramos el Estado, que es el objeto de datos estructurados y compartidos que sirve como memoria central del gráfico. Incluye campos que capturan información clave, como el contenido de la publicación y el estado de aprobación.

La clave post_data es donde se almacenará el contenido de la publicación generada.

(4.3) Interrupción a nivel de nodo

Mencionamos anteriormente que las interrupciones pueden ocurrir a nivel de nodo o dentro de llamadas a herramientas. Veamos cómo funciona el primero configurando el nodo de revisión humana.

El propósito del nodo de revisión es pausar la ejecución y presentar el borrador del contenido al usuario para su revisión.

Aquí vemos la interrupción() en acción (líneas 8 a 13), donde la ejecución del gráfico se detiene en la primera sección de la función del nodo.

La clave de detalles pasada a interrupt() contiene el contenido generado, mientras que la clave de acción activa una función de controlador (handle_content_interrupt()) para respaldar la revisión:

El contenido generado se imprime en el terminal para que el usuario lo vea y pueda aprobarlo tal cual, rechazarlo directamente o editarlo directamente en el terminal antes de aprobarlo.

Según la decisión, la función de controlador devuelve uno de tres valores:

Verdadero (aprobado), Falso (rechazado) o Valor de cadena correspondiente al contenido editado por el usuario (editado).

Este valor de retorno se devuelve al nodo de revisión usando graph.invoke(Command=resume…), que reanuda la ejecución desde donde se llamó a interrupción() (línea 15) y determina a qué nodo ir a continuación: aprobar, rechazar o editar el contenido y proceder a aprobar.

(4.4) Interrupción a nivel de herramienta

Las interrupciones también se pueden definir en el nivel de llamada de herramienta. Esto se demuestra en el siguiente punto de control de revisión humana en el nodo de aprobación antes de que el contenido se publique en línea en Bluesky.

En lugar de colocar interrupción() dentro de un nodo, lo colocamos dentro de la herramienta publicar_post que crea publicaciones a través de la API de Bluesky:

Al igual que lo que vimos a nivel de nodo, llamamos a una función de controlador (handle_publish_interrupt) para capturar la decisión humana:

El valor de retorno de este paso de revisión es:

{"acción": "confirmar"}, o {"acción": "cancelar},

La última parte del código (es decir, desde la línea 19) en la herramienta publicar_post utiliza este valor de retorno para determinar si se debe continuar con la publicación en Bluesky o no.

(4.5) Configurar gráfico con puntero de control

A continuación, conectamos los nodos en un gráfico para su compilación e introducimos un puntero de control SQLite para capturar instantáneas del estado en cada interrupción.

SQLite de forma predeterminada solo permite que el hilo que creó la conexión de la base de datos lo use. Dado que LangGraph utiliza un grupo de subprocesos para escrituras de puntos de control, debemos configurar check_same_thread=False para permitir que esos subprocesos también accedan a la conexión.

(4.6) Configurar el flujo de trabajo completo con Config

Con el gráfico listo, ahora lo colocamos en un flujo de trabajo que inicia el proceso de generación de contenido.

Este flujo de trabajo incluye la configuración de un ID de subproceso, que se pasa a eachgraph.invoke(). Este ID es el vínculo que une las invocaciones, de modo que el gráfico se detiene en una interrupción y se reanuda desde donde lo dejó.

Es posible que hayas notado la clave __interrupt__ en el código anterior. Es simplemente una clave especial que LangGraph agrega al resultado cada vez que se presiona una interrupción ().

En otras palabras, es la señal principal que indica que la ejecución del gráfico se ha detenido y está esperando la intervención humana antes de continuar.

Al colocar __interrupt__ como parte de un bucle while, significa que el bucle sigue comprobando si una interrupción aún está en curso. Una vez que se resuelve la interrupción, la clave desaparece y el ciclo while sale.

Con el flujo de trabajo completo, podemos ejecutarlo así:

run_hitl_workflow(query="últimas noticias sobre Anthropic")

(5) Mejores prácticas de interrupciones

Si bien las interrupciones son poderosas para habilitar los flujos de trabajo HITL, pueden ser perjudiciales si se usan incorrectamente.

Como tal, recomiendo leer esta documentación de LangGraph. Aquí hay algunas reglas prácticas a tener en cuenta:

No envuelva las llamadas de interrupción en bloques try/except, o no pausarán la ejecución correctamente Mantenga las llamadas de interrupción en el mismo orden cada vez y no las omita ni reorganice Solo pase valores seguros JSON a las interrupciones y evite objetos complejos Asegúrese de que cualquier código antes de una interrupción pueda ejecutarse de manera segura varias veces (es decir, idempotencia) o muévalo después de la interrupción

Por ejemplo, tuve un problema en el nodo de búsqueda web donde puse una interrupción justo después de la búsqueda de Tavily. La intención era hacer una pausa y permitir a los usuarios revisar los resultados de búsqueda para la generación de contenido.

Pero debido a que las interrupciones funcionan volviendo a ejecutar los nodos desde los que fueron llamadas, el nodo simplemente volvió a ejecutar la búsqueda web y pasó un conjunto de resultados de búsqueda diferente a los que aprobé anteriormente.

Por lo tanto, las interrupciones funcionan mejor como puerta antes de una acción, pero si las usamos después de un paso no determinista (como una búsqueda), debemos conservar el resultado o arriesgarnos a obtener algo diferente en el currículum.

Resumiendo

La revisión humana puede parecer un cuello de botella en las tareas de agencia, pero sigue siendo crítica, especialmente en dominios donde los resultados son subjetivos o difíciles de verificar.

LangGraph facilita la creación de flujos de trabajo HITL con interrupciones y puntos de control.

Por lo tanto, el desafío es decidir dónde ubicar esos puntos de decisión humana para lograr un buen equilibrio entre supervisión y eficiencia.