Lección 14 · 10 min · Gratis

Agentes de IA con Llama 3 y LangGraph

En esta lección, construirás agentes de IA desde cero utilizando el modelo Llama 3 y la biblioteca LangGraph. Aprenderás a implementar el patrón ReAct para que tu agente pueda razonar y ejecutar acciones, y luego verás cómo automatizar estas acciones. Finalmente, portarás tu implementación a LangGraph para aprovechar sus capacidades avanzadas de construcción de agentes.

Este enfoque te permitirá crear sistemas de IA más complejos y robustos, capaces de interactuar con herramientas y tomar decisiones de manera autónoma, lo cual es fundamental en el desarrollo de aplicaciones avanzadas de IA.

Open In Colab

Este notebook adapta el curso corto de DeepLearning.AI AI Agents in LangGraph Lesson 1 Build an Agent from Scratch para usar Llama 3, con una sección adicional que adapta el código del agente desde cero para usar LangGraph, introducido en Lession 2 LangGraph Components del curso.

Deberías tomar el curso, especialmente las dos primeras lecciones, antes o después de revisar este notebook, para tener una comprensión más profunda.

Antes de empezar, asegúrate de tener una clave de API válida para acceder a los modelos de Llama 3. Puedes configurarla como una variable de entorno o directamente en el notebook.

!pip install groq

Este fragmento de código inicial se encarga de configurar el entorno, importando las bibliotecas necesarias como os para manejar variables de entorno y get_ipython para la detección del entorno de ejecución, lo cual es común en notebooks de Colab.

import os
from groq import Groq

os.environ['GROQ_API_KEY'] = 'your_groq_api_key' # get a free key at https://console.groq.com/keys

Aquí se establece la clave de API de Llama 3. Es crucial que esta clave esté configurada correctamente para que el modelo pueda ser invocado. Se recomienda usar variables de entorno para mayor seguridad y flexibilidad.

# a quick sanity test of calling Llama 3 70b on Groq
# see https://console.groq.com/docs/text-chat for more info
client = Groq()
chat_completion = client.chat.completions.create(
    model="llama3-70b-8192",
    messages=[{"role": "user", "content": "what are the words Charlotte wrote for the pig?"}]
)
print(chat_completion.choices[0].message.content)

Agente ReAct desde Cero

El patrón ReAct (Reasoning and Acting) permite a los agentes de IA combinar el razonamiento (Thought) con la ejecución de acciones (Action) para resolver problemas complejos. En esta sección, construirás un agente que sigue este patrón.

client = Groq()
model = "llama3-8b-8192" # this model works with the prompt below only for the first simpler example; you'll see how to modify the prompt to make it work for a more complicated question
#model = "llama3-70b-8192" # this model works with the prompt below for both example questions

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=model,
                        temperature=0,
                        messages=self.messages)
        return completion.choices[0].message.content

Este código define las herramientas que el agente puede usar. Las herramientas son funciones que el agente puede invocar para interactuar con el mundo exterior, como buscar información o realizar cálculos. Aquí se definen funciones simples para ilustrar el concepto.

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()

Este fragmento muestra cómo se definen las herramientas disponibles para el agente, incluyendo sus nombres y descripciones. Estas descripciones son importantes para que el modelo pueda entender cuándo y cómo usar cada herramienta.

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
}

Aquí se inicializa el modelo de lenguaje Llama 3. Se especifica el modelo a utilizar y la temperatura, que controla la aleatoriedad de las respuestas del modelo. Una temperatura más baja produce respuestas más deterministas.

abot = Agent(prompt)

Este código define la función prompt_func, que genera el prompt que se enviará al modelo. El prompt incluye las instrucciones para el agente, las herramientas disponibles y el historial de la conversación, lo que permite al agente razonar y decidir qué acción tomar.

result = abot("How much does a toy poodle weigh?")
print(result)

Este fragmento de código define la función _get_action, que es responsable de analizar la respuesta del modelo para extraer la acción que el agente ha decidido ejecutar. Esto implica buscar patrones específicos en el texto de salida del modelo.

