はじめに
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 のワークスペースにログインし、右上ドロップダウンメニューから「個人設定」をクリックします
個人設定の左メニューから「API」を開き、「メモ」に適当な値を入れて「登録」をクリックすれば作成できます
OpenAI API の API キー作成
公式サイトにアクセスし、サインアップしてください
OpenAI 社が提供する ChatGPT とは別契約になります
すでに ChatGPT のアカウントを保有をしていたとしても、別途 OpenAI API のサインアップが必要です
初回アカウント作成時は無償トライアルを受けることが可能ですが、基本的に前払いでクレジットを購入しておき、 API 利用のたびに消費していく契約になります
左メニュー API keys を開き、右上 "+ Create new secret key" をクリックします
モーダルが開くので、適当な名前を入力して "Create secret key" をクリックします
API キーが作成されるので、安全な場所に保管しておいてください
Livebook の実装
セットアップ
Livebook で新しいノートブックを開き、先頭のセットアップセル(Notebook dependencies and setup
と書いてある枠)に以下のコードを入力します
Mix.install([
{:req, "~> 0.5"},
{:kino, "~> 0.14"}
])
セルの左上 Reconnect and setup
ボタンをクリックすると、処理に必要な依存モジュールがインストールされます
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 キーの値を入力します
API キーはワークスペース毎に認可されています
違うワークスペースで作成した API キーは使えません
API キー入力エリアの下あたりにマウスカーソルを持っていくと、下画像のように 3 つボタンが表示されます
+ Elixir
をクリックすると、下に次のセル(黒い四角)が追加されます
追加されたセルに以下のコードを貼り付け、 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 では画像のバイナリデータをそのまま画像として表示してくれます
プロジェクトの取得
扱いたいプロジェクトのプロジェクトキーを確認しておきます
Backlog でプロジェクトの左メニュー「プロジェクト設定」開き、「基本設定」内の「プロジェクトキー」で確認できます
プロジェクトホーム画面の 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()
実行結果(例)
Backlog 上と同じ内容になっていることが確認できます
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)
実行結果
指示した通り、ちゃんと評価してくれています
評価内容を課題のコメントとして登録します
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()
実行結果
実行結果はリンクになっているので、クリックすると Backlog 上のコメントが確認できます
課題登録時の自動実行
ここまでやってきた内容を課題登録時に自動実行するよう、 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 キー
only this session
と in Personal
の選択肢では必ず in Personal
を指定してください( secrets をアプリケーション上で利用するため)
下画像のようになっていれば OK です
アプリケーションの定義
以下のコードをセルに貼り付けます
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 左メニューのロケットアイコンをクリックし、アプリケーション設定を開きます
Configure
ボタンでアプリケーション設定モーダルを開き、以下の通り入力してください
-
Slug
(アプリケーション URL の末尾):backlog
- Password-protected: チェックを外す
Save
ボタンで設定を保存します
Launch preview
ボタンをクリックすると、アプリケーションがローカルで起動します
起動後の表示(Version の値は最初 v1 で、 Relaunch する度に増加)
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
- 課題の追加: チェックを付ける
フォームの一番下にある「Webhook を追加する」をクリックします
以下のように Webhook が追加されていれば OK です
課題登録
Backlog で適当な課題を登録すると、辛辣な評価コメントが付けられます
まとめ
Livebook から Backlog API と OpenAI API を呼び出し、課題説明文を評価できました
Backlog には他にも色々な API があるので、もっと色々できそうです
Webhook アプリケーションを Fly.io 等にデプロイしておけば、様々な自動化にも活用できます
Livebook はグラフや表の出力も得意なので、 Backlog の拡張コンソールも実装できると思います