Zephyrnet Logo

Construa um agente de codificação de IA com LangGraph da LangChain

Data:

Introdução

Houve um grande aumento no número de aplicativos que usam agentes de codificação de IA. Com a qualidade crescente dos LLMs e a diminuição do custo de inferência, está cada vez mais fácil construir agentes de IA capazes. Além disso, o ecossistema de ferramentas está evoluindo rapidamente, facilitando a construção de agentes complexos de codificação de IA. A estrutura Langchain tem sido líder nesta frente. Possui todas as ferramentas e técnicas necessárias para criar aplicativos de IA prontos para produção.

Mas até agora, faltava uma coisa. E essa é uma colaboração multiagente com ciclicidade. Isto é crucial para a resolução de problemas complexos, onde o problema pode ser dividido e delegado a agentes especializados. É aqui que entra em cena o LangGraph, uma parte da estrutura Langchain projetada para acomodar a colaboração com estado de vários atores entre agentes de codificação de IA. Além disso, neste artigo, discutiremos o LangGraph e seus blocos de construção básicos enquanto construímos um agente com ele.

Objetivos de aprendizagem

  • Entenda o que é LangGraph.
  • Explore os fundamentos do LangGraph para construir agentes com estado.
  • Explore o TogetherAI para acessar modelos de acesso aberto como DeepSeekCoder.
  • Crie um agente de codificação de IA usando LangGraph para escrever testes de unidade.
LangChain

Este artigo foi publicado como parte do Blogatona de Ciência de Dados.

Índice

O que é LangGraph?

LangGraph é uma extensão do ecossistema LangChain. Embora o LangChain permita a construção de agentes de codificação de IA que podem usar múltiplas ferramentas para executar tarefas, ele não pode coordenar múltiplas cadeias ou atores nas etapas. Este é um comportamento crucial para a criação de agentes que realizam tarefas complexas. LangGraph foi concebido tendo essas coisas em mente. Ele trata os fluxos de trabalho do Agente como uma estrutura gráfica cíclica, onde cada nó representa uma função ou um objeto Langchain Runnable, e as bordas são conexões entre nós. 

Os principais recursos do LangGraph incluem 

  • Nodes: Qualquer função ou objeto Langchain Runnable como uma ferramenta.
  • Arestas: Define a direção entre os nós.
  • Gráficos com estado: O principal tipo de gráfico. Ele foi projetado para gerenciar e atualizar objetos de estado à medida que processa dados por meio de seus nós.

LangGraph aproveita isso para facilitar a execução cíclica de chamadas LLM com persistência de estado, o que é crucial para o comportamento de agente. A arquitetura se inspira em Pregel e Feixe Apache

Neste artigo, construiremos um agente para escrever testes de unidade Pytest para uma classe Python com métodos. E este é o fluxo de trabalho.

LangChain

Discutiremos os conceitos em detalhes à medida que construímos nosso agente de codificação de IA para escrever testes de unidade simples. Então, vamos para a parte de codificação.

Mas antes disso, vamos configurar nosso ambiente de desenvolvimento.

Instalar dependências

As coisas importantes primeiro. Como acontece com qualquer projeto Python, crie um ambiente virtual e ative-o.

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

Agora, instale as dependências.

!pip install langgraph langchain langchain_openai colorama

Importe todas as bibliotecas e suas classes.

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

Também desejaremos criar os diretórios e arquivos para os casos de teste. Você pode criar arquivos manualmente ou usar Python para isso.

# 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"))

Agora, atualize o arquivo crud.py com o código para um aplicativo CRUD na memória. Usaremos este trecho de código para escrever testes de unidade. Você pode usar seu programa Python para isso. Adicionaremos o programa abaixo ao nosso arquivo 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 o LLM

Agora, especificaremos o LLM que usaremos neste projeto. Qual modelo usar aqui depende das tarefas e da disponibilidade de recursos. Você pode usar modelos proprietários e poderosos como GPT-4, Gemini Ultra ou GPT-3.5. Além disso, você pode usar modelos de acesso aberto como Mixtral e Llama-2. Neste caso, por envolver a escrita de códigos, podemos usar um modelo de codificação ajustado como o codificador DeepSeekCoder-33B ou Llama-2. Agora, existem várias plataformas para inferência LLM, como Anayscale, Abacus e Together. Usaremos o Together AI para inferir o DeepSeekCoder. Então, pegue um Chave API de Juntos antes de prosseguir. 

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 a API Together é compatível com o OpenAI SDK, podemos usar o OpenAI SDK da Langchain para nos comunicarmos com modelos hospedados no Together alterando o parâmetro base_url para “https://api.together.xyz/v1”. Em api_key, passe sua chave de API Together e, no lugar dos modelos, passe o nome do modelo disponível em Juntos.

Definir estado do agente

Esta é uma das partes cruciais do LangGraph. Aqui definiremos um AgentState, responsável por acompanhar os estados dos Agentes ao longo da execução. Esta é principalmente uma classe TypedDict com entidades que mantêm o estado dos Agentes. Vamos definir nosso AgentState

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

