Arquitectura de un agente IA: una introducción a ReAct con LangGraph

En la era de la inteligencia artificial, los agentes autónomos han transformado la manera en que interactuamos con la tecnología, llevando a cabo tareas complejas con precisión y eficiencia. Pero, ¿cómo funcionan realmente? Este artículo desglosa los componentes básicos de un agente IA y presenta un enfoque innovador para su diseño basado en el concepto ReAct (Razonamiento y Acción) utilizando LangGraph y la biblioteca LangChain. Exploraremos cómo estas herramientas permiten construir agentes capaces de razonar, planificar y actuar de manera estructurada y efectiva.

Los Componentes Fundamentales de un Agente de IA: ¿Qué son y por qué importan?

Los agentes de inteligencia artificial se diseñan para funcionar de forma autónoma en entornos complejos, integrando cinco bloques esenciales: percepción, razonamiento, memoria, planificación y acción. Estos trabajan en conjunto, formando un ciclo que permite al agente analizar, decidir y ejecutar tareas con un propósito claro. A continuación, desglosamos cada uno de estos componentes:

1. Percepción

¿Qué es?: Es la capacidad del agente para captar información del entorno, como texto, imágenes o datos estructurados.

¿Por qué importa?: Sin una percepción precisa, el agente no puede comprender su contexto ni adaptarse a los cambios.

2. Razonamiento

¿Qué es?: Es el análisis de los datos percibidos para tomar decisiones basadas en lógica y contexto.

¿Por qué importa?: El razonamiento permite al agente evaluar escenarios y actuar con propósito, no solo reaccionar de manera automática.

3. Memoria

¿Qué es?: Es el almacenamiento de información relevante a corto y largo plazo, como interacciones previas o conocimientos generales.

¿Por qué importa?: La memoria le da continuidad al agente, mejorando su precisión y eficiencia en futuras interacciones.

4. Planificación

¿Qué es?: Es el proceso de definir pasos concretos para alcanzar los objetivos establecidos.

¿Por qué importa?: La planificación guía al agente para manejar tareas complejas y prever desafíos.

5. Acción

¿Qué es?: Es la ejecución de las decisiones del agente, como enviar una respuesta o realizar un ajuste en un sistema.

¿Por qué importa?: La acción convierte el análisis del agente en resultados tangibles.

Arquitectura Técnica

Los agentes de IA también tienen una arquitectura técnica que incluye:

1. Núcleo del Agente: Procesa e integra todas las funcionalidades.

2. Módulo de Memoria: Retiene y recupera información.

3. Herramientas y APIs: Recursos externos para tareas específicas.

4. Módulo de Planificación: Diseña estrategias para resolver problemas.

Cuando estos componentes trabajan en sinergia, el agente no solo funciona, sino que puede adaptarse, aprender y ofrecer un rendimiento óptimo en entornos dinámicos.

Simple ReAct Agent from Scratch

Algunos papers sobre agentes que pueden resultar interesantes para comprender su fucnionamiento y proceso de «razonamiento»:

  1. https://arxiv.org/pdf/2210.03629
  2. https://arxiv.org/pdf/2303.17651
  3. https://arxiv.org/pdf/2401.08500

Comencemos por crear un agente básico utilizando python, prompt engineering, funciones de calculo simples y todo bajo las lógicas de ReAct donde simplemente pongo a pensar al LLM en un proceso iterativo para que reflexione, haga observaciones sobre el desafío que le estamos dando y luego utilice funciones auxiliares para llegar a respuestas(la lógica de tools).

Para trabajar con este código debes crear un archivo .env en el directorio de tu IDE y definir alli las claves de tu API de Openai.

import openai
import re
import httpx
import os
from dotenv import load_dotenv

_ = load_dotenv()
from openai import OpenAI
_ = load_dotenv()
client = OpenAI(
    api_key=os.getenv('OPENAI_API_KEY')
)

Luego creamos la instancia para llamar al modelo y la probamos:

chat_completion = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": "Hello world"}]
)
chat_completion.choices[0].message.content

Ahora lo que hacemos es crear a nuestro Agente a partir de clases definidas por Langchain, en las que definimos los mensajes del usuario, el mensaje system y el mensaje de nuestro agente. Adicionalmente indicamos el modelo con el que estaremos trabajando y la temperatura que lo hace más determinista o más creativo según sea el caso de uso:

