14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ElixirAdvent Calendar 2024

Day 1

Backlog の課題登録時 GPT に評価してもらう(Livebook アプリで実装)

Last updated at Posted at 2024-11-08

はじめに

Elixir の Livebook から Backlog の API を呼び出してみます

ついでに、 Backlog API で取得した課題の記述について、 GPT に評価してもらって、評価コメントを課題に登録します

なお、この記事は JBUG(Backlog ユーザー会)の LT 用に書いています

興味のある方はご参加ください

実装したノートブックはこちら

前提知識

Livebook について

Livebook は Elixir をブラウザ上で実行できるツールです

簡単にインストールできて、誰でもすぐに使えます

Python で言う Jupyter だと思ってください

Backlog について

Backlog は課題管理を中心に据えたプロジェクト管理ツールです

API が公開されているので、外部の様々なサービスや自作ツールとも連携可能です

OpenAI API について

OpenAI 社が提供する AI 用の API です

GPT-4o など、 ChatGPT で使っている LLM を API 経由で使えます

事前準備

Livebook のインストール

公式サイトから macOS や Windows 用のインストーラーをダウンロードし、実行するだけでインストールできます

Backlog API の API キー作成

Backlog のワークスペースにログインし、右上ドロップダウンメニューから「個人設定」をクリックします

スクリーンショット 2024-11-08 13.34.23.png

個人設定の左メニューから「API」を開き、「メモ」に適当な値を入れて「登録」をクリックすれば作成できます

スクリーンショット 2024-11-08 13.34.07.png

OpenAI API の API キー作成

公式サイトにアクセスし、サインアップしてください

OpenAI 社が提供する ChatGPT とは別契約になります

すでに ChatGPT のアカウントを保有をしていたとしても、別途 OpenAI API のサインアップが必要です

初回アカウント作成時は無償トライアルを受けることが可能ですが、基本的に前払いでクレジットを購入しておき、 API 利用のたびに消費していく契約になります

左メニュー API keys を開き、右上 "+ Create new secret key" をクリックします

スクリーンショット 2024-09-23 10.35.55.png

モーダルが開くので、適当な名前を入力して "Create secret key" をクリックします

スクリーンショット 2024-09-23 10.36.40.png

API キーが作成されるので、安全な場所に保管しておいてください

スクリーンショット 2024-09-23 10.36.53.png

Livebook の実装

セットアップ

Livebook で新しいノートブックを開き、先頭のセットアップセル(Notebook dependencies and setup と書いてある枠)に以下のコードを入力します

Mix.install([
  {:req, "~> 0.5"},
  {:kino, "~> 0.14"}
])

セルの左上 Reconnect and setup ボタンをクリックすると、処理に必要な依存モジュールがインストールされます

  • Req: HTTP クライアント( API の呼び出しに使用)
  • Kino: Livebook の UI/UX を提供

API 設定

次のセル(黒い四角)内に以下のコードを貼り付け、 Evaluate ボタンをクリックします

workspace_input = Kino.Input.text("WORKSPACE")
api_key_input = Kino.Input.password("API_KEY")

[workspace_input, api_key_input]
|> Kino.Layout.grid(columns: 2)

以下のようなテキスト入力エリアが表示されるので、ワークスペース名と API キーの値を入力します

スクリーンショット 2024-11-08 13.47.48.png

API キーはワークスペース毎に認可されています

違うワークスペースで作成した API キーは使えません

API キー入力エリアの下あたりにマウスカーソルを持っていくと、下画像のように 3 つボタンが表示されます

+ Elixir をクリックすると、下に次のセル(黒い四角)が追加されます

スクリーンショット 2024-11-08 13.53.02.png

追加されたセルに以下のコードを貼り付け、 Evaluate ボタンで実行してください

workspace = Kino.Input.read(workspace_input)
api_key = Kino.Input.read(api_key_input)

endpoint = "https://#{workspace}.backlog.jp/api/v2"

実行結果として、セルの下に API の URL が表示されます

ユーザー情報の取得

次のセルで以下のコードを実行します

user_info =
  "#{endpoint}/users/myself?apiKey=#{api_key}"
  |> Req.get!()
  |> Map.get(:body)

これにより、 API キーを発行した本人のユーザー情報が取得できます

実行結果は以下のようになります

%{
  "id" => 123123,
  "keyword" => "<氏名> <英字氏名>",
  "lang" => "ja",
  "lastLoginTime" => "2024-11-08T01:37:42Z",
  "mailAddress" => "<メールアドレス>",
  "name" => "<氏名>",
  "nulabAccount" => %{
    "iconUrl" => "<アイコン画像のURL>",
    "name" => "<氏名>",
    "nulabId" => "abcabcabcabc",
    "uniqueId" => "abc-xyz"
  },
  "roleType" => 1,
  "userId" => "xyzxyzxyz"
}

