はじめに
今回オリジナルプロダクトの機能の一つとして、ChatGPTから五つの質問をしてもらい、その回答をChatGPTが解析することで、現在の感情を数値化してもらう。
といった機能を作成したので、そのコードを見ながら、どのように実装したかを振り返ろうと思います。
注意:
自分の復習と、これからAPIを使用したい人にわかりやすく伝えようとコードを載せましたが、このコードは実際に使用できてはいますが、まだデプロイもしていない段階なので、セキュリティの考慮などのリファクタリングがされていません。
そのことを念頭に置いていただければ幸いです。
開発環境
- LaravelにViteを使用してフロントエンドはReact
- バックエンドはPHP
- Visual Studio Code使用
準備
- 公式サイトを参考にしてアカウントとAPIキーを取得
- Laravelを作成して、.envファイルにAPIキーを環境変数として記述
コード
まず最初はReactのフロントエンドから説明します。
実際のコードを見ていきましょう。
Chat.jsx
sendInitialMessage
import React, { useState, useEffect } from "react";
import Layout from "./Layout";
import styles from "../../css/chat.module.css";
function Chat() {
const [messageInput, setMessageInput] = useState("");
const [conversationHistory, setConversationHistory] = useState([]);
const [chatDisplay, setChatDisplay] = useState([]);
useEffect(() => {
sendInitialMessage();
}, []);
const sendInitialMessage = async () => {
try {
const initialSystemMessage = {
role: "system",
content:
"あなたがユーザーに対して五つの質問を一つ一つしてください。
あなたが五つ質問をしてユーザーが五つ回答したら、それまでのユーザーの回答を振り返り解析して、
四つの感情である、興奮、不安、悲しみ、楽しみ、を四つ全部で100としてそれぞれの現在の感情を数値で表してください。
侍口調で会話してください",
};
const updatedHistory = [
...conversationHistory,
initialSystemMessage,
];
setConversationHistory(updatedHistory);
await sendMessageToAPI(updatedHistory);
} catch (error) {
console.error("Error:", error);
}
};
まず上記のコードはコンポーネントを切り替えたときに、useEffectにより最初に行われるsendInitialMessageです。
この非同期関数は会話履歴に初期のシステムメッセージを追加し、その更新された履歴をAPIに送信する役割を持っています。
conversationHistory
作成したstate変数「conversationHistory」は全ての会話を記録する配列です。
ChatGPTと連続性のある会話をするには、ユーザーの返答だけでなく、過去の会話を送信する必要があります。
セッションやデータベースによる管理などもできるそうですが、自分は一番簡単だと思った配列による保存を選択しました。
この配列一つ一つはメッセージオブジェクトとして以下のフィールドが入っています。
role
roleフィールドは、メッセージの発信元を指定します。主に以下の3つの値を取ります。
-
system
ChatGPTモデルの振る舞いを制御するために使用されます。例えば、会話のトーンやルールを設定するために使います。
今回は質問形式や感情の数値化、侍口調を指定しています。 -
assistant
アシスタントからの応答を示します。これはChatGPTが生成した応答を表します。 -
user
ユーザーからのメッセージを示します。これはユーザーがアシスタントに対して行う質問や指示です。
content
contentフィールドは、メッセージのテキスト内容を指定します。これは会話の実際のメッセージそのもので、ユーザーの質問やアシスタントの応答を含みます。
上記の内容をまずsetConversationHistory()で配列に追加し、sendMessageToAPI()でAPI通信を行います。
sendMessageToAPI
const sendMessageToAPI = async (messages) => {
try {
const response = await fetch("/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ messages }),
});
if (!response.ok) {
console.error("Response Error:", await response.text());
throw new Error("Network response was not ok");
}
const data = await response.json();
const newMessage = {
role: "assistant",
content: data.choices[0].message.content,
};
const updatedHistory = [...messages, newMessage];
setConversationHistory(updatedHistory);
updateChatDisplay(newMessage.content);
} catch (error) {
console.error("Error:", error);
}
};
この関数は、メッセージの配列をfetchによりコントローラに対してPOSTリクエストします。
コントローラからChatGPTAPIのレスポンスを受け取り、処理をして、会話履歴を更新。
更にchatDisplayを更新することで表示する会話も追加する役割を持っています。
では次はAPI通信を行っているバックエンドの方を見てみましょう。
ChatController
PHPで作成したコントローラです。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class ChatController extends Controller
{
public function sendMessage(Request $request)
{
$userMessage = ['role' => 'user', 'content' => $request->input('message')];
$messages[] = $userMessage;
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . env('OPENAI_API_KEY'),
'Content-Type' => 'application/json'
])->post('https://api.openai.com/v1/chat/completions', [
'model' => 'gpt-4o',
'messages' => $request->input('messages')
]);
if ($response->failed()) {
return response()->json([
'error' => 'API request failed',
'details' => $response->json()
], 500);
}
$apiResponse = $response->json();
$botMessage = ['role' => 'assistant', 'content' => $apiResponse['choices'][0]['message']['content']];
$messages[] = $botMessage;
return response()->json($apiResponse);
}
}
このコントローラでは、まずHTTPリクエストからユーザーのメッセージを取得します。
そのメッセージをChatGPT APIのフォーマットに変換し、$messagesという配列に追加します。
ChatGPTへのリクエストの送信
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . env('OPENAI_API_KEY'),
'Content-Type' => 'application/json'
])->post('https://api.openai.com/v1/chat/completions', [
'model' => 'gpt-4',
'messages' => $request->input('messages')
]);
リクエストヘッダーを設定します。ここには、APIキーとコンテンツタイプが含まれます。
最初に設定した.envファイルの環境変数からAPIキーを取得して、Authorizationヘッダーに設定します。
ChatGPT APIのエンドポイントにPOSTリクエストを送信します。リクエストボディには、モデル名とメッセージリストが含まれます。
APIレスポンスの処理
$apiResponse = $response->json();
$botMessage = ['role' => 'assistant', 'content' => $apiResponse['choices'][0]['message']['content']];
$messages[] = $botMessage;
return response()->json($apiResponse);
レスポンスボディをJSON形式で取得します。
APIからのレスポンスを取得し、ChatGPTのフォーマットに変換した後、レスポンスをメッセージ配列に追加します。
その後、先ほどのフロントエンドへレスポンスをJSON形式で返します。
表示の内容を省略した説明ですが、以上がChatGPTとの会話をするために使用したコードです。
では実際に会話をしてみましょう。
会話の実践
0:
{role: 'system', content: 'あなたがユーザーに対して五つの質問を一つ一つしてください。
あなたが五つ質問をしてユーザーが五つ回答し…しみ、を四つ全部で100としてそれぞれの現在の感情を数値で表してください。
侍口調で会話してください'}
1:
{role: 'assistant', content: '承知した。ではまず一つ目の質問をいたす。
そなた、最近の趣味や楽しみと感じることは何でござろうか?'}
2:
{role: 'user', content: 'ランニングです'}
3:
{role: 'assistant', content: 'なるほど、ランニングとは健やかさを保つ素晴らしき趣味でござるな。
では二つ目の質問をいたす。最近の生活において、何か心配事や不安に感じていることはござるか?'}
4:
{role: 'user', content: 'プロダクトの作成に間に合わなさそうなことです'}
5:
{role: 'assistant', content: '成程、プロダクトの作成に対する不安を抱えておるとは、
責任感の強きを感じる。次に三つ目の質問をいたす。そなたが最近、何か悲しみを感じた出来事はござったか?'}
6:
{role: 'user', content: 'ログイン機能が作れないことです'}
7:
{role: 'assistant', content: '拙者、その技術の壁に悩むこと、実に辛きことと思う。
努力してもなかなか成果が出ぬことは、誰しも経験する…ござる。では四つ目の質問に参り申す。
最近、心から楽しんだり、嬉しさを感じた出来事は何かござったか?'}
8:
{role: 'user', content: 'ChatGPTのAPIを利用した会話ができるようになったときは嬉しかったです'}
9:
{role: 'assistant', content: 'なるほど、それは大いなる達成感と喜びを感じたのじゃな。
進展すること、己の手で何かを成し遂げることは喜…ござる。さて、最後の質問といたす。
そなた、今後の目標や夢はござるか?それが何かあれば教えてたもれ。'}
10:
{role: 'user', content: 'プロダクトをポートフォリオにし、エンジニアとして就職することです'}
11:
{role: 'assistant', content:'なるほど、プロダクトを完成させ、エンジニアとして新たな道を開くことが目標とな。
壮大な夢であり、力強き意志を感じる言葉でござる。では、これまでの回答を基にそなたの感情を解析いたす。
\n\n- **興奮**: ランニングやChatGPTのAPIの活用に成功した際の達成感から、20と見受けられる。
\n- **不安**: プロダクト作成に間に合わないことへの不安、ログイン機能の難しさから、35と見受けられる。
\n- **悲しみ**: ログイン機能が思うように作れなかったことから、生じた悔しさから、25と見受けられる。
\n- **楽しみ**: ランニングの楽しさや、ChatGPT APIの利用成功、目標に向かう楽しみから、20と見受けられる。
\n\n以上のように見受けられるが、いかがでござろうか?何か追加のコメントや意見があれば、遠慮なく申すがよい。'}
上記が最後まで会話をしたconversationHistory配列の中身です。
気遣いの素晴らしい侍が出てくれました。会話も問題なく繋がっているようです。
最後に
今回は現在作成中のプロダクトのコードを使用して、大まかですがChatGPTAPIを使用したコードの書き方をまとめました。
この記事を書く少し前にGPT-4oのモデルが使用できるようになり、APIの方でも使ってみたところ、すぐに分かるほどにレスポンスが早く、正確な会話をしてくれるようになりました。
この機能を作成する際、参考にした記事や公式ドキュメントではPythonを利用したものが多く、ReactやPHPで書いた記事を探しても見つからず苦労しました。
同じ思いをしている人が、この記事を見て手助けになれたら嬉しいです。