paint-brush
Comment j'ai construit un programmeur d'IA pour automatiser les résolutions de problèmes GitHub 🤯par@sunilkumardash9
486 lectures
486 lectures

Comment j'ai construit un programmeur d'IA pour automatiser les résolutions de problèmes GitHub 🤯

par Sunil Kumar Dash16m2024/08/01
Read on Terminal Reader

Trop long; Pour lire

Découvrez comment créer un agent SWE basé sur l'IA pour automatiser les flux de travail GitHub à l'aide de Typescript. Ce guide couvre la configuration, les composants et la mise en œuvre d'un agent autonome capable de gérer des tâches telles que la mise à jour de la documentation, la correction de bogues et la diffusion de correctifs, permettant ainsi aux développeurs de se concentrer sur un travail plus créatif.
featured image - Comment j'ai construit un programmeur d'IA pour automatiser les résolutions de problèmes GitHub 🤯
Sunil Kumar Dash HackerNoon profile picture


Au cours des dernières semaines, nous avons travaillé avec diligence sur un référentiel à croissance rapide chez Composio . Nous avons vite réalisé que des tâches telles que la mise à jour des fichiers ReadMe, la correction des docstrings et la correction de bugs mineurs, bien que répétitives et banales, consommaient une grande partie de notre bande passante.


Alors, je me suis demandé pourquoi ne pas créer un agent autonome alimenté par l’IA pour gérer ces tâches fastidieuses ?

Nous voulions un agent IA capable de le faire.


  • Accédez au référentiel GitHub.
  • Prenez n’importe quel problème donné et rédigez une solution.
  • Si nécessaire, exécutez le code pour vérifier s'il est bon.
  • Enfin, transférez les fichiers de correctifs vers le référentiel distant.


Enfin, nous avons construit un méta-framework simple et extensible pour créer des agents d'ingénierie logicielle.

Ces agents peuvent effectuer de nombreuses tâches similaires à leurs homologues humains. Il est logique de confier des tâches banales à ces agents pour permettre à vos développeurs de se concentrer sur des tâches plus créatives.


jerry


Dans cet article, je vais vous montrer comment créer un agent SWE dans Typescript pour automatiser vos flux de travail GitHub.

Mais avant cela, comprenons ce que sont même les agents AI et SWE.


Que sont les agents IA ?

Les agents d'IA sont des systèmes alimentés par des modèles d'IA qui peuvent effectuer des tâches de manière autonome, interagir avec leur environnement et prendre des décisions en fonction de leur programmation et des données qu'ils traitent.

Composants des agents IA

Un agent IA se compose de trois éléments cruciaux :

  • LLM : Le Large Language Model est responsable du raisonnement, de la prise de décision, de l'appel d'outils, etc.
  • Mémoire : gère les informations à court et à long terme pour suivre les flux de travail.
  • Outils : activez l'interaction avec l'environnement externe, comme un outil GitHub pour extraire des informations des référentiels et apporter les modifications nécessaires.

Que sont les agents SWE ?

Alors, quand appelle-t-on un agent IA un agent SWE ?

Les agents SWE sont des agents d'IA qui imitent les qualités et les caractéristiques d'un ingénieur logiciel humain, comme

  • Planification et raisonnement à long terme.
  • Utiliser les outils de développement standards.
  • Améliorer la qualité du code grâce à des tests et des commentaires.
  • Débogage et résolution des problèmes de manière autonome.

swkit

Présentation de l'agent SWE

Voici quelques-unes des caractéristiques de l’agent SWE que nous allons construire :

  • Il est indépendant du framework, vous pouvez donc utiliser n'importe quel framework, tel que LangChain, OpenAI SDK, etc…
  • Vous pouvez ajouter des outils pour étendre sa polyvalence, comme Tavily pour l'accès à Internet.


Les agents SWE peuvent accéder à vos référentiels publics et privés, travailler sur les problèmes fournis et apporter des modifications aux référentiels.

Il peut exécuter des codes à l'aide de la machine hôte, de Docker ou de tout autre environnement cloud (E2B, FlyIo). Cependant, il serait préférable que vous préfériez utiliser les deux derniers pour l’exécution de code en sandboxing.


Le sandboxing permet d’éviter toute conséquence involontaire de l’exécution de code arbitraire.


Conditions préalables pour l'agent SWE

Voici les conditions préalables pour réussir à créer l’agent :

  1. Clé API OpenAI : nous utiliserons le SDK OpenAI pour accéder aux modèles GPT et orchestrer les appels d'outils.
  2. Jeton d'accès GitHub : vous devez lier votre compte GitHub à l'aide d'un jeton d'accès personnel pour permettre à l'agent SWE d'accéder et de modifier votre référentiel de code.
  3. Clé API Composio : Vous aurez également besoin d'une clé API de Composio. Pour en obtenir un, créez un utilisateur compte avec Composio et accédez à l’onglet Paramètres du tableau de bord.

page de connexion


Commençons 🔥

Dépendances

Commencez par installer les dépendances à l’aide de votre gestionnaire de packages préféré. La méthode recommandée est pnpm , mais vous pouvez également utiliser npm ou yarn .

 pnpm install -g composio-core

Configurer les variables d'environnement 🌐

Vous aurez besoin d'un GITHUB_ACCESS_TOKEN, d'un OPENAI_API_KEY, d'un COMPOSIO_API_KEY, d'un GITHUB_USERNAME et d'un GITHUB_USER_EMAIL pour terminer le projet.


Créez donc un fichier .env et ajoutez les variables ci-dessus.

 GITHUB_ACCESS_TOKEN="your access token" OPENAI_API_KEY="openai_key" COMPOSIO_API_KEY="composio-api-key" GITHUB_USER_NAME="GitHub username" GITHUB_USER_EMAIL="GitHub user email"

Structure du projet 📁

Le projet est organisé comme suit :

src
├──agents
│ └── doux.ts
├── app.ts
├── invites.ts
└── utils.ts

Voici une brève description des fichiers.

  • agents/swe.ts : Contient l'implémentation de l'agent SWE.
  • app.ts : Le point d'entrée principal de l'application.
  • prompts.ts : définit les invites utilisées par les agents.
  • utils.ts : Fonctions utilitaires utilisées tout au long du projet.

Pour commencer rapidement, clonez ce référentiel et installez le reste des dépendances.

 git clone https://github.com/ComposioHQ/swe-js-template.git swe-js cd swe-js && pnpm i


Maintenant que vous avez terminé toute la configuration. Codons notre agent IA.


codeur pépé



Définir des invites et des objectifs 🎯

Nous commençons par définir les invites et les objectifs de l'agent SWE. Il est crucial d'expliquer chaque étape en détail, car ces définitions influencent considérablement les performances et l'exécution de l'agent.

Alors, créez un fichier prompts.ts si vous ne l’avez pas fait.

Ensuite, définissez le rôle et l'objectif de l'agent.

 export const ROLE = "Software Engineer"; export const GOAL = "Fix the coding issues given by the user, and finally generate a patch with the newly created files using `filetool_git_patch` tool";


Ici, nous avons défini le rôle comme SWE, et l'objectif est de résoudre tout problème de codage et de créer un correctif pour le correctif à l'aide filetool_git_patch . Il s'agit d'une action Compsoio pour l'intégration GitHub permettant de créer des fichiers de correctifs.

Maintenant, définissez la trame de fond et une description de l'agent Swe.

 export const BACKSTORY = `You are an autonomous programmer; your task is to solve the issue given in the task with the tools in hand. Your mentor gave you the following tips. 1. Please clone the github repo using the 'FILETOOL_GIT_CLONE' tool, and if it already exists - you can proceed with the rest of the instructions after going into the directory using \`cd\` shell command. 2. PLEASE READ THE CODE AND UNDERSTAND THE FILE STRUCTURE OF THE CODEBASE USING GIT REPO TREE ACTION. 3. POST THAT READ ALL THE RELEVANT READMES AND TRY TO LOOK AT THE FILES RELATED TO THE ISSUE. 4. Form a thesis around the issue and the codebase. Think step by step. Form pseudocode in case of large problems. 5. THEN TRY TO REPLICATE THE BUG THAT THE ISSUES DISCUSS. - If the issue includes code for reproducing the bug, we recommend that you re-implement that in your environment, and run it to make sure you can reproduce the bug. - Then start trying to fix it. - When you think you've fixed the bug, re-run the bug reproduction script to make sure that the bug has indeed been fixed. - If the bug reproduction script does not print anything when it is successfully runs, we recommend adding a print("Script completed successfully, no errors.") command at the end of the file so that you can be sure that the script indeed, it ran fine all the way through. 6. If you run a command that doesn't work, try running a different one. A command that did not work once will not work the second time unless you modify it! 7. If you open a file and need to get to an area around a specific line that is not in the first 100 lines, say line 583, don't just use the scroll_down command multiple times. Instead, use the goto 583 command. It's much quicker. 8. If the bug reproduction script requires inputting/reading a specific file, such as buggy-input.png, and you'd like to understand how to input that file, conduct a search in the existing repo code to see whether someone else has I've already done that. Do this by running the command find_file "buggy-input.png" If that doesn't work, use the Linux 'find' command. 9. Always make sure to look at the currently open file and the current working directory (which appears right after the currently open file). The currently open file might be in a different directory than the working directory! Some commands, such as 'create', open files, so they might change the currently open file. 10. When editing files, it is easy to accidentally specify a wrong line number or write code with incorrect indentation. Always check the code after You issue an edit to ensure it reflects what you want to accomplish. If it didn't, issue another command to fix it. 11. When you FINISH WORKING on the issue, USE THE 'filetool_git_patch' ACTION with the new files using the "new_file_paths" parameters created to create the final patch to be submitted to fix the issue. Example, if you add \`js/src/app.js\`, then pass \`new_file_paths\` for the action like below, { "new_file_paths": ["js/src/app.js"] } `; export const DESCRIPTION = `We're solving the following issue within our repository. Here's the issue text: ISSUE: {issue} REPO: {repo} Now, you're going to solve this issue on your own. When you're satisfied with all your changes, you can submit them to the code base by simply running the submit command. Note, however, that you cannot use any interactive session commands (eg python, vim) in this environment, but you can write scripts and run them. Eg you can write a Python script and then run it with \`python </path/to/script>.py\`. If you face a "module not found error", you can install dependencies. Example: in case the error is "pandas not found", install pandas like this \`pip install pandas\` Respond to the human as helpfully and accurately as possible`;

Dans le bloc de code ci-dessus, nous avons soigneusement et clairement défini les étapes que l'agent doit entreprendre pour accomplir la tâche. Ceci est important pour garantir que l’agent sait quoi faire face aux obstacles de programmation courants.


Définir les fonctions utilitaires 🛠️

Dans cette section, nous définirons deux fonctions principales, from GitHub et getBranchNameFromIssue , qui extrairont des informations sur un problème.

 import * as fs from 'fs'; import * as path from 'path'; import * as readline from 'readline'; import { ComposioToolSet } from "composio-core/lib/sdk/base.toolset"; import { nanoid } from "nanoid"; type InputType = any; function readUserInput( prompt: string, metavar: string, validator: (value: string) => InputType ): InputType { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise<InputType>((resolve, reject) => { rl.question(`${prompt} > `, (value) => { try { const validatedValue = validator(value); rl.close(); resolve(validatedValue); } catch (e) { console.error(`Invalid value for \`${metavar}\` error parsing \`${value}\`; ${e}`); rl.close(); reject(e); } }); }); } function createGithubIssueValidator(owner: string, name: string, toolset: ComposioToolSet) { return async function githubIssueValidator(value: string): Promise<string> { const resolvedPath = path.resolve(value); if (fs.existsSync(resolvedPath)) { return fs.readFileSync(resolvedPath, 'utf-8'); } if (/^\d+$/.test(value)) { const responseData = await toolset.executeAction('github_issues_get', { owner, repo: name, issue_number: parseInt(value, 10), }); return responseData.body as string; } return value; }; } export async function fromGithub(toolset: ComposioToolSet): Promise<{ repo: string; issue: string }> { const owner = await readUserInput( 'Enter github repository owner', 'github repository owner', (value: string) => value ); const name = await readUserInput( 'Enter github repository name', 'github repository name', (value: string) => value ); const repo = `${owner}/${name.replace(",", "")}`; const issue = await readUserInput( 'Enter the github issue ID or description or path to the file containing the description', 'github issue', createGithubIssueValidator(owner, name, toolset) ); return { repo, issue }; }

Voici donc ce qui se passe dans le bloc de code ci-dessus.

  • readUserInput : Cette fonction lit les entrées de l'utilisateur à partir de la ligne de commande. Nous n'avons besoin que de l'ID utilisateur GitHub, du nom du référentiel et du numéro ou de la description du problème.
  • createGithubIssueValidator : Cette fonction renvoie un validateur pour les problèmes GitHub. Il peut gérer les entrées sous forme de chemin de fichier, d'ID de problème numérique ou de description de chaîne simple. Si l'entrée est un ID de problème numérique, elle récupère les détails du problème depuis GitHub à l'aide de l'action github_issues_get de Composio.
  • fromGitHub : Cette fonction combine ces éléments pour rassembler et valider les informations nécessaires sur un référentiel GitHub et un problème.

Maintenant, définissez getBranchNameFromIssue pour créer un nom de branche à partir de la description du problème.

 export function getBranchNameFromIssue(issue: string): string { return "swe/" + issue.toLowerCase().replace(/\s+/g, '-') + "-" + nanoid(); }

Définir l'agent Swe 🤖

Il s'agit de la section la plus importante, dans laquelle vous définirez l'agent Swe à l'aide des assistants OpenAI et des ensembles d'outils Composio.

Alors, commencez par importer les bibliothèques et définissez le LLM et les outils.

 import { OpenAIToolSet, Workspace } from 'composio-core'; import { BACKSTORY, DESCRIPTION, GOAL } from '../prompts'; import OpenAI from 'openai'; // Initialize tool. const llm = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); const composioToolset = new OpenAIToolSet({ workspaceConfig: Workspace.Docker({}) }); // To use E2B Code interpreter /* const composioToolset = new OpenAIToolSet({ workspaceConfig: Workspace.E2B({ apiKey: process.env.E2B_API_KEY, }) }); */

Dans le bloc de code ci-dessus,

  • Nous avons créé une instance d'OpenAI avec la clé API.
  • Nous avons également créé une instance d' OpenAIToolSet avec workspaceConfig défini sur Docker. Il s'agit d'utiliser Docker pour mettre en sandbox l'environnement de codage de l'agent Swe. Vous pouvez également utiliser des interpréteurs de code cloud comme E2B et FlyIo.

Maintenant, nous allons définir l'agent Swe.

 export async function initSWEAgent(): Promise<{composioToolset: OpenAIToolSet; assistantThread: OpenAI.Beta.Thread; llm: OpenAI; tools: Array<any>}> { let tools = await composioToolset.getTools({ apps: [ "filetool", "fileedittool", "shelltool" ], }); tools = tools.map((a) => { if (a.function?.description?.length || 0 > 1024) { a.function.description = a.function.description?.substring(0, 1024); } return a; }); tools = tools.map((tool) => { const updateNullToEmptyArray = (obj) => { for (const key in obj) { if (obj[key] === null) { obj[key] = []; } else if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { updateNullToEmptyArray(obj[key]); } } }; updateNullToEmptyArray(tool); return tool; }); const assistantThread = await llm.beta.threads.create({ messages: [ { role: "assistant", content:`${BACKSTORY}\n\n${GOAL}\n\n${DESCRIPTION}` } ] }); return { assistantThread, llm, tools, composioToolset }; }

Voici ce qui se passe dans le bloc de code ci-dessus.

  • Get Tools : récupère les outils du jeu d'outils Composio pour filetool , file edit tool et shelltool . Comme leur nom l'indique, ceux-ci seront utilisés pour accéder aux fichiers, modifier des fichiers et utiliser le shell pour exécuter des commandes.
  • Descriptions des outils de découpe : limite les descriptions des outils à un maximum de 1 024 caractères.
  • Mettre à jour les valeurs nulles : remplace les valeurs nulles dans les configurations d'outils par des tableaux vides.
  • Créer un thread assistant : lance un thread assistant OpenAI avec des invites prédéfinies.
  • Return Statement : fournit les outils initialisés, le thread assistant, l'instance OpenAI et l'ensemble d'outils Composio.

Définir le point d'entrée de l'application 🚀

Il s'agit de la dernière section, où nous définissons le point d'entrée de l'application. Par conséquent, chargez les variables d’environnement et importez les modules requis.

 import dotenv from "dotenv"; dotenv.config(); import { fromGithub, getBranchNameFromIssue } from './utils'; import { initSWEAgent } from './agents/swe'; import { GOAL } from './prompts';

Le bloc de code

  • Charge les variables d'environnement.
  • Importe les fonctions utilitaires nécessaires.
  • Importe l'agent Swe et l'objectif de l'agent que nous avons définis précédemment.

Maintenant, définissez la fonction main .

 async function main() { /**Run the agent.**/ const { assistantThread, llm, tools, composioToolset } = await initSWEAgent(); const { repo, issue } = await fromGithub(composioToolset); const assistant = await llm.beta.assistants.create({ name: "SWE agent", instructions: GOAL + `\nRepo is: ${repo} and your goal is to ${issue}`, model: "gpt-4o", tools: tools }); await llm.beta.threads.messages.create( assistantThread.id, { role: "user", content: issue } ); const stream = await llm.beta.threads.runs.createAndPoll(assistantThread.id, { assistant_id: assistant.id, instructions: `Repo is: ${repo} and your goal is to ${issue}`, tool_choice: "required" }); await composioToolset.waitAndHandleAssistantToolCalls(llm as any, stream, assistantThread, "default"); const response = await composioToolset.executeAction("filetool_git_patch", { }); if (response.patch && response.patch?.length > 0) { console.log('=== Generated Patch ===\n' + response.patch, response); const branchName = getBranchNameFromIssue(issue); const output = await composioToolset.executeAction("SHELL_EXEC_COMMAND", { cmd: `cp -r ${response.current_working_directory} git_repo && cd git_repo && git config --global --add safe.directory '*' && git config --global user.name ${process.env.GITHUB_USER_NAME} && git config --global user.email ${process.env.GITHUB_USER_EMAIL} && git checkout -b ${branchName} && git commit -m 'feat: ${issue}' && git push origin ${branchName}` }); // Wait for 2s await new Promise((resolve) => setTimeout(() => resolve(true), 2000)); console.log("Have pushed the code changes to the repo. Let's create the PR now", output); await composioToolset.executeAction("GITHUB_PULLS_CREATE", { owner: repo.split("/")[0], repo: repo.split("/")[1], head: branchName, base: "master", title: `SWE: ${issue}` }) console.log("Done! The PR has been created for this issue in " + repo); } else { console.log('No output available - no patch was generated :('); } await composioToolset.workspace.close(); } main();


Il s'agit de notre fichier app.ts complet, qui sera utilisé pour exécuter le flux de travail agent.


Voici donc ce qui se passe dans le code ci-dessus.

  • Initialiser l'agent SWE : appelle initSWEAgent pour obtenir le thread assistant, l'instance OpenAI, les outils et l'ensemble d'outils Composio.
  • Récupérer le référentiel et le problème : récupère les détails du référentiel et du problème depuis fromGithub .
  • Créer un assistant : initialise un assistant OpenAI avec des instructions, des outils et le modèle de langage.
  • Envoyer le problème à l'assistant : envoie le contenu du problème sous forme de message au fil de discussion de l'assistant.
  • Exécuter l'assistant et l'interrogation : exécute l'assistant et interroge les réponses aux appels d'outils. Pour plus d'informations sur les réponses aux sondages, reportez-vous au Dépôt du SDK OpenAI.
  • Exécuter l'action de correctif : exécute filetool_git_patch pour générer un correctif.
  • Gérer la réponse du correctif : si un correctif est généré, enregistrez-le, créez une branche, validez et envoyez les modifications. Attendez 2 secondes avant de créer une pull request. Créez une pull request sur GitHub.
  • Fermer l'espace de travail : ferme l'espace de travail du jeu d'outils Composio.
  • Exécuter la fonction principale : appelle main() pour exécuter les étapes ci-dessus.


Maintenant, exécutez l'application en utilisant pnpm start .


Cela vous demandera de saisir l'ID utilisateur GitHub, le nom du référentiel et l'ID du problème ou la description du problème que vous souhaitez résoudre.

Une fois terminé, il extraira un conteneur Composio Docker du registre et commencera à travailler sur le problème.


Enfin, une fois le workflow terminé, le correctif sera transféré vers le référentiel distant. Désormais, lorsque vous ouvrirez votre référentiel GitHub, vous verrez une nouvelle branche avec le correctif proposé pour le problème. Vous pouvez le comparer avec la branche principale et créer une pull request.
merci, Mike Scott

Vous pouvez trouver le code complet ici sur GitHub .


Prochaines étapes ⏭️

La meilleure chose à propos de cet agent SWE est que vous pouvez étendre ses capacités à l'aide des outils et des intégrations Composio.

Vous pouvez ajouter Slack ou Discord à votre agent pour vous avertir lorsque l'exécution est terminée. Vous pouvez également connecter Jira ou Linear pour créer et mettre à jour automatiquement des tâches en fonction des activités de l'agent.


Connectons-nous ! 🔌

Vous pouvez rejoindre notre communauté pour interagir avec les responsables et contribuer en tant que développeur open source. N'hésitez pas à visiter notre dépôt GitHub pour contribuer et créer des tickets liés à Composio. Dév.

Star le Composio. dépôt de développement ⭐

star du repo


Merci pour la lecture!