Artículo
· 8 hr atrás Lectura de 16 min

Creando agentes de IA: de cero a héroe

Aprenda a diseñar agentes de IA escalables y autónomos que combinen razonamiento, búsqueda vectorial e integración de herramientas utilizando LangGraph.

cover

Demasiado Largo; No lo Leí

  • Los agentes de IA son sistemas proactivos que combinan memoria, contexto e iniciativa para automatizar tareas que van más allá de los chatbots tradicionales.
  • LangGraph es un framework que nos permite crear flujos de trabajo complejos de IA, utilizando nodos (tareas) y aristas (conexiones) con gestión de estado integrada.
  • Esta guía te guiará en la creación de un agente de atención al cliente basado en IA que clasifica prioridades, identifica temas relevantes y determina si escalar o responder automáticamente.

Entonces, ¿Qué son Exactamente los Agentes de Inteligencia Artificial?

Seamos realistas: los "agentes de IA" pueden parecer los robots que tomarán el control de su sala de juntas. En realidad, son sus aliados proactivos que pueden optimizar flujos de trabajo complejos y eliminar tareas repetitivas. Piense en ellos como el siguiente paso evolutivo más allá de los chatbots: no se limitan a esperar indicaciones; inician acciones, coordinan múltiples pasos y se adaptan sobre la marcha.

Antes, crear un sistema "inteligente" significaba hacer malabarismos con distintos modelos para la comprensión del lenguaje, la generación de código, la búsqueda de datos, etc., y luego unirlos con cinta adhesiva. La mitad del tiempo se perdía en el infierno de la integración, mientras que la otra mitad se dedicaba a depurar el pegamento.

Los agentes dan la vuelta al guión. Combinan contexto, iniciativa y adaptabilidad en un único flujo orquestado. No se trata sólo de automatización, sino de inteligencia con una misión. Y gracias a marcos de trabajo como LangGraph, crear tu propio equipo de agentes puede ser... ¿me atrevería a decir que divertido?

image

¿Qué es LangGraph, Exactamente?

LangGraph es un framework innovador que revoluciona la forma de crear aplicaciones complejas con grandes modelos lingüísticos (LLM).

Imagina que diriges una orquesta: cada instrumento (o "nodo") necesita saber cuándo tocar, con qué volumen y en qué secuencia. LangGraph, en este caso, es tu batuta, dándote lo siguiente:

  • Estructura gráfica: Emplea una estructura de tipo gráfico con nodos y aristas, lo que permite a los desarrolladores diseñar flujos de trabajo flexibles y no lineales que admiten ramas y bucles. Refleja procesos complejos de toma de decisiones que se asemejan al funcionamiento de las vías neuronales.
  • Gestión de estados: LangGraph ofrece herramientas integradas para la persistencia del estado y la recuperación de errores, lo que simplifica el mantenimiento de los datos contextuales a través de diversas etapas dentro de una aplicación. Puede alternar eficazmente entre la memoria a corto y a largo plazo, mejorando la calidad de la interacción gracias a herramientas como Zep.
  • Integración de herramientas: Con LangGraph, los agentes LLM pueden colaborar fácilmente con servicios externos o bases de datos para obtener datos del mundo real, mejorando la funcionalidad y capacidad de respuesta de sus aplicaciones.
  • Human-in-the-Loop: Más allá de la automatización, LangGraph da cabida a las intervenciones humanas en los flujos de trabajo, que son cruciales para los procesos de toma de decisiones que requieren supervisión analítica o consideración ética.

Tanto si está construyendo un chatbot con memoria real, un motor de historias interactivo o un equipo de agentes que abordan un problema complejo, LangGraph convierte la fontanería que provoca dolores de cabeza en una máquina de estados limpia y visual.

Primeros Pasos

Para empezar con LangGraph, necesitarás una configuración básica que normalmente implica la instalación de bibliotecas esenciales como langgraph y langchain-openai. A partir de ahí, puedes definir los nodos (tareas) y las aristas (conexiones) dentro del grafo, implementando de forma efectiva puntos de control para la memoria a corto plazo y utilizando Zep para necesidades de memoria más persistentes.

