Logo Zéphyrnet

Créez un agent de codage IA avec LangGraph par LangChain

Date :

Introduction

Il y a eu une augmentation massive des applications utilisant des agents de codage d’IA. Avec la qualité croissante des LLM et la diminution du coût de l'inférence, il devient de plus en plus facile de créer des agents d'IA performants. De plus, l’écosystème des outils évolue rapidement, ce qui facilite la création d’agents de codage d’IA complexes. Le framework Langchain a été un leader sur ce front. Il dispose de tous les outils et techniques nécessaires pour créer des applications d'IA prêtes pour la production.

Mais jusqu’à présent, il lui manquait une chose. Et c’est une collaboration multi-agents avec cyclicité. Ceci est crucial pour résoudre des problèmes complexes, où le problème peut être divisé et délégué à des agents spécialisés. C'est là qu'intervient LangGraph, une partie du framework Langchain conçu pour permettre une collaboration avec état multi-acteurs entre les agents de codage d'IA. De plus, dans cet article, nous discuterons de LangGraph et de ses éléments de base pendant que nous construisons un agent avec lui.

Objectifs d'apprentissage

  • Comprenez ce qu'est LangGraph.
  • Explorez les bases de LangGraph pour créer des agents avec état.
  • Explorez TogetherAI pour accéder à des modèles en libre accès comme Codeur DeepSeek.
  • Créez un agent de codage IA à l'aide de LangGraph pour écrire des tests unitaires.
LangChaîne

Cet article a été publié dans le cadre du Blogathon sur la science des données.

Table des matières

Qu’est-ce que LangGraph ?

LangGraph est une extension de l'écosystème LangChain. Bien que LangChain permette de créer des agents de codage d’IA capables d’utiliser plusieurs outils pour exécuter des tâches, il ne peut pas coordonner plusieurs chaînes ou acteurs au fil des étapes. Il s'agit d'un comportement crucial pour créer des agents qui accomplissent des tâches complexes. LangGraph a été conçu en gardant ces éléments à l'esprit. Il traite les flux de travail de l'agent comme une structure graphique cyclique, où chaque nœud représente une fonction ou un objet Langchain Runnable, et les bords sont des connexions entre les nœuds. 

Les principales fonctionnalités de LangGraph incluent 

  • Nodes: Toute fonction ou objet Langchain Runnable comme un outil.
  • Bords: Définit la direction entre les nœuds.
  • Graphiques avec état: Le principal type de graphique. Il est conçu pour gérer et mettre à jour les objets d'état à mesure qu'il traite les données via ses nœuds.

LangGraph exploite cela pour faciliter l'exécution d'un appel LLM cyclique avec persistance de l'état, ce qui est crucial pour le comportement agent. L'architecture s'inspire de Prégel ainsi que  Faisceau Apache

Dans cet article, nous allons créer un agent pour écrire des tests unitaires Pytest pour une classe Python avec des méthodes. Et c'est le flux de travail.

LangChaîne

Nous discuterons des concepts en détail au fur et à mesure que nous construisons notre agent de codage IA pour écrire des tests unitaires simples. Passons donc à la partie codage.

Mais avant cela, configurons notre environnement de développement.

Installer les dépendances

Commençons par le commencement. Comme pour tout projet Python, créez un environnement virtuel et activez-le.

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

Maintenant, installez les dépendances.

!pip install langgraph langchain langchain_openai colorama

Importez toutes les bibliothèques et leurs 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

Nous souhaiterons également créer les répertoires et les fichiers pour les cas de test. Vous pouvez créer manuellement des fichiers ou utiliser Python pour cela.

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

Maintenant, mettez à jour le fichier crud.py avec le code d'une application CRUD en mémoire. Nous utiliserons ce morceau de code pour écrire des tests unitaires. Vous pouvez utiliser votre programme Python pour cela. Nous ajouterons le programme ci-dessous à notre fichier 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)

Configurer le LLM

