paint-brush
Como desenvolvi um programador de IA para automatizar correções de problemas do GitHub 🤯por@sunilkumardash9
486 leituras
486 leituras

Como desenvolvi um programador de IA para automatizar correções de problemas do GitHub 🤯

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

Muito longo; Para ler

Descubra como criar um agente SWE com tecnologia de IA para automatizar fluxos de trabalho do GitHub usando Typescript. Este guia cobre a configuração, os componentes e a implementação de um agente autônomo que pode lidar com tarefas como atualização de documentação, correção de bugs e envio de patches, liberando os desenvolvedores para se concentrarem em um trabalho mais criativo.
featured image - Como desenvolvi um programador de IA para automatizar correções de problemas do GitHub 🤯
Sunil Kumar Dash HackerNoon profile picture


Nas últimas semanas, temos trabalhado diligentemente em um repositório de rápido crescimento no Composio . Logo percebemos que tarefas como atualizar ReadMes, corrigir docstrings e fazer pequenas correções de bugs — embora repetitivas e mundanas — estavam consumindo grande parte da nossa largura de banda.


Então, pensei, por que não construir um agente autônomo com tecnologia de IA para lidar com esses trabalhos pesados?

Queríamos um agente de IA que pudesse.


  • Acesse o repositório GitHub.
  • Pegue qualquer problema e escreva uma solução para ele.
  • Se necessário, execute o código para verificar se está bom.
  • Por fim, envie os arquivos de patch para o repositório remoto.


Por fim, construímos uma metaestrutura simples e extensível para a construção de agentes de engenharia de software.

Esses agentes podem atuar de forma semelhante aos seus homólogos humanos em muitas dessas tarefas. Transferir tarefas rotineiras para esses agentes faz sentido para liberar seus desenvolvedores para se concentrarem em tarefas mais criativas.


Jerry


Neste artigo, mostrarei como construir um agente SWE em Typescript para automatizar seus fluxos de trabalho GitHub.

Mas antes disso, vamos entender o que são até mesmo os agentes de IA e SWE.


O que são agentes de IA?

Os agentes de IA são sistemas alimentados por modelos de IA que podem executar tarefas de forma autônoma, interagir com seu ambiente e tomar decisões com base em sua programação e nos dados que processam.

Componentes de Agentes de IA

Um agente de IA consiste em três componentes cruciais:

  • LLMs : O Large Language Model é responsável pelo raciocínio, tomada de decisão, chamada de ferramentas, etc.
  • Memória : gerencia informações de curto e longo prazo para rastrear fluxos de trabalho.
  • Ferramentas : Possibilita a interação com o ambiente externo, como uma ferramenta GitHub para extrair informações de repositórios e realizar as alterações necessárias.

O que são Agentes SWE?

Então, quando você chama um agente de IA de agente SWE?

Agentes SWE são agentes de IA que imitam as qualidades e características de um engenheiro de software humano, como

  • Planejamento e raciocínio de longo prazo.
  • Usando ferramentas de desenvolvedor padrão.
  • Melhorando a qualidade do código por meio de testes e feedback.
  • Depurar e resolver problemas de forma autônoma.

swekit

Visão geral do agente SWE

Aqui estão algumas das características do agente SWE que iremos construir:

  • É independente de framework, então você pode usar qualquer framework, como LangChain, OpenAI SDK, etc…
  • Você pode adicionar ferramentas para ampliar sua versatilidade, como o Tavily para acesso à internet.


Os agentes SWE podem acessar seus repositórios públicos e privados, trabalhar nos problemas fornecidos e enviar alterações aos repositórios.

Ele pode executar códigos usando a máquina host, Docker ou qualquer outro ambiente de nuvem (E2B, FlyIo). No entanto, seria melhor se você preferisse usar os dois últimos para execução de código em sandbox.


O sandboxing ajuda a evitar quaisquer consequências indesejadas da execução arbitrária de código.


Pré-requisitos para Agente SWE

