paint-brush
GitHub 문제 수정을 자동화하기 위해 AI 프로그래머를 구축한 방법 🤯~에 의해@sunilkumardash9
514 판독값
514 판독값

GitHub 문제 수정을 자동화하기 위해 AI 프로그래머를 구축한 방법 🤯

~에 의해 Sunil Kumar Dash16m2024/08/01
Read on Terminal Reader

너무 오래; 읽다

Typescript를 사용하여 GitHub 워크플로를 자동화하는 AI 기반 SWE 에이전트를 만드는 방법을 알아보세요. 이 가이드에서는 문서 업데이트, 버그 수정, 패치 푸시와 같은 작업을 처리할 수 있는 자율 에이전트의 설정, 구성 요소 및 구현을 다루므로 개발자는 보다 창의적인 작업에 집중할 수 있습니다.
featured image - GitHub 문제 수정을 자동화하기 위해 AI 프로그래머를 구축한 방법 🤯
Sunil Kumar Dash HackerNoon profile picture


지난 몇 주 동안 우리는 Composio 에서 빠르게 성장하는 저장소를 부지런히 작업해 왔습니다. 우리는 ReadMe 업데이트, 문서 문자열 수정, 사소한 버그 수정과 같은 작업이 반복적이고 평범하기는 하지만 대역폭의 많은 부분을 소비하고 있다는 사실을 곧 깨달았습니다.


그래서 저는 이러한 지루한 작업을 처리하기 위해 AI 기반 자율 에이전트를 구축하는 것이 어떨까요?

우리는 그럴 수 있는 AI 에이전트를 원했습니다.


  • GitHub 저장소에 액세스하세요.
  • 주어진 문제를 해결하고 이에 대한 솔루션을 작성하십시오.
  • 필요한 경우 코드를 실행하여 문제가 없는지 확인하세요.
  • 마지막으로 패치 파일을 원격 저장소에 푸시합니다.


마지막으로 우리는 소프트웨어 엔지니어링 에이전트 구축을 위한 간단하고 확장 가능한 메타 프레임워크를 구축했습니다.

이러한 에이전트는 많은 작업에서 인간과 유사하게 수행할 수 있습니다. 일상적인 작업을 이러한 에이전트에 오프로드하는 것은 개발자가 더 창의적인 작업에 집중할 수 있도록 하는 데 도움이 됩니다.


실내 변기


이 기사에서는 GitHub 워크플로를 자동화하기 위해 Typescript에서 SWE 에이전트를 구축하는 방법을 보여 드리겠습니다.

하지만 그 전에 AI 및 SWE 에이전트가 무엇인지 이해해 봅시다.


AI 에이전트란 무엇입니까?

AI 에이전트는 자율적으로 작업을 수행하고 환경과 상호 작용하며 프로그래밍 및 처리하는 데이터를 기반으로 결정을 내릴 수 있는 AI 모델을 기반으로 하는 시스템입니다.

AI 에이전트의 구성 요소

AI 에이전트는 세 가지 중요한 구성 요소로 구성됩니다.

  • LLM : 대형 언어 모델은 추론, 의사 결정, 도구 호출 등을 담당합니다.
  • 메모리 : 워크플로우를 추적하기 위해 단기 및 장기 정보를 관리합니다.
  • 도구 : 리포지토리에서 정보를 추출하고 필요한 변경을 수행하기 위한 GitHub 도구와 같은 외부 환경과의 상호 작용을 활성화합니다.

SWE 에이전트란 무엇입니까?

그렇다면 언제 AI 에이전트를 SWE 에이전트라고 부를까요?

SWE 에이전트는 인간 소프트웨어 엔지니어의 자질과 특성을 모방하는 AI 에이전트입니다.

  • 장기 계획 및 추론.
  • 표준 개발자 도구를 사용합니다.
  • 테스트와 피드백을 통해 코드 품질을 개선합니다.
  • 문제를 자율적으로 디버깅하고 해결합니다.

스위킷

개요 SWE 에이전트

우리가 구축할 SWE 에이전트의 몇 가지 특징은 다음과 같습니다.

  • 프레임워크에 구애받지 않으므로 LangChain, OpenAI SDK 등과 같은 모든 프레임워크를 사용할 수 있습니다.
  • 인터넷 액세스를 위한 Tavily와 같이 다양성을 확장하는 도구를 추가할 수 있습니다.


SWE 에이전트는 공개 및 비공개 저장소에 액세스하고, 제공된 문제에 대해 작업하고, 저장소에 변경 사항을 푸시할 수 있습니다.

호스트 머신, Docker 또는 기타 클라우드 환경(E2B, FlyIo)을 사용하여 코드를 실행할 수 있습니다. 그러나 샌드박싱 코드 실행을 위해 후자의 두 가지를 사용하려는 경우 가장 좋습니다.


샌드박싱은 임의 코드 실행으로 인한 의도하지 않은 결과를 방지하는 데 도움이 됩니다.