Maintenant, nous allons préciser le LLM que nous utiliserons dans ce projet. Le modèle à utiliser ici dépend des tâches et de la disponibilité des ressources. Vous pouvez utiliser des modèles propriétaires puissants comme GPT-4, Gemini Ultra ou GPT-3.5. Vous pouvez également utiliser des modèles en libre accès comme Mixtral et Llama-2. Dans ce cas, comme il s’agit d’écrire des codes, nous pouvons utiliser un modèle de codage affiné comme DeepSeekCoder-33B ou Llama-2 coder. Il existe désormais plusieurs plates-formes pour l'inférence LLM, comme Anayscale, Abacus et Together. Nous utiliserons Together AI pour déduire DeepSeekCoder. Alors, procurez-vous un Clé API de Together avant de continuer. 

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

Comme l'API Together est compatible avec le SDK OpenAI, nous pouvons utiliser le SDK OpenAI de Langchain pour communiquer avec les modèles hébergés sur Together en modifiant le paramètre base_url en «https://api.together.xyz/v1». Dans api_key, transmettez votre clé API Together, et à la place des modèles, transmettez le le nom du modèle disponible sur Ensemble.

Définir l'état de l'agent

C'est l'une des parties cruciales de LangGraph. Ici, nous définirons un AgentState, chargé de garder une trace des états des agents tout au long de l'exécution. Il s'agit principalement d'une classe TypedDict avec des entités qui maintiennent l'état des agents. Définissons notre AgentState

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

Dans la classe AgentState ci-dessus, class_source stocke la classe Python d'origine, class_methods pour stocker les méthodes de la classe et tests_source pour les codes de tests unitaires. Nous les avons définis comme AgentState pour les utiliser dans les étapes d'exécution. 

Maintenant, définissez le graphique avec AgentState.

# Create the graph.
workflow = StateGraph(AgentState)

Comme mentionné précédemment, il s'agit d'un graphe avec état, et nous avons maintenant ajouté notre objet d'état.

Définir des nœuds

Maintenant que nous avons défini l'AgentState, nous devons ajouter des nœuds. Alors, que sont exactement les nœuds ? Dans LangGraph, les nœuds sont des fonctions ou tout objet exécutable, comme les outils Langchain, qui effectuent une seule action. Dans notre cas, nous pouvons définir plusieurs nœuds, comme une fonction pour trouver des méthodes de classe, une fonction pour déduire et mettre à jour des tests unitaires vers des objets d'état et une fonction pour les écrire dans un fichier de test.

Nous avons également besoin d'un moyen d'extraire les codes d'un message LLM. Voici comment.

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

L'extrait de code ici suppose que les codes se trouvent entre guillemets triples.

Maintenant, définissons nos nœuds.

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
)

Dans l'extrait de code ci-dessus, nous avons défini une fonction pour découvrir les codes. Il extrait les codes de l'AgentState classe_source élément, disséque la classe en méthodes individuelles et la transmet au LLM avec des invites. La sortie est stockée dans le fichier AgentState tests_source élément. Nous lui faisons uniquement écrire des instructions d'importation pour les cas de tests unitaires.

Nous avons également ajouté le premier nœud à l'objet StateGraph.

Maintenant, passons au nœud suivant. 

Nous pouvons également configurer certains modèles d'invites dont nous aurons besoin ici. Ce sont des exemples de modèles que vous pouvez modifier selon vos besoins.

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

Maintenant, définissez le nœud.

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

Ici, nous allons demander au LLM d'écrire des cas de test pour chaque méthode, de les mettre à jour avec l'élément tests_source de AgentState et de les ajouter à l'objet StateGraph du workflow.

Bords

Maintenant que nous avons deux nœuds, nous allons définir des arêtes entre eux pour spécifier la direction d'exécution entre eux. Le LangGraph fournit principalement deux types d’arêtes.

  • Bord conditionnel: Le flux d'exécution dépend de la réponse des agents. Ceci est crucial pour ajouter de la cyclicité aux flux de travail. L'agent peut décider quels nœuds déplacer ensuite en fonction de certaines conditions. S'il faut revenir à un nœud précédent, répéter le nœud actuel ou passer au nœud suivant.
  • Bord normal: C'est le cas normal, où un nœud est toujours appelé après l'invocation des précédents.