Aqui estão os pré-requisitos para construir o agente com sucesso:

  1. Chave de API OpenAI: usaremos o OpenAI SDK para acessar modelos GPT e orquestrar chamadas de ferramentas.
  2. Token de acesso do GitHub: você deve vincular sua conta do GitHub usando um token de acesso pessoal para permitir que o agente SWE acesse e modifique seu repositório de código.
  3. Chave de API do Composio: você também precisará de uma chave de API do Composio. Para obter um, crie um usuário conta com o Composio e navegue até a guia Configurações no painel.

página de login


Vamos começar 🔥

Dependências

Comece instalando dependências usando seu gerenciador de pacotes favorito. O método recomendado é pnpm , mas você também pode usar npm ou yarn .

 pnpm install -g composio-core

Configurar variáveis de ambiente 🌐

Você precisará de GITHUB_ACCESS_TOKEN, OPENAI_API_KEY, COMPOSIO_API_KEY, GITHUB_USERNAME e GITHUB_USER_EMAIL para concluir o projeto.


Portanto, crie um arquivo .env e adicione as variáveis acima.

 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"

Estrutura do Projeto 📁

O projeto está organizado da seguinte forma:

fonte
├── agentes
│ └── doce
├── aplicativo.ts
├── prompts.ts
└── utilitários.ts

Aqui está uma breve descrição dos arquivos.

  • agentes/swe.ts : Contém a implementação do agente SWE.
  • app.ts : O principal ponto de entrada do aplicativo.
  • prompts.ts : Define os prompts usados pelos agentes.
  • utils.ts : Funções utilitárias usadas em todo o projeto.

Para começar rapidamente, clone este repositório e instale o restante das dependências.

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


Agora que você concluiu toda a configuração. Vamos codificar nosso agente de IA.


codificador pepe



Definindo Prompts e Metas 🎯

Começamos definindo os prompts e objetivos do agente SWE. Explicar detalhadamente cada etapa é crucial, pois essas definições influenciam significativamente o desempenho e a execução do agente.

Portanto, crie um arquivo prompts.ts se ainda não o fez.

A seguir, defina a função e o objetivo do 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";


Aqui, definimos a função como SWE, e o objetivo é corrigir qualquer problema de codificação e criar um patch para a correção usando filetool_git_patch . Esta é uma ação Compsoio para integração com GitHub para criação de arquivos de patch.

