在Composio ,過去幾週我們一直在努力開發一個快速成長的儲存庫。
很快,我們意識到許多重複的、沒有創意的、平凡的事情,例如更新自述文件、修復文件字串和修復小錯誤,佔用了我們的大部分頻寬。
所以,我想,為什麼不建構一個人工智慧驅動的自主代理來處理這些繁重的工作呢?
我們想要一個能夠做到這一點的人工智慧代理。
存取 GitHub 儲存庫。
考慮任何給定的問題並為其編寫解決方案。
如果需要,請執行程式碼來檢查它是否正常。
最後將補丁檔案推送到遠端倉庫。
最後,我們建立了一個簡單且可擴展的元框架來建立軟體工程代理。
在許多此類任務中,這些智能體可以與人類對應物執行類似的操作。將平凡的任務交給這些代理商是有意義的,可以讓開發人員專注於更具創意的任務。
在本文中,我將向您展示如何在 Typescript 中建立 SWE 代理來自動化您的 GitHub 工作流程。
但在此之前,讓我們先來了解一下 AI 和 SWE 代理是什麼。
人工智慧代理是由人工智慧模型驅動的系統,可以自主執行任務,與環境交互,並根據其編程和處理的資料做出決策。
人工智慧代理由三個關鍵元件組成:
LLMs :大型語言模型,負責推理、決策、工具呼叫等。
記憶體:管理短期和長期資訊以追蹤工作流程。
工具:啟用與外部環境的交互,例如用於從儲存庫提取資訊並進行必要更改的 GitHub 工具。
那麼,什麼時候將 AI 代理稱為 SWE 代理呢?
SWE 代理是模仿人類軟體工程師的品質和特徵的人工智慧代理,例如
長期規劃和推理。
使用標準開發工具。
透過測試和回饋提高程式碼品質。
自主除錯和解決問題。
以下是我們要建構的 SWE 代理的一些特徵
它與框架無關。所以你可以選擇你喜歡的框架,像是LangChain、OpenAI SDK等等…
您可以加入工具來擴展其多功能性,例如用於網路存取的 Tavily。
SWE 代理可以存取您的公用和私人儲存庫,處理提供的問題,並將變更推送到儲存庫。
它可以使用主機、Docker 或任何其他雲端環境(E2B、FlyIo)執行程式碼。但是,如果您更願意使用後兩者來執行沙箱程式碼,那就最好了。
沙盒有助於防止任意程式碼執行產生任何意外後果。
以下是成功建構代理的先決條件
OpenAI API 金鑰:我們將使用 OpenAI SDK 存取 GPT 模型並編排工具呼叫。
GitHub 存取權杖:您必須使用個人存取權杖連結您的 GitHub 帳戶,以使 SWE 代理程式能夠存取和修改您的程式碼儲存庫。
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"
該專案的組織如下:
原始碼
├── 代理商
│ └── 甜食
├── 應用程式.ts
├── 提示.ts
└── utils.ts
以下是這些文件的簡要說明。
Agents/swe.ts :包含 SWE 代理程式的實作。
app.ts :應用程式的主要入口點。
Promps.ts :定義代理程式使用的提示。
utils.ts :整個專案中使用的實用函數。
若要快速開始,請複製此儲存庫並安裝其餘相依性。
git clone https://github.com/ComposioHQ/swe-js-template.git swe-js
cd swe-js && pnpm i
現在您已經完成了整個設定。讓我們來編寫人工智慧代理的程式碼。
我們首先定義 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 (e.g. python, vim) in this environment, but you can write
scripts and run them. E.g. 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,
})
});
*/
在上面的程式碼區塊中,
我們使用 API 金鑰建立了一個 OpenAI 實例。
我們也建立了一個OpenAIToolSet
實例,其中workspaceConfig
設定為 Docker。這是使用Docker對Swe代理的編碼環境進行沙箱處理。您也可以使用雲端程式碼解釋器,例如 E2B 和 FlyIo。
現在,我們將定義 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 };
}
這是上面程式碼區塊中發生的事情。
取得工具:從 Composio 工具集中取得filetool
、 file edit tool
和shelltool
的工具。顧名思義,它們將用於存取檔案、編輯檔案以及使用 shell 執行命令。
修剪工具描述:將工具描述限制為最多 1024 個字元。
更新空值:用空陣列取代工具配置中的空值。
建立助手執行緒:使用預先定義的提示啟動 OpenAI 助理執行緒。
返回語句:提供初始化工具、輔助執行緒、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 Agent :呼叫initSWEAgent
取得助手執行緒、OpenAI 實例、工具和 Composio 工具集。
取得儲存庫和問題:從fromGithub
取得儲存庫和問題詳細資訊。
建立助理:使用指令、工具和語言模型初始化 OpenAI 助理。
將問題傳送到助手:將問題內容作為訊息傳送到助手執行緒。
執行助手和輪詢:執行助手並輪詢工具呼叫回應。有關輪詢回應的更多訊息,請參閱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 以根據代理程式的活動自動建立和更新任務。
您可以加入我們的社區,與維護者互動並作為開源開發人員做出貢獻。請隨時存取我們的 GitHub 儲存庫來貢獻和建立與 Composio 相關的問題。開發。
https://git.new/composio 為 Composio.dev 儲存庫加註星標 ⭐
感謝您的閱讀!
原文出處:https://dev.to/composiodev/i-built-an-ai-programmer-to-automate-resolving-github-issues-5377