paint-brush
GitHub の問題修正を自動化する AI プログラマーの構築方法 🤯@sunilkumardash9
486 測定値
486 測定値

GitHub の問題修正を自動化する AI プログラマーの構築方法 🤯

Sunil Kumar Dash16m2024/08/01
Read on Terminal Reader

長すぎる; 読むには

Typescript を使用して GitHub ワークフローを自動化する AI 搭載 SWE エージェントを作成する方法を学びます。このガイドでは、ドキュメントの更新、バグの修正、パッチのプッシュなどのタスクを処理できる自律エージェントのセットアップ、コンポーネント、実装について説明し、開発者がよりクリエイティブな作業に集中できるようにします。
featured image - GitHub の問題修正を自動化する AI プログラマーの構築方法 🤯
Sunil Kumar Dash HackerNoon profile picture


過去数週間、私たちはComposioで急速に成長しているリポジトリに熱心に取り組んできました。そしてすぐに、ReadMe の更新、docstring の修正、小さなバグの修正などのタスク (反復的で単調ではありますが) が、私たちの帯域幅の多くを消費していることに気付きました。


そこで、こうした単調な作業を処理するために、AI を搭載した自律エージェントを構築したらどうだろうと考えました。

私たちはそれができる AI エージェントを求めていました。


  • GitHub リポジトリにアクセスします。
  • 与えられた問題を取り上げて、その解決策を書いてください。
  • 必要に応じて、コードを実行して問題がないかどうかを確認します。
  • 最後に、パッチ ファイルをリモート リポジトリにプッシュします。


最後に、ソフトウェア エンジニアリング エージェントを構築するためのシンプルで拡張可能なメタフレームワークを構築しました。

これらのエージェントは、多くのタスクにおいて人間と同様のパフォーマンスを発揮できます。日常的なタスクをこれらのエージェントにオフロードすることで、開発者はよりクリエイティブなタスクに集中できるようになります。


ジェリー


この記事では、Typescript で SWE エージェントを構築して GitHub ワークフローを自動化する方法を説明します。

しかしその前に、AI エージェントと SWE エージェントが何であるかを理解しましょう。


AI エージェントとは何ですか?

AI エージェントは、タスクを自律的に実行し、環境と対話し、プログラミングと処理するデータに基づいて意思決定を行うことができる AI モデルを搭載したシステムです。

AIエージェントのコンポーネント

AI エージェントは、次の 3 つの重要なコンポーネントで構成されています。

  • LLM : 大規模言語モデルは、推論、意思決定、ツールの呼び出しなどを担当します。
  • メモリ: ワークフローを追跡するために短期および長期の情報を管理します。
  • ツール: リポジトリから情報を抽出し、必要な変更を加えるための GitHub ツールなど、外部環境とのやり取りを可能にします。

SWE エージェントとは何ですか?

では、AI エージェントを SWE エージェントと呼ぶのはどのような場合でしょうか?

SWEエージェントは、人間のソフトウェアエンジニアの資質や特徴を模倣したAIエージェントです。

  • 長期的な計画と推論。
  • 標準開発者ツールの使用。
  • テストとフィードバックを通じてコードの品質を向上します。
  • 問題を自律的にデバッグして解決します。

スウェキット

概要 SWE エージェント

これから構築する SWE エージェントの特徴をいくつか紹介します。

  • フレームワークに依存しないため、LangChain、OpenAI SDK などの任意のフレームワークを使用できます。
  • インターネット アクセス用の Tavily など、汎用性を拡張するツールを追加できます。


SWE エージェントは、パブリック リポジトリとプライベート リポジトリにアクセスし、提供された問題に対処し、リポジトリに変更をプッシュできます。

ホストマシン、Docker、またはその他のクラウド環境 (E2B、FlyIo) を使用してコードを実行できます。ただし、サンドボックス コード実行には後者の 2 つを使用することをお勧めします。


サンドボックス化は、任意のコード実行による予期しない結果を防ぐのに役立ちます。


SWE エージェントの前提条件