class Agent:
    def __init__(self, system=""):
        self.system = system
        self.messages = []
        if self.system:
            self.messages.append({"role": "system", "content": system})

    def __call__(self, message):
        self.messages.append({"role": "user", "content": message})
        result = self.execute()
        self.messages.append({"role": "assistant", "content": result})
        return result

    def execute(self):
        completion = client.chat.completions.create(
                        model="gpt-4o", 
                        temperature=0,
                        messages=self.messages)
        return completion.choices[0].message.content

Definimos el prompt para esta lógica de «razonamiento»:

prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.

Your available actions are:

calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary

average_dog_weight:
e.g. average_dog_weight: Collie
returns average weight of a dog when given the breed

Example session:

Question: How much does a Bulldog weigh?
Thought: I should look the dogs weight using average_dog_weight
Action: average_dog_weight: Bulldog
PAUSE

You will be called again with this:

Observation: A Bulldog weights 51 lbs

You then output:

Answer: A bulldog weights 51 lbs
""".strip()

Le damos una funcion auxiliar para que utilice en los calculos que necesita hacer(vendría a ser su tool):

def calculate(what):
    return eval(what)

def average_dog_weight(name):
    if name in "Scottish Terrier": 
        return("Scottish Terriers average 20 lbs")
    elif name in "Border Collie":
        return("a Border Collies average weight is 37 lbs")
    elif name in "Toy Poodle":
        return("a toy poodles average weight is 7 lbs")
    else:
        return("An average dog weights 50 lbs")

known_actions = {
    "calculate": calculate,
    "average_dog_weight": average_dog_weight
}

Creamos la lógica para que el agente piense, observe y tome una acción:

action_re = re.compile('^Action: (\w+): (.*)$')   # python regular expression to selection action
def query(question, max_turns=5):
    i = 0
    bot = Agent(prompt)
    next_prompt = question
    while i < max_turns:
        i += 1
        result = bot(next_prompt)
        print(result)
        actions = [
            action_re.match(a) 
            for a in result.split('\n') 
            if action_re.match(a)
        ]
        if actions:
            # There is an action to run
            action, action_input = actions[0].groups()
            if action not in known_actions:
                raise Exception("Unknown action: {}: {}".format(action, action_input))
            print(" -- running {} {}".format(action, action_input))
            observation = known_actions[action](action_input)
            print("Observation:", observation)
            next_prompt = "Observation: {}".format(observation)
        else:
            return

Desgloce del código:

1. Regular Expression (Regex): esta línea define un patrón para buscar instrucciones en el formato: “Action: [nombre_acción]: [entrada]”, por ejemplo: Action: search: Python.

2. Función query: esta es la función principal que orquesta el flujo de la interacción.

Parámetros:

  • question: La pregunta inicial para el agente.
  • max_turns: Número máximo de iteraciones para evitar bucles infinitos.

Flujo General:

  • a. Se inicializa el agente con un contexto (prompt).
  • b. El agente analiza la pregunta inicial y responde.
  • c. Si la respuesta contiene una acción, el código:
    • Ejecuta la acción usando funciones previamente definidas (known_actions).
    • Analiza el resultado de la acción (observación).
    • Usa esa observación para seguir razonando o responder.
  • d. Si no hay acciones en la respuesta, el agente devuelve su resultado final.

Le hacemos una pregunta para ver como lo hace:

question = """I have 2 dogs, a border collie and a scottish terrier. \
What is their combined weight"""
query(question)

Y podemos ver el resultado del proceso:

Thought: I need to find the average weight of a Border Collie and a Scottish Terrier, then add them together to get the combined weight.
Action: average_dog_weight: Border Collie
PAUSE
 -- running average_dog_weight Border Collie
Observation: a Border Collies average weight is 37 lbs
Action: average_dog_weight: Scottish Terrier
PAUSE
 -- running average_dog_weight Scottish Terrier
Observation: Scottish Terriers average 20 lbs
Thought: Now that I have the average weights of both dogs, I can calculate their combined weight by adding the two values together.
Action: calculate: 37 + 20
PAUSE
 -- running calculate 37 + 20
Observation: 57
Answer: The combined weight of a Border Collie and a Scottish Terrier is 57 lbs.

Con esto estamos creando una lógica básica de agente, como puedes ver la lógica está principalmente definida en el prompt y en este proceso «iterativo» por el que el agente es capaz de pasar siempre que lo vea necesario. Esto le da una capa de reflexión que funciona muy bien para algunos proyectos que requieren una suerte de «razonamiento» por parte del modelo.

Puedes aprender más y construir cosas como estas con el curso gratuito de Agentes con LangGraph impoartido por Deeplearning.ai

Scroll al inicio