チャットボットやバーチャルアシスタントの利用が拡大する中、多くの企業や開発者がAIを搭載した独自のチャットボット作成の方法を模索しています。ChatGPTはそのようなチャットボットの一つで、OpenAIによって作成され、人間のように会話をし、様々な質問に答えることができます。
構築するもの
今回の記事では、ReactとOpenAI APIを使用してChatGPTクローンアプリケーションを構築する方法をご紹介します。知的好奇心を満たすプロジェクトに挑戦したい方は、ReactとOpenAIの世界に飛び込んでみてはいかがでしょうか。
また、GitHubのリポジトリからKinstaのアプリケーションホスティングプラットフォームに直接デプロイする方法も扱います。Kinstaでは、プロジェクトを素早く本番環境に移行できるように、無料の.kinsta.appドメインをご用意しています。また、Kinstaの無料利用枠やホビープランを利用すれば、お気軽にお試し可能です。
ChatGPTクローンアプリのライブデモはこちらでご確認ください。
このプロジェクトについての詳細は、GitHubのリポジトリをご覧ください。
また、スタイル、Font Awesome CDNリンク、OpenAIパッケージ、基本構造などの要素を含むReactアプリケーションスタータープロジェクトを複製して、使い始めることも可能です。
前提条件
この記事は、真似しながら読み進められるように設計されています。そのため、並行してコーディングできるように、以下のものを用意・理解しておくことをおすすめします。
- HTML、CSS、JavaScriptの基礎的な知識
- Reactにある程度慣れていること
- Node.jsとnpm(Node Package Manager)またはyarnがPCにインストールされていること
OpenAI APIとは
OpenAI APIは、GPT-3などのOpenAIの言語モデルにAPIでアクセスすることを可能にしたクラウド型プラットフォームです。これにより、開発者はモデルの開発やゼロからの学習を行うことなく、テキスト補完、感情(意図)分析、要約、翻訳などの自然言語処理機能をアプリケーションに追加することができます。
OpenAI APIを利用するには、OpenAIのウェブサイトでアカウントを作成し、APIキーを取得する必要があります。APIキーは、APIリクエストの認証や利用状況の把握に使用されます。
APIキーを取得すると、これを利用して言語モデルにテキストを送信し、レスポンスを受け取ることができます。
Reactの特徴
Reactは、ユーザーインターフェース構築を支える人気のJavaScriptライブラリです。2022年のStack Overflowの開発者調査によると、2番目によく使われているウェブ技術で、市場シェアの42.62%を占めています。
Reactを使って、ユーザーインターフェースのさまざまな部分を表す宣言型コンポーネントを作成できます。そのコンポーネントは、JavaScriptとHTMLを組み合わせたJSXと呼ばれる構文を用いて定義します。
コンポーネントライブラリやキットが豊富に存在し、OpenAI APIなどのAPIと簡単に連携・統合して、複雑なチャットインターフェースを構築可能です。このような理由で、ChatGPTクローンアプリの構築に最適な選択肢となっています。
React開発環境の構築
構築を始めるにあたり、create-react-appが利用できます。前提条件として、PCにNode.jsがインストールされている必要があります。Nodeがインストールされていることを確認するには、ターミナルで以下のコマンドを実行します。
node -v
これでバージョン情報が表示されれば、存在することになります。npxを使うには、Nodeのバージョンがv14.0.0以上、NPMのバージョンがv5.6以上である必要があります。そうでなければ、npm update -g
を実行してアップデートしてください。その後、以下のコマンドを実行して、Reactプロジェクトをセットアップします。
npx create-react-app chatgpt-clone
補足)今回作成するアプリケーション名として「chatgpt-clone」を採用していますが、お好きな名前に変更可能です。
インストール作業には数分かかる場合があります。完了したら、ディレクトリに移動して、以下のコマンドを使用し、(Node.jsからOpenAI APIへのアクセスを助ける)Node.js OpenAIパッケージをインストールします。
npm install openai
これで、npm start
を実行すると、localhost:3000でアプリケーションの動作を確認することができます。
create-react-app
コマンドを使用してReactプロジェクトを作成すると、自動的にフォルダ構造が設定されます。主要なフォルダは、src
です─ここで開発を行います。このフォルダには、デフォルトで多くのファイルが含まれていますが、App.js、index.js、index.cssファイルだけ特に役割を理解しておくことをおすすめします。
- App.js:Reactアプリケーションのメインコンポーネントです。通常、アプリケーション内の他のすべてのコンポーネントをレンダリングするトップレベルのコンポーネントを扱うことになります。
- index.js:Reactアプリケーションのエントリポイントです。アプリを開いたときに最初に読み込まれるファイルであり、App.jsコンポーネントをブラウザにレンダリングする役割を担います。
- index.css:このファイルは、Reactアプリケーションの全体的なスタイルとレイアウトを定義するものです。
ReactとOpenAI APIでChatGPTクローンを構築する
ChatGPTクローンアプリケーションは、アプリケーションの理解や保守を容易にするために、2つのコンポーネントで構成することにします。その2つのコンポーネントとは、以下の通りです。
- フォームセクション:利用者がチャットボットと対話するためのテキストエリアとボタンで構成されます。
- 回答セクション:質問とそれに対応する回答は配列に格納され、このセクションに表示されます。配列の中を時系列に従いループすることで、最新のものを最初に表示します。
ChatGPT Cloneアプリケーションのセットアップ
まずアプリケーションのインターフェースを構築することから始め、その後、アプリケーションとOpenAI APIのやり取りに関する機能を実装します。まずは2つのコンポーネントの作成から始めましょう。整理のために、srcフォルダ内にcomponentsフォルダを作成し、すべてのコンポーネントをここに格納します。
フォームセクションのコンポーネント
シンプルなフォームです。textarea
と送信button
で構成されます。
// components/FormSection.jsx
const FormSection = () => {
return (
<div className="form-section">
<textarea
rows="5"
className="form-control"
placeholder="Ask me anything..."
></textarea>
<button className="btn">
Generate Response 🤖
</button>
</div>
)
}
export default FormSection;
App.jsファイルにインポートすることで、以下のように表示されます。
回答セクションのコンポーネント
このセクションに、すべての質問と回答が表示されます。これをApp.jsファイルにインポートすると、以下のような見た目になります。
質問と回答を配列から取得し、ループすることで、コードの可読性と保守性を高めます。
// components/AnswerSection.jsx
const AnswerSection = () => {
return (
<>
<hr className="hr-line" />
<div className="answer-container">
<div className="answer-section">
<p className="question">Who is the founder of OpenAi?</p>
<p className="answer">OpenAI was founded in December 2015 by Elon Musk, Sam Altman, Greg Brockman, Ilya Sutskever, Wojciech Zaremba, and John Schulman.</p>
<div className="copy-icon">
<i className="fa-solid fa-copy"></i>
</div>
</div>
</div>
</>
)
}
export default AnswerSection;
トップページ
これで両方のコンポーネントが作成できましたが、アプリケーションを実行しても何も表示されません。App.jsファイルにコンポーネントをインポートする必要があります。このアプリケーションでは、ルーティングを実装しません。つまり、App.jsファイルがアプリケーションのホームコンポーネント/ページとして機能することになります。
コンポーネントをインポートする前に、アプリケーションのタイトルや説明など、コンテンツを追加しておきましょう。
// App.js
import FormSection from './components/FormSection';
import AnswerSection from './components/AnswerSection';
const App = () => {
return (
<div>
<div className="header-section">
<h1>ChatGPT CLONE 🤖</h1>
<p>
I am an automated question and answer system, designed to assist you
in finding relevant information. You are welcome to ask me any queries
you may have, and I will do my utmost to offer you a reliable
response. Kindly keep in mind that I am a machine and operate solely
based on programmed algorithms.
</p>
</div>
<FormSection />
<AnswerSection />
</div>
);
};
export default App;
上記のコードで、2つのコンポーネントをインポートし、アプリケーションに追加しています。アプリケーションを実行すると、以下のような表示になります。
機能追加とOpenAI APIの連携
これで、アプリケーションのユーザーインターフェースが完成しました。次のステップは、アプリケーションの機能面の実装です。OpenAI APIとやりとりしてレスポンスを取得できるようにします。まず、送信されたフォームの値を取得する必要があります。これを、OpenAI APIへのクエリに使用します。
フォームからデータを取得する
Reactでは、データの保存・更新にstate(状態)を使用します。機能コンポーネントでは、useState()
フックを使って状態を扱います。状態を作成し、フォームからの値をそれに割り当て、値が変化するたびに状態を更新します。まず、useState()
フックをFormSection.jsxコンポーネントにインポートし、newQuestions
を保存・更新するstateを作成しましょう。
// components/FormSection.jsx
import { useState } from 'react';
const FormSection = ({ generateResponse }) => {
const [newQuestion, setNewQuestion] = useState('');
return (
// Form to submit a new question
)
}
export default FormSection;
次に、textarea
フィールドの値をstateに割り当て、入力値が変化するたびに状態を更新するonChange()
イベントを作成します。
<textarea
rows="5"
className="form-control"
placeholder="Ask me anything..."
value={newQuestion}
onChange={(e) => setNewQuestion(e.target.value)}
></textarea>
最後に、onClick()
イベントを作成し、送信ボタンがクリックされるたびに関数を読み込みます。このメソッドはApp.jsファイルで作成され、propsとしてFormSection.jsxコンポーネントに渡されます。尚、newQuestion
とsetNewQuestion
の値が引数になります。
<button className="btn" onClick={() => generateResponse(newQuestion, setNewQuestion)}>
Generate Response 🤖
</button>
これで、フォームの値を保存・更新するstateができ、さらに、App.jsファイルからpropsとして渡されるメソッドも実装できました。クリックイベントを問題なく処理することができます。最終的に、コードは以下のようになります。
// components/FormSection.jsx
import { useState } from 'react';
const FormSection = ({ generateResponse }) => {
const [newQuestion, setNewQuestion] = useState('');
return (
<div className="form-section">
<textarea
rows="5"
className="form-control"
placeholder="Ask me anything..."
value={newQuestion}
onChange={(e) => setNewQuestion(e.target.value)}
></textarea>
<button className="btn" onClick={() => generateResponse(newQuestion, setNewQuestion)}>
Generate Response 🤖
</button>
</div>
)
}
export default FormSection;
次のステップでは、OpenAI APIとの対話の全プロセスを処理するメソッドをApp.jsファイル内に作成していきます。
OpenAI APIとの連動
ReactアプリケーションでOpenAI APIと対話し、APIキーを取得するには、OpenAI APIアカウントを作成する必要があります。OpenAIのウェブサイトから、Googleアカウントまたはメールを使用してアカウントを登録します。APIキーを生成するには、ウェブサイトの右上にある「Personal」をクリックします。いくつかのオプションが表示されるので、その中から「View API keys」をクリックします。
「Create new secret key」ボタンをクリックし、OpenAIとのやり取りに使用する鍵(キー)を安全な場所に保存します。これで、OpenAIパッケージ(すでにインストール済みです)を設定方法と一緒にインポートして、OpenAIの初期化を進めることができます。生成されたキーでコンフィギュレーションを作成し、それを使ってOpenAIを初期化します。
// src/App.js
import { Configuration, OpenAIApi } from 'openai';
import FormSection from './components/FormSection';
import AnswerSection from './components/AnswerSection';
const App = () => {
const configuration = new Configuration({
apiKey: process.env.REACT_APP_OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
return (
// Render FormSection and AnswerSection
);
};
export default App;
上記のコードでは、OpenAI APIキーは環境変数として.envファイルに格納されます。アプリケーションのルートフォルダに.envファイルを作成し、変数REACT_APP_OPENAI_API_KEY
にキーを格納することができます。
// .env
REACT_APP_OPENAI_API_KEY = sk-xxxxxxxxxx…
App.jsファイル内にgenerateResponse
メソッドを作成し、すでに作成したフォームから期待される2つのパラメータを渡し、リクエストの処理とAPIからのレスポンスの取得を行います。
// src/App.js
import FormSection from './components/FormSection';
import AnswerSection from './components/AnswerSection';
const App = () => {
const generateResponse = (newQuestion, setNewQuestion) => {
// Set up OpenAI API and handle response
};
return (
// Render FormSection and AnswerSection
);
};
export default App;
そして、OpenAI APIにリクエストを送る処理へと進みます。OpenAI APIでは、質問と回答(Q&A)、文法修正、翻訳など、多くの操作を行うことができます。これらの操作のそれぞれに対応する記法が用意されています。例えば、Q&Aのエンジン値はtext-davinci-00
で、SQL翻訳のエンジン値はcode-davinci-002
です。様々な例については、OpenAIのサンプルドキュメントをご覧ください。
今回は、Q&Aのみを扱うので次のようになります。
{
model: "text-davinci-003",
prompt: "Who is Obama?",
temperature: 0,
max_tokens: 100,
top_p: 1,
frequency_penalty: 0.0,
presence_penalty: 0.0,
stop: ["\"],
}
注)プロンプトの値を変更しています。
プロンプトは、フォームから送信される質問です。つまり、generateResponse
メソッドにパラメータとして渡すフォームの入力内容から受け取る必要があります。これを行うには、オプションを定義してから、スプレッド演算子を使用して、プロンプトを含むかたちで変数completeOptionsを作成します。
// src/App.js
import { Configuration, OpenAIApi } from 'openai';
import FormSection from './components/FormSection';
import AnswerSection from './components/AnswerSection';
const App = () => {
const configuration = new Configuration({
apiKey: process.env.REACT_APP_OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
const generateResponse = async (newQuestion, setNewQuestion) => {
let options = {
model: 'text-davinci-003',
temperature: 0,
max_tokens: 100,
top_p: 1,
frequency_penalty: 0.0,
presence_penalty: 0.0,
stop: ['/'],
};
let completeOptions = {
...options,
prompt: newQuestion,
};
};
return (
// Render FormSection and AnswerSection
);
};
export default App;
あとはOpenAIにcreateCompletion
メソッドでリクエストを送り、レスポンスを受け取るだけです。
// src/App.js
import { Configuration, OpenAIApi } from 'openai';
import FormSection from './components/FormSection';
import AnswerSection from './components/AnswerSection';
import { useState } from 'react';
const App = () => {
const configuration = new Configuration({
apiKey: process.env.REACT_APP_OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
const [storedValues, setStoredValues] = useState([]);
const generateResponse = async (newQuestion, setNewQuestion) => {
let options = {
model: 'text-davinci-003',
temperature: 0,
max_tokens: 100,
top_p: 1,
frequency_penalty: 0.0,
presence_penalty: 0.0,
stop: ['/'],
};
let completeOptions = {
...options,
prompt: newQuestion,
};
const response = await openai.createCompletion(completeOptions);
console.log(response.data.choices[0].text);
};
return (
// Render FormSection and AnswerSection
);
};
export default App;
上のコードでは、答えのテキストがコンソールに表示されます。何か質問をして、アプリケーションをテストしてみてください。最後のステップでは、質問と回答の配列を保持するstateを作成し、その配列をAnswerSectionコンポーネントにpropsとして送信します。以下がApp.jsの最終的なコードになります。
// src/App.js
import { Configuration, OpenAIApi } from 'openai';
import FormSection from './components/FormSection';
import AnswerSection from './components/AnswerSection';
import { useState } from 'react';
const App = () => {
const configuration = new Configuration({
apiKey: process.env.REACT_APP_OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
const [storedValues, setStoredValues] = useState([]);
const generateResponse = async (newQuestion, setNewQuestion) => {
let options = {
model: 'text-davinci-003',
temperature: 0,
max_tokens: 100,
top_p: 1,
frequency_penalty: 0.0,
presence_penalty: 0.0,
stop: ['/'],
};
let completeOptions = {
...options,
prompt: newQuestion,
};
const response = await openai.createCompletion(completeOptions);
if (response.data.choices) {
setStoredValues([
{
question: newQuestion,
answer: response.data.choices[0].text,
},
...storedValues,
]);
setNewQuestion('');
}
};
return (
<div>
<div className="header-section">
<h1>ChatGPT CLONE 🤖</h1>
<p>
I am an automated question and answer system, designed to assist you
in finding relevant information. You are welcome to ask me any
queries you may have, and I will do my utmost to offer you a
reliable response. Kindly keep in mind that I am a machine and
operate solely based on programmed algorithms.
</p>
</div>
<FormSection generateResponse={generateResponse} />
<AnswerSection storedValues={storedValues} />
</div>
);
};
export default App;
AnswerSectionコンポーネントの編集に進みましょう。App.jsからpropsの値を受け取り、JavaScriptのMap()
メソッドを使って配列storedValues
を確認できるようにします。
// components/AnswerSection.jsx
const AnswerSection = ({ storedValues }) => {
return (
<>
<hr className="hr-line" />
<div className="answer-container">
{storedValues.map((value, index) => {
return (
<div className="answer-section" key={index}>
<p className="question">{value.question}</p>
<p className="answer">{value.answer}</p>
<div className="copy-icon">
<i className="fa-solid fa-copy"></i>
</div>
</div>
);
})}
</div>
</>
)
}
export default AnswerSection;
アプリケーションを実行してみましょう。質問をすると、回答が下に表示されます。しかし、コピーボタンはこの時点では使えません。ボタンにonClick()
イベントを追加し、機能を処理するメソッドが動作するにようにする必要があります。これには、navigator.clipboard.writeText()
メソッドを使用します。実装すると、AnswerSectionコンポーネントは次のようになります。
// components/AnswerSection.jsx
const AnswerSection = ({ storedValues }) => {
const copyText = (text) => {
navigator.clipboard.writeText(text);
};
return (
<>
<hr className="hr-line" />
<div className="answer-container">
{storedValues.map((value, index) => {
return (
<div className="answer-section" key={index}>
<p className="question">{value.question}</p>
<p className="answer">{value.answer}</p>
<div
className="copy-icon"
onClick={() => copyText(value.answer)}
>
<i className="fa-solid fa-copy"></i>
</div>
</div>
);
})}
</div>
</>
)
}
export default AnswerSection;
アプリケーションを実行すると、ChatGPTクローンアプリケーションが動作するはずです。これで、アプリケーションをデプロイして、オンラインでアクセスしたり、他の人に共有したりできます。
ReactアプリケーションをKinstaにデプロイする方法
せっかくアプリケーションを構築したので、ローカルに置いておくだけでなく、ネット上でアクセスできるようにしてみましょう。GitHubとKinstaを使って、その方法をご紹介します。
コードをGitHubにプッシュする
GitHubにコードをプッシュするには、Gitコマンドを使用します。Gitコマンドは、コードの変更を管理し、他の開発者と作業を進め、バージョン履歴を保持するのに便利です。
まずGitHubアカウントにログインして、画面右上の「+」ボタンをクリックし、ドロップダウンメニューから「New repository」を選択して、新しいリポジトリを作成します。
リポジトリに名前を付け、説明を追加(任意)し、公開か非公開かを選択します。完了したら「Create repository」をクリックして、リポジトリを作成します。
リポジトリが作成されたら、リポジトリのメインページから、コードをGitHubにプッシュするために必要なリポジトリURLを確認します。
ターミナルまたはコマンドプロンプトを開き、プロジェクトのディレクトリに移動します。以下のコマンドを1つずつ実行し、コードをGitHubリポジトリにプッシュします。
git init
git add .
git commit -m "my first commit"
git remote add origin [repository URL]
git push -u origin master
git init
はローカルの Git リポジトリを初期化するもので、git add .
は既存のディレクトリとそのサブディレクトリにあるすべてのファイルを新しいGitリポジトリに追加します。git commit -m "my first commit"
は簡単なメッセージとともに変更をリポジトリにコミットします。git remote add origin [repository URL]
はリポジトリのURLをリモートリポジトリとして設定し、git push -u origin master
はリモートリポジトリ(origin)にmasterブランチでコードをプッシュしています。
ChatGPTクローンアプリをKinstaにデプロイする
リポジトリをKinstaにデプロイする手順は以下の通りです。
-
- MyKinstaにログインします。
- 左サイドバーの「アプリケーション」をクリックし「サービスを追加」をクリックします。
- ReactアプリケーションをKinstaにデプロイするので、ドロップダウンメニューから「アプリケーション」を選択します。
- 表示されるポップアップから、デプロイするリポジトリを選択します。複数のブランチがある場合は、デプロイするブランチを選択し、アプリケーションに名前をつけることができます。利用可能な25の中からデータセンターの場所を選択します。Kinstaのシステムにより自動でstartコマンドが検出されます。
- GitHubのようなパブリックホストにAPIキーをプッシュするのは安全ではないので、ローカルで環境変数として追加しています。ホスティングの際は、同じ変数名でキーを値とし、環境変数として追加することができます。
アプリケーションのデプロイが開始され、数分後には、アプリケーションのリンクが表示されます(この例では「https://chatgpt-clone-g9q10.kinsta.app/Note」)自動デプロイメントを有効にすると、コードベースを変更してGitHubにプッシュするたびに、アプリケーションの再デプロイが行われます。
まとめ
OpenAI APIは、カスタマーサポートやパーソナルアシスタント、言語翻訳やコンテンツ作成など、幅広いアプリケーションの構築に応用できます。
今回は、ReactとOpenAIを使ってChatGPTクローンアプリケーションを構築する方法をご紹介しました。このアプリケーション/機能を他のアプリケーションに統合して、人間と会話しているかのようなサービスを展開することも可能です。
OpenAI APIでできること、このクローンアプリを改善する方法はまだまだあります。例えば、ブラウザを更新しても以前の質問と回答が消えないように、ローカルストレージを実装するといった調整も考えられます。
Kinstaの無料利用枠やホビープランでは、アプリケーションホスティングを気軽に試してみることができます。是非ご活用ください。
今回の記事に関連するプロジェクトやご経験をお持ちでしたら、以下のコメント欄からお聞かせください。
コメントを残す