次のコードを実行することで、アイコン画像がそのままダウンロードできます

# アイコン画像の取得
"#{endpoint}/users/#{user_info["id"]}/icon?apiKey=#{api_key}"
|> Req.get!()
|> Map.get(:body)

Livebook では画像のバイナリデータをそのまま画像として表示してくれます

スクリーンショット 2024-11-08 14.03.38.png

プロジェクトの取得

扱いたいプロジェクトのプロジェクトキーを確認しておきます

Backlog でプロジェクトの左メニュー「プロジェクト設定」開き、「基本設定」内の「プロジェクトキー」で確認できます

スクリーンショット 2024-11-08 14.13.19.png

プロジェクトホーム画面の URL 末尾でも確認できます

Livebook でプロジェクトキーの入力エリアを作成し、プロジェクトキーを入力します

project_key_input = Kino.Input.text("PROJECT KEY")

プロジェクトキーを指定してプロジェクトの情報を取得します

project_key = Kino.Input.read(project_key_input)

project =
  "#{endpoint}/projects/#{project_key}?apiKey=#{api_key}"
  |> Req.get!()
  |> Map.get(:body)

実行結果

%{
  "archived" => false,
  "chartEnabled" => true,
  "displayOrder" => 123123,
  "id" => 123456,
  "name" => "<プロジェクト名>",
  "projectKey" => "<プロジェクトキー>",
  "projectLeaderCanEditProjectLeader" => false,
  "subtaskingEnabled" => false,
  "textFormattingRule" => "backlog",
  "useDevAttributes" => true,
  "useFileSharing" => true,
  "useGit" => false,
  "useOriginalImageSizeAtWiki" => false,
  "useResolvedForChart" => false,
  "useSubversion" => false,
  "useWiki" => true,
  "useWikiTreeView" => true
}

課題の取得

取得したプロジェクトのIDを指定し、課題の一覧を取得します

issues =
  "#{endpoint}/issues?apiKey=#{api_key}&projectId[]=#{project["id"]}"
  |> Req.get!()
  |> Map.get(:body)

先頭の課題の説明文を Markdown 文書として表示します

issues
|> hd()
|> Map.get("description")
|> Kino.Markdown.new()

実行結果(例)

スクリーンショット 2024-11-08 14.24.37.png

Backlog 上と同じ内容になっていることが確認できます

スクリーンショット 2024-11-08 14.25.12.png

OpenAI API による課題の評価、コメント追加

OpenAI API キーの入力エリアを用意し、値を入力します

openai_api_key_input = Kino.Input.password("OpenAI API KEY")

OpenAI API で GPT-4o mini を指定し、課題説明文の評価を指示します

openai_api_key = Kino.Input.read(openai_api_key_input)
openai_base_url = "https://api.openai.com/v1/chat/completions"
openai_model_id = "gpt-4o-mini"
openai_headers = %{
  "Content-Type" => "application/json",
  "Authorization" => "Bearer #{openai_api_key}"
}

system_content = """
あなたは優秀なプロジェクトマネージャーです。
プロジェクトの課題がユーザーの入力として与えられるので、以下の観点で課題を評価してください。

課題評価の観点
- 明確さ(10点満点)
- 簡潔さ(10点満点)
- 整合性(10点満点)

評価結果は各評価点の合計点と、それぞれの観点に対するコメント、総評を以下の形式で出力してください。

## 評価結果の出力形式

課題記述の評価

- 明確さ(<点数>/10点): <コメント>
- 簡潔さ(<点数>/10点): <コメント>
- 整合性(<点数>/10点): <コメント>

総合 <点数> 点
総評: <総評>
"""

user_content =
  issues
  |> hd()
  |> Map.get("description")

request_body = %{
  model: openai_model_id,
  messages: [
    %{
      role: "system",
      content: system_content
    },
    %{
      role: "user",
      content: user_content
    }
  ]
}

openai_response =
  "#{openai_base_url}"
  |> Req.post!(json: request_body, headers: openai_headers)
  |> Map.get(:body)

実行結果

%{
  "choices" => [
    %{
      "finish_reason" => "stop",
      "index" => 0,
      "logprobs" => nil,
      "message" => %{
        "content" => "課題記述の評価\n\n- 明確さ(9/10点):...",
        "refusal" => nil,
        "role" => "assistant"
      }
    }
  ],
  "object" => "chat.completion",
  "system_fingerprint" => "fp_8bfc6a7dc2",
  "usage" => %{
    "completion_tokens" => 337,
    "completion_tokens_details" => %{
      "accepted_prediction_tokens" => 0,
      "audio_tokens" => 0,
      "reasoning_tokens" => 0,
      "rejected_prediction_tokens" => 0
    },
    "prompt_tokens" => 887,
    "prompt_tokens_details" => %{"audio_tokens" => 0, "cached_tokens" => 0},
    "total_tokens" => 1224
  }
}

