LoginSignup
2
0

ChatGPTのAPIを使用して会話をする(React、PHP)

Last updated at Posted at 2024-05-19

はじめに

今回オリジナルプロダクトの機能の一つとして、ChatGPTから五つの質問をしてもらい、その回答をChatGPTが解析することで、現在の感情を数値化してもらう。
といった機能を作成したので、そのコードを見ながら、どのように実装したかを振り返ろうと思います。

注意
自分の復習と、これからAPIを使用したい人にわかりやすく伝えようとコードを載せましたが、このコードは実際に使用できてはいますが、まだデプロイもしていない段階なので、セキュリティの考慮などのリファクタリングがされていません。

そのことを念頭に置いていただければ幸いです。

開発環境

  • LaravelにViteを使用してフロントエンドはReact
  • バックエンドはPHP
  • Visual Studio Code使用

準備

  • 公式サイトを参考にしてアカウントとAPIキーを取得
  • Laravelを作成して、.envファイルにAPIキーを環境変数として記述

コード

まず最初はReactのフロントエンドから説明します。
実際のコードを見ていきましょう。

Chat.jsx

sendInitialMessage

Chat.jsx
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

Chat.jsx
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で作成したコントローラです。

ChatController
<?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へのリクエストの送信

ChatController
$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レスポンスの処理

ChatController
$apiResponse = $response->json();
$botMessage = ['role' => 'assistant', 'content' => $apiResponse['choices'][0]['message']['content']];
$messages[] = $botMessage;

return response()->json($apiResponse);

レスポンスボディをJSON形式で取得します。
APIからのレスポンスを取得し、ChatGPTのフォーマットに変換した後、レスポンスをメッセージ配列に追加します。
その後、先ほどのフロントエンドへレスポンスをJSON形式で返します。

表示の内容を省略した説明ですが、以上がChatGPTとの会話をするために使用したコードです。

では実際に会話をしてみましょう

会話の実践

conversation History
 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で書いた記事を探しても見つからず苦労しました。
同じ思いをしている人が、この記事を見て手助けになれたら嬉しいです。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0