エージェントを正常に構築するための前提条件は次のとおりです。

  1. OpenAI API キー: OpenAI SDK を使用して GPT モデルにアクセスし、ツール呼び出しを調整します。
  2. GitHubアクセストークン: GitHubアカウントをリンクするには、個人アクセストークンSWE エージェントがコード リポジトリにアクセスして変更できるようにします。
  3. 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 : アプリケーションのメイン エントリ ポイント。
  • prompts.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 GitHubgetBranchNameFromIssueという 2 つの主要な関数を定義します。

 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({}) }); // To use E2B Code interpreter /* const composioToolset = new OpenAIToolSet({ workspaceConfig: Workspace.E2B({ apiKey: process.env.E2B_API_KEY, }) }); */

上記のコードブロックでは、

  • API キーを使用して OpenAI のインスタンスを作成しました。
  • また、 workspaceConfigを Docker に設定したOpenAIToolSetのインスタンスも作成しました。これは、Swe エージェントのコーディング環境をサンドボックス化するために Docker を使用するためです。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 ツールセットからfiletoolfile edit tool 、およびshelltoolツールを取得します。名前が示すように、これらはファイルにアクセスしたり、ファイルを編集したり、シェルを使用してコマンドを実行したりするために使用されます。
  • ツールの説明をトリム: ツールの説明を最大 1024 文字に制限します。
  • Null 値の更新: ツール構成内の null 値を空の配列に置き換えます。
  • アシスタント スレッドの作成: 定義済みのプロンプトを使用して 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 エージェントの初期化: initSWEAgentを呼び出して、アシスタント スレッド、OpenAI インスタンス、ツール、および Composio ツールセットを取得します。
  • リポジトリと問題を取得: fromGithubからリポジトリと問題の詳細を取得します。
  • アシスタントの作成: 指示、ツール、言語モデルを使用して OpenAI アシスタントを初期化します。
  • 問題をアシスタントに送信: 問題の内容をメッセージとしてアシスタントのスレッドに送信します。
  • アシスタントとポーリングを実行: アシスタントを実行し、ツール呼び出し応答をポーリングします。ポーリング応答の詳細については、 OpenAI SDK リポジトリ。
  • パッチアクションの実行: filetool_git_patchを実行してパッチを生成します。
  • パッチ応答の処理: パッチが生成されたら、それをログに記録し、ブランチを作成し、コミットして変更をプッシュします。プル リクエストを作成する前に 2 秒待ちます。GitHub でプル リクエストを作成します。
  • ワークスペースを閉じる: Composio ツールセットのワークスペースを閉じます。
  • メイン関数の実行: main()を呼び出して上記の手順を実行します。


次に、 pnpm startを使用してアプリケーションを実行します。


これにより、GitHub ユーザー ID、リポジトリ名、および対処する問題の問題 ID または説明を入力するよう求められます。

完了すると、レジストリから Composio Docker コンテナが取得され、問題の解決が開始されます。


最後に、ワークフローが完了すると、パッチがリモート リポジトリにプッシュされます。これで、GitHub リポジトリを開くと、問題の修正案を含む新しいブランチが表示されます。これをメイン ブランチと比較して、プル リクエストを作成できます。
ありがとう、マイク・スコット

完全なコードは以下をご覧くださいGitHubはこちら


次のステップ⏭️

この SWE エージェントの最も優れた点は、Composio ツールと統合を使用して機能を拡張できることです。

エージェントに Slack または Discord を追加して、実行が完了したときに通知を受け取ることができます。また、Jira または Linear を接続して、エージェントのアクティビティに基づいてタスクを自動的に作成および更新することもできます。


つながりましょう!🔌

私たちのコミュニティに参加して、メンテナーと交流し、オープンソース開発者として貢献することができます。ぜひ GitHub リポジトリにアクセスして、Composio.Dev に関連する貢献や問題の作成を行ってください。

Composio.dev リポジトリにスターを付ける ⭐

リポジトリにスターを付ける


読んでくれてありがとう!