阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈

長話短說

在本文中,您將建立一個 AI 編碼器來修復 GitHub 程式碼庫的文件和錯誤、建立新功能並偵錯現有問題。 AI Coder 收到問題,找到解決方案,建立單獨的分支,編寫程式碼,並將拉取請求推送到遠端儲存庫。

野豬騎士 git hub

我們將使用 Composio、GitHub、Docker 和 OpenAI GPT-4o 來完成專案。

以下是您將如何建造它。

  • 將 GitHub 與 Composio 連接起來以管理程式碼庫。

  • 使用 OpenAI GPT-4o 理解程式碼並執行操作。

  • 新增 Docker 讓機器人編寫並執行程式碼。


Composio - 第一款人工智慧整合平台

這是關於我們的快速介紹。

Composio 是一個開源工具基礎設施,用於建立強大且可靠的 AI 應用程式。我們提供超過 100 多個跨行業垂直領域的工具和集成,從 CRM、HRM 和銷售到生產力、開發和社交媒體。

它們處理所有這些應用程式的使用者身份驗證和授權,並使所有 API 端點與各種 AI 模型和框架的連接變得簡單。

傢伙掙扎 gif

請幫我們加一顆星。 🥹

這將幫助我們建立更多這樣的文章💖

{% cta https://dub.composio.dev/aicoder %}為 Composio.dev 儲存庫加註星標 ⭐{% endcta %}


它是如何運作的?

因此,在深入編碼部分之前,讓我們先了解一下人工智慧程式設計師的工作原理。

該機器人可以

  • 存取任何給定的 GitHub 儲存庫。

  • 使用 Composio 的本機工具根據需要讀取、寫入、更新和刪除檔案。

  • 使用 OpenAI SDK 編排工作流程。但是,Composio 與框架無關,因此您可以使用 LangChain 和 LlamaIndex 等框架。

  • 接受問題並為更改建立單獨的分支。

  • 在沙盒編碼環境(例如 Docker)或雲端託管環境(例如 E2B、FlyIo 等)中執行程式。

  • 最後,將修復推送到遠端儲存庫。

SWE 代理工作流程


建構 SWE Agent 的先決條件

這些是完成該專案的先決條件。在繼續之前,請確保您已正確配置這些內容。

API金鑰組成

使用 Composio 建立一個使用者帳戶以獲得一個。

登入頁面

現在,使用您的 GitHub、Gmail 或任何其他電子郵件 ID 登入。登入後,您可以存取儀表板以查看可連接以支援人工智慧機器人的應用程式目錄。

整合列表

現在,導航到「設定」標籤並複製 API 金鑰。

OpenAI API 金鑰

建立使用者帳戶並產生 API 金鑰。確保您已加入積分以使用其模型之一進行推理。

OpenAI 儀表板

GitHub 存取令牌

接下來,您必須為 GitHub 帳戶建立存取令牌,以便 Composio 可以向其推送變更。點擊連結並建立基本存取令牌。

github 存取令牌


讓我們開始吧🔥

依賴關係

首先使用您喜歡的套件管理器安裝相依性。建議的方法是pnpm ,但您也可以使用npmyarn

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"

執行以下命令將它們設定為環境變數。

export $(grep -v '^#' .env | xargs)

專案結構

該專案的組織如下:

原始碼

├── 代理商

│ └── 甜食

├── 應用程式.ts

├── 提示.ts

└── utils.ts

以下是這些文件的簡要說明。

  • Agents/swe.ts :包含用於實現軟體工程機器人的程式碼。

  • app.ts :應用程式的主入口點。

  • Promps.ts :定義代理程式使用的提示。

  • utils.ts :整個專案中使用的實用函數。

要快速開始,請複製此儲存庫並安裝其餘相依性。

git clone https://github.com/ComposioHQ/swe-js-template.git swe-js

cd swe-js && pnpm i

現在您已經完成了整個設定。讓我們來編寫人工智慧代理的程式碼。


定義提示和目標

讓我們從定義人工智慧程式設計師的提示和目標開始。詳細解釋每個步驟至關重要,因為這些定義會顯著影響代理程式的效能和執行能力。

因此,如果您還沒有這樣做,請建立一個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 操作,用於建立補丁檔案。

現在,定義人工智慧程式設計師的詳細背景故事和描述。

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

在上面的程式碼區塊中,我們仔細且清楚地定義了代理完成任務必須採取的步驟。

這有助於LLMs在面臨常見的程式設計挑戰時做出反應。


定義實用函數

在本節中,我們將定義兩個主要函數, from GitHubgetBranchNameFromIssue ,這將提取有關給定 GitHub 問題的資訊。

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({})
});

