athome-developer’s blog

不動産情報サービスのアットホームの開発者が発信するブログ

「社内版ChatGPT」ツールの開発について

デジタルイノベーショングループのチンです。
2020年に新卒で入社し、不動産情報アプリ「アットホーム」の開発・保守に2年ほど携わってから、
今はメタバース・生成AIなどの新しい技術を用いた実験的なプロジェクトを担当しております。

今回は、最近参画した「社内専用ChatGPTサービス開発プロジェクト」について紹介したいと思います。

「社内版ChatGPT」とは?

社内から「ChatGPTを業務利用したい」との声が増え、実験的に導入したいと考えましたが、
ChatGPT Web版よりも、
社内版ChatGPTの方がメリットが多いため、作成しました。

「社内版」にしたい理由

最初に「利用者全員にOpenAIアカウント(ChatGPTを利用するためのアカウント)を配布する」という方法を考えました。
しかしながら、下記のような問題が出てきました。

  • 入力内容が学習されてしまう

ChatGPTを利用するには、Webブラウザー上で使えるWeb版を使う方法と、
ChatGPT APIを経由する方法の2通りがあります。
Web版を使うと、入力内容はAIトレーニングに使われる可能性があります。
一方、「APIを経由すれば入力内容はAIトレーニングに使われない」とChatGPTの利用規約に明記されていますので、誤って社内の情報やソースコードを入力しても問題がないよう、APIを使う方針を固めました。

  • 社員分のOpenAIアカウントを作成できない

Web版を利用する場合、社員数分のメールアドレスと携帯電話番号が必要になってしまう点と、各アカウントごとに支払い設定を行う必要があり、手間がかかります。
APIを利用する場合、APIキー1枚を持っていれば、誰でも何人でも使えます。故にAPIキー1枚 〜 数枚を発行して使い回し、アカウント作成のコストを抑えたいと考えました。

  • 使用状況や利用料金を把握したい

管理・保守目的に部署ごとの利用料金とログを保持したくても、公式の集計機能は一部しか取れません。なので、自作のChatGPTではリクエストが発生するごとに利用料金やログを保存し、集計に使えるようにしました。
また、部署ごとの利用状況を把握するため、最低限のユーザー管理機能も必要と考えました。

これらの課題を、ChatGPT API + 自作Webシステムで解決します。

「社内版ChatGPT」をつくる

では、「社内版ChatGPT」の開発の流れと開発時に直面した課題を紹介します。

システム要件

最初は利用者にヒアリングし、登場人物・機能・権限についてまとめました。
下記が実際に洗い出された項目の一部になります。

分類

機能

利用範囲

概要

本家版

社内版

生成機能

チャット

-

ChatGPT(Web)と同様

プラグイン

-

ChatGPT(Web)に追加機能を提供するツール

×

記事作成 GPT

一部の部署

自作機能
※ 将来的に増えていく

×

SNS GPT

ユーザー管理機能

管理画面

リーダー

・個人単位のリクエストログ一覧
・部署単位のリクエスト量(トークン数)を可視化 

アカウント管理機能

-

・パスワード変更機能

リーダー

・アカウント発行・削除・無効化機能
・パスワードリセット機能

セキュリティー

情報漏洩の防止

-

・入力情報の学習利用を防止

×

不正アクセスの防止

-

・ネットワーク、APIキーによるアクセス制限

×

不正利用の防止

-

・個人単位のリクエストログ保管

×

運用・保守性

コスト監視

-

・部署単位の利用料金を可視化

「社内版」しかない機能

ChatGPT(Web)では、サードパーティプラグインが多数使えますが、Web版に組み込んだ機能なのでAPI版では使えません。
その代わりに、「ChatGPT社内版」の方では、自社の業務向けに特化した「自作機能」をいくつか作成しました。

記事作成機能

ChatGPTが記事を作成してくれる機能です。
キーワードを入力すると、左側のチャットエリアから、タイトルの提案をいくつかもらえます。
そこから好きなタイトルを選ぶと、そのタイトルに沿ったアウトラインや本文まで作成してもらえます。
また、全て出力内容は即時に右の編集エリアに反映しており、その場で校正することができます。

記事作成画面

SNS投稿文作成機能

X(旧twitter)で投稿する内容を作成する機能です。 投稿したいURLや「日付を追加」といった追加の指示を指定し、いくつかのテンプレートから形式を指定することで投稿内容が作成されます。
記事作成と同様に、作成された投稿内容はその場で編集、校正ができます。

SNS投稿文作成画面

以前の投稿「Webデザイナーが考えるショート動画クリエイティブ」で作成した結果

システム構成

システム全体は、サーバーレスアーキテクチャーを採用しています。
自社の物理サーバーを使わず、ハードウェアもミドルウェアもOSもクラウドサービス(今回はAWS)を用いることでコアなコーディングに集中でき、素早く開発ができるのが一番のメリットです。
また、「Serverless Framework」というライブラリとテンプレートを使うことで、コマンド1行でデプロイでき、AWS初心者の私には結構ありがたいです。

各部分で採用した技術と理由は下記になります。

フロントエンド

Next.js

