
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、まだまだChatGPT
の研究をしていて可能性を感じている今日この頃です。
そして、ある程度ウェブ版のChatGPT
は一段落したので、プログラマの私としてはやりたいことが思い浮かびました。
それが・・・・・・
Laravel から ChatGPT を使う
です。
そうです。
もちろんウェブ版があれば何でも質問できるのでいいのですが、システムに関連することをいちいちコピペするのは面倒なので、自動化したいところです。
そこで
今回は「Laravel & ChatGPT API」をコラボさせて簡単に「関連データ検索」できるようにしてみます。
※ ここで言う関連検索とは、「キーワードが含まれていないのに検索に引っかかるようにする」という意味で使っています
ぜひ何かの参考になりましたら嬉しいです。
「ああ、GWに焼肉で
食った、あのダッカルビ
うまかったなぁ〜」
開発環境: Laravel 10.x、React、Inertia.js、Chat API
目次 [非表示]
やりたいこと
今回は、事前にDBに「ピアノ」に関連するテキストを保存しておきます。
ただし、このテキストには「ピアノ」という文字は含めません。
そして、逆に検索は「ピアノ」をキーワードにします。(つまり、普通では検索できないものが検索結果に出てくれば成功というわけですね)
では楽しんでやっていきましょう
前提として
今回の実行環境(Inertia
)は手っ取り早いので、Laravel Breeze
からReact
バージョンでをインストールしています。
インストールは以下をご覧ください。
参考ページ: Laravel Breeze
OpenAI の API キーを取得する
まずはAPI
キーがないと始まりませんので、先に取得しておきましょう。
API
キーはChatGPT
の方ではなく、OpenAI
のプラットフォームページで取得しますので、以下のページへアクセスしてください。
すると、ページ右上にログインページへ移動するリンクがありますので、「Log in」をクリック。
そして、フォームからログインをします。
すると、API
キーを管理するページに移動するので、「Create new secret key」ボタンをクリックしてください。
すると、ポップアップが表示されるのでお好みで後でわかりやすい名前を入力し(任意)、「Create secret key」ボタンをクリックします。
新しいAPI
キーが作成されるので、コピーして.env
へ登録しておきましょう。
.env
// 省略
# OpenAI
OPENAI_API_KEY=sk-*********************************************
※ご注意:このキーを使えば他の人であってもAPI
が使える(=つまり課金される)ので絶対に流出しないようにしてください。
OpenAIに課金登録する
勘違いしていたのですが、ChatGPT
に課金していてもOpenAI
で課金登録しないとAPI
は使えません。
ですので、まだ登録されていない方はこの作業も行ってください。(すでに完了している人は次の項目まで読み飛ばしてください)
まず、OpenAI
プラットフォームの画面左側にあるメニューから「Billing > Overview」を選択します。
ページ移動した先に「Set up paid account」というボタンがあるので、これをクリック。
すると、支払いタイプが表示されるので、今回は「I’m an individual(個人)」で使用します。(会社のために使う場合は下のボタンをクリックしてください)
すると、クレジットカード情報のフォームが表示されるので中身を入力してください。(なお、住所は一応 君に届け を使って英語表記にしました)
そして、「Set up payment method」ボタンをクリックするとクレジットカードの登録は完了です。
では、これでOpenAI
側の作業はOKです
次からはLaravel
での作業になります。
パッケージをインストールする
なんと、OpenAI
はLaravel
用にパッケージを用意してくれているので、こちらのパッケージをインストールします。
以下のコマンドを実行してください。
composer require openai-php/laravel
※ なお、私のケースでは依存関係でインストールできませんでしたが、composer.lock
を削除してから実行するとうまくいきました。
そして、続けて以下のコマンドで専用config
ファイルを作成します。
php artisan vendor:publish --provider="OpenAI\Laravel\ServiceProvider"
これを実行するとconfig/openai.php
が作成されます。
モデル&マイグレーションをつくる
では、ここからはLaravel
のコードを書いていく部分になります。
まずはDB
まわりです。
以下のコマンドを実行してください。
php artisan make:model Article -ms
すると、モデル&マイグレーション&Seederの3ファイルが作成されるのでそれぞれ中身を次のようにします。(モデルは変更なしです)
database/migrations/****_**_**_******_create_articles_table.php
// 省略
public function up(): void
{
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->string('title')->comment('タイトル');
$table->text('content')->comment('本文');
$table->timestamps();
});
}
そして、Seeder
です。
database/seeders/ArticleSeeder.php
// 省略
public function run(): void
{
// 1件だけ追加する
$article = new Article();
$article->title = 'フレデリック・ショパン';
$article->content =
<<<TEXT
ポーランド出身の、前期ロマン派音楽を代表する作曲家。当時のヨーロッパにおいても*****として、また作曲家としても有名だった。その作曲のほとんどを***独奏曲が占め、***の詩人[注 4]とも呼ばれるようになった。様々な形式・美しい旋律・半音階的和声法などによって***の表現様式を拡大し、***音楽の新しい地平を切り開いていった。夜想曲やワルツなど、今日でも彼の作曲した***曲はクラシック音楽ファン以外にもよく知られており、***の演奏会において取り上げられることが多い作曲家の一人である。また、強いポーランドへの愛国心からフランスの作曲家としての側面が強調されることは少ないが、父の出身地で主要な活躍地だった同国の音楽史に占める重要性も無視できない。
1988年からポーランドで発行されていた5,000ズウォティ紙幣に肖像が使用されていた。また、2010年にもショパンの肖像を使用した20ズウォティの記念紙幣が発行されている。2001年、ポーランド最大の空港「オケンチェ空港(Port lotniczy Warszawa-Okęcie)」が「ワルシャワ・ショパン空港」に改名された。
TEXT;
$article->save();
}
// 省略
※ content
の部分は wikipedia から引用し「ピアノ」「ピアニスト」というキーワードを「***」「*****」に変更したものです(直接検索に引っかからないようにするため)
なお、Seeder
は作成しただけでは有効になりませんので、Laravel
へセットしておきましょう。
database/seeders/DatabaseSeeder.php
// 省略
public function run(): void
{
// 省略
$this->call([
ArticleSeeder::class,
]);
}
// 省略
では、この状態でDB
を再構築します。
以下のコマンドを実行してください。
php artisan migrate:fresh --seed
実際のテーブルはこうなりました。
検索用のコントローラーをつくる
では、続いて検索機能を作成します。
php artisan make:controller ArticleController
すると、ファイルが作成されるので、中身を以下のように変更します。
app/Http/Controllers/ArticleController.php
<?php
namespace App\Http\Controllers;
use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Inertia\Inertia;
use OpenAI\Laravel\Facades\OpenAI;
use Illuminate\Support\Facades\Cache;
class ArticleController extends Controller
{
public function index()
{
return Inertia::render('Article/Index');
}
public function list(Request $request)
{
$keyword = $request->input('keyword', '');
$gpt_keywords = $this->getKeywordsByChatGpt($keyword);
$articles = [];
if(count($gpt_keywords) > 0) {
$articles = Article::query()
->when($gpt_keywords, function ($query, $gpt_keywords) {
foreach ($gpt_keywords as $gpt_keyword) {
$query->orWhere('title', 'LIKE', "%{$gpt_keyword}%")
->orWhere('content', 'LIKE', "%{$gpt_keyword}%");
}
})
->get();
}
return [
'gpt_keywords' => $gpt_keywords,
'articles' => $articles,
];
}
private function getKeywordsByChatGpt(string $keyword): array
{
$keyword = mb_convert_kana($keyword, 's', 'UTF-8');
$keywords = collect(explode(' ', $keyword))
->filter(function ($keyword) {
return Str::length($keyword) > 0; // 空文字を除外
})
->map(function ($keyword) {
return '・'. $keyword;
});
if($keywords->count() === 0) {
return [];
}
$target_keyword_content = $keywords->implode("\n");
$prompt =
<<<TEXT
あなたは優秀なコピーライターとして回答してください。
以下の「対象キーワード」に関連するキーワードを挙げてください
# 対象キーワード
{$target_keyword_content}
なお、条件は以下のとおりです。
・回答はキーワードのみで「,」で区切る
TEXT;
$cache_key = md5($target_keyword_content);
return Cache::rememberForever($cache_key, function () use($prompt) {
$result = OpenAI::chat()->create([
'model' => 'gpt-3.5-turbo',
'messages' => [
['role' => 'user', 'content' => $prompt],
],
]);
$gpt_keyword = Arr::get($result, 'choices.0.message.content');
return explode(',',
str_replace(' ', '', $gpt_keyword)
);
});
}
}
この中でやっていることは、シンプルにキーワードを使ってChatGPT
にプロンプトを送信し、その返信に含まれている「関連キーワード」を使って検索をしているだけです。
なお、APIを使うごとに料金がかかるので、キャッシュを使っています。
また、プロンプトの中身は、例えばキーワードが「ピアノ ギター」だった場合は以下のようになります。
プロンプト例:
あなたは優秀なコピーライターとして回答してください。
以下の「対象キーワード」に関連するキーワードを挙げてください
# 対象キーワード
・ピアノ
・ギター
なお、条件は以下のとおりです。
・回答はキーワードのみで「,」で区切る
なお、(私はビビリなので)今回は安い「gpt-3.5-turbo」にしましたが、もちろんgpt-4
も利用可能です。その他のモデルは以下のページを参考にしてみてください。
参考ページ: Model endpoint compatibility
また、料金については以下ページです。
参考ページ: Pricing
ビュー(検索フォーム)をつくる
次に、検索フォームです。
以下のファイルを作成してください。
resources/js/Pages/Article/Index.jsx
import { useState } from "react";
import axios from "axios";
export default function ArticleIndex () {
const [keyword, setKeyword] = useState("");
const [articles, setArticles] = useState([]);
const [gptKeywords, setGptKeywords] = useState([]);
const handleSubmit = () => {
const url = route('article.list');
const params = {
params: { keyword },
}
axios.get(url, params)
.then((response) => {
setArticles(response.data.articles);
setGptKeywords(response.data.gpt_keywords);
});
};
return (
<div className="container mx-auto px-4 py-8">
<div className="flex items-center">
<input
type="text"
className="w-full p-2 border border-gray-300 rounded mr-2"
placeholder="検索キーワード..."
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
/>
<button
type="button"
className="bg-blue-500 text-white py-2 px-4 rounded whitespace-nowrap"
onClick={handleSubmit}
>
検索
</button>
</div>
{gptKeywords.length > 0 && (
<div className="mt-5">
<h2 className="text-xl font-bold">GPT-3によるキーワード</h2>
{gptKeywords.map((gptKeyword, index) => (
<span key={index} className="mb-4">・{gptKeyword} </span>
))}
</div>
)}
{gptKeywords.length > 0 && (
<div className="mt-5">
{articles.map((article, index) => (
<div key={index} className="mb-4">
<h3 className="text-xl font-bold">{article.title}</h3>
<p>{article.content}</p>
</div>
))}
</div>
)}
</div>
);
};
これもシンプルにキーワードをAjax
で送信し、帰ってきたデータをループで表示しているだけです。
ルートをつくる
では、最後にルートです。
routes/web.php
// 省略
use App\Http\Controllers\ArticleController;
Route::get('/article', [ArticleController::class, 'index'])->name('article.index');
Route::get('/article/list', [ArticleController::class, 'list'])->name('article.list');
これで作業は完了です
お疲れ様でした
テストしてみる
では、実際にテストしてみましょう
まず、Vite
を起動して「https://*****/article」にアクセスすると入力ボックスが表示されるので、「ピアノ」と入力して「検索」ボタンをクリックします。
すると・・・・・・
はい
「ピアノ」というキーワードを含んでいなかった先ほどのデータを検索することができました。
成功です
企業様へのご提案
今やChatGPT
はウェブ上からだけでなくAPI
を通しても利用することができます。
そのため、現在利用になられているシステムと統合することで以下のような機能を追加することができます。
- 議事録や会議録が登録されたときに自動で要約を作成する
- 過去に登録されたデータから「よく入力される単語」を作成しておき自動で入力補完できるようにする
- 投稿されたテキストの「感情」を読み取り、ある一定以上のものを優先的に対応する
などなど。
もちろんChatGpt
でできることは今回のAPI
でも同じことができます。
もしそういった開発をご希望の場合はぜひお問い合わせからご相談ください。
お待ちしております
おわりに
ということで、今回はChatGPT API
を利用して「あいまい検索」ができるようにしてみました。
ただ、はじめてAPI
を使ってみた感想としては「ちょっと時間がかかる」というものでした。
「gpt-3.5-turbo」でも少し待ちますが、「gpt-4」だと、「もしかして止まってない!?」と思うぐらい遅かったので、もしかするとまだ今回のようなリアルタイム性が必要な機能への統合は向いていないのかもしれません。
(ウェブ版ではそんなに待つイメージはないのにな…と思いましたが、ウェブ版はちょっとずつ書いてくれるから待っていられるんですね)
ということで、これも使い方次第でいろいろな便利機能をつくることができると思います。
ぜひみなさんもチャレンジしてみてくださいね。
ではでは〜
「友人と人狼ゲームしたら
優勝しました。
私は嘘つきなのかな…」