Vue.jsでOpenAI APIを使って話し相手をつくる
はじめに
2024年1月からエンジニアとして勤務開始予定です。
2023年12月からプログラミング学習を開始して、10月から転職活動を始めて、11月に転職先が決まりました。
今は入社に向けて学習を進めているところです。
転職先の会社でVue.jsを使っているとわかり、最近学習を始めました。
動画教材を見てインプットを進めていましたが、覚えることが多くダレてきました。
いったん今わかっていることで、何か作ってみて、足りないところを後から足していこうと考えました。
作ったもの
普段話し相手がいないので、ChatGPTのAPIを使って話し相手になってもらおうと考えました。
もちろん普通にChatGPTを使えばいいのですが、今回は学習用ということで自分で作ってみることにしました。(世の中にまだないものを思いつくのは難しいですね...)
最初に完成したVue.jsのコードを見せます。
<script setup>
import { ref } from 'vue'
import OpenAI from 'openai'
const client = new OpenAI({
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
dangerouslyAllowBrowser: true,
})
const message = ref('')
const chat_response = ref('')
// ChatGPT APIにメッセージを送る
const send = async () => {
const response = await client.chat.completions.create({
messages: [{ role: 'user', content: message.value }],
model: 'gpt-4o',
})
chat_response.value = response.choices[0].message.content
}
</script>
<template>
<div class="container">
<div>
<textarea
v-model="message"
type="text"
placeholder="メッセージを入力してください。"
></textarea>
<button @click="send">送信</button>
</div>
<div v-if="chat_response">
<div class="response">{{ chat_response }}</div>
</div>
</div>
</template>
手順
- Vue.jsアプリを立ち上げる
- openaiライブラリをインストール
- APIキーの設定
- Vue.jsにOpenAI APIにアクセスするためのインスタンス生成する処理を書く
- 4で作ったインスタンスを使ってOpenAI APIにアクセスする非同期処理を書く
-
npm run dev
でアプリケーションを起動してブラウザで確認
1. Vue.jsアプリケーションを作成
$ npm create vue@latest
2. Node.jsでopenaiライブラリをインストール
$ npm install openai
3. APIキーを設定
右上のボタンをクリックします。
Nameは空欄のままでボタンを押せばAPIキーが作られます。
コピーして.envで環境変数に渡して使います。
4. OpenAI APIにアクセスするためのインスタンス生成
<script setup>
import OpenAI from 'openai'
const client = new OpenAI({
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
dangerouslyAllowBrowser: true,
})
</script>
apiKey属性の値としてAPIキーを渡して、APIのインスタンスを作ります。
公式ドキュメントには、apiKey属性の説明は見つけられませんでしたが、ブラウザのエラーが教えてくれました。
import.meta.env.VITE_OPENAI_API_KEY
では、事前にAPIキーを格納してある環境変数VITE_OPENAI_API_KEY
を呼び出しています。
Viteで環境変数を設定するやり方は後述します。
5. OpenAI APIにアクセスする非同期処理
<script setup>
// ChatGPTの返答の初期化
const chat_response = ref('')
// ChatGPT APIにメッセージを送る
const send = async () => {
const response = await client.chat.completions.create({
messages: [{ role: 'user', content: message.value }],
model: 'gpt-4o',
})
chat_response.value = response.choices[0].message.content
}
</script>
client.chat.completions.create
-
client
:OpenAIにアクセスするためのオブジェクト。ライブラリからインポートしてインスタンスを生成したのがこのclientに代入されている。 -
chat.completions.create
:OpenAI APIで指定されているメソッドチェーン。今回はテキスト生成用のメソッドを使っているが、画像生成や音声生成はまた別のメソッドが用意されている。
model: 'gpt-4o'
注意点として、モデルの設定は必須です。
モデルが自分が契約しているモデルではないとエラーになります。
自分の場合、gpt-4oを契約しているのにサンプルコードをコピペしてgpt-4o-miniを指定していてエラーになりました。
処理の流れ
const chat_response = ref('')
Vue.jsではref関数を使うと、ブラウザ上の変更を追えるようになります。
あらかじめ変更が終えるように準備しておきます。
chat_response.value = response.choices[0].message.content
ref関数はrefオブジェクトを返します。
refオブジェクトは多くのデータが入っています。
その中で値にアクセスするにはvalue属性を使います。
response.choices[0].message.content
にはOpenAI APIからのレスポンス内容が入っています。
それがrefオブジェクトのvalue属性の値に渡されるとrefオブジェクトが更新されます。
<templete>タグ内に@click="send"
という箇所があります。
これはクリックイベントが発火した時にsend関数が呼び出されるようにしています。
send関数が呼び出されると、openAI APIにアクセスしてテキスト生成する関数が実行されます。
そしてref関数が実行されて、refオブジェクトが更新されます。
レスポンスの表示
<div v-if="chat_response">
<div class="response">{{ chat_response }}</div>
</div>
ChatGPTからの返答を表示する箇所は、v-ifディレクティブを使っています。
v-ifディレクティブはtruthyな値が代入されたときだけその要素が表示されます。
変数chat_responseの初期値は空の文字列を指定しているので、ページ表示時はこの要素は表示されません。
ChatGPTからレスポンスが返ってきたらchat_responseがtrueになるので表示されます。
templete内では暗黙的に.valueがつけられるので.valueをつけずに値が表示されます。
同期関数とするとundefindになる
OpenAI APIにアクセスする処理は、APIの標準の設定で非同期関数が使われています。
const send = async () => { const response = await client.chat.completions.create
asyncとawaitを削除して同期関数とすると、以下のエラーが出ます。
Uncaught TypeError: Cannot read properties of undefined (reading '0')
responseがundefinedになってしまいます。ここがなぜ同期関数だとundefinedになるのかがまだ理解できていません。
Viteで環境変数を設定する
Vue.jsでAPIキーを環境変数に渡せていないと、ブラウザでこのようなエラーが出ます。
Uncaught OpenAIError: The OPENAI_API_KEY environment variable is missing or empty; either provide it, or instantiate the OpenAI client with an apiKey option, like new OpenAI({ apiKey: 'My API Key' }).
Vue.jsの公式ドキュメントには、最初のnpm create vue@latest
を実行した時にビルドツールViteでビルドが行われると書いてあります。
作成されたプロジェクトは、Vite に基づいたビルド設定を使用し、Vue の単一ファイルコンポーネント(SFC)を使用できるようにします。
https://ja.vuejs.org/guide/quick-start
Viteの場合、環境変数に設定して呼び出す手順は以下のとおりです。
- ルートディレクトリ下に.envファイルをつくる
- .envファイルに環境変数を定義する
- .vueの中で
import.meta.env.環境変数名
と書いて呼び出す
.envファイルを作ったら、このようにAPIキーをコピペして代入します。
VITE_OPENAI_API_KEY=abcde...
ここで注意しておきたいのが、VITE_
で始めるというルールがあります。
公式ドキュメントの説明がわかりやすいです。
詰まったところ
いくつか詰まったところをメモしました。
どうやってエンドポイントにリクエストを送るか?
最初に考えたのは、送信ボタンを押したらシェルスクリプトが実行され、エンドポイントにリクエストが送られる仕組みです。
OpenAI APIのサンプルコードで、curlコマンドを使う例が載っていたので、このようなシェルスクリプトを作って実行すればいいのかなと考えました。
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Hello!"
}
]
}'
実行するとAPIキーが設定されていないと言われます。
[vue-lesson]$ bash ./src/message.sh
{
"error": {
"message": "You didn't provide an API key. You need to provide your API key in an Authorization header using Bearer auth (i.e. Authorization: Bearer YOUR_KEY), or as the password field (with blank username) if you're accessing the API from your browser and are prompted for a username and password. You can obtain an API key from https://platform.openai.com/account/api-keys.",
"type": "invalid_request_error",
"param": null,
"code": null
}
}
環境変数に渡せていないのか?それともAPIキーをそのまま打ち込んでも渡せていないのか?
APIキーをそのまま打ち込むとエラーが変わったので、環境変数に渡せていないのが問題だと判断しました。
シェルスクリプトに環境変数を設定する
定数に代入してから、exportコマンドで登録する。
$ OPENAI_API_KEY='OpenAI APIキー'
$ export OPENAI_API_KEY
環境変数が設定できたかは、printenvコマンドで確認できる。
$ printenv | grep OPEN
OPENAI_API_KEY=abcde...
これでシェルスクリプトの中で$OPENAI_API_KEY
として呼び出せます。
シェルスクリプトからエンドポイントにアクセスはできるようになりました。
シェルスクリプトの実行だとブラウザのフォームからデータを渡すことができないとわかったので、vueファイルからアクセスする方法で進めることにしました。
ブラウザからAPIキーを渡すのは御法度
実は、今回の方法はセキュリティ的な問題があります。
const client = new OpenAI({
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
dangerouslyAllowBrowser: true,
})
インスタンス生成時にdangerouslyAllowBrowser: true
を付け足した理由は、以下のエラーが出たからです。
openai.js?v=aef2f2d6:5023 Uncaught OpenAIError: It looks like you're running in a browser-like environment.
This is disabled by default, as it risks exposing your secret API credentials to attackers.
If you understand the risks and have appropriate mitigations in place,
you can set the `dangerouslyAllowBrowser` option to `true`, e.g.,
new OpenAI({ apiKey, dangerouslyAllowBrowser: true });
「ブラウザからAPIキーを渡すのはリスクがあるからやめたほうがいい。リスクを承知のうえでそれでもやるならこのオプションを使ってください」と言われています。
本来はVue.jsからAPIキーを送るのは御法度なんですね。他の人に知られてはまずい秘密情報を送るのは、フロントエンドではなくバックエンドでやるべきなのだと学びました。
今回は学習のためにオプションを使って続行します。
Nuxt.jsを使うとどうやるのかな?と気になりました。
まとめ
今回のおもなポイントは以下の2つです。
- ref関数を使うと変更を追えるようになる。あらかじめ初期値を設定しておくのを忘れずに
- APIキーのような秘密情報をブラウザから送るのはやめたほうがいい
転職活動中は、転職活動と並行してポートフォリオのブラッシュアップを行っていました。
転職活動後は、基本情報技術者試験の学習とRailsチュートリアルを進めていました。
そんな中、2日前のZennの通知が来て、そこで見に行った記事の人がアウトプットしまくっていて衝撃を受けました。
やっぱり「実際に自分で作らないと技術力は身につかないのではないか?」「小さくていいから何か作ってみよう」と思って今回アプリ作って記事にしました。
アプリとも言えないレベルですが、今の自分ではこれだけ実装するのに5時間かかり、この記事を作るにも4時間半かかりました。
これからちょっとずつ速さも質も上げていければと思います。
Discussion