九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、この間、News API + ChatGPT で1日のニュースを要約してメール送信する という記事を公開しましたが、その際に10ドル課金しました。
そして、せっかく課金したんだから他にも何か作ってみようと思っていたところ、過去にAWS
で実装したある機能を思い出しました。
それは・・・・・・
日報からスタッフの「ストレスレベル」を判別する
です。
📝 参考ページ: Laravel + Amazon Comprehend で従業員の感情を日報から判別する
というのも、前回はChatGPT
が存在しておらず、AWS
の感情分析API
を使いましたが、AI
が進化した今となっては「いや、それ ChatGPT でいけるんじゃない!?」と思ったからなんですね。
そこで❗
今回はLaravel
を使ってフォームから送信された日報をChatGPT API
に投げてスタッフのストレスレベルを解析し、上司に通知をするという機能を実装してみたいと思います。
ぜひ何かの参考になりましたら嬉しいです。😊✨
「私のストレス発散は
遠出して、その土地の
クラフトビールを飲むことです🍺」
開発環境: Laravel 10.x、PHP 8.2
目次
前提として
今回はChatGPT API
を使いますので、(普通のChatGPT
とは別に)Open API
へ登録&課金してAPI
キーを用意しておく必要があります。
やり方は以下のURL
を参考にしてみてください。
📝 参考ページ: OpenAI の API キーを取得する
また、Laravel breeze
でログイン機能がインストールされ、ログインできるユーザーがすでに登録されていることが前提です。
では、準備が整ったら実際にやっていきましょう❗
パッケージをインストールする
まずはじめにOpenAPI
のAPI
にアクセスするためのパッケージが用意されているので、以下のページを参考にしてインストールしておいてください。
📝参考ページ: パッケージをインストールする
送信フォームをつくる
次に、日報を送信するフォームをつくります。
ちなみに、Laravel 10.23
からはphp artisan make:view
が使えます!
せっかくなので、今回から使っていきます。(というか、なぜ今まで作らなかったのでしょうね…🤔)
以下のコマンドを実行してください。
php artisan make:view daily_report.create
すると、ビューが作成されるので、以下のコードをセットしてください。
resources/views/daily_report/create.blade.php
<html> <head> <script src="https://cdn.tailwindcss.com"></script> </head> <body> @if (session('status') === 'success') <div class="bg-green-500 p-4 rounded-lg mb-6 text-white text-center"> 日報の送信が完了しました。 </div> @endif <form method="POST" action="{{ route('daily_report.store') }}" class="p-6 bg-white rounded-lg shadow-lg w-full max-w-md mx-auto"> @csrf <div class="mb-4"> <label for="name" class="block text-sm font-medium text-gray-600">名前:</label> <span id="name" class="text-lg font-semibold">{{ $user->name }}</span> </div> <div class="mb-4"> <label for="date" class="block text-sm font-medium text-gray-600">日付:</label> <input type="date" name="date" value="{{ old('date') }}" class="p-2 rounded border focus:border-indigo-500 w-full"> @error('date') <div class="text-red-500">{{ $message }}</div> @enderror </div> <div class="mb-4"> <label for="report_text" class="block text-sm font-medium text-gray-600">日報内容:</label> <textarea name="report_text" rows="4" class="p-2 rounded border focus:border-indigo-500 w-full">{{ old('report_text') }}</textarea> @error('report_text') <div class="text-red-500">{{ $message }}</div> @enderror </div> <div class="flex justify-end"> <button id="submit_button" type="submit" class="py-2 px-4 bg-indigo-500 text-white rounded hover:bg-indigo-600 focus:outline-none focus:border-indigo-700 focus:ring focus:ring-indigo-200"> 送信 </button> </div> </form> <script> window.onload = () => { const submitButton = document.getElementById('submit_button'); submitButton.addEventListener('click', e => { if(! confirm('送信してよろしいですか?')) { e.preventDefault(); } }); } </script> </body> </html>
コントローラーをつくる
では、メインのコントローラー部分をつくっていきましょう。
以下のコマンドを実行してください。
php artisan make:controller DailyReportController
すると、ファイルが作成されるので中身を以下のようにします。
app/Http/Controllers/DailyReportController.php
<?php namespace App\Http\Controllers; use App\Http\Requests\DailyReportRequest; use App\Mail\DailyReportPosted; use App\Models\User; use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Notification; use OpenAI\Laravel\Facades\OpenAI; class DailyReportController extends Controller { public function create(Request $request) { $user = $request->user(); return view('daily_report.create')->with([ 'user' => $user, ]); } public function store(DailyReportRequest $request) { $user = $request->user(); $report_text = $request->report_text; $date = $request->date; // ChatGPT API でストレスレベルを判別 $stress_level = $this->getStressLevel($report_text); // 実際にはここで DB などへ保存(今回は省略) $to = 'admin@example.com'; Mail::to($to)->send(new DailyReportPosted( $user, $stress_level, Carbon::parse($date), )); return back()->with('status', 'success'); } private function getStressLevel($text) { $stress_level = -1; $prompt = view('daily_report.prompt', [ 'text' => $text ])->render(); try { $result = OpenAI::chat()->create([ 'model' => 'gpt-3.5-turbo', 'messages' => [ ['role' => 'user', 'content' => $prompt], ], ]); $json_text = Arr::get($result, 'choices.0.message.content'); $stress_level = json_decode($json_text, true)['result']; } catch (\Throwable $th) { $this->error($th->getMessage()); throw $th; } return $stress_level; } }
なお、ここで重要なのがgetStressLevel()
の中にある、ChatGPT API
からデータが返ってくるところです。
後で紹介しますが、プロンプトに「JSON
だけで回答してください」と指定します。つまり、取得できる回答はJSON
形式になっているので、json_encode()
で配列化しないといけないというわけですね。
バリデーションをつくる
では、先ほど作ったコントローラーの中で使っていたバリデーション(FormRequest
)をつくりましょう。
以下のコマンドを実行してください。
php artisan make:request DailyReportRequest
すると、ファイルが作成されるので中身を次のように変更します。
app/Http/Requests/DailyReportRequest.php
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class DailyReportRequest extends FormRequest { /** * Determine if the user is authorized to make this request. */ public function authorize(): bool { return true; } /** * Get the validation rules that apply to the request. * * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string> */ public function rules(): array { return [ 'date' => ['required', 'date'], 'report_text' => ['required', 'string', 'min:200', 'max:1000'], ]; } }
プロンプト用のテンプレートをつくる
これもDailyReportController
でセットしたテンプレート(Blade
)です。
※ ちなみに、なぜプロンプトのためにBlade
を使っているかと言うと「コードに書くよりスッキリするから」です。後から見てわかりやすいコードはきっと喜ばれると思います👍
以下のコマンドを実行してください。
php artisan make:view daily_report.prompt
するとファイルが作成されるので中身を以下のようにします。
resources/views/daily_report/prompt.blade.php
あなたはとても優秀な心理カウンセラーとして回答してください。 以下の日報からスタッフのストレスレベルを判別してください。 なお、評価は0(最も良い)<->100(最も悪い)で、出力は絶対にJSONのみにしてください。余計な文章は一切不要です! また、英語で考えてください。 #日報 {{ $text }} #JSON {"result": 0 to 100}
ちなみに、なぜ「英語で考えてください」とわざわざ付け加えているかというと、その方が精度が高くなるから(個人的な意見です)です。
やはり、ChatGPT
は基本的に英語を使っているので、こう書くことで日本語を一旦英語にしてから考えてくれるんじゃないかと推測しています。
通知用の Mailable をつくる
では、メール通知用のMailable
をつくります。
以下のコマンドを実行してください。
php artisan make:mail DailyReportPosted
すると、ファイルが作成されるので中身を以下のようにします。
app/Mail/DailyReportPosted.php
<?php namespace App\Mail; use App\Models\User; use Carbon\Carbon; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; class DailyReportPosted extends Mailable { use Queueable, SerializesModels; /** * Create a new message instance. */ public function __construct(private User $user, private int $stress_level, private Carbon $dt) {} /** * Get the message envelope. */ public function envelope(): Envelope { $subject = $this->user->name .'さんが「'. $this->dt->format('Y年m月d日') .'」の日報を投稿しました。'; if($this->stress_level > 70) { // ストレスレベルが 70 以上の場合は緊急 $subject = '【緊急】'. $subject; } return new Envelope( subject: $subject, ); } /** * Get the message content definition. */ public function content(): Content { return new Content( view: 'emails.daily_report_posted', with: [ 'user' => $this->user, 'stress_level' => $this->stress_level, 'dt' => $this->dt, ], ); } /** * Get the attachments for the message. * * @return array<int, \Illuminate\Mail\Mailables\Attachment> */ public function attachments(): array { return []; } }
そして、ビューです。
php artisan make:view emails.daily_report_posted
resources/views/emails/daily_report_posted.blade.php
{{ $user->name }} さんが「<strong>{{ $dt->format('Y年m月d日') }}</strong>」の日報を投稿しました。<br><br> ストレスレベル: {{ $stress_level }} です。<br> ※ 0(良い)↔ 100(悪い)<br><br> <a href="{{ url('/') }}">日報の内容を確認する(今回は省略)</a>
ルートをつくる
では、最後にルートです。
routes/web.php
use App\Http\Controllers\DailyReportController; // 省略 Route::prefix('daily_report')->middleware('auth')->controller(DailyReportController::class)->group(function(){ Route::get('create', 'create')->name('daily_report.create'); Route::post('store', 'store')->name('daily_report.store'); });
テストしてみる
では、実際にテストしてみましょう❗
※ メールテストはmailcatcher
を使っています。
日報は以下3つ(良い、悪い、可もなく不可もなく)の内容を用意しました。
(良い内容)
今日は非常に生産的な一日でした。午前中にプロジェクトXのフェーズ1が無事に完了し、その後のレビューでも特に大きな問題点は見つかりませんでした。チームメンバー全員が高いモチベーションと集中力で作業を進め、結果として予定よりも早くフェーズ1を終えることができました。午後には早めにフェーズ2の計画を立て、明日に備える時間も確保することができました。また、今週の金曜日に控えているクライアントミーティングの資料もほぼ完成し、余裕をもって最終確認に入れそうです。
(悪い内容)
今日は非常に厳しい一日でした。プロジェクトYにおいて、多数の想定外のバグと技術的な障壁に直面しました。特に、データベースのパフォーマンスが低下し、それが他のタスクにも影響を与えています。チームメンバーはフラストレーションが溜まっており、その影響で予定が大きく遅れています。明日緊急会議を設けて対策を練る必要があります。さらに、来週のクライアントプレゼンテーションの資料作成も停滞している状態です。このままでは納期を守るのが困難になる可能性が高まっています。
(可もなく不可もなくの内容)
今日はプロジェクトZに集中しました。午前中には進捗状況をチームで確認し、各メンバーのタスクリストを更新しました。いくつかの問題はありましたが、特に深刻な障害は確認されませんでした。午後には、新しいフェーズの設計に移りました。設計の方針についてはチーム内で意見が分かれましたが、最終的には中間のアプローチで合意しました。また、今週末に予定されているクライアントミーティングに向けて、プレゼンテーションのアウトラインを作成しました。進捗状況に大きな遅れはなく、順調に進行しています。
では、ブラウザで「https://******/daily_report/create」へアクセスします。
日報のフォームが表示されました。
では、まずは「良い内容」を送信してみます。
すると・・・・・・
はい❗
メールが送信されてきて、ストレスレベルは「0」となりました。
うまく判別できているようですね。
では次に「悪い内容」です。
どうなるでしょうか・・・・・・
はい❗
ストレスレベルは「80」と判別され、さらにメール件名に「緊急」がつきました。
いいですね👍
では、最後に「可もなく不可もなくの内容」を送信してみます。
うまくいくでしょうか・・・・・・
はい❗ストレスレベルは「20」です。
こちらも妥当な感じでストレスレベルを判別できているようです。
成功です😊✨
やっぱりChatGPT
はすごいですね(しかもChatGPT 3.5
でこの精度です!)
企業様へのご提案
今回のように、ChatGPT API
を使うとそれまで専用のサービスを使わざるを得なかったものを代替できるようになります。
また、もちろん専門家と比べると正確な判別ができるわけではないかもしれませんが、依頼する費用は軽減できる可能性はあります。
※ なお、今回は「gpt-3.5-turbo」版を使っていますが、「gpt-4」も利用可能なので、より精度の高い判別ができるようになるでしょう。(API
の料金は こちら)
もしそういった機能をご希望でしたらいつでもお気軽にお問い合わせからご相談ください。
お待ちしております。😊✨
おわりに
ということで、今回はLaravel + ChatGPT API
で「ストレスレベルの判別」を行ってみました。
正直なところ、ChatGPT
といえども人の感情を読み取ることは難しいんじゃないかとも思っていたのですが、想像以上に精度が高くてビックリしました❗
しかも、GPT-5
が出てくるともウワサされているので今後はより精度が高くなるのでしょうね。
世間的には生成AIの熱みたいなものは冷めてきているようですが、使い方さえ覚えればよりよい結果につながるんだなとよく思います。
ではでは〜❗
「生姜&白だしをプラスした
レトルトカレーうどん
バリウマです😆✨」