在过去的几周里,我们一直在努力开发Composio上一个快速增长的存储库。我们很快意识到,更新自述文件、修复文档字符串和修复小错误等任务虽然重复又单调,但却消耗了我们大量的带宽。
所以,我想,为什么不建立一个人工智能驱动的自主代理来处理这些繁重的工作呢?
我们想要一个能够做到这一点的人工智能代理。
最后,我们构建了一个用于构建软件工程代理的简单且可扩展的元框架。
这些代理在许多此类任务中的表现与人类相似。将单调乏味的任务交给这些代理是明智之举,这样开发人员就可以腾出时间专注于更具创造性的任务。
在本文中,我将向您展示如何在 Typescript 中构建 SWE 代理来自动化您的 GitHub 工作流程。
但在此之前,让我们先了解一下 AI 和 SWE 代理是什么。
人工智能代理是由人工智能模型驱动的系统,可以自主执行任务、与环境交互并根据其编程和处理的数据做出决策。
人工智能代理由三个关键组件组成:
那么,什么时候将 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
│ ...
│ │ 提示.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, }) }); */
在上面的代码块中,
OpenAIToolSet
实例,并将workspaceConfig
设置为 Docker。这是使用 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
。顾名思义,这些工具将用于访问文件、编辑文件以及使用 shell 执行命令。这是最后一部分,我们在这里定义应用程序的入口点。因此,加载环境变量并导入所需的模块。
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,以根据代理的活动自动创建和更新任务。
您可以加入我们的社区,与维护人员互动,并作为开源开发者做出贡献。请随时访问我们的 GitHub 存储库,以做出贡献并创建与 Composio 相关的问题。开发。
感谢您的阅读!