食べチョク開発者ブログ

食べチョクエンジニアによるプロダクト開発ブログ

生成AIを活用してGitHubのIssueをSlackから簡単に起票できる仕組みを作った話

皆さんこんにちは、ビビッドガーデンCTOの西尾です。 今回はGitHubのIssueをSlackから簡単に起票できるツールを開発し、運用しているお話をしたいと思います。

Issueの起票は意外と手間がかかる

弊社ではエンジニア、ビジネスメンバーともにGitHub Issuesにて課題管理をしています。 Issueに記載する内容はいくつかテンプレート化されているのですが、以下のような内容を記載することが多いです。

## 💫 目的や背景
## 💪 実現したいこと
## 🉑 受け入れ基準
## 📎 資料
## 🔧 タスク

Issueは誰が見てもわかりやすい記載をするのがベストではあります。

しかし、こんなことをいったら怒られてしまうかもしれませんが、このIssueを起票するという行為、さらにいうと文章をきちんと考えるのが意外と億劫だったりします。

弊社でも、「〇〇をIssue化しておいてくれませんか?」みたいなやりとりが発生することがたびたびありました。

生成AIを活用して、Slack上での会話を元にいい感じにIssueを起票する

Issueを起票をする前は、たいていSlack上でディスカッションをしていたりします。

これまでは、その会話を元に誰かがIssue化していたのですが、これをChatGPTにまるっとお願いしたら楽できるのでは? と思い、社内用のツール「alpha」を作りました。

例えば、Slackでディスカッションした内容をもとにalphaに依頼すると、自動でGitHub Issueしてくれます。

以下はその動作例です。これは適当な会話と適当なIssueですので、ご了承ください。

せっかく生成AIを使っているので、壁打ちできる機能も搭載しています。 Slackのスレッド上でいろいろディスカッションし、alphaにIssue化してと依頼をすると、以下のようにGitHub Issueが自動で起票されます。 過去の自分の発言やSlackスレッド内の会話を踏まえて、Issue化されます。

ツール alpha の仕組み

ここからは作成したツール alpha の仕組みを紹介します。

現時点では社内向けのコードが書かれているため、残念ながらソースコードがそのまま公開できません😢 これはどこかで直して公開できればいいなあとは思っています。

alphaはAWS Lambda上で動作しており、データはDynamoDBに保存されています。コードはRubyで書かれており以下のように動作します。

  1. SlackのEvent Subscriptionsの仕組みを使い、alphaにメンションされたら会話をAWS Lambdaに通知する
  2. メンションされた会話はDynamoDBに保存する
    • いままでのコンテキストを踏まえて動作するために、過去の発言はDynamoDBにいれておき参照する
  3. Issueを起票するべきか、ただ壁打ち相手となってもらうかのどちらのアクションをとるべきか、まずは判断する
    • 壁打ち相手になってほしい場合は、いままでの発言を含めてChatGPTに送り文章を生成して返す
    • Issueを起票すべきと判断して場合は、Step4に進む
  4. いままでの会話を踏まえて、Issueを起票する
    • Issueを起票すべきと判断した場合は、まずメンションされたスレッド内の会話をSlack APIで引っ張ってくる。会話が長いとChatGPTに渡せないので、ある程度要約をするというアクションも必要であれば取る
    • Issueのタイトルと本文をChatGPTで生成する
    • GitHub APIを利用して、Issueを起票する
    • 起票したIssueのURLをSlackのレスポンスとして返す

具体的には以下のようなコードを実行しています。 まずStep3では次に実行すべきアクションをChatGPTに判断してもらいます。

def get_action_command(message)
    messages = [
      {
        role: "system",
        content: <<~COMMAND,
          チャットボットです。回答は必ず日本語でしてください。
          あなたは以下コマンドを実行できます。ユーザーのメッセージをもとに、実行するコマンドを返してください。
          #{' '}
          A001. GitHubのIssueを起票する
          C003. 上記に当てはまらないコマンド

          コマンドを実行する場合は、コマンド番号を返してください。
          次にユーザーからのメッセージを送ります。
        COMMAND
      },
      {
        role: "user",
        content: message,
      }
    ]
    response = @client.chat(
      parameters: {
        model: "gpt-4o",
        messages: messages,
        temperature: 1.0,
      },
    )
    response.dig("choices", 0, "message", "content")
end

次にとるアクションをコマンド形式で返したあと、以下のように処理を振り分けます。 A001 なら Issue起票、それ以外ならいままでの会話をもとに壁打ちします。

  def dispatch_action(user_id, message, channel, timestamp, thread_timestamp)
    com_message = get_action_command(message)

    case
    when com_message.include?("A001")
      @slack_action.add_reaction(channel, timestamp, "github")
      summary = summary_thread_message(channel, thread_timestamp)
      github_title = get_github_issue_title(message, summary)
      github_message = get_github_issue_pbi(message, summary)

      create_message = create_github_issue(github_title, github_message)
      @slack_action.send_message(channel, create_message, timestamp)
    else
      ai_response = get_memory_openai_response(user_id, message)
      @slack_action.send_message(channel, ai_response, timestamp)
    end
  rescue => e
    pp e
    @slack_action.add_reaction(channel, timestamp, "dango")
  end

Issueを起票するための文章生成は、以下のようなコマンドを投げています。 ここは都度チューニングしていく必要があります。

  def get_github_issue_pbi(message, summary)
    com_messages = []
    com = <<~COMMAND
            あなたはプロダクトマネージャーです。GitHubのIssueを作成してください。だ、である調で書いてください。
            形式は以下のようにしてください。あなたの発言はそのままIssueとして作成されますので、回答は余計なメッセージを記載しないでください。

      ## 💫 目的や背景

      ## 💪 実現したいこと

      ## 🉑 受け入れ基準
                   #{'  '}
    COMMAND
    com_messages << { role: "system", content: com }

    com = <<~COMMAND
      次にいままでのコンテキストを渡します。この発言が関係あれば、それをIssueに反映してください。

      #{summary}
    COMMAND
    com_messages << { role: "system", content: com }

    get_openai_response(message, additional_messages: com_messages)
  end

最後にIssueをAPI経由で起票します。

require "octokit"

class GithubAction
  def initialize
    @client = Octokit::Client.new(access_token: ENV.fetch("GITHUB_TOKEN", nil))
  end

  def create_issue(repo:, issue_title:, issue_body:)
    issue = @client.create_issue(repo, issue_title, issue_body)

    issue.html_url
  rescue Octokit::Error => e
    puts "Error creating issue: #{e.message}"

    ""
  end
end

おわりに

Slackから気軽にIssueを作成できる仕組みを作成し、運用をはじめています。 社内でも少しずつ使われはじめています。

生成AIを活用することで、これまで煩わしかった作業を自動化できました。 AIを活用してどんどん楽をしていきたいですね!