LaravelとChatGPT APIの強力コラボ!関連データ検索がこれで簡単・効率的になる!

こんにちは❗フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、まだまだ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のプラットフォームページで取得しますので、以下のページへアクセスしてください。

📝 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での作業になります。

パッケージをインストールする

なんと、OpenAILaravel用にパッケージを用意してくれているので、こちらのパッケージをインストールします。

以下のコマンドを実行してください。

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」だと、「もしかして止まってない!?」と思うぐらい遅かったので、もしかするとまだ今回のようなリアルタイム性が必要な機能への統合は向いていないのかもしれません。

(ウェブ版ではそんなに待つイメージはないのにな…🤔と思いましたが、ウェブ版はちょっとずつ書いてくれるから待っていられるんですね)

ということで、これも使い方次第でいろいろな便利機能をつくることができると思います。

ぜひみなさんもチャレンジしてみてくださいね。

ではでは〜❗

「友人と人狼ゲームしたら
優勝しました。
私は嘘つきなのかな…😅」

開発のご依頼お待ちしております 😊✨
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly  

開発効率を上げるための機材・まとめ