SWE 에이전트 필수 구성 요소

에이전트를 성공적으로 빌드하기 위한 전제 조건은 다음과 같습니다.

  1. OpenAI API 키: OpenAI SDK를 사용하여 GPT 모델에 액세스하고 도구 호출을 조정합니다.
  2. GitHub 액세스 토큰: GitHub 계정을 연결해야 합니다. 개인 액세스 토큰 SWE 에이전트가 코드 저장소에 액세스하고 수정할 수 있도록 합니다.
  3. Composio API 키: Composio의 API 키도 필요합니다. 하나를 얻으려면 사용자를 만드세요. 계정 Composio를 사용하여 대시보드의 설정 탭으로 이동합니다.

로그인 페이지


시작해 보세요 🔥

종속성

선호하는 패키지 관리자를 사용하여 종속성을 설치하는 것부터 시작하세요. 권장되는 방법은 pnpm 이지만 npm 또는 yarn 사용할 수도 있습니다.

 pnpm install -g composio-core

환경 변수 설정 🌐

프로젝트를 완료하려면 GITHUB_ACCESS_TOKEN, OPENAI_API_KEY, COMPOSIO_API_KEY, GITHUB_USERNAME 및 GITHUB_USER_EMAIL이 필요합니다.


따라서 .env 파일을 만들고 위의 변수를 추가하십시오.

 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"

프로젝트 구조 📁

프로젝트는 다음과 같이 구성됩니다.

소스
├── 대리인
│ └── swe.ts
├── app.ts
├── 프롬프트.ts
└── utils.ts

다음은 파일에 대한 간략한 설명입니다.

  • Agents/swe.ts : SWE 에이전트 구현이 포함되어 있습니다.
  • app.ts : 애플리케이션의 주요 진입점입니다.
  • 프롬프트.ts : 에이전트가 사용하는 프롬프트를 정의합니다.
  • utils.ts : 프로젝트 전반에 걸쳐 사용되는 유틸리티 기능입니다.

빠르게 시작하려면 이 저장소를 복제하고 나머지 종속 항목을 설치하세요.

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


이제 모든 설정이 끝났습니다. AI 에이전트를 코딩해 보겠습니다.


코더 페페



프롬프트와 목표 정의하기 🎯

먼저 SWE 상담원의 프롬프트와 목표를 정의합니다. 각 단계를 자세히 설명하는 것이 중요합니다. 이러한 정의는 에이전트의 성능과 실행에 큰 영향을 미치기 때문입니다.

따라서 아직 작성하지 않은 경우 prompts.ts 파일을 작성하십시오.

다음으로 에이전트의 역할과 목표를 정의합니다.

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


여기서는 역할을 SWE로 정의했으며 목표는 코딩 문제를 수정하고 filetool_git_patch 사용하여 수정 사항에 대한 패치를 만드는 것입니다. 패치 파일 생성을 위한 GitHub 통합을 위한 Compsoio 작업입니다.