Nous n'avons pas besoin de condition pour connecter discover et write_tests, nous utiliserons donc un bord normal. Définissez également un point d’entrée qui spécifie où l’exécution doit commencer.

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

L'exécution commence par la découverte des méthodes et passe à la fonction d'écriture des tests. Nous avons besoin d'un autre nœud pour écrire les codes de test unitaire dans le fichier de test.

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

Comme il s'agit de notre dernier nœud, nous définirons un bord entre write_tests et write_file. C'est ainsi que nous pouvons procéder.

# 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 fonction add_conditional_edge prend la fonction write_tests, une fonction Should_continue qui décide quelle étape suivre en fonction des entrées class_methods et un mappage avec des chaînes comme clés et d'autres fonctions comme valeurs.

Le Edge commence à write_tests et, en fonction du résultat de Should_continue, exécute l'une ou l'autre des options du mappage. Par exemple, si le state[« class_methods »] n’est pas vide, nous n’avons pas écrit de tests pour toutes les méthodes ; nous répétons la fonction write_tests, et lorsque nous avons fini d'écrire les tests, le write_file est exécuté.

Lorsque les tests de toutes les méthodes ont été déduits de LLM, les tests sont écrits dans le fichier de test.

Maintenant, ajoutez le bord final à l'objet de workflow pour la fermeture.

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

Exécuter le flux de travail

La dernière chose qui restait était de compiler le workflow et de l'exécuter.

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

Cela invoquera l'application. La limite de récursion est le nombre de fois que le LLM sera déduit pour un flux de travail donné. Le workflow s'arrête lorsque la limite est dépassée.

Vous pouvez voir les journaux sur le terminal ou dans le notebook. Il s'agit du journal d'exécution d'une simple application CRUD.

Langchain

Une grande partie du gros du travail sera effectuée par le modèle sous-jacent, il s'agissait d'une application de démonstration avec le modèle de codeur Deepseek, pour de meilleures performances, vous pouvez utiliser GPT-4 ou Claude Opus, haïku, etc.

 Vous pouvez également utiliser les outils Langchain pour la navigation sur le Web, l'analyse du cours des actions, etc.

LangChain et LangGraph

Maintenant, la question est de savoir quand utiliser LangChain vs LangGraph.

Si l’objectif est de créer un système multi-agents avec coordination entre eux, LangGraph est la voie à suivre. Cependant, si vous souhaitez créer des DAG ou des chaînes pour effectuer des tâches, le langage d'expression LangChain est le mieux adapté.

Pourquoi utiliser LangGraph ?

LangGraph est un framework puissant qui peut améliorer de nombreuses solutions existantes. 

  • Améliorer les pipelines RAG: LangGraph peut augmenter le RAG avec sa structure graphique cyclique. Nous pouvons introduire une boucle de rétroaction pour évaluer la qualité de l’objet récupéré et, si nécessaire, améliorer la requête et répéter le processus.
  • Flux de travail multi-agents: LangGraph est conçu pour prendre en charge les flux de travail multi-agents. Ceci est crucial pour résoudre des tâches complexes divisées en sous-tâches plus petites. Différents agents avec un état partagé et différents LLM et outils peuvent collaborer pour résoudre une seule tâche.
  • Humain dans la boucle: LangGraph prend en charge le flux de travail Human-in-the-loop. Cela signifie qu'un humain peut examiner les états avant de passer au nœud suivant.
  • Agent de planification: LangGraph est bien adapté à la création d'agents de planification, où un planificateur LLM planifie et décompose une demande utilisateur, un exécuteur invoque des outils et des fonctions, et le LLM synthétise les réponses en fonction des résultats précédents.
  • Agents multimodaux: LangGraph peut créer des agents multimodaux, comme ceux compatibles avec la vision navigateurs Web.

Cas d'utilisation réels