Cuando utilices LangGraph, ten en cuenta lo siguiente:

  • Diseñar con flexibilidad: Aproveche la potente estructura gráfica para tener en cuenta las posibles ramificaciones e interacciones del flujo de trabajo que no sean estrictamente lineales.
  • Interactuar con herramientas cuidadosamente: Mejore, pero no sustituya, las capacidades de LLM con herramientas externas. Proporcione a cada herramienta descripciones completas para permitir un uso preciso.
  • Emplear soluciones de memoria enriquecida: Utilice la memoria de forma eficiente, tenga en cuenta la ventana de contexto del LLM y considere la posibilidad de integrar soluciones externas para la gestión automática de hechos.

Ahora que hemos cubierto los fundamentos de LangGraph, vamos a sumergirnos en un ejemplo práctico. Para ello, desarrollaremos un agente de IA diseñado específicamente para la atención al cliente.

Este agente recibirá solicitudes por correo electrónico, analizará la descripción del problema en el cuerpo del mensaje y determinará la prioridad de la solicitud y el tema/categoría/sector apropiado.

Así que abróchense los cinturones y ¡adelante!

buckle up

Para empezar, tenemos que definir qué es una "Herramienta" (Tool). Puedes pensar en ella como un "gestor asistente" especializado para tu agente, que le permite interactuar con funcionalidades externas.

El decorador @tool es esencial aquí. LangChain simplifica la creación de herramientas personalizadas, lo que significa que primero se define una función de Python y luego se aplica el decorador @tool.

tools

Ilustremos esto creando nuestra primera herramienta. Esta herramienta ayudará al agente a clasificar la prioridad de un ticket de soporte de TI basándose en su contenido de correo electrónico:

    from langchain_core.tools import tool

    @tool
    def classify_priority(email_body: str) -> str:
        """Classify the priority of an IT support ticket based on email content."""
        prompt = ChatPromptTemplate.from_template(
            """Analyze this IT support email and classify its priority as High, Medium, or Low.

            High: System outages, security breaches, critical business functions down
            Medium: Non-critical issues affecting productivity, software problems
            Low: General questions, requests, minor issues

            Email: {email}

            Respond with only: High, Medium, or Low"""
        )
        chain = prompt | llm
        response = chain.invoke({"email": email_body})
        return response.content.strip()

Excelente. Ahora tenemos un aviso que ordena a la IA recibir el cuerpo del correo electrónico, analizarlo y clasificar su prioridad como Alta, Media o Baja.

¡Ya está!. ¡Acaba de componer una herramienta que su agente puede llamar!

A continuación, creemos una herramienta similar para identificar el tema principal (o categoría) de la solicitud de asistencia:


@tool def identify_topic(email_body: str) -> str: """Identify the main topic/category of the IT support request.""" prompt = ChatPromptTemplate.from_template( """Analyze this IT support email and identify the main topic category. Categories: password_reset, vpn, software_request, hardware, email, network, printer, other Email: {email} Respond with only the category name (lowercase with underscores).""" ) chain = prompt | llm response = chain.invoke({"email": email_body}) return response.content.strip()

Ahora tenemos que crear un estado, y en LangGraph esta pequeña pieza es, más o menos, una gran cosa.

Considéralo el sistema nervioso central de tu gráfico. Es la forma en que los nodos se comunican entre sí, pasándose notas como si fueran alumnos aventajados en clase.

Según los documentos:

"Un estado es una estructura de datos compartida que representa la instantánea actual de tu aplicación".

¿En la práctica? El estado es un mensaje estructurado que se mueve entre nodos. Transporta la salida de un paso como entrada para el siguiente. Básicamente, es el pegamento que mantiene unido todo el flujo de trabajo.

Por lo tanto, antes de construir el gráfico, debemos definir la estructura de nuestro estado. En este ejemplo, nuestro estado incluirá lo siguiente:

  • La solicitud del usuario (cuerpo del correo electrónico)
  • La prioridad asignada
  • El tema identificado (categoría)