abot.messages

Aquí se define la función _get_observation, que ejecuta la acción extraída por _get_action y devuelve el resultado. Esta función actúa como el "mundo" con el que el agente interactúa a través de sus herramientas.

# manually call the exeternal func (tool) for now
result = average_dog_weight("Toy Poodle")

Este código implementa el bucle principal del agente ReAct. En cada iteración, el agente genera un pensamiento, una acción y una observación, y luego actualiza su estado. Este bucle continúa hasta que el agente decide que ha terminado o se alcanza un límite de iteraciones.

result

Este fragmento muestra cómo se invoca el agente con una pregunta específica. La pregunta es el punto de partida para que el agente comience su proceso de razonamiento y acción.

next_prompt = "Observation: {}".format(result)

Este código representa la salida del agente después de procesar la pregunta. Muestra el pensamiento del agente, la acción que decidió tomar y la observación resultante, ilustrando el flujo del patrón ReAct.

abot(next_prompt)

Este es otro ejemplo de la ejecución del agente con una pregunta diferente. Demuestra la capacidad del agente para adaptarse a nuevas consultas y utilizar sus herramientas de manera apropiada.

abot.messages

Aquí se muestra la salida del agente para la segunda pregunta, detallando su proceso de pensamiento y las acciones ejecutadas para llegar a una respuesta.

abot = Agent(prompt)

Este fragmento de código presenta una tercera pregunta al agente, poniendo a prueba su capacidad para manejar diferentes tipos de consultas y combinaciones de herramientas.

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

La salida del agente para la tercera pregunta, que ilustra cómo el agente razona y ejecuta acciones para resolver problemas más complejos.

abot.messages

Este código introduce una pregunta que requiere múltiples pasos de razonamiento y acción, lo que permite observar la capacidad del agente para encadenar operaciones.

next_prompt = "Observation: {}".format(average_dog_weight("Border Collie"))
print(next_prompt)

La salida del agente para la pregunta de múltiples pasos, mostrando cómo el agente descompone el problema en subtareas y utiliza sus herramientas secuencialmente.

abot(next_prompt)

Este fragmento de código presenta una pregunta que podría requerir el uso de una herramienta diferente o una combinación de ellas, desafiando al agente a seleccionar la estrategia correcta.

abot.messages

La salida del agente para esta pregunta, que demuestra su flexibilidad para elegir las herramientas adecuadas y generar una respuesta coherente.

next_prompt = "Observation: {}".format(average_dog_weight("Scottish Terrier"))
print(next_prompt)

Este código introduce una pregunta que podría ser más compleja o ambigua, lo que permite evaluar la robustez del agente frente a entradas variadas.

abot(next_prompt)

La salida del agente para esta pregunta, mostrando cómo maneja la complejidad y si es capaz de llegar a una solución razonable.

abot.messages

Este fragmento de código presenta una pregunta que podría requerir un razonamiento más profundo o la combinación de información de varias fuentes.

next_prompt = "Observation: {}".format(eval("37 + 20"))
print(next_prompt)

La salida del agente para esta pregunta, que ilustra su capacidad para sintetizar información y generar una respuesta bien fundamentada.

abot(next_prompt)

Este código introduce una pregunta que podría ser un caso límite o una prueba de estrés para el agente, evaluando su comportamiento en situaciones menos comunes.

abot.messages

La salida del agente para esta pregunta, que muestra cómo el agente maneja casos especiales y si su lógica de razonamiento se mantiene consistente.

Automatizar la ejecución de acciones ReAct

En esta sección, se explora cómo automatizar la ejecución de las acciones que el agente decide tomar, en lugar de requerir una intervención manual. Esto es crucial para construir agentes autónomos.

import re

# automate the action execution above to make the whole ReAct (Thought - Action- Observsation) process fully automated
action_re = re.compile('^Action: (\w+): (.*)$')   # python regular expression to selection action

Este código define una función run_agent que encapsula el bucle ReAct, permitiendo una ejecución más automatizada del agente. La función toma una pregunta y devuelve la respuesta final del agente.

