지난 몇 주 동안 우리는 Composio 에서 빠르게 성장하는 저장소를 부지런히 작업해 왔습니다. 우리는 ReadMe 업데이트, 문서 문자열 수정, 사소한 버그 수정과 같은 작업이 반복적이고 평범하기는 하지만 대역폭의 많은 부분을 소비하고 있다는 사실을 곧 깨달았습니다.
그래서 저는 이러한 지루한 작업을 처리하기 위해 AI 기반 자율 에이전트를 구축하는 것이 어떨까요?
우리는 그럴 수 있는 AI 에이전트를 원했습니다.
마지막으로 우리는 소프트웨어 엔지니어링 에이전트 구축을 위한 간단하고 확장 가능한 메타 프레임워크를 구축했습니다.
이러한 에이전트는 많은 작업에서 인간과 유사하게 수행할 수 있습니다. 일상적인 작업을 이러한 에이전트에 오프로드하는 것은 개발자가 더 창의적인 작업에 집중할 수 있도록 하는 데 도움이 됩니다.
이 기사에서는 GitHub 워크플로를 자동화하기 위해 Typescript에서 SWE 에이전트를 구축하는 방법을 보여 드리겠습니다.
하지만 그 전에 AI 및 SWE 에이전트가 무엇인지 이해해 봅시다.
AI 에이전트는 자율적으로 작업을 수행하고 환경과 상호 작용하며 프로그래밍 및 처리하는 데이터를 기반으로 결정을 내릴 수 있는 AI 모델을 기반으로 하는 시스템입니다.
AI 에이전트는 세 가지 중요한 구성 요소로 구성됩니다.
그렇다면 언제 AI 에이전트를 SWE 에이전트라고 부를까요?
SWE 에이전트는 인간 소프트웨어 엔지니어의 자질과 특성을 모방하는 AI 에이전트입니다.
우리가 구축할 SWE 에이전트의 몇 가지 특징은 다음과 같습니다.
SWE 에이전트는 공개 및 비공개 저장소에 액세스하고, 제공된 문제에 대해 작업하고, 저장소에 변경 사항을 푸시할 수 있습니다.
호스트 머신, Docker 또는 기타 클라우드 환경(E2B, FlyIo)을 사용하여 코드를 실행할 수 있습니다. 그러나 샌드박싱 코드 실행을 위해 후자의 두 가지를 사용하려는 경우 가장 좋습니다.
샌드박싱은 임의 코드 실행으로 인한 의도하지 않은 결과를 방지하는 데 도움이 됩니다.
에이전트를 성공적으로 빌드하기 위한 전제 조건은 다음과 같습니다.
선호하는 패키지 관리자를 사용하여 종속성을 설치하는 것부터 시작하세요. 권장되는 방법은 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
다음은 파일에 대한 간략한 설명입니다.
빠르게 시작하려면 이 저장소를 복제하고 나머지 종속 항목을 설치하세요.
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 GitHub
및 getBranchNameFromIssue
의 두 가지 주요 함수를 정의합니다.
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(); }
이는 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, }) }); */
위의 코드 블록에서,
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 tool
및 shelltool
용 Composio 도구 세트에서 도구를 가져옵니다. 이름에서 알 수 있듯이 파일에 액세스하고, 파일을 편집하고, 명령을 실행하기 위해 셸을 사용하는 데 사용됩니다. 이는 애플리케이션의 진입점을 정의하는 마지막 섹션입니다. 따라서 환경 변수를 로드하고 필요한 모듈을 가져옵니다.
import dotenv from "dotenv"; dotenv.config(); import { fromGithub, getBranchNameFromIssue } from './utils'; import { initSWEAgent } from './agents/swe'; import { GOAL } from './prompts';
코드 블록
이제 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
파일입니다.
위의 코드에서 무슨 일이 일어나고 있는지 살펴보겠습니다.
initSWEAgent
호출하여 보조 스레드, OpenAI 인스턴스, 도구 및 Composio 도구 세트를 가져옵니다.fromGithub
에서 저장소와 이슈 세부정보를 가져옵니다.filetool_git_patch
실행하여 패치를 생성합니다.main()
을 호출하여 위의 단계를 실행합니다.
이제 pnpm start
사용하여 애플리케이션을 실행하세요.
그러면 GitHub 사용자 ID, 저장소 이름, 문제 ID 또는 해결하려는 문제에 대한 설명을 입력하라는 메시지가 표시됩니다.
완료되면 레지스트리에서 Composio Docker 컨테이너를 가져와 문제 해결 작업을 시작합니다.
마지막으로 워크플로가 완료되면 패치가 원격 저장소에 푸시됩니다. 이제 GitHub 저장소를 열면 문제에 대해 제안된 수정 사항이 포함된 새 분기가 표시됩니다. 메인 브랜치와 비교하여 풀 리퀘스트를 생성할 수 있습니다.
전체 코드를 찾을 수 있습니다
이 SWE 에이전트의 가장 좋은 점은 Composio 도구 및 통합을 사용하여 기능을 확장할 수 있다는 것입니다.
에이전트에 Slack 또는 Discord를 추가하여 실행이 완료되면 알림을 받을 수 있습니다. Jira 또는 Linear를 연결하여 에이전트 활동을 기반으로 작업을 자동으로 생성하고 업데이트할 수도 있습니다.
우리 커뮤니티에 가입하여 관리자와 교류하고 오픈 소스 개발자로서 기여할 수 있습니다. Composio와 관련된 이슈에 기여하고 생성하려면 주저하지 말고 GitHub 저장소를 방문하세요. 개발.
읽어 주셔서 감사합니다!