Trong vài tuần qua, chúng tôi đã làm việc chăm chỉ trên kho lưu trữ đang phát triển nhanh chóng tại Composio . Chúng tôi sớm nhận ra rằng các tác vụ như cập nhật ReadMe, sửa chuỗi tài liệu và sửa các lỗi nhỏ—mặc dù lặp đi lặp lại và nhàm chán—đang tiêu tốn nhiều băng thông của chúng tôi.
Vì vậy, tôi nghĩ, tại sao không xây dựng một tác nhân tự trị được hỗ trợ bởi AI để xử lý những công việc khó khăn này?
Chúng tôi muốn một đặc vụ AI có thể làm được điều đó.
Cuối cùng, chúng tôi đã xây dựng một siêu khung đơn giản và có thể mở rộng để xây dựng các tác nhân kỹ thuật phần mềm.
Các tác nhân này có thể thực hiện tương tự như các tác nhân con người trong nhiều nhiệm vụ như vậy. Việc giảm tải các nhiệm vụ nhàm chán cho các tác nhân này là điều hợp lý để giải phóng các nhà phát triển của bạn để tập trung vào các nhiệm vụ sáng tạo hơn.
Trong bài viết này, tôi sẽ chỉ cho bạn cách xây dựng tác nhân SWE trong Typescript để tự động hóa quy trình công việc GitHub của bạn.
Nhưng trước đó, chúng ta hãy hiểu ngay cả đặc vụ AI và SWE là gì.
Tác nhân AI là các hệ thống được hỗ trợ bởi các mô hình AI có thể tự động thực hiện các nhiệm vụ, tương tác với môi trường của chúng và đưa ra quyết định dựa trên chương trình và dữ liệu chúng xử lý.
Một tác nhân AI bao gồm ba thành phần quan trọng:
Vậy khi nào bạn gọi một đại lý AI là đại lý SWE?
Tác nhân SWE là tác nhân AI bắt chước những phẩm chất và đặc điểm của kỹ sư phần mềm con người, chẳng hạn như
Dưới đây là một số đặc điểm của tác nhân SWE mà chúng tôi sẽ xây dựng:
Các đại lý SWE có thể truy cập vào kho lưu trữ công khai và riêng tư của bạn, xử lý các vấn đề được cung cấp và đẩy các thay đổi vào kho lưu trữ.
Nó có thể thực thi mã bằng máy chủ, Docker hoặc bất kỳ môi trường đám mây nào khác (E2B, FlyIo). Tuy nhiên, sẽ tốt nhất nếu bạn thích sử dụng hai cái sau để thực thi mã hộp cát.
Hộp cát giúp ngăn chặn mọi hậu quả không mong muốn của việc thực thi mã tùy ý.
Dưới đây là những điều kiện tiên quyết để xây dựng thành công tác nhân:
Bắt đầu bằng cách cài đặt các phần phụ thuộc bằng trình quản lý gói yêu thích của bạn. Phương pháp được đề xuất là pnpm
, nhưng bạn cũng có thể sử dụng npm
hoặc yarn
.
pnpm install -g composio-core
Bạn sẽ cần GITHUB_ACCESS_TOKEN, OPENAI_API_KEY, COMPOSIO_API_KEY, GITHUB_USERNAME và GITHUB_USER_EMAIL để hoàn thành dự án.
Vì vậy, hãy tạo tệp .env
và thêm các biến trên.
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"
Dự án được tổ chức như sau:
src
├── đại lý
│ └── thật tuyệt vời
├── ứng dụng.ts
├── nhắc nhở.ts
└── utils.ts
Đây là mô tả ngắn gọn về các tập tin.
Để bắt đầu nhanh chóng, hãy sao chép kho lưu trữ này và cài đặt phần phụ thuộc còn lại.
git clone https://github.com/ComposioHQ/swe-js-template.git swe-js cd swe-js && pnpm i
Bây giờ bạn đã hoàn thành toàn bộ thiết lập. Hãy mã hóa tác nhân AI của chúng ta.
Chúng tôi bắt đầu bằng việc xác định các lời nhắc và mục tiêu cho tác nhân SWE. Việc giải thích chi tiết từng bước là rất quan trọng vì những định nghĩa này ảnh hưởng đáng kể đến hiệu suất và việc thực thi của tác nhân.
Vì vậy, hãy tạo tệp prompts.ts
nếu bạn chưa làm như vậy.
Tiếp theo, xác định vai trò và mục tiêu của tác nhân.
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";
Ở đây, chúng tôi đã xác định vai trò là SWE và mục tiêu là khắc phục mọi sự cố mã hóa và tạo bản vá để khắc phục bằng cách sử dụng filetool_git_patch
. Đây là Compsoio Action để tích hợp GitHub nhằm tạo các tệp bản vá.
Bây giờ, hãy xác định cốt truyện và mô tả về đặc vụ 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`;
Trong khối mã trên, chúng tôi đã xác định cẩn thận và rõ ràng các bước mà tác nhân cần thực hiện để hoàn thành nhiệm vụ. Điều này rất quan trọng để đảm bảo tổng đài viên biết phải làm gì khi gặp phải những trở ngại lập trình thông thường.
Trong phần này, chúng ta sẽ xác định hai hàm chính, from GitHub
và getBranchNameFromIssue
, sẽ trích xuất thông tin về một vấn đề.
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 }; }
Vì vậy, đây là những gì đang diễn ra trong khối mã trên.
readUserInput
: Hàm này đọc đầu vào của người dùng từ dòng lệnh. Chúng tôi chỉ cần ID người dùng GitHub, tên kho lưu trữ và số phát hành hoặc mô tả.createGithubIssueValidator
: Hàm này trả về trình xác thực cho các sự cố GitHub. Nó có thể xử lý đầu vào dưới dạng đường dẫn tệp, ID vấn đề dạng số hoặc mô tả chuỗi đơn giản. Nếu thông tin đầu vào là ID vấn đề dạng số thì nó sẽ tìm nạp chi tiết vấn đề từ GitHub bằng hành động github_issues_get
của Composio.fromGitHub
: Hàm này kết hợp các thành phần này để thu thập và xác thực thông tin cần thiết về kho lưu trữ GitHub và một vấn đề. Bây giờ, hãy xác định getBranchNameFromIssue
để tạo tên nhánh từ mô tả vấn đề.
export function getBranchNameFromIssue(issue: string): string { return "swe/" + issue.toLowerCase().replace(/\s+/g, '-') + "-" + nanoid(); }
Đây là phần quan trọng nhất, nơi bạn sẽ xác định tác nhân Swe bằng cách sử dụng bộ công cụ Composio và trợ lý OpenAI.
Vì vậy, trước tiên, hãy nhập các thư viện và xác định LLM cũng như các công cụ.
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, }) }); */
Trong khối mã trên,
OpenAIToolSet
với workspaceConfig
được đặt thành Docker. Mục đích là sử dụng Docker để sandbox môi trường mã hóa cho tác nhân Swe. Bạn cũng có thể sử dụng trình thông dịch mã đám mây như E2B và FlyIo. Bây giờ chúng ta sẽ định nghĩa 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 }; }
Đây là những gì đang diễn ra trong khối mã trên.
filetool
, file edit tool
và shelltool
. Như tên cho thấy, chúng sẽ được sử dụng để truy cập tệp, chỉnh sửa tệp và sử dụng shell để thực thi lệnh. Đây là phần cuối cùng, nơi chúng tôi xác định điểm vào của ứng dụng. Do đó, hãy tải các biến môi trường và nhập các mô-đun cần thiết.
import dotenv from "dotenv"; dotenv.config(); import { fromGithub, getBranchNameFromIssue } from './utils'; import { initSWEAgent } from './agents/swe'; import { GOAL } from './prompts';
Khối mã
Bây giờ, xác định chức năng 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();
Đây là tệp app.ts
hoàn chỉnh của chúng tôi, tệp này sẽ được sử dụng để thực thi quy trình làm việc tổng đài.
Vì vậy, đây là những gì đang xảy ra trong đoạn mã trên.
initSWEAgent
để lấy chuỗi trợ lý, phiên bản OpenAI, các công cụ và bộ công cụ Composio.fromGithub
.filetool_git_patch
để tạo một bản vá.main()
để thực hiện các bước trên.
Bây giờ, hãy chạy ứng dụng bằng cách sử dụng pnpm start
.
Điều này sẽ nhắc bạn nhập ID người dùng GitHub, tên kho lưu trữ và ID vấn đề hoặc mô tả về vấn đề bạn muốn giải quyết.
Sau khi hoàn tất, nó sẽ lấy vùng chứa Composio Docker từ sổ đăng ký và bắt đầu khắc phục sự cố.
Cuối cùng, khi quy trình làm việc hoàn tất, bản vá sẽ được đẩy đến kho lưu trữ từ xa. Bây giờ, khi mở kho GitHub, bạn sẽ thấy một nhánh mới có đề xuất khắc phục sự cố. Bạn có thể so sánh nó với nhánh chính và tạo yêu cầu kéo.
Bạn có thể tìm thấy mã hoàn chỉnh
Điều tốt nhất về tác nhân SWE này là bạn có thể mở rộng khả năng của nó bằng cách sử dụng các công cụ và tích hợp Composio.
Bạn có thể thêm Slack hoặc Discord vào đại lý của mình để thông báo cho bạn khi quá trình thực thi hoàn tất. Bạn cũng có thể kết nối Jira hoặc Linear để tự động tạo và cập nhật nhiệm vụ dựa trên hoạt động của tác nhân.
Bạn có thể tham gia cộng đồng của chúng tôi để tương tác với những người bảo trì và đóng góp với tư cách là nhà phát triển nguồn mở. Đừng ngần ngại truy cập kho lưu trữ GitHub của chúng tôi để đóng góp và giải quyết các vấn đề liên quan đến Composio. Dev.
Gắn dấu sao cho bản soạn. kho lưu trữ dev ⭐
Cảm ơn bạn đã đọc!