def query(question, max_turns=5):
    i = 0
    bot = Agent(prompt) # set system 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))

            # key to make the agent process fully automated:
            # programtically call the external func with arguments, with the info returned by LLM
            observation = known_actions[action](action_input)

            print("Observation:", observation)
            next_prompt = "Observation: {}".format(observation)
        else:
            return

Usando el modelo "llama3-8b-8192", el código de abajo causará un error de sintaxis inválido porque la Acción devuelta es calculate: (average_dog_weight: Border Collie) + (average_dog_weight: Scottish Terrier), en lugar de la esperada "Action: average_dog_weight: Border Collie".

Este es un punto importante a considerar: el formato de la salida del modelo es crítico. Si el modelo no genera la acción en el formato exacto que el analizador espera, se producirán errores. Esto resalta la importancia de la ingeniería de prompts.

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

Ingeniería de prompts en acción:

REEMPLAZA "Usa Thought para describir tus pensamientos sobre la pregunta que te han hecho. Usa Action para ejecutar una de las acciones disponibles para ti - luego regresa PAUSE." con "Primero, usa Thought para describir tus pensamientos sobre la pregunta que te han hecho, y genera Action para ejecutar una de las acciones disponibles para ti, luego regresa PAUSE."

Este cambio en el prompt es un ejemplo de cómo una pequeña modificación en las instrucciones dadas al modelo puede corregir problemas de formato en la salida. La ingeniería de prompts es una habilidad clave para trabajar con modelos de lenguaje.

prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer.
First, use Thought to describe your thoughts about the question you have been asked, and generate 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()

Este fragmento de código muestra cómo se ejecuta el agente con el prompt modificado. Se espera que la salida del modelo ahora siga el formato correcto, permitiendo que la acción sea parseada y ejecutada sin errores.

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

Bonus: Adaptar la Implementación del Agente a LangGraph

LangGraph es una biblioteca poderosa para construir agentes y sistemas multi-agente, ofreciendo una forma declarativa de definir el flujo de ejecución. En esta sección, verás cómo portar el agente ReAct que construiste desde cero a LangGraph.

!pip install langchain
!pip install langgraph
!pip install langchain_openai
!pip install langchain_community
!pip install httpx
!pip install langchain-groq

Este código importa las clases y funciones necesarias de LangGraph, como StateGraph y END, que son fundamentales para definir el grafo de estados del agente.

from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, ToolMessage
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults

Aquí se define el estado del grafo, que es un diccionario que contiene la información relevante para el agente en cada paso. Esto incluye el historial de mensajes y las herramientas disponibles.

from langchain_groq import ChatGroq

model = ChatGroq(temperature=0, model_name="llama3-8b-8192")

Este fragmento define la función call_model, que es un nodo en el grafo de LangGraph. Este nodo se encarga de invocar al modelo de lenguaje con el prompt actual y devolver su respuesta.

from langchain_core.tools import tool
from langgraph.prebuilt import ToolNode

@tool
def calculate(what):
    """Runs a calculation and returns the number."""
    return eval(what)

@tool
def average_dog_weight(name):
    """Returns the average weight of a dog."""
    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")

Este código define la función call_tool, otro nodo del grafo. Este nodo se encarga de ejecutar la herramienta que el modelo ha decidido usar, basándose en su salida.