이제 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`;

위의 코드 블록에서는 에이전트가 작업을 수행하기 위해 수행해야 하는 단계를 신중하고 명확하게 정의했습니다. 이는 일반적인 프로그래밍 문제에 직면했을 때 에이전트가 무엇을 해야 할지 알 수 있도록 하는 데 중요합니다.


유틸리티 함수 정의 🛠️

이 섹션에서는 문제에 대한 정보를 추출하는 from GitHubgetBranchNameFromIssue 의 두 가지 주요 함수를 정의합니다.

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

위의 코드 블록에서 진행되는 작업은 다음과 같습니다.

  • readUserInput : 이 함수는 명령줄에서 사용자 입력을 읽습니다. GitHub 사용자 ID, 저장소 이름, 문제 번호 또는 설명만 필요합니다.
  • createGithubIssueValidator : 이 함수는 GitHub 문제에 대한 유효성 검사기를 반환합니다. 입력을 파일 경로, 숫자 문제 ID 또는 일반 문자열 설명으로 처리할 수 있습니다. 입력이 숫자 문제 ID인 경우 Composio의 github_issues_get 작업을 사용하여 GitHub에서 문제 세부 정보를 가져옵니다.
  • fromGitHub : 이 기능은 이러한 요소를 결합하여 GitHub 저장소 및 문제에 대해 필요한 정보를 수집하고 유효성을 검사합니다.

이제 getBranchNameFromIssue 정의하여 문제 설명에서 분기 이름을 만듭니다.

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

Swe 에이전트 정의 🤖

이는 OpenAI 도우미 및 Composio 도구 세트를 사용하여 Swe 에이전트를 정의하는 가장 중요한 섹션입니다.

따라서 먼저 라이브러리를 가져오고 LLM 및 도구를 정의하십시오.

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

위의 코드 블록에서,

  • API 키를 사용하여 OpenAI 인스턴스를 만들었습니다.
  • 또한 Docker로 설정된 workspaceConfig 사용하여 OpenAIToolSet 의 인스턴스를 생성했습니다. 이는 Docker를 사용하여 Swe 에이전트의 코딩 환경을 샌드박스하는 것입니다. E2B 및 FlyIo와 같은 클라우드 코드 해석기를 사용할 수도 있습니다.

이제 Swe Agent를 정의하겠습니다.

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

위의 코드 블록에서 진행되는 작업은 다음과 같습니다.

  • 도구 가져오기 : filetool , file edit toolshelltool 용 Composio 도구 세트에서 도구를 가져옵니다. 이름에서 알 수 있듯이 파일에 액세스하고, 파일을 편집하고, 명령을 실행하기 위해 셸을 사용하는 데 사용됩니다.
  • 자르기 도구 설명 : 도구 설명을 최대 1024자로 제한합니다.
  • Null 값 업데이트 : 도구 구성의 Null 값을 빈 배열로 바꿉니다.
  • 보조 스레드 생성 : 사전 정의된 프롬프트를 사용하여 OpenAI 보조 스레드를 시작합니다.
  • Return 문 : 초기화된 도구, 보조 스레드, OpenAI 인스턴스 및 Composio 도구 세트를 제공합니다.

애플리케이션에 대한 진입점 정의 🚀

이는 애플리케이션의 진입점을 정의하는 마지막 섹션입니다. 따라서 환경 변수를 로드하고 필요한 모듈을 가져옵니다.

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

코드 블록

  • 환경 변수를 로드합니다.
  • 필요한 유틸리티 기능을 가져옵니다.
  • 이전에 정의한 Swe 에이전트와 에이전트 목표를 가져옵니다.

이제 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();


이는 에이전트 워크플로를 실행하는 데 사용되는 완전한 app.ts 파일입니다.


위의 코드에서 무슨 일이 일어나고 있는지 살펴보겠습니다.

  • SWE 에이전트 초기화 : initSWEAgent 호출하여 보조 스레드, OpenAI 인스턴스, 도구 및 Composio 도구 세트를 가져옵니다.
  • Fetch Repository and Issue : fromGithub 에서 저장소와 이슈 세부정보를 가져옵니다.
  • Create Assistant : 지침, 도구 및 언어 모델을 사용하여 OpenAI 도우미를 초기화합니다.
  • Send Issue to Assistant : 이슈 내용을 어시스턴트 스레드에 메시지로 보냅니다.
  • Run Assistant and Poll : 도구 호출 응답을 위해 도우미 및 폴링을 실행합니다. 폴링 응답에 대한 자세한 내용은 다음을 참조하세요. OpenAI SDK 저장소.
  • 패치 작업 실행 : filetool_git_patch 실행하여 패치를 생성합니다.
  • 패치 응답 처리 : 패치가 생성되면 이를 기록하고, 브랜치를 생성하고, 커밋하고, 변경 사항을 푸시합니다. 풀 요청을 생성하기 전에 2초 동안 기다리십시오. GitHub에서 풀 요청을 생성합니다.
  • 작업공간 닫기 : Composio 도구 세트 작업공간을 닫습니다.
  • Run Main Function : main() 을 호출하여 위의 단계를 실행합니다.


이제 pnpm start 사용하여 애플리케이션을 실행하세요.


그러면 GitHub 사용자 ID, 저장소 이름, 문제 ID 또는 해결하려는 문제에 대한 설명을 입력하라는 메시지가 표시됩니다.

완료되면 레지스트리에서 Composio Docker 컨테이너를 가져와 문제 해결 작업을 시작합니다.


마지막으로 워크플로가 완료되면 패치가 원격 저장소에 푸시됩니다. 이제 GitHub 저장소를 열면 문제에 대해 제안된 수정 사항이 포함된 새 분기가 표시됩니다. 메인 브랜치와 비교하여 풀 리퀘스트를 생성할 수 있습니다.
고마워요, 마이크 스콧

전체 코드를 찾을 수 있습니다 여기 GitHub에서 .


다음 단계 ⏭️

이 SWE 에이전트의 가장 좋은 점은 Composio 도구 및 통합을 사용하여 기능을 확장할 수 있다는 것입니다.

에이전트에 Slack 또는 Discord를 추가하여 실행이 완료되면 알림을 받을 수 있습니다. Jira 또는 Linear를 연결하여 에이전트 활동을 기반으로 작업을 자동으로 생성하고 업데이트할 수도 있습니다.


연결하자! 🔌

우리 커뮤니티에 가입하여 관리자와 교류하고 오픈 소스 개발자로서 기여할 수 있습니다. Composio와 관련된 이슈에 기여하고 생성하려면 주저하지 말고 GitHub 저장소를 방문하세요. 개발.

컴포지오에 별표를 표시하세요. 개발 저장소 ⭐

저장소에 별표 표시


읽어 주셔서 감사합니다!