GPT-4o mini の返答を Markdown として表示します

evaluation_message =
  openai_response["choices"]
  |> Enum.at(0)
  |> Map.get("message")
  |> Map.get("content")

Kino.Markdown.new(evaluation_message)

実行結果

スクリーンショット 2024-11-08 14.29.50.png

指示した通り、ちゃんと評価してくれています

評価内容を課題のコメントとして登録します

issue_id =
  issues
  |> hd()
  |> Map.get("id")

issue_comment_headers = %{
  "Content-Type" => "application/x-www-form-urlencoded"
}

encoded_comment = URI.encode(evaluation_message)

comment_response =
  "#{endpoint}/issues/#{issue_id}/comments?apiKey=#{api_key}&content=#{encoded_comment}"
  |> Req.post!(headers: issue_comment_headers)

実行結果からコメントの URL を取得します

comment_response
|> Map.get(:headers)
|> Map.get("location")
|> hd()
|> Kino.Markdown.new()

実行結果

スクリーンショット 2024-11-08 14.32.49.png

実行結果はリンクになっているので、クリックすると Backlog 上のコメントが確認できます

スクリーンショット 2024-11-08 14.34.34.png

課題登録時の自動実行

ここまでやってきた内容を課題登録時に自動実行するよう、 Livebook でアプリケーションを立ち上げます

自動実行のための前提知識

Livebook アプリケーション

Livebook には、ノートブック上に実装した機能を Web アプリケーションとして公開する機能が存在しています

さらに、 Web 画面だけでなく、 Web API として動作させることも可能です

今回は手軽にローカル起動しますが、クラウド上にデプロイし、本番運用することも可能です

Backlog Webhook

Backlog はプロジェクト単位に「〇〇したときは https://xxx/yyy を呼び出す」という Webhook の設定ができます

これを利用し、課題登録時に Livebook アプリケーションの URL を呼び出します

ngrok

ngrok は外部からの通信をローカルの HTTP 通信に転送するサービスです

今回はローカル起動した Livebook アプリケーションに ngrok 経由で Webhook を接続します

自動実行のための事前準備

ngrok の認証設定

公式サイトから ngrok のアカウントを作成し、認証トークンを取得しておきます

ngrok CLI をインストールして、認証トークンを設定しておきます

ngrok config add-authtoken <ngrok の認証トークン>

自動実行のセットアップ

Livebook で新しいノートブックを開きます

セットアップセルで依存モジュールをインストールします

Mix.install([
  {:kino, "~> 0.14"},
  {:req, "~> 0.5"},
  {:plug, "~> 1.16"}
])

Livebook 左メニューの錠前アイコンをクリックし、+ New secret ボタンで secrets (秘密情報)を登録します

  • BACKLOG_API_KEY: Backlog の API キー
  • BACKLOG_WORKSPACE: Backlog のワークスペース
  • OPENAI_API_KEY: OpenAI の API キー

スクリーンショット 2024-11-08 15.34.25.png

only this sessionin Personal の選択肢では必ず in Personal を指定してください( secrets をアプリケーション上で利用するため)

下画像のようになっていれば OK です

スクリーンショット 2024-11-08 15.34.33.png

アプリケーションの定義

以下のコードをセルに貼り付けます