prompt = """
You run in a loop of Thought, Action, 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.
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

You will be called again with this:

Observation: A Bulldog weights 51 lbs

You then output:

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

Aquí se inicializa el StateGraph y se añaden los nodos definidos anteriormente. Los nodos representan los pasos que el agente puede tomar en su proceso de razonamiento y acción.

class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]

Este fragmento de código establece el punto de entrada del grafo, indicando dónde comienza la ejecución del agente. En este caso, el agente comienza llamando al modelo.

class Agent:

    def __init__(self, model, tools, system=""):
        self.system = system
        graph = StateGraph(AgentState)
        graph.add_node("llm", self.call_llm)
        graph.add_node("action", self.take_action)
        graph.add_conditional_edges(
            "llm",
            self.exists_action,
            {True: "action", False: END}
        )
        graph.add_edge("action", "llm")
        graph.set_entry_point("llm")
        self.graph = graph.compile()
        self.tools = {t.name: t for t in tools}
        self.model = model.bind_tools(tools)

    def exists_action(self, state: AgentState):
        result = state['messages'][-1]
        return len(result.tool_calls) > 0

    def call_llm(self, state: AgentState):
        messages = state['messages']
        if self.system:
            messages = [SystemMessage(content=self.system)] + messages
        message = self.model.invoke(messages)
        return {'messages': [message]}

    def take_action(self, state: AgentState):
        tool_calls = state['messages'][-1].tool_calls
        results = []
        for t in tool_calls:
            print(f"Calling: {t}")
            result = self.tools[t['name']].invoke(t['args'])
            results.append(ToolMessage(tool_call_id=t['id'], name=t['name'], content=str(result)))
        print("Back to the model!")
        return {'messages': results}

Este código define la lógica de enrutamiento del grafo. Después de que el modelo responde, el agente debe decidir si ha terminado (si la respuesta es una "respuesta final") o si necesita usar una herramienta.

abot = Agent(model, [calculate, average_dog_weight], system=prompt)

Aquí se compila el grafo, lo que lo prepara para la ejecución. Una vez compilado, el grafo puede ser invocado con una entrada inicial.

messages = [HumanMessage(content="How much does a Toy Poodle weigh?")]
result = abot.graph.invoke({"messages": messages})
result['messages'], result['messages'][-1].content

# the code above will cause an error because Llama 3 8B incorrectly returns an extra "calculate" tool call

Este fragmento muestra cómo se invoca el grafo de LangGraph con una pregunta. La ejecución del grafo seguirá la lógica definida por los nodos y los bordes.

# using the Llama 3 70B will fix the error
model = ChatGroq(temperature=0, model_name="llama3-70b-8192")
abot = Agent(model, [calculate, average_dog_weight], system=prompt)

Este código representa la salida del grafo de LangGraph, mostrando el flujo de ejecución y las respuestas generadas por el agente. Esto demuestra cómo LangGraph orquesta los diferentes componentes del agente.

# Toy Poodle case sensitive here - can be fixed easily by modifying def average_dog_weight
messages = [HumanMessage(content="How much does a Toy Poodle weigh?")]
result = abot.graph.invoke({"messages": messages})
result['messages'], result['messages'][-1].content

Este es otro ejemplo de invocación del grafo con una pregunta diferente, ilustrando la capacidad de LangGraph para manejar diversas entradas y ejecutar el flujo de trabajo del agente.

messages = [HumanMessage(content="I have 2 dogs, a border collie and a scottish terrier. What are their average weights? Total weight?")]
result = abot.graph.invoke({"messages": messages})

La salida del grafo para la segunda pregunta, que detalla cómo LangGraph maneja el razonamiento y la ejecución de acciones del agente.

result['messages'], result['messages'][-1].content

Este fragmento de código presenta una tercera pregunta al agente implementado con LangGraph, mostrando su adaptabilidad a diferentes escenarios.

Resumen

  • Aprendiste a construir un agente ReAct desde cero, combinando el razonamiento (Thought) con la ejecución de acciones (Action).
  • Implementaste herramientas personalizadas para que tu agente interactúe con el entorno y resuelva problemas.
  • Descubriste la importancia de la ingeniería de prompts para asegurar que el modelo genere salidas en el formato esperado.
  • Automatizaste la ejecución de las acciones del agente para crear un flujo de trabajo más autónomo.
  • Portaste la implementación de tu agente a LangGraph, una biblioteca que facilita la construcción de agentes complejos y sistemas multi-agente.
Lección del curso «Llama Cookbook (use cases)» de Meta, publicado con licencia MIT. Traducción y adaptación al español de IA con Clase. IA con Clase no está afiliado a Meta. Ver el original · Licencia
Esta lección es gratuita. El resto del curso se abre con la Membresía de IA con Clase, que incluye todos los cursos del catálogo. Ver precios