paint-brush
Cómo construí un programador de inteligencia artificial para automatizar la solución de problemas de GitHub 🤯por@sunilkumardash9
542 lecturas
542 lecturas

Cómo construí un programador de inteligencia artificial para automatizar la solución de problemas de GitHub 🤯

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

Demasiado Largo; Para Leer

Descubra cómo crear un agente SWE con tecnología de IA para automatizar los flujos de trabajo de GitHub usando Typecript. Esta guía cubre la configuración, los componentes y la implementación de un agente autónomo que puede manejar tareas como actualizar la documentación, corregir errores y aplicar parches, liberando a los desarrolladores para que se concentren en un trabajo más creativo.
featured image - Cómo construí un programador de inteligencia artificial para automatizar la solución de problemas de GitHub 🤯
Sunil Kumar Dash HackerNoon profile picture


Durante las últimas semanas, hemos estado trabajando diligentemente en un repositorio en rápido crecimiento en Composio . Pronto nos dimos cuenta de que tareas como actualizar archivos Léame, corregir cadenas de documentos y corregir errores menores (aunque repetitivos y mundanos) consumían gran parte de nuestro ancho de banda.


Entonces pensé, ¿por qué no construir un agente autónomo impulsado por IA para manejar estos trabajos pesados?

Queríamos un agente de IA que pudiera hacerlo.


  • Accede al repositorio de GitHub.
  • Tome cualquier problema determinado y escriba una solución para él.
  • Si es necesario, ejecute el código para comprobar si es bueno.
  • Finalmente, envíe los archivos del parche al repositorio remoto.


Finalmente, construimos un metamarco simple y extensible para crear agentes de ingeniería de software.

Estos agentes pueden desempeñarse de manera similar a sus homólogos humanos en muchas de estas tareas. Delegar tareas mundanas a estos agentes tiene sentido para liberar a los desarrolladores y poder concentrarse en tareas más creativas.


alemán


En este artículo, le mostraré cómo crear un agente SWE en Typecript para automatizar sus flujos de trabajo de GitHub.

Pero antes de eso, comprendamos qué son incluso los agentes AI y SWE.


¿Qué son los agentes de IA?

Los agentes de IA son sistemas impulsados por modelos de IA que pueden realizar tareas de forma autónoma, interactuar con su entorno y tomar decisiones basadas en su programación y los datos que procesan.

Componentes de los agentes de IA

Un agente de IA consta de tres componentes cruciales:

  • LLM : el modelo de lenguaje grande es responsable del razonamiento, la toma de decisiones, la llamada de herramientas, etc.
  • Memoria : gestiona información a corto y largo plazo para realizar un seguimiento de los flujos de trabajo.
  • Herramientas : permite la interacción con el entorno externo, como una herramienta GitHub para extraer información de los repositorios y realizar los cambios necesarios.

¿Qué son los agentes SWE?

Entonces, ¿cuándo se llama agente SWE a un agente de IA?

Los agentes SWE son agentes de IA que imitan las cualidades y características de un ingeniero de software humano, como

  • Planificación y razonamiento a largo plazo.
  • Utilizar herramientas de desarrollo estándar.
  • Mejorar la calidad del código mediante pruebas y comentarios.
  • Depurar y resolver problemas de forma autónoma.

dulce

Descripción general Agente SWE

Estas son algunas de las características del agente SWE que vamos a construir:

  • Es independiente del marco, por lo que puede utilizar cualquier marco, como LangChain, OpenAI SDK, etc.
  • Puede agregar herramientas para ampliar su versatilidad, como Tavily para acceso a Internet.


Los agentes de SWE pueden acceder a sus repositorios públicos y privados, trabajar en problemas proporcionados y enviar cambios a los repositorios.

Puede ejecutar códigos utilizando la máquina host, Docker o cualquier otro entorno de nube (E2B, FlyIo). Sin embargo, sería mejor si prefiriera utilizar los dos últimos para la ejecución del código de espacio aislado.


El sandboxing ayuda a prevenir consecuencias no deseadas de la ejecución de código arbitrario.


Requisitos previos para el agente SWE