Es sencillo y limpio, para que puedas moverte por el gráfico como un profesional.

    from typing import TypedDict

    # Define the state structure
    class TicketState(TypedDict):
        email_body: str
        priority: str
        topic: str


    # Initialize state
    initial_state = TicketState(
        email_body=email_body,
        priority="",
        topic=""
    )

Nodos vs. Aristas: Componentes Clave de LangGraph

Los elementos fundamentales de LangGraph son los nodos y las aristas.

  • Nodos: Son las unidades operativas dentro del grafo, que realizan el trabajo real. Un nodo suele consistir en código Python que puede ejecutar cualquier lógica, desde cálculos hasta interacciones con modelos lingüísticos (LLM) o integraciones externas. Esencialmente, los nodos son como funciones individuales o agentes en la programación tradicional.
  • Aristas: Las aristas definen el flujo de ejecución entre nodos, determinando lo que ocurre a continuación. Actúan como conectores que permiten la transición del estado de un nodo a otro basándose en condiciones predefinidas. En el contexto de LangGraph, las aristas son cruciales para orquestar la secuencia y el flujo de decisiones entre nodos.

Para comprender la funcionalidad de las aristas, consideremos una simple analogía de una aplicación de mensajería:

  • Los nodos se asemejan a los usuarios (o sus dispositivos) que participan activamente en una conversación.
  • Las aristas simbolizan los hilos de conversación o las conexiones entre usuarios que facilitan la comunicación.

Cuando un usuario selecciona un hilo de chat para enviar un mensaje, se crea efectivamente un borde que lo vincula a otro usuario. Cada interacción, ya sea el envío de un mensaje de texto, voz o vídeo, sigue una secuencia predefinida, comparable al esquema estructurado del estado de LangGraph. Esto garantiza la uniformidad y la interpretabilidad de los datos transmitidos a lo largo del borde.

A diferencia de la naturaleza dinámica de las aplicaciones basadas en eventos, LangGraph emplea un esquema estático que se mantiene constante durante toda la ejecución. Simplifica la comunicación entre nodos, permitiendo a los desarrolladores confiar en un formato de estado estable, garantizando así una comunicación de borde sin fisuras.

Diseño de un Flujo de Trabajo Básico

La ingeniería de flujos en LangGraph puede conceptualizarse como el diseño de una máquina de estados. En este paradigma, cada nodo representa un estado o paso de procesamiento distinto, mientras que las aristas definen las transiciones entre esos estados. Este enfoque es particularmente beneficioso para los desarrolladores que buscan un equilibrio entre las secuencias de tareas deterministas y las capacidades de toma de decisiones dinámicas de la IA. Empecemos a construir nuestro flujo inicializando un StateGraph con la clase TicketState que definimos anteriormente.

    from langgraph.graph import StateGraph, START, END

    workflow = StateGraph(TicketState)

Adición de Nodos: Los nodos son bloques de construcción fundamentales, definidos para ejecutar tareas tan específicas como clasificar la prioridad de un ticket o identificar su tema.

Cada función de nodo recibe el estado actual, realiza su operación y devuelve un diccionario para actualizar el estado:

   def classify_priority_node(state: TicketState) -> TicketState:
        """Node to classify ticket priority."""
        priority = classify_priority.invoke({"email_body": state["email_body"]})
        return {"priority": priority}

    def identify_topic_node(state: TicketState) -> TicketState:
        """Node to identify ticket topic."""
        topic = identify_topic.invoke({"email_body": state["email_body"]})
        return {"topic": topic}


    workflow.add_node("classify_priority", classify_priority_node)
    workflow.add_node("identify_topic", identify_topic_node)

Los métodos classify_priority_node e identify_topic_node cambiarán el TicketState y enviarán la entrada de parámetros.

Creación de Aristas: Definir aristas para conectar nodos:


workflow.add_edge(START, "classify_priority") workflow.add_edge("classify_priority", "identify_topic") workflow.add_edge("identify_topic", END)