defmodule ApiRouter do
  use Plug.Router

  @openai_base_url "https://api.openai.com/v1/chat/completions"
  @openai_model_id "gpt-4o-mini"

  @system_content """
  あなたは優秀なプロジェクトマネージャーです。
  プロジェクトの課題がユーザーの入力として与えられるので、以下の観点で課題を評価してください。
  
  課題評価の観点
  - 明確さ(10点満点)
  - 簡潔さ(10点満点)
  - 整合性(10点満点)
  
  評価結果は各評価点の合計点と、それぞれの観点に対するコメント、総評を以下の形式で出力してください。
  
  ## 評価結果の出力形式
  
  課題記述の評価
  
  - 明確さ(<点数>/10点): <コメント>
  - 簡潔さ(<点数>/10点): <コメント>
  - 整合性(<点数>/10点): <コメント>
  
  総合 <点数> 点
  総評: <総評>
  """

  @issue_comment_headers %{
      "Content-Type" => "application/x-www-form-urlencoded"
  }

  plug(:match)
  plug(Plug.Parsers, parsers: [:json], json_decoder: Jason)
  plug(:dispatch)

  post "/" do
    process_webhook(conn.body_params)

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(200, ~s({"message": "ok"}))
  end

  match _ do
    conn
    |> put_resp_content_type("application/json")
    |> send_resp(404, ~s({"message": "not found"}))
  end

  defp process_webhook(webhook_payload) do
    backlog_workspace = System.get_env("LB_BACKLOG_WORKSPACE")
    backlog_api_key = System.get_env("LB_BACKLOG_API_KEY")
    openai_api_key = System.get_env("LB_OPENAI_API_KEY")

    endpoint = "https://#{backlog_workspace}.backlog.jp/api/v2"

    openai_headers = %{
      "Content-Type" => "application/json",
      "Authorization" => "Bearer #{openai_api_key}"
    }

    %{
      "id" => issue_id,
      "description" => issue_description
    } =
      Map.get(webhook_payload, "content")

    IO.inspect(issue_description)

    request_body = %{
      model: @openai_model_id,
      messages: [
        %{
          role: "system",
          content: @system_content
        },
        %{
          role: "user",
          content: issue_description
        }
      ]
    }
    
    openai_response =
      "#{@openai_base_url}"
      |> Req.post!(json: request_body, headers: openai_headers)
      |> Map.get(:body)

    evaluation_message =
      openai_response["choices"]
      |> Enum.at(0)
      |> Map.get("message")
      |> Map.get("content")

    IO.inspect(evaluation_message)

    encoded_comment = URI.encode(evaluation_message)
    
    "#{endpoint}/issues/#{issue_id}/comments?apiKey=#{backlog_api_key}&content=#{encoded_comment}"
    |> Req.post!(headers: @issue_comment_headers)
    |> IO.inspect()
  end
end

# API を起動
Kino.Proxy.listen(ApiRouter)

詳しい解説は割愛しますが、以下のような流れを実装しています

  • / に POST リクエストを受けたときに以降の処理を実行する
  • リクエスト内に含まれる課題説明文を OpenAI API に渡し、評価してもらう
  • 評価結果を Backlog API でコメントとして登録する

アプリケーションの起動

Livebook 左メニューのロケットアイコンをクリックし、アプリケーション設定を開きます

スクリーンショット 2024-11-08 16.24.51.png

Configure ボタンでアプリケーション設定モーダルを開き、以下の通り入力してください

  • Slug (アプリケーション URL の末尾): backlog
  • Password-protected: チェックを外す

スクリーンショット 2024-11-08 16.23.58.png

Save ボタンで設定を保存します

Launch preview ボタンをクリックすると、アプリケーションがローカルで起動します

スクリーンショット 2024-11-08 16.28.34.png

起動後の表示(Version の値は最初 v1 で、 Relaunch する度に増加)

スクリーンショット 2024-11-08 16.28.14.png

ngrok の起動

ターミナルで以下のコマンドを実行し、 ngrok による転送を開始します

ngrok http <Livebook の起動しているポート番号>

実行すると、ターミナルに以下のように出力されます

ngrok                                                                                                                                                                                                                            (Ctrl+C to quit)

Share what you're building with ngrok https://ngrok.com/share-your-ngrok-story

Session Status                online
Account                       xxx@yyy.com (Plan: Free)
Update                        update available (version 3.18.3, Ctrl-U to update)
Version                       3.3.4
Region                        Japan (jp)
Latency                       98ms
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://xxxx-xxxx-xx-xxxx-xxxx-xxx-xxxx-xxxx-xxxx.ngrok-free.app -> http://localhost:<Livebook の起動しているポート番号>

Forwarding に表示されている URL からローカルに転送されています

Webhook 設定

Backlog の「プロジェクト設定」 |> 「インテグレーション」から「Webhook」の「設定」をクリックします

「Webhook を追加する」をクリックし、フォームに以下のように入力します

  • Webbook名: 任意の名前
  • 説明: 任意の値
  • WebHook URL: ngrok の Forwarding URL + /proxy/apps/backlog
  • 課題の追加: チェックを付ける

スクリーンショット 2024-11-08 16.36.28.png

フォームの一番下にある「Webhook を追加する」をクリックします

以下のように Webhook が追加されていれば OK です

スクリーンショット 2024-11-08 16.40.05.png

課題登録

Backlog で適当な課題を登録すると、辛辣な評価コメントが付けられます

スクリーンショット 2024-11-08 16.42.00.png

まとめ

Livebook から Backlog API と OpenAI API を呼び出し、課題説明文を評価できました

Backlog には他にも色々な API があるので、もっと色々できそうです

Webhook アプリケーションを Fly.io 等にデプロイしておけば、様々な自動化にも活用できます

Livebook はグラフや表の出力も得意なので、 Backlog の拡張コンソールも実装できると思います

14
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?