Estos son los requisitos previos para crear correctamente el agente:

  1. Clave API de OpenAI: utilizaremos el SDK de OpenAI para acceder a los modelos GPT y organizar llamadas a herramientas.
  2. Token de acceso de GitHub: debes vincular tu cuenta de GitHub usando un token de acceso personal para permitir que el agente de SWE acceda y modifique su repositorio de códigos.
  3. Clave API de Composio: también necesitará una clave API de Composio. Para conseguir uno, crea un usuario cuenta con Composio y navegue hasta la pestaña Configuración en el panel.

página de inicio de sesión


Empecemos 🔥

Dependencias

Comience instalando dependencias usando su administrador de paquetes favorito. El método recomendado es pnpm , pero también puedes usar npm o yarn .

 pnpm install -g composio-core

Configurar variables de entorno 🌐

Necesitará GITHUB_ACCESS_TOKEN, OPENAI_API_KEY, COMPOSIO_API_KEY, GITHUB_USERNAME y GITHUB_USER_EMAIL para completar el proyecto.


Entonces, cree un archivo .env y agregue las variables anteriores.

 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"

Estructura del proyecto 📁

El proyecto está organizado de la siguiente manera:

src
├── agentes
│ └── dulces
├── aplicaciones.ts
├── indicaciones.ts
└── utils.ts

Aquí hay una breve descripción de los archivos.

  • agentes/swe.ts : Contiene la implementación del agente SWE.
  • app.ts : el punto de entrada principal de la aplicación.
  • avisos.ts : define los avisos utilizados por los agentes.
  • utils.ts : funciones de utilidad utilizadas en todo el proyecto.

Para comenzar rápidamente, clona este repositorio e instala el resto de dependencias.

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


Ahora que ha terminado con toda la configuración. Codifiquemos nuestro agente de IA.


pepe codificador



Definición de indicaciones y objetivos 🎯

Comenzamos definiendo las indicaciones y objetivos del agente SWE. Explicar cada paso en detalle es crucial, ya que estas definiciones influyen significativamente en el desempeño y la ejecución del agente.

Por lo tanto, cree un archivo prompts.ts si aún no lo ha hecho.

A continuación, defina el rol y el objetivo del agente.

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


Aquí, definimos el rol como SWE y el objetivo es solucionar cualquier problema de codificación y crear un parche para solucionarlo usando filetool_git_patch . Esta es una acción de Compsoio para la integración de GitHub para crear archivos de parche.