Il existe de nombreux domaines dans lesquels des agents de codage d’IA complexes peuvent être utiles. 

  1. Agent personnels : Imaginez avoir votre propre assistant de type Jarvis sur vos appareils électroniques, prêt à vous aider dans les tâches que vous commandez, que ce soit par texte, par voix ou même par un geste. C’est l’une des utilisations les plus intéressantes des agents IA !
  2. Instructeurs d'IA: Les chatbots sont géniaux, mais ils ont leurs limites. Les agents IA équipés des bons outils peuvent aller au-delà des conversations de base. Les instructeurs d’IA virtuelle qui peuvent adapter leurs méthodes d’enseignement en fonction des commentaires des utilisateurs peuvent changer la donne.
  3. Logiciel UX: L'expérience utilisateur des logiciels peut être améliorée grâce aux agents IA. Au lieu de naviguer manuellement dans les applications, les agents peuvent accomplir des tâches avec des commandes vocales ou gestuelles.
  4. Informatique spatiale: À mesure que la technologie AR/VR gagne en popularité, la demande d’agents IA va augmenter. Les agents peuvent traiter les informations environnantes et exécuter des tâches à la demande. Cela pourrait être prochainement l’un des meilleurs cas d’utilisation des agents d’IA.
  5. Système d'exploitation LLM: des systèmes d'exploitation axés sur l'IA où les agents sont des citoyens de première classe. Les agents seront chargés d’effectuer des tâches banales ou complexes.

Conclusion

LangGraph est un framework efficace pour créer des systèmes d'agents multi-acteurs avec état cyclique. Il comble les lacunes du framework LangChain original. Comme il s'agit d'une extension de LangChain, nous pouvons bénéficier de toutes les bonnes choses de l'écosystème LangChain. À mesure que la qualité et les capacités des LLM augmentent, il sera beaucoup plus facile de créer des systèmes d'agents pour automatiser des flux de travail complexes. Voici donc les principaux points à retenir de l’article. 

Faits marquants

  • LangGraph est une extension de LangChain, qui nous permet de créer des systèmes d'agents cycliques, avec état et multi-acteurs.
  • Il implémente une structure graphique avec des nœuds et des arêtes. Les nœuds sont des fonctions ou des outils, et les arêtes sont les connexions entre les nœuds.
  • Les bords sont de deux types : conditionnels et normaux. Les bords conditionnels ont des conditions lorsqu'ils passent de l'un à l'autre, ce qui est important pour ajouter de la cyclicité au flux de travail.
  • LangGraph est préféré pour créer des agents multi-acteurs cycliques, tandis que LangChain est meilleur pour créer des chaînes ou des systèmes acycliques dirigés.

Foire aux Questions

T1. Qu’est-ce que LangGraph ?

Rép. LangGraph est une bibliothèque open source permettant de créer des systèmes d'agents multi-acteurs cycliques avec état. Il est construit au-dessus de l’écosystème LangChain.

Q2. Quand utiliser LangGraph sur LangChain ?

Rép. LangGraph est préféré pour créer des agents multi-acteurs cycliques, tandis que LangChain est meilleur pour créer des chaînes ou des systèmes acycliques dirigés.

Q3. Qu'est-ce qu'un agent IA ?

Rép. Les agents IA sont des programmes logiciels qui interagissent avec leur environnement, prennent des décisions et agissent pour atteindre un objectif final.

Q4. Quel est le meilleur LLM à utiliser avec les agents IA ?

Rép. Cela dépend de vos cas d'utilisation et de votre budget. GPT 4 est le plus performant mais le plus cher. Pour le codage, DeepSeekCoder-33b est une option très moins chère. 

Q5. Quelle est la différence entre les chaînes et les agents ?

Rép. Les chaînes sont une séquence d'actions codées en dur à suivre, tandis que les agents utilisent des LLM et d'autres outils (également des chaînes) pour raisonner et agir en fonction des informations.

Les médias présentés dans cet article n'appartiennent pas à Analytics Vidhya et sont utilisés à la discrétion de l'auteur.

spot_img

Dernières informations

spot_img