Na classe AgentState acima, class_source armazena a classe Python original, class_methods para armazenar métodos da classe e testes_source para códigos de teste de unidade. Nós os definimos como AgentState para usá-los nas etapas de execução. 

Agora, defina o gráfico com AgentState.

# Create the graph.
workflow = StateGraph(AgentState)

Conforme mencionado anteriormente, este é um gráfico com estado e agora adicionamos nosso objeto de estado.

Definir nós

Agora que definimos o AgentState, precisamos adicionar nós. Então, o que exatamente são nós? No LangGraph, os nós são funções ou qualquer objeto executável, como as ferramentas Langchain, que executam uma única ação. No nosso caso, podemos definir vários nós, como uma função para encontrar métodos de classe, uma função para inferir e atualizar testes unitários para objetos de estado e uma função para gravá-los em um arquivo de teste.

Também precisamos de uma maneira de extrair códigos de uma mensagem LLM. Veja como.

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

O trecho de código aqui assume que os códigos estão entre aspas triplas.

Agora, vamos definir nossos nós.

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
)

No trecho de código acima, definimos uma função para descobrir códigos. Ele extrai os códigos do AgentState fonte_classe elemento, disseca a classe em métodos individuais e a passa para o LLM com prompts. A saída é armazenada no AgentState testes_fonte elemento. Nós apenas fazemos com que ele escreva instruções de importação para os casos de teste unitários.

Também adicionamos o primeiro nó ao objeto StateGraph.

Agora, para o próximo nó. 

Além disso, podemos configurar alguns modelos de prompt que precisaremos aqui. Estes são modelos de amostra que você pode alterar conforme suas necessidades.