Ahora, define la historia de fondo y una descripción del agente 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`;

En el bloque de código anterior, hemos definido cuidadosa y claramente los pasos que el agente debe seguir para realizar la tarea. Esto es importante para garantizar que el agente sepa qué hacer cuando se enfrenta a obstáculos de programación comunes.


Definición de funciones de utilidad 🛠️

En esta sección, definiremos dos funciones principales, from GitHub y getBranchNameFromIssue , que extraerán información sobre un problema.

 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 }; }

Entonces, esto es lo que sucede en el bloque de código anterior.

  • readUserInput : esta función lee la entrada del usuario desde la línea de comando. Solo necesitamos el ID de usuario de GitHub, el nombre del repositorio y el número o descripción del problema.
  • createGithubIssueValidator : esta función devuelve un validador para problemas de GitHub. Puede manejar entradas como una ruta de archivo, un ID de problema numérico o una descripción de cadena simple. Si la entrada es un ID de problema numérico, obtiene los detalles del problema de GitHub usando la acción github_issues_get de Composio.
  • fromGitHub : esta función combina estos elementos para recopilar y validar la información necesaria sobre un repositorio de GitHub y un problema.

Ahora, defina getBranchNameFromIssue para crear un nombre de sucursal a partir de la descripción del problema.

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

Definiendo al Agente Swe 🤖

Esta es la sección más importante, donde definirá el agente Swe utilizando los asistentes de OpenAI y los conjuntos de herramientas de Composio.

Entonces, primero, importe las bibliotecas y defina el LLM y las herramientas.

 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, }) }); */

En el bloque de código anterior,

  • Creamos una instancia de OpenAI con la clave API.
  • También creamos una instancia de OpenAIToolSet con workspaceConfig configurado en Docker. Esto es para utilizar Docker para proteger el entorno de codificación del agente Swe. También puedes utilizar intérpretes de código en la nube como E2B y FlyIo.

Ahora definiremos el Agente 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 }; }

Esto es lo que sucede en el bloque de código anterior.

  • Obtener herramientas : recupera herramientas del conjunto de herramientas de Composio para filetool , file edit tool y shelltool . Como sugiere el nombre, se utilizarán para acceder a archivos, editar archivos y utilizar el shell para ejecutar comandos.
  • Recortar descripciones de herramientas : limita las descripciones de herramientas a un máximo de 1024 caracteres.
  • Actualizar valores nulos : reemplaza los valores nulos en las configuraciones de herramientas con matrices vacías.
  • Crear hilo asistente : inicia un hilo asistente OpenAI con indicaciones predefinidas.
  • Declaración de devolución : proporciona las herramientas inicializadas, el subproceso asistente, la instancia de OpenAI y el conjunto de herramientas Composio.

Definiendo el punto de entrada a la aplicación 🚀

Esta es la sección final, donde definimos el punto de entrada de la aplicación. Por lo tanto, cargue las variables de entorno e importe los módulos necesarios.

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

El bloque de código

  • Carga variables de entorno.
  • Importa las funciones de utilidad necesarias.
  • Importa el Agente Swe y el Objetivo del agente que definimos anteriormente.

Ahora, defina la función 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();


Este es nuestro archivo app.ts completo, que se utilizará para ejecutar el flujo de trabajo agente.


Entonces, esto es lo que está sucediendo en el código anterior.

  • Inicializar agente SWE : llama a initSWEAgent para obtener el hilo del asistente, la instancia de OpenAI, las herramientas y el conjunto de herramientas de Composio.
  • Obtener repositorio y problema : recupera detalles del repositorio y del problema desde fromGithub .
  • Crear asistente : inicializa un asistente OpenAI con instrucciones, herramientas y el modelo de lenguaje.
  • Enviar problema al asistente : envía el contenido del problema como un mensaje al hilo del asistente.
  • Ejecutar asistente y encuesta : ejecuta el asistente y sondea las respuestas a las llamadas de herramientas. Para obtener más información sobre las respuestas a las encuestas, consulte la Repositorio del SDK de OpenAI.
  • Ejecutar acción de parche : ejecuta filetool_git_patch para generar un parche.
  • Manejar la respuesta del parche : si se genera un parche, regístrelo, cree una rama, confirme y envíe los cambios. Espere 2 segundos antes de crear una solicitud de extracción. Cree una solicitud de extracción en GitHub.
  • Cerrar espacio de trabajo : cierra el espacio de trabajo del conjunto de herramientas Composio.
  • Ejecutar función principal : llama a main() para ejecutar los pasos anteriores.


Ahora, ejecute la aplicación usando pnpm start .


Esto le pedirá que ingrese la ID de usuario de GitHub, el nombre del repositorio y la ID del problema o la descripción del problema que desea abordar.

Una vez completado, extraerá un contenedor Composio Docker del registro y comenzará a trabajar en el problema.


Finalmente, cuando se complete el flujo de trabajo, el parche se enviará al repositorio remoto. Ahora, cuando abras tu repositorio de GitHub, verás una nueva rama con la solución propuesta para el problema. Puedes compararlo con la rama principal y crear una solicitud de extracción.
gracias mike scott

Puedes encontrar el código completo. aquí en GitHub .


Próximos pasos ⏭️

Lo mejor de este agente SWE es que puede ampliar sus capacidades utilizando las herramientas e integraciones de Composio.

Puede agregar Slack o Discord a su agente para que le notifique cuando se complete la ejecución. También puede conectar Jira o Linear para crear y actualizar tareas automáticamente en función de las actividades del agente.


¡Conectémonos! 🔌

Puede unirse a nuestra comunidad para interactuar con los mantenedores y contribuir como desarrollador de código abierto. No dudes en visitar nuestro repositorio de GitHub para contribuir y crear ediciones relacionadas con Composio. Desarrollo.

Protagoniza el Composio. repositorio de desarrollo ⭐

protagonizar el repositorio


¡Gracias por leer!