在上面的程式碼區塊中,

  • 我們使用 API 金鑰建立了一個 OpenAI 實例。

  • 我們也建立了一個OpenAIToolSet實例,其中workspaceConfig設定為 Docker。這是利用Docker來沙箱AI Coder的程式設計環境。您也可以使用雲端程式碼解釋器,例如 E2B 和 FlyIo。

現在,讓我們定義一下人工智慧程式設計師。

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 工具集中取得filetoolfile edit toolshelltool的工具。顧名思義,它們將用於存取檔案、編輯檔案以及使用 shell 執行命令。

  • 修剪工具描述:將工具描述限制為 1024 個字元。這是為了限制佔用 LLM 上下文視窗。

  • 更新空值:用空陣列取代工具配置中的空值。

  • 建立助手執行緒:使用我們先前定義的提示啟動 OpenAI 助理執行緒。

  • 傳回語句:Pz傳回工具、輔助執行緒、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();

所以,這就是上面程式碼中發生的事情。

  • 初始化 SWE Agent :呼叫initSWEAgent取得助手執行緒、OpenAI 實例、工具和 Composio 工具集。

  • 取得儲存庫和問題:從fromGithub取得儲存庫名稱和問題詳細資訊。

  • 建立助理:使用工具、GPT-4o 和自訂指令初始化 OpenAI 助理實例。

  • 將問題傳送到助手:將問題內容作為訊息傳送到助手執行緒。

  • 執行助手和輪詢:執行助手並輪詢工具呼叫回應。有關輪詢回應的更多訊息,請參閱OpenAI SDK 儲存庫。

  • 執行補丁操作:執行filetool_git_patch產生補丁檔案。

  • 處理補丁回應:如果產生補丁,則記錄它,建立分支,提交並推送變更。建立拉取請求之前等待 2 秒。在 GitHub 上建立拉取請求。

  • 關閉工作區:關閉 Composio 工具集工作區。

  • Run Main Function :呼叫main()來執行上述步驟。

現在,使用pnpm start執行應用程式。

這將提示您輸入 GitHub 使用者 ID、儲存庫名稱以及問題 ID 或您要解決的問題的描述。

外殼要求

完成後,它將從註冊表中提取 Composio Docker 容器並開始解決該問題。

最後,當工作流程完成時,修補程式將被推送到遠端儲存庫。現在,當您開啟 GitHub 儲存庫時,您將看到一個新分支,其中包含針對該問題的建議修復方案。您可以將其與主分支進行比較並建立拉取請求。

SWE 代理在行動

您可以在 GitHub 上找到完整的程式碼。


下一步

這個 AI 程式設計師的最大優點是您可以使用 Composio 工具和整合來擴展 SWE 代理的功能。

您可以將 Slack 或 Discord 新增至代理程式中,以便在執行完成時通知您。您也可以連接 Jira 或 Linear 以根據代理程式的活動自動建立和更新任務。

探索 Composio 儲存庫並給我們一顆星。

給回購加註星標

{% cta https://dub.composio.dev/aicoder %}為 Composio.dev 儲存庫加註星標 ⭐{% endcta %}


原文出處:https://dev.to/composiodev/i-got-tired-of-solving-issues-over-github-so-i-created-my-own-ai-bot-1m0i


共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。

阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