Logotipo de Zephyrnet

Cree un agente de codificación de IA con LangGraph de LangChain

Fecha:

Introducción

Ha habido un aumento masivo de aplicaciones que utilizan agentes de codificación de IA. Con la creciente calidad de los LLM y la disminución del costo de la inferencia, cada vez es más fácil crear agentes de IA capaces. Además de esto, el ecosistema de herramientas está evolucionando rápidamente, lo que facilita la creación de agentes de codificación de IA complejos. El marco Langchain ha sido líder en este frente. Tiene todas las herramientas y técnicas necesarias para crear aplicaciones de IA listas para producción.

Pero hasta ahora le faltaba una cosa. Y esa es una colaboración de múltiples agentes con ciclicidad. Esto es crucial para resolver problemas complejos, donde el problema se puede dividir y delegar en agentes especializados. Aquí es donde LangGraph entra en escena, una parte del marco Langchain diseñado para dar cabida a la colaboración con estado de múltiples actores entre agentes de codificación de IA. Además, en este artículo, analizaremos LangGraph y sus componentes básicos mientras construimos un agente con él.

OBJETIVOS DE APRENDIZAJE

  • Comprenda qué es LangGraph.
  • Explore los conceptos básicos de LangGraph para crear agentes con estado.
  • Explore TogetherAI para acceder a modelos de acceso abierto como ProfundoSeekCoder.
  • Cree un agente de codificación de IA utilizando LangGraph para escribir pruebas unitarias.
LangChain

Este artículo fue publicado como parte del Blogatón de ciencia de datos.

Tabla de contenidos.

¿Qué es LangGraph?

LangGraph es una extensión del ecosistema LangChain. Si bien LangChain permite crear agentes de codificación de IA que pueden usar múltiples herramientas para ejecutar tareas, no puede coordinar múltiples cadenas o actores a lo largo de los pasos. Este es un comportamiento crucial para crear agentes que realicen tareas complejas. LangGraph fue concebido teniendo estas cosas en mente. Trata los flujos de trabajo del Agente como una estructura gráfica cíclica, donde cada nodo representa una función o un objeto Langchain Runnable, y los bordes son conexiones entre nodos. 

Las características principales de LangGraph incluyen 

  • Nodes: Cualquier función u objeto Langchain Runnable como una herramienta.
  • Bordes: Define la dirección entre nodos.
  • Gráficos con estado: El tipo principal de gráfico. Está diseñado para gestionar y actualizar objetos de estado mientras procesa datos a través de sus nodos.

LangGraph aprovecha esto para facilitar la ejecución cíclica de llamadas LLM con persistencia de estado, lo cual es crucial para el comportamiento agente. La arquitectura se inspira en Pregel y Haz Apache

En este artículo, crearemos un Agente para escribir pruebas unitarias de Pytest para una clase de Python con métodos. Y este es el flujo de trabajo.

LangChain

Discutiremos los conceptos en detalle a medida que construyamos nuestro agente de codificación de IA para escribir pruebas unitarias simples. Entonces, vayamos a la parte de codificación.

Pero antes de eso, configuremos nuestro entorno de desarrollo.

Instalar dependencias

Primero lo primero. Como con cualquier proyecto de Python, cree un entorno virtual y actívelo.

python -m venv auto-unit-tests-writer
cd auto-unit-tests-writer
source bin/activate

Ahora, instala las dependencias.

!pip install langgraph langchain langchain_openai colorama

Importa todas las bibliotecas y sus clases.

from typing import TypedDict, List
import colorama
import os

from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage
from langchain_core.messages import HumanMessage
from langchain_core.runnables import RunnableConfig

from langgraph.graph import StateGraph, END
from langgraph.pregel import GraphRecursionError

También querremos crear los directorios y archivos para los casos de prueba. Puede crear archivos manualmente o usar Python para eso.

# Define the paths.
search_path = os.path.join(os.getcwd(), "app")
code_file = os.path.join(search_path, "src/crud.py")
test_file = os.path.join(search_path, "test/test_crud.py")

# Create the folders and files if necessary.
if not os.path.exists(search_path):
    os.mkdir(search_path)
    os.mkdir(os.path.join(search_path, "src"))
    os.mkdir(os.path.join(search_path, "test"))

Ahora, actualice el archivo crud.py con código para una aplicación CRUD en memoria. Usaremos este fragmento de código para escribir pruebas unitarias. Puedes usar tu programa Python para esto. Agregaremos el siguiente programa a nuestro archivo code.py.

#crud.py
code = """class Item:
    def __init__(self, id, name, description=None):
        self.id = id
        self.name = name
        self.description = description

    def __repr__(self):
        return f"Item(id={self.id}, name={self.name}, description={self.description})"

class CRUDApp:
    def __init__(self):
        self.items = []

    def create_item(self, id, name, description=None):
        item = Item(id, name, description)
        self.items.append(item)
        return item

    def read_item(self, id):
        for item in self.items:
            if item.id == id:
                return item
        return None

    def update_item(self, id, name=None, description=None):
        for item in self.items:
            if item.id == id:
                if name:
                    item.name = name
                if description:
                    item.description = description
                return item
        return None

    def delete_item(self, id):
        for index, item in enumerate(self.items):
            if item.id == id:
                return self.items.pop(index)
        return None

    def list_items(self):
        return self.items"""
        
with open(code_file, 'w') as f:
  f.write(code)

Configurar el LLM

Ahora, especificaremos el LLM que usaremos en este proyecto. El modelo a utilizar aquí depende de las tareas y la disponibilidad de recursos. Puede utilizar modelos patentados y potentes como GPT-4, Gemini Ultra o GPT-3.5. Además, puedes utilizar modelos de acceso abierto como Mixtral y Llama-2. En este caso, como se trata de escribir códigos, podemos utilizar un modelo de codificación ajustado como el codificador DeepSeekCoder-33B o Llama-2. Ahora, existen múltiples plataformas para la inferencia de LLM, como Anayscale, Abacus y Together. Usaremos Together AI para inferir DeepSeekCoder. Entonces, consigue un Clave API de Together antes de seguir adelante. 

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(base_url="https://api.together.xyz/v1",
    api_key="your-key",
    model="deepseek-ai/deepseek-coder-33b-instruct")

Como Together API es compatible con OpenAI SDK, podemos usar OpenAI SDK de Langchain para comunicarnos con modelos alojados en Together cambiando el parámetro base_url a “https://api.together.xyz/v1”. En api_key, pase su clave API de Together y, en lugar de modelos, pase la nombre del modelo disponible en Juntos.

Definir el estado del agente

Esta es una de las partes cruciales de LangGraph. Aquí definiremos un AgentState, responsable de realizar un seguimiento de los estados de los Agentes durante la ejecución. Se trata principalmente de una clase TypedDict con entidades que mantienen el estado de los Agentes. Definamos nuestro AgentState

class AgentState(TypedDict):
    class_source: str
    class_methods: List[str]
    tests_source: str

En la clase AgentState anterior, class_source almacena la clase Python original, class_methods para almacenar métodos de la clase y tests_source para códigos de prueba unitaria. Los definimos como AgentState para usarlos en los pasos de ejecución. 

Ahora, defina el gráfico con AgentState.

# Create the graph.
workflow = StateGraph(AgentState)

Como se mencionó anteriormente, este es un gráfico con estado y ahora hemos agregado nuestro objeto de estado.

Definir nodos

Ahora que hemos definido AgentState, necesitamos agregar nodos. Entonces, ¿qué son exactamente los nodos? En LangGraph, los nodos son funciones o cualquier objeto ejecutable, como las herramientas Langchain, que realizan una única acción. En nuestro caso, podemos definir varios nodos, como una función para encontrar métodos de clase, una función para inferir y actualizar pruebas unitarias para declarar objetos y una función para escribirlas en un archivo de prueba.

También necesitamos una forma de extraer códigos de un mensaje LLM. Así es cómo.

def extract_code_from_message(message):
    lines = message.split("n")
    code = ""
    in_code = False
    for line in lines:
        if "```" in line:
            in_code = not in_code
        elif in_code:
            code += line + "n"
    return code

El fragmento de código aquí supone que los códigos están dentro de comillas triples.

Ahora, definamos nuestros nodos.

import_prompt_template = """Here is a path of a file with code: {code_file}.
Here is the path of a file with tests: {test_file}.
Write a proper import statement for the class in the file.
"""
# Discover the class and its methods.
def discover_function(state: AgentState):
    assert os.path.exists(code_file)
    with open(code_file, "r") as f:
        source = f.read()
    state["class_source"] = source

    # Get the methods.
    methods = []
    for line in source.split("n"):
        if "def " in line:
            methods.append(line.split("def ")[1].split("(")[0])
    state["class_methods"] = methods

    # Generate the import statement and start the code.
    import_prompt = import_prompt_template.format(
        code_file=code_file,
        test_file=test_file
    )
    message = llm.invoke([HumanMessage(content=import_prompt)]).content
    code = extract_code_from_message(message)
    state["tests_source"] = code + "nn"

    return state


# Add a node to for discovery.
workflow.add_node(
    "discover",
    discover_function
)

En el fragmento de código anterior, definimos una función para descubrir códigos. Extrae los códigos del AgentState. fuente_clase elemento, analiza la clase en métodos individuales y la pasa al LLM con indicaciones. La salida se almacena en el archivo AgentState. fuente_pruebas elemento. Solo le hacemos escribir declaraciones de importación para los casos de prueba unitaria.

También agregamos el primer nodo al objeto StateGraph.

Ahora, al siguiente nodo. 

Además, podemos configurar algunas plantillas de avisos que necesitaremos aquí. Estas son plantillas de muestra que puede cambiar según sus necesidades.

# System message template.

system_message_template = """You are a smart developer. You can do this! You will write unit 
tests that have a high quality. Use pytest.

Reply with the source code for the test only. 
Do not include the class in your response. I will add the imports myself.
If there is no test to write, reply with "# No test to write" and 
nothing more. Do not include the class in your response.

Example:

```
def test_function():
    ...
```

I will give you 200 EUR if you adhere to the instructions and write a high quality test. 
Do not write test classes, only methods.
"""

# Write the tests template.
write_test_template = """Here is a class:
'''
{class_source}
'''

Implement a test for the method "{class_method}".
"""

Ahora, defina el nodo.

# This method will write a test.
def write_tests_function(state: AgentState):

    # Get the next method to write a test for.
    class_method = state["class_methods"].pop(0)
    print(f"Writing test for {class_method}.")

    # Get the source code.
    class_source = state["class_source"]

    # Create the prompt.
    write_test_prompt = write_test_template.format(
        class_source=class_source,
        class_method=class_method
    )
    print(colorama.Fore.CYAN + write_test_prompt + colorama.Style.RESET_ALL)

    # Get the test source code.
    system_message = SystemMessage(system_message_template)
    human_message = HumanMessage(write_test_prompt)
    test_source = llm.invoke([system_message, human_message]).content
    test_source = extract_code_from_message(test_source)
    print(colorama.Fore.GREEN + test_source + colorama.Style.RESET_ALL)
    state["tests_source"] += test_source + "nn"

    return state

# Add the node.
workflow.add_node(
    "write_tests",
    write_tests_function
)

Aquí, haremos que el LLM escriba casos de prueba para cada método, los actualizaremos en el elemento tests_source de AgentState y los agregaremos al objeto StateGraph del flujo de trabajo.

Bordes

Ahora que tenemos dos nodos, definiremos los bordes entre ellos para especificar la dirección de ejecución entre ellos. LangGraph proporciona principalmente dos tipos de aristas.

  • Borde condicional: El flujo de ejecución depende de la respuesta de los agentes. Esto es crucial para agregar ciclicidad a los flujos de trabajo. El agente puede decidir qué nodos mover a continuación en función de algunas condiciones. Ya sea para volver a un nodo anterior, repetir el actual o pasar al siguiente nodo.
  • Borde normal: Este es el caso normal, donde siempre se llama a un nodo después de la invocación de los anteriores.

No necesitamos una condición para conectar descubrir y escribir_pruebas, por lo que usaremos una ventaja normal. Además, defina un punto de entrada que especifique dónde debe comenzar la ejecución.

# Define the entry point. This is where the flow will start.
workflow.set_entry_point("discover")

# Always go from discover to write_tests.
workflow.add_edge("discover", "write_tests")

La ejecución comienza con el descubrimiento de los métodos y pasa a la función de escribir pruebas. Necesitamos otro nodo para escribir los códigos de prueba unitaria en el archivo de prueba.

# Write the file.
def write_file(state: AgentState):
    with open(test_file, "w") as f:
        f.write(state["tests_source"])
    return state

# Add a node to write the file.
workflow.add_node(
    "write_file",
    write_file)

Como este es nuestro último nodo, definiremos una ventaja entre write_tests y write_file. Así es como podemos hacer esto.

# Find out if we are done.
def should_continue(state: AgentState):
    if len(state["class_methods"]) == 0:
        return "end"
    else:
        return "continue"

# Add the conditional edge.
workflow.add_conditional_edges(
    "write_tests",
    should_continue,
    {
        "continue": "write_tests",
        "end": "write_file"
    }
)

La función add_conditional_edge toma la función write_tests, una función debería_continuar que decide qué paso tomar en función de las entradas de class_methods y un mapeo con cadenas como claves y otras funciones como valores.

El borde comienza en write_tests y, según el resultado de must_continue, ejecuta cualquiera de las opciones en el mapeo. Por ejemplo, si el estado[“class_methods”] no está vacío, no hemos escrito pruebas para todos los métodos; repetimos la función write_tests y cuando terminamos de escribir las pruebas, se ejecuta write_file.

Cuando las pruebas para todos los métodos se han inferido de LLM, las pruebas se escriben en el archivo de prueba.

Ahora, agregue el borde final al objeto de flujo de trabajo para el cierre.

# Always go from write_file to end.
workflow.add_edge("write_file", END)

Ejecutar el flujo de trabajo

Lo último que quedaba era compilar el flujo de trabajo y ejecutarlo.

# Create the app and run it
app = workflow.compile()
inputs = {}
config = RunnableConfig(recursion_limit=100)
try:
    result = app.invoke(inputs, config)
    print(result)
except GraphRecursionError:
    print("Graph recursion limit reached.")

Esto invocará la aplicación. El límite de recursividad es la cantidad de veces que se inferirá el LLM para un flujo de trabajo determinado. El flujo de trabajo se detiene cuando se excede el límite.

Puedes ver los registros en el terminal o en el cuaderno. Este es el registro de ejecución de una aplicación CRUD sencilla.

cadena larga

Gran parte del trabajo pesado lo realizará el modelo subyacente; esta fue una aplicación de demostración con el modelo de codificador Deepseek; para un mejor rendimiento, puede usar GPT-4 o Claude Opus, haiku, etc.

 También puede utilizar las herramientas de Langchain para navegar por la web, analizar el precio de las acciones, etc.

LangChain y LangGraph

Ahora, la pregunta es cuándo usar LangChain vs. LangGraph.

Si el objetivo es crear un sistema multiagente con coordinación entre ellos, LangGraph es el camino a seguir. Sin embargo, si desea crear DAG o cadenas para completar tareas, el lenguaje de expresión LangChain es el más adecuado.

¿Por qué utilizar LangGraph?

LangGraph es un marco potente que puede mejorar muchas soluciones existentes. 

  • Mejorar los oleoductos RAG: LangGraph puede aumentar el RAG con su estructura de gráfico cíclico. Podemos introducir un circuito de retroalimentación para evaluar la calidad del objeto recuperado y, si es necesario, podemos mejorar la consulta y repetir el proceso.
  • Flujos de trabajo de múltiples agentes: LangGraph está diseñado para admitir flujos de trabajo de múltiples agentes. Esto es crucial para resolver tareas complejas divididas en subtareas más pequeñas. Diferentes agentes con un estado compartido y diferentes LLM y herramientas pueden colaborar para resolver una única tarea.
  • Humano en el circuito: LangGraph tiene soporte integrado para el flujo de trabajo Human-in-the-loop. Esto significa que un humano puede revisar los estados antes de pasar al siguiente nodo.
  • Agente de planificación: LangGraph es muy adecuado para crear agentes de planificación, donde un planificador de LLM planifica y descompone una solicitud de usuario, un ejecutor invoca herramientas y funciones, y el LLM sintetiza respuestas basadas en resultados anteriores.
  • Agentes multimodales: LangGraph puede crear agentes multimodales, como los habilitados para visión navegadores web.

Casos de uso de la vida real

Existen numerosos campos en los que los agentes de codificación de IA complejos pueden resultar útiles. 

  1. Agente personals: Imagine tener su propio asistente tipo Jarvis en sus dispositivos electrónicos, listo para ayudarlo con las tareas que usted ordene, ya sea a través de texto, voz o incluso un gesto. ¡Ese es uno de los usos más interesantes de los agentes de IA!
  2. Instructores de IA: Los chatbots son geniales, pero tienen sus límites. Los agentes de IA equipados con las herramientas adecuadas pueden ir más allá de las conversaciones básicas. Los instructores virtuales de IA que pueden adaptar sus métodos de enseñanza en función de los comentarios de los usuarios pueden cambiar las reglas del juego.
  3. Experiencia de usuario del software: La experiencia del usuario del software se puede mejorar con agentes de IA. En lugar de navegar manualmente por las aplicaciones, los agentes pueden realizar tareas con comandos de voz o gestos.
  4. Computación Espacial: A medida que la tecnología AR/VR crezca en popularidad, crecerá la demanda de agentes de IA. Los agentes pueden procesar información circundante y ejecutar tareas bajo demanda. Este puede ser uno de los mejores casos de uso de agentes de IA en breve.
  5. Maestría en Sistemas Operativos: Primeros sistemas operativos de IA donde los agentes son ciudadanos de primera clase. Los agentes serán responsables de realizar tareas desde mundanas hasta complejas.

Conclusión

LangGraph es un marco eficiente para construir sistemas cíclicos de agentes multiactor con estado. Llena el vacío en el marco original de LangChain. Como es una extensión de LangChain, podemos beneficiarnos de todas las cosas buenas del ecosistema LangChain. A medida que crezcan la calidad y la capacidad de los LLM, será mucho más fácil crear sistemas de agentes para automatizar flujos de trabajo complejos. Entonces, aquí están las conclusiones clave del artículo. 

Puntos clave

  • LangGraph es una extensión de LangChain, que nos permite construir sistemas de agentes cíclicos, con estado y de múltiples actores.
  • Implementa una estructura gráfica con nodos y aristas. Los nodos son funciones o herramientas y los bordes son las conexiones entre nodos.
  • Los bordes son de dos tipos: condicionales y normales. Los bordes condicionales tienen condiciones al pasar de uno a otro, lo cual es importante para agregar ciclicidad al flujo de trabajo.
  • Se prefiere LangGraph para construir agentes cíclicos de múltiples actores, mientras que LangChain es mejor para crear cadenas o sistemas acíclicos dirigidos.

Preguntas frecuentes

P1. ¿Qué es LangGraph?

Respuesta. LangGraph es una biblioteca de código abierto para crear sistemas de agentes multiactor cíclicos con estado. Está construido sobre el ecosistema LangChain.

P2. ¿Cuándo utilizar LangGraph sobre LangChain?

Respuesta. Se prefiere LangGraph para construir agentes cíclicos de múltiples actores, mientras que LangChain es mejor para crear cadenas o sistemas acíclicos dirigidos.

P3. ¿Qué es un agente de IA?

Respuesta. Los agentes de IA son programas de software que interactúan con su entorno, toman decisiones y actúan para lograr un objetivo final.

P4. ¿Cuál es el mejor LLM para usar con agentes de IA?

Respuesta. Esto depende de sus casos de uso y presupuesto. GPT 4 es el más capaz pero caro. Para codificar, DeepSeekCoder-33b es una excelente opción más económica. 

P5. ¿Cuál es la diferencia entre cadenas y agentes?

Respuesta. Las cadenas son una secuencia de acciones codificadas a seguir, mientras que los agentes usan LLM y otras herramientas (también cadenas) para razonar y actuar de acuerdo con la información.

Los medios que se muestran en este artículo no son propiedad de Analytics Vidhya y se utilizan a discreción del autor.

punto_img

Información más reciente

punto_img