Agora, defina a história de fundo e uma descrição do 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`;

No bloco de código acima, definimos cuidadosa e claramente as etapas que o agente precisa realizar para realizar a tarefa. Isto é importante para garantir que o agente saiba o que fazer quando confrontado com obstáculos comuns de programação.


Definindo Funções Utilitárias 🛠️

Nesta seção, definiremos duas funções principais, from GitHub e getBranchNameFromIssue , que extrairão informações sobre um 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 }; }

Então, aqui está o que está acontecendo no bloco de código acima.

  • readUserInput : esta função lê a entrada do usuário na linha de comando. Precisamos apenas do ID do usuário do GitHub, nome do repositório e número ou descrição do problema.
  • createGithubIssueValidator : esta função retorna um validador para problemas do GitHub. Ele pode manipular a entrada como um caminho de arquivo, um ID numérico de problema ou uma descrição de string simples. Se a entrada for um ID numérico do problema, ele buscará os detalhes do problema no GitHub usando a ação github_issues_get do Composio.
  • fromGitHub : esta função combina esses elementos para coletar e validar as informações necessárias sobre um repositório GitHub e um problema.

Agora, defina getBranchNameFromIssue para criar um nome de ramificação a partir da descrição do problema.

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

Definindo o Agente Swe 🤖

Esta é a seção mais importante, onde você definirá o agente Swe usando os assistentes OpenAI e conjuntos de ferramentas Composio.

Então, primeiro importe as bibliotecas e defina o LLM e as ferramentas.

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

No bloco de código acima,

  • Criamos uma instância do OpenAI com a chave API.
  • Também criamos uma instância do OpenAIToolSet com workspaceConfig definido como Docker. Isso serve para usar o Docker para proteger o ambiente de codificação do agente Swe. Você também pode usar interpretadores de código em nuvem como E2B e FlyIo.

Agora, definiremos o 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 }; }

Aqui está o que está acontecendo no bloco de código acima.

  • Obter ferramentas : busca ferramentas do conjunto de ferramentas Composio para filetool , file edit tool e shelltool . Como o nome sugere, eles serão usados para acessar arquivos, editar arquivos e usar o shell para executar comandos.
  • Descrições da ferramenta de corte : limita as descrições da ferramenta a um máximo de 1.024 caracteres.
  • Atualizar valores nulos : substitui valores nulos em configurações de ferramentas por matrizes vazias.
  • Criar thread de assistente : inicia um thread de assistente OpenAI com prompts predefinidos.
  • Instrução de retorno : fornece as ferramentas inicializadas, thread assistente, instância OpenAI e conjunto de ferramentas Composio.

Definindo o ponto de entrada para o aplicativo 🚀

Esta é a seção final, onde definimos o ponto de entrada da aplicação. Portanto, carregue as variáveis de ambiente e importe os módulos necessários.

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

O bloco de código

  • Carrega variáveis de ambiente.
  • Importa as funções utilitárias necessárias.
  • Importa o Agente Swe e a Meta do agente que definimos anteriormente.

Agora, defina a função 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 é o nosso arquivo app.ts completo, que será usado para executar o fluxo de trabalho do agente.


Então, aqui está o que está acontecendo no código acima.

  • Inicializar SWE Agent : chama initSWEAgent para obter o thread do assistente, a instância OpenAI, as ferramentas e o conjunto de ferramentas Composio.
  • Buscar repositório e problema : busca detalhes do repositório e do problema no fromGithub .
  • Criar assistente : inicializa um assistente OpenAI com instruções, ferramentas e o modelo de linguagem.
  • Enviar problema para o assistente : envia o conteúdo do problema como uma mensagem para o tópico do assistente.
  • Executar assistente e pesquisa : executa o assistente e pesquisa respostas de chamadas de ferramenta. Para obter mais informações sobre as respostas da pesquisa, consulte o Repositório OpenAI SDK.
  • Executar ação de patch : executa filetool_git_patch para gerar um patch.
  • Lidar com a resposta do patch : se um patch for gerado, registre-o, crie uma ramificação, confirme e envie as alterações. Aguarde 2 segundos antes de criar uma solicitação pull. Crie uma solicitação pull no GitHub.
  • Fechar espaço de trabalho : fecha o espaço de trabalho do conjunto de ferramentas Composio.
  • Execute a função principal : chama main() para executar as etapas acima.


Agora, execute o aplicativo usando pnpm start .


Isso solicitará que você insira o ID do usuário do GitHub, o nome do repositório e o ID do problema ou a descrição do problema que deseja resolver.

Depois de concluído, ele extrairá um contêiner Composio Docker do registro e começará a trabalhar no problema.


Finalmente, quando o fluxo de trabalho for concluído, o patch será enviado para o repositório remoto. Agora, ao abrir seu repositório GitHub, você verá um novo branch com a correção proposta para o problema. Você pode compará-lo com o branch principal e criar uma solicitação pull.
obrigado, Mike Scott

Você pode encontrar o código completo aqui no GitHub .


Próximas etapas ⏭️

A melhor coisa sobre esse agente SWE é que você pode estender seus recursos usando ferramentas e integrações do Composio.

Você pode adicionar Slack ou Discord ao seu agente para notificá-lo quando a execução for concluída. Você também pode conectar o Jira ou o Linear para criar e atualizar automaticamente tarefas com base nas atividades do agente.


Vamos nos conectar! 🔌

Você pode se juntar à nossa comunidade para interagir com os mantenedores e contribuir como desenvolvedor de código aberto. Não hesite em visitar nosso repositório GitHub para contribuir e criar problemas relacionados ao Composio. Dev.

Estrelar o Composio. repositório de desenvolvimento ⭐

estrelar o repositório


Obrigado por ler!