Next.jsは、Reactベースのフロントエンドフレームワークです。
パフォーマンスが優れているのと、開発を加速させる機能を備えているのが特徴です。
.jsなのにTypeScriptへのサポートを完備。
※ 一番のメリットはプリレンダリングできることですが、サーバーに負荷がかかるので使用しません。

シングルページアプリケーション

ページを遷移せず、コンテンツだけ切り替えられるアプリケーションです。
「チャット機能」から他の機能に遷移する際、ヘッダーと左のサイドバーはそのままにして、
チャットエリアのレイアウトだけ再描画すれば良いので、画面遷移は素早くなります。
一方、初回ローディングは若干遅いです。

バックエンド

RESTful API (Lambda + API Gateway

「シンプルなCRUDで実現できる」ため、
「RESTful APIAPI Gateway+Lambda)※」という構成を採用しました。

API Gatewayを用いた、サーバーレスマイクロサービスでのAPI実装 と同様の構成のため、詳細は割愛します。

DynamoDB

「NoSQL データベース」というデータベース設計を採用したAWSサービスです。
システム要件の「部署単位のリクエスト量(トークン数)を可視化」を実現するには、高速な読み取りが必要なため、DynamoDBを採用しました。
また、エンティティ数が少なく、リレーションも簡単なので、RDBを使うほどではありませんでした。

未経験エンジニアが挑むAWS DynamoDB設計 でより詳しく説明しているため、割愛します。

システム開発

実際の開発では、いろいろな課題が挙げられましたが、その中から2つピックアップしてお話します。

課題① ChatGPTの回答をカスタマイズしよう!

「記事作成機能」の開発の話ですが、ChatGPT APIにアウトラインを提案してもらう際に、下記のように箇条書きで返却してもらいたいです。

【アウトライン】

■1. {大見出し1}
- (1) {小見出し1-1}
- (2) {小見出し1-2}
- (3) {小見出し1-3}

記事作成には、本来「タイトル」「サブタイトル」「指示と要望」などの情報を全て組み込んで質問文を作成する必要があります。
しかし、文章を作成するのに手間がかかり使い勝手が悪いため、「サブタイトル」だけをプルダウンで選び、裏側で質問文を組み立てることを考えました。
ChatGPT APIに箇条書きでアウトラインを作成してもらうと、見た目が良く、単純な文字列処理で「サブタイトル」の配列を抽出できるので、ユーザーも開発者もハッピーです。

しかし、「アウトラインを作成してください」とだけ指示しても、回答のフォーマットは定められません。基本は箇条書きで作成されますが、番号が英字になったり、数字になったり、冒頭や終わりに余計な情報が含まれたりするため、後続の処理が困難になることがあります。

正確な結果を得るため、質問文を組む際にいくつかのプロンプト手法を使用しました。
ここでは「100以内の素数を3つ教えてください」という質問を例に、これらのプロンプトの手法を説明します。

「system」ロールとして指示を与える

ChatGPT(Web版)を使う際に、下記の「ChatGPT API」が呼び出されます。このAPIは名前の通り、会話を補完します(completion)。
Web版を使うと、ユーザーの入力は全て「user」として送信されますが、APIを使う場合「system」としてモデルを指示できます。「system」が出した指示は「user」より優先度が高いので、期待した結果が作成される可能性も高いです。

curl --location 'https://api.openai.com/v1/chat/completions' \
--header 'Content-Type: application/json' \
--header 'Authorization: {APIキー}' \
--data '{
     "model": "gpt-4",
     "messages": [
        {"role": "system","content": "[a,b,c]`の形式で回答してください。"},
        {"role": "system", "content": "一番シンプルな答え(最小な数字)にしないでください"},
        {"role": "system", "content": "正解ではないものも1つ混ぜてください。"},
        {"role": "user","content": "100以内の素数を3つ教えてください"}
    ],
    "n": 5,
     "temperature": 0.7
}'
// 回答 (n = 5)
[
    { "role": "assistant",  "content": "[11, 67, 200]"},
    { "role": "assistant",  "content": "[17, 23, 68]"},
    { "role": "assistant",  "content": "[11, 31, 55]"},
    { "role": "assistant",  "content": "[13, 99, 97]"},
    { "role": "assistant",  "content": "[23, 89, 101]"},
]

// 期待した回答なのか
100% (5/5)

見本を見せる

本当に聞きたい質問をする前に、「質問例」と「回答例」を作成し、真似させる手法です。

// 質問
[
    {"role": "system", "content": "以下全ての問題に対し"},
    {"role": "system", "content": "正解ではないものも1つ混ぜてください。"},
    {"role": "user", "content": "100以内の偶数を3つ教えてください"}, // 質問例
    {"role": "assistant", "content": "[56,8,17]"},           // 回答例
    {"role": "user","content": "100以内の素数を3つ教えてください"}   // 実際の質問
]

// 回答(n = 5)
[
    { "role": "assistant",  "content": "[23, 45, 89]" },
    { "role": "assistant",  "content": "[23, 33, 47]" },
    { "role": "assistant",  "content": "[7, 29, 100]" },
    { "role": "assistant",  "content": "[23, 37, 81]" },
    { "role": "assistant",  "content": "[31, 67, 81] "}
]

// 期待した回答なのか
100%(5/5)

// 省略できたプロンプト
{"role": "system","content": "[a,b,c]`の形式で回答してください。"},
{"role": "system", "content": "一番シンプルな答え(最小な数字)にしないでください"},

注目すべき点は、「[a, b, c]の形式で回答してください」と「一番シンプルな答えをしない」を明示的に指示をしなくても、[56, 8, 17]という回答例からその意図を読み取ってくれたことです。

アウトライン作成機能の開発では、初めに「system」ロールを使用して詳細な指示を出しましたが、文章の意図を完全には理解されず、指示の一部しか実行されませんでした。なので見本も併用し、安定したフォーマットで答えが作成されるまでプロンプトを調整し続けました。

課題② コストを一目でわかるようにしよう!

「ユーザー管理機能」の設計の話ですが、
「社内版ChatGPT」のアカウントを発行している一つの目的は、
利用状況と利用料金の把握です。

要件の通り、
「利用状況」: 個人単位の「リクエスト内容」「リクエスト数」「トークン数」
「利用料金」: 部署単位の「実際に利用した費用」、
と定義します。

まず、問題となるのは「実際に利用した費用」です。
このデータは、レスポンスに含まれておらず、算出も困難なので、
OpenAIアカウントの管理画面から直接データを取得しようと考えました。

※ 費用は「トークン数 x モデル単位料金」で算出することができますが、各モデルの単位トークン料金が予告なしに変更されることがあります。
そのため、「過去のモデル料金」や「データ保存時のモデル単位料金」のように、料金情報をマスターとして管理する必要が生じます。しかし、このような管理は複雑であり、公式サイトに掲載されている合計金額との差異が生じる可能性があります。

OpenAIアカウントの管理方式は下記のように、複数のユーザーを一つの組織にまとめることができます。
その組織から発生した費用は、親アカウント(決済用アカウント)からまとめて支払われます。
管理画面にアクセスすると、費用の明細も取得できます。
但し、明細の精度は「アカウント」までなので、1つのアカウントから複数のAPIキーを払い出した場合、どのAPIキーがどのくらい費用を使ったのが分かりません。

また、OpenAIアカウントの管理画面には、毎月の合計金額をきれいに表示する機能が備わっています。
しかし、表示されるのは合計金額のみなので、アカウントごとの費用を別途で可視化する必要があります。

OpenAI社のアカウント管理画面
上記の条件と制限を考え、
「社内版ChatGPT」の「ユーザー管理機能」を設計してみました。

部署ごとの費用を把握することが目的のため、少なくとも部署ごとに一つのアカウントが必要です。
しかし、全員にOpenAIアカウントを作成することには難しいため、各部署のリーダーにのみアカウントを作成します。

各部署のリーダー(下図のMさん)にAPIキーを一枚発行してもらい、この部署の共有キーとしてデータベースに格納します。

この部署(部署M)に所属するメンバーAさんが「社内版ChatGPT」経由で、共有キーを使ってChatGPT APIを叩くと、公式の方から見たら「Mさんの(部署の)費用」になります。

「社内版ChatGPT」のログイン機能

これで費用の明細から、各部署が実際使った費用を確認できるようになりました。
以降は、スクリプトで取得して、可視化すれば、「利用料金」の課題は解決します。

「利用状況」のどの項目も、ChatGPTのリクエストログに含まれていますので、上記と同じように、Aさんがリクエストするケースを考えましょう。

Aさんが「部署Mの社員A」として、「社内版GPT」を利用する際に、
直接ChatGPTのAPIにリクエストを送るのではなく、Lambda上の「ChatGPT社内版API」にリクエストを送るようにします。
このAPIは、「共有キーの振り分け」->「ChatGPT APIにリクエスト」->「ログを保存」を行います。
ログを保存する際に、時間、部署、どのように検索してもすぐに結果が出るように、「日付」「部署MのID」「社員AのID」を検索キーと設定します。

「社内版ChatGPT」 - 管理者画面。費用以外の項目が揃ってます。
「利用状況」の確認は「社内版GPT」の「管理画面」で行っています。

「社内版ChatGPT」をつくってみての感想

いかがでしたでしょうか。

このプロジェクトでは、主にバックエンドとデータベースの設計・実装を担当しました。
これまでフロントエンドの開発がメインでしたので、バックエンド開発は私にとって新鮮で非常にチャレンジングな業務でした。
また、AWSへのデプロイ手順や環境設定にも少し触れる機会ができて勉強になりました。

実は、「社内版ChatGPT」の開発中には、ChatGPT 4の助けも受けました。
自分が一番使ったところは、コマンドやCSSの逆引き・コードの説明・ドキュメント集約などです。
簡単なスクリプトであれば、ChatGPT 4 に生成してもらえます。
目でチェックする必要は依然ありますが、ChatGPT 3よりは信ぴょう性が高くなったと感じています。
生成AIの進歩は本当に驚くべきものです。
実際、このブログの記事の一部も、「記事作成機能」によって生成された可能性があるんですよ?

お読みいただき、ありがとうございます!