El classify_priority establece el inicio, mientras que el identify_topic determina el final de nuestro flujo de trabajo hasta el momento.

Compilación y Ejecución: Una vez configurados los nodos y las aristas, compile el flujo de trabajo y ejecútelo.


graph = workflow.compile() result = graph.invoke(initial_state)

¡Genial! También puede generar una representación visual de nuestro flujo LangGraph.

graph.get_graph().draw_mermaid_png(output_file_path="graph.png")

Si ejecutara el código hasta este punto, observaría un gráfico similar al siguiente:

first_graph.png

Esta ilustración visualiza una ejecución secuencial: inicio, seguido de clasificación de la prioridad, luego identificación del tema y, por último, finalización.

Uno de los aspectos más potentes de LangGraph es su flexibilidad, que nos permite crear flujos y aplicaciones más complejos. Por ejemplo, podemos modificar el flujo de trabajo para añadir aristas desde START a ambos nodos con la siguiente línea:

    workflow.add_edge(START, "classify_priority")
    workflow.add_edge(START, "identify_topic")

Este cambio implicará que el agente ejecute classify_priority e identify_topic simultáneamente.

Otra característica muy valiosa de LangGraph es la posibilidad de utilizar aristas condicionales. Permiten que el flujo de trabajo se ramifique en función de la evaluación del estado actual, permitiendo el enrutamiento dinámico de las tareas.

Vamos a mejorar nuestro flujo de trabajo. Crearemos una nueva herramienta que analice el contenido, la prioridad y el tema de la solicitud para determinar si se trata de un problema de alta prioridad que requiere escalado (es decir, la apertura de un ticket para un equipo humano). Si no es así, se generará una respuesta automática para el usuario.


@tool def make_escalation_decision(email_body: str, priority: str, topic: str) -> str: """Decide whether to auto-respond or escalate to IT team.""" prompt = ChatPromptTemplate.from_template( """Based on this IT support ticket, decide whether to: - "auto_respond": Send an automated response for simple/common or medium priority issues - "escalate": Escalate to the IT team for complex/urgent issues Email: {email} Priority: {priority} Topic: {topic} Consider: High priority items usually require escalation, while complex technical issues necessitate human review. Respond with only: auto_respond or escalate""" ) chain = prompt | llm response = chain.invoke({ "email": email_body, "priority": priority, "topic": topic }) return response.content.strip()

Además, si se determina que la solicitud es de prioridad baja o media (lo que lleva a una decisión de "auto_responder"), realizaremos una búsqueda vectorial para recuperar respuestas históricas. Esta información se utilizará entonces para generar una respuesta automática adecuada. Sin embargo, se necesitarán dos herramientas adicionales:


@tool def retrieve_examples(email_body: str) -> str: """Retrieve relevant examples from past responses based on email_body.""" try: examples = iris.cls(__name__).Retrieve(email_body) return examples if examples else "No relevant examples found." except: return "No relevant examples found." @tool def generate_reply(email_body: str, topic: str, examples: str) -> str: """Generate a suggested reply based on the email, topic, and RAG examples.""" prompt = ChatPromptTemplate.from_template( """Generate a professional IT support response based on: Original Email: {email} Topic Category: {topic} Example Response: {examples} Create a helpful, professional response that addresses the user's concern. Keep it concise and actionable.""" ) chain = prompt | llm response = chain.invoke({ "email": email_body, "topic": topic, "examples": examples }) return response.content.strip()

Ahora, definamos los nodos correspondientes a esas nuevas herramientas:


def decision_node(state: TicketState) -> TicketState: """Node to decide on escalation or auto-response.""" decision = make_escalation_decision.invoke({ "email_body": state["email_body"], "priority": state["priority"], "topic": state["topic"] }) return {"decision": decision} def rag_node(state: TicketState) -> TicketState: """Node to retrieve relevant examples using RAG.""" examples = retrieve_examples.invoke({"email_body": state["email_body"]}) return {"rag_examples": examples} def generate_reply_node(state: TicketState) -> TicketState: """Node to generate suggested reply.""" reply = generate_reply.invoke({ "email_body": state["email_body"], "topic": state["topic"], "examples": state["rag_examples"] }) return {"suggested_reply": reply} def execute_action_node(state: TicketState) -> TicketState: """Node to execute final action based on decision.""" if state["decision"] == "escalate": action = f"🚨 ESCALATED TO IT TEAM\nPriority: {state['priority']}\nTopic: {state['topic']}\nTicket created in system." print(f"[SYSTEM] Escalating ticket to IT team - Priority: {state['priority']}, Topic: {state['topic']}") else: action = f"✅ AUTO-RESPONSE SENT\nReply: {state['suggested_reply']}\nTicket logged for tracking." print(f"[SYSTEM] Auto-response sent to user - Topic: {state['topic']}") return {"final_action": action} workflow.add_node("make_decision", decision_node) workflow.add_node("rag", rag_node) workflow.add_node("generate_reply", generate_reply_node) workflow.add_node("execute_action", execute_action_node)

El borde condicional utilizará entonces la salida del nodo make_decision para dirigir el flujo:

    workflow.add_conditional_edges(
        "make_decision",
        lambda x: x.get("decision"),
        {
            "auto_respond": "rag",
            "escalate": "execute_action"
        }
    )

Si la herramienta make_escalation_decision (a través de decision_node) da como resultado "auto_respond", el flujo de trabajo procederá a través del nodo rag (para recuperar ejemplos), luego a generate_reply (para elaborar la respuesta), y finalmente a execute_action (para registrar la auto-respuesta).

Por el contrario, si la decisión es "escalar", el flujo pasará por alto el RAG y tomará pasos de generación, moviéndose directamente a execute_action para manejar la escalada. Para completar el gráfico añadiendo las aristas estándar restantes, haga lo siguiente:

    workflow.add_edge("rag", "generate_reply")
    workflow.add_edge("generate_reply", "execute_action")
    workflow.add_edge("execute_action", END)

Nota sobre el Dataset: Para este proyecto, el dataset que utilizamos para alimentar la generación mejorada de recuperación (RAG) procedía del dataset Customer Support Tickets dataset on Hugging Face. El dataset se filtró para incluir exclusivamente los elementos categorizados como "Asistencia técnica" y se restringió a entradas en inglés. De este modo, se garantizaba que el sistema RAG sólo recuperara ejemplos muy relevantes y específicos del ámbito de las tareas de asistencia técnica.

Llegados a este punto, nuestro gráfico debería parecerse al siguiente:

graph.png

Cuando ejecute este gráfico con un correo electrónico que resulte en una clasificación de alta prioridad y una decisión de "escalar", verá la siguiente respuesta:

image.png

Al mismo tiempo, una solicitud clasificada como de baja prioridad y que dé lugar a una decisión "auto_respuesta" desencadenará una respuesta parecida a la que se muestra a continuación:

image.png

Entonces... ¿Esto es todo Solete?

No del todo. Hay que tener cuidado con algunos puntos débiles:

  • Privacidad de los datos: Tenga cuidado con la información sensible - estos agentes requieren barandillas.
  • Costes de computación: Algunas configuraciones avanzadas requieren recursos importantes.
  • Alucinaciones: Los LLMs pueden ocasionalmente inventar cosas (aunque son más inteligentes que la mayoría de los internos).
  • No determinismo: La misma entrada puede devolver diferentes salidas, lo que es genial para la creatividad, pero complicado para los procesos estrictos.

Sin embargo, la mayoría de estos puntos débiles pueden gestionarse con una buena planificación, las herramientas adecuadas y -lo has adivinado- un poco de reflexión.

LangGraph hace que los agentes de IA dejen de ser palabras de moda para convertirse en soluciones reales y operativas. Tanto si desea automatizar la atención al cliente, gestionar incidencias informáticas o crear aplicaciones autónomas, este marco lo hace posible y, de hecho, agradable.

¿Tienes alguna pregunta o comentario? Hablemos. La revolución de la IA necesita constructores como tú.

Comentarios (2)1
Inicie sesión o regístrese para continuar