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.
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.
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.
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.
Un agent IA se compose de trois éléments cruciaux :
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
Voici quelques-unes des caractéristiques de l’agent SWE que nous allons construire :
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.
Voici les conditions préalables pour réussir à créer l’agent :
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
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"
Le projet est organisé comme suit :
src
├──agents
│ └── doux.ts
├── app.ts
├── invites.ts
└── utils.ts
Voici une brève description des fichiers.
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.
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.
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(); }
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,
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.
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. 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
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.
initSWEAgent
pour obtenir le thread assistant, l'instance OpenAI, les outils et l'ensemble d'outils Composio.fromGithub
.filetool_git_patch
pour générer un correctif.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.
Vous pouvez trouver le code complet
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.
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 ⭐
Merci pour la lecture!