# 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}".
"""

Agora, defina o nó.

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

Aqui, faremos o LLM escrever casos de teste para cada método, atualizá-los para o elemento testes_source do AgentState e adicioná-los ao objeto StateGraph do fluxo de trabalho.

Arestas

Agora que temos dois nós, definiremos arestas entre eles para especificar a direção de execução entre eles. O LangGraph fornece principalmente dois tipos de arestas.

  • Borda Condicional: O fluxo de execução depende da resposta dos agentes. Isso é crucial para adicionar ciclicidade aos fluxos de trabalho. O agente pode decidir quais nós mover em seguida com base em algumas condições. Seja para retornar a um nó anterior, repetir o atual ou passar para o próximo nó.
  • Borda normal: Este é o caso normal, onde um nó é sempre chamado após a invocação dos anteriores.

Não precisamos de uma condição para conectar discover e write_tests, então usaremos uma borda normal. Além disso, defina um ponto de entrada que especifique onde a execução deve começar.

# 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")

A execução começa com a descoberta dos métodos e vai até a função de escrever testes. Precisamos de outro nó para escrever os códigos de teste de unidade no arquivo de teste.

# 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 é nosso último nó, definiremos uma aresta entre write_tests e write_file. É assim que podemos fazer isso.

# 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"
    }
)

A função add_conditional_edge usa a função write_tests, uma função should_continue que decide qual etapa executar com base nas entradas class_methods e um mapeamento com strings como chaves e outras funções como valores.

A borda começa em write_tests e, com base na saída de should_continue, executa qualquer uma das opções do mapeamento. Por exemplo, se state[“class_methods”] não estiver vazio, não escrevemos testes para todos os métodos; repetimos a função write_tests e, quando terminamos de escrever os testes, o write_file é executado.

Quando os testes para todos os métodos tiverem sido inferidos a partir LLM, os testes serão gravados no arquivo de teste.

Agora, adicione a borda final ao objeto de fluxo de trabalho para o fechamento.

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

Execute o fluxo de trabalho

A última coisa que faltou foi compilar o fluxo de trabalho e executá-lo.

# 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.")

Isso invocará o aplicativo. O limite de recursão é o número de vezes que o LLM será inferido para um determinado fluxo de trabalho. O fluxo de trabalho é interrompido quando o limite é excedido.

Você pode ver os logs no terminal ou no notebook. Este é o log de execução de um aplicativo CRUD simples.

Langchain

Muito do trabalho pesado será feito pelo modelo subjacente, este foi um aplicativo de demonstração com o modelo de codificador Deepseek, para melhor desempenho você pode usar GPT-4 ou Claude Opus, haiku, etc.

 Você também pode usar ferramentas Langchain para navegar na web, análise de preços de ações, etc.

LangChain vs LangGraph

Agora, a questão é quando usar LangChain vs. LangGraph.

Se o objetivo é criar um sistema multiagente com coordenação entre eles, LangGraph é o caminho a percorrer. No entanto, se você deseja criar DAGs ou cadeias para concluir tarefas, a LangChain Expression Language é a mais adequada.

Por que usar o LangGraph?

LangGraph é uma estrutura potente que pode melhorar muitas soluções existentes. 

  • Melhorar pipelines RAG: LangGraph pode aumentar o RAG com sua estrutura gráfica cíclica. Podemos introduzir um ciclo de feedback para avaliar a qualidade do objeto recuperado e, se necessário, podemos melhorar a consulta e repetir o processo.
  • Fluxos de trabalho multiagentes: LangGraph foi projetado para oferecer suporte a fluxos de trabalho multiagentes. Isto é crucial para resolver tarefas complexas divididas em subtarefas menores. Diferentes agentes com um estado compartilhado e diferentes LLMs e ferramentas podem colaborar para resolver uma única tarefa.
  • Humano no circuito: LangGraph tem suporte integrado para fluxo de trabalho Human-in-the-loop. Isso significa que um ser humano pode revisar os estados antes de passar para o próximo nó.
  • Agente de planejamento: LangGraph é adequado para construir agentes de planejamento, onde um planejador LLM planeja e decompõe uma solicitação do usuário, um executor invoca ferramentas e funções e o LLM sintetiza respostas com base em resultados anteriores.
  • Agentes Multimodais: LangGraph pode construir agentes multimodais, como agentes habilitados para visão navegadores da web.

Casos de uso da vida real

Existem vários campos onde agentes complexos de codificação de IA podem ser úteis. 

  1. Agente Pessoals: Imagine ter seu próprio assistente tipo Jarvis em seus dispositivos eletrônicos, pronto para ajudar nas tarefas sob seu comando, seja por texto, voz ou até mesmo um gesto. Esse é um dos usos mais interessantes dos agentes de IA!
  2. Instrutores de IA: Os chatbots são ótimos, mas têm seus limites. Agentes de IA equipados com as ferramentas certas podem ir além das conversas básicas. Instrutores de IA virtual que podem adaptar seus métodos de ensino com base no feedback do usuário podem mudar o jogo.
  3. Experiência do usuário de software: A experiência do usuário do software pode ser melhorada com agentes de IA. Em vez de navegar manualmente pelos aplicativos, os agentes podem realizar tarefas com comandos de voz ou gestos.
  4. Computação Espacial: À medida que a popularidade da tecnologia AR/VR cresce, a demanda por agentes de IA aumentará. Os agentes podem processar informações circundantes e executar tarefas sob demanda. Este pode ser um dos melhores casos de uso de agentes de IA em breve.
  5. SO LLM: Sistemas operacionais com IA em que os agentes são cidadãos de primeira classe. Os agentes serão responsáveis ​​por realizar tarefas mundanas a complexas.

Conclusão

LangGraph é uma estrutura eficiente para a construção de sistemas de agentes multiatores com estado cíclicos. Ele preenche a lacuna na estrutura original do LangChain. Por ser uma extensão do LangChain, podemos nos beneficiar de todas as coisas boas do ecossistema LangChain. À medida que a qualidade e a capacidade dos LLMs aumentam, será muito mais fácil criar sistemas de agentes para automatizar fluxos de trabalho complexos. Então, aqui estão as principais conclusões do artigo. 

Principais lições

  • LangGraph é uma extensão do LangChain, que nos permite construir sistemas de agentes cíclicos, com estado e com vários atores.
  • Ele implementa uma estrutura gráfica com nós e arestas. Os nós são funções ou ferramentas e as arestas são as conexões entre os nós.
  • As arestas são de dois tipos: condicionais e normais. As arestas condicionais têm condições ao passar de uma para outra, o que é importante para adicionar ciclicidade ao fluxo de trabalho.
  • LangGraph é preferido para construir agentes multiatores cíclicos, enquanto LangChain é melhor para criar cadeias ou sistemas acíclicos direcionados.

Perguntas Frequentes

Q1. O que é LangGraph?

Resp. LangGraph é uma biblioteca de código aberto para a construção de sistemas de agentes multiatores cíclicos com estado. Ele é construído sobre o ecossistema LangChain.

Q2. Quando usar LangGraph em vez de LangChain?

Resp. LangGraph é preferido para construir agentes multiatores cíclicos, enquanto LangChain é melhor para criar cadeias ou sistemas acíclicos direcionados.

Q3. O que é um agente de IA?

Resp. Agentes de IA são programas de software que interagem com seu ambiente, tomam decisões e agem para atingir um objetivo final.

Q4. Qual é o melhor LLM para usar com agentes de IA?

Resp. Isso depende dos seus casos de uso e orçamento. GPT 4 é o mais capaz, mas caro. Para codificação, DeepSeekCoder-33b é uma ótima opção mais barata. 

Q5. Qual é a diferença entre cadeias e agentes?

Resp. As cadeias são uma sequência de ações codificadas a serem seguidas, enquanto os agentes usam LLMs e outras ferramentas (também cadeias) para raciocinar e agir de acordo com as informações

A mídia mostrada neste artigo não é propriedade da Analytics Vidhya e é usada a critério do Autor.

local_img

Inteligência mais recente

local_img