【AGI】OpenAI APIをPHP環境で試してみる
こんにちは、株式会社Gizumoでエンジニアをしている日野です。
OpenAIがAPIの提供を始めた頃、弊社でも技術検証を行いました。
せっかくなので検証の振り返りとして、PHPでOpenAI APIを使用した簡易アプリケーションの実装方法をまとめていきます。
最終的に、PHPでOpenAI APIへリクエストを行いレスポンスを受け取るところまでを出来るようにします。
前提・対象とする読者
前提:
- サンプルコードは
PHP
で記述
※筆者が検証を行った環境はPHP v8.2.8
OpenAI API
を試すことが目的のため、PHPのファイル分割は行わない- composer(PHPのパッケージマネージャー)をインストール済み
- 使用するAPIはChat completions API
- OpenAI APIがどんなものかについての説明は情報がたくさんあるので本投稿の対象外
対象とする読者:
- OpenAI APIを試してみたいと考えている
- PHPを使ったことがある
準備
API Keyの取得
まずはOpenAI APIを使用するためのAPI Keyを取得します。
以下のリンク(公式ページ)から取得して保存しておきます。
簡易アプリ用のプロジェクトを作成
先に、こちらが今回の最終的なフォルダ構成です。
./src
├── .env
├── .gitignore
├── composer.json
└── index.php
まずはお好きな場所にプロジェクト用のディレクトリを作成し、.env
と.gitignore
を用意します。
- .env
.env
では先程取得したAPI Key、使用するModelを定義して管理することにしました。
※各APIとModelの対応表についてはModel endpoint compabilityで確認できます
OPENAI_API_KEY=
OPENAI_API_MODEL=gpt-3.5-turbo
- .gitignore
.gitignore
の記載内容は以下の通りです。
.env
/vendor
パッケージをインストール
続いて、必要なパッケージをインストールしていきます。
先程作成したディレクトリに移動し、以下のコマンドを実行します。
composer require vlucas/phpdotenv orhanerday/open-ai
- vlucas/phpdotenv
用意した.env
をPHPスクリプトで使用するためのパッケージです。 - orhanerday/open-ai
PHPで簡単にOpenAI APIを使用するためのパッケージです。
検証していた時に一番メジャーだったこと、READMEを見た感じ使い方がシンプルだったことがこのパッケージを選定した理由です。
※コミュニティによってメンテナンスされているOpenAI API関連パッケージはこちらで確認できます
.envの値を取得出来るか確認
ここまでで.env
とパッケージのインストールまで済んでいるので、次はPHPで.env
に設定した環境変数を参照できるようにしていきます。
プロジェクトのディレクトリに下記の内容のindex.php
を作成します。
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
(function(): void {
(Dotenv\Dotenv::createImmutable(__DIR__))->load();
})();
echo $_ENV['OPENAI_API_MODEL'];
作成出来たら、CLIでphp index.php
を実行してみます。gpt-3.5-turbo
が表示されるはずです。
ここまでで準備は完了です。
APIを使用してテキスト生成
では、ここからは実際にOpenAIのChat completions API
を使った処理をPHPで実装していきます。
既に作成済のindex.php
を開き、下記の内容に書き換えます。index.php(全体のコード)
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use Orhanerday\OpenAi\OpenAi;
(function(): void {
(Dotenv\Dotenv::createImmutable(__DIR__))->load();
})();
/**
* 標準入力を受け付けるための準備
* @return resource
*/
function prepare()
{
$resource = fopen('php://stdin', 'r');
if (!$resource) {
exit("[error] STDIN failure.\n");
}
return $resource;
}
/**
* ファイルポインタのクローズ
* @return void
*/
function cleanup($resource): void
{
fclose($resource);
print("Bye!\n");
}
/**
* 改行出力
* @return void
*/
function newLine(): void
{
print("\n");
}
/**
* 役割の入力を受け付ける
* @return string
*/
function askRole(resource $resource): string
{
$times = 0;
while (true) {
echo "ChatGPTの役割を入力してください。ex)翻訳者 > ";
$input = trim(fgets($resource, 64));
if ($input === '') {
$times++;
if ($times === 3) {
exit("[error] Too many attempts");
}
continue;
}
return $input;
}
}
/**
* 質問の入力を受け付ける
* @return string
*/
function askQuestion($resource): string
{
$times = 0;
while (true) {
echo "質問を入力してください。ex)'Better than do nothing'を日本語に翻訳してください。 > ";
$input = trim(fgets($resource, 64));
if ($input === '') {
$times++;
if ($times === 3) {
exit("[error] Too many attempts");
}
continue;
}
return $input;
}
}
/**
* ユーザーから受け付けた入力を元にAPIへリクエスト
* @return mixed
*/
function askToAi($inputRole, $question)
{
$openAi = new OpenAi($_ENV['OPENAI_API_KEY']);
$chat = $openAi->chat([
'model' => $_ENV['OPENAI_API_MODEL'],
'messages' => [
[
"role" => "system",
"content" => "あなたは{$inputRole}です。",
],
[
"role" => "user",
"content" => $question,
],
],
'temperature' => 1.0,
'max_tokens' => 500,
'frequency_penalty' => 0,
'presence_penalty' => 0,
]);
$decoded = json_decode($chat);
return $decoded;
}
/**
* APIからの回答を出力
* @return void
*/
function showAnswer($result): void
{
print("---------- Answer ----------\n");
// Get Content
print($result->choices[0]->message->content . "\n");
print("----------------------------\n");
}
// INFO: 一連の処理実行
$resource = prepare();
$role = askRole($resource);
$question = askQuestion($resource);
newLine();
$result = askToAi($role, $question);
newLine();
showAnswer($result);
cleanUp($resource);
それぞれの関数を見ていきます。
/**
* 標準入力を受け付けるための準備
* @return resource
*/
function prepare()
{
$resource = fopen('php://stdin', 'r');
if (!$resource) {
exit("[error] STDIN failure.\n");
}
return $resource;
}
CLIでユーザーからの入力を受け付ける準備として、fopen
でPHPの標準入力を指定します。
万が一この処理に失敗したら処理を中断します。
/**
* 役割の入力を受け付ける
* @return string
*/
function askRole(resource $resource): string
{
$times = 0;
while (true) {
echo "ChatGPTの役割を入力してください。ex)翻訳者 > ";
$input = trim(fgets($resource, 64));
if ($input === '') {
$times++;
if ($times === 3) {
exit("[error] Too many attempts");
}
continue;
}
return $input;
}
}
プロンプトのテクニックとして、AIに役割を設定してあげるとより正確な回答が得られる為、ここではユーザーに役割の入力を行ってもらいます。trim
でユーザーの入力から先頭と末尾のホワイトスペースを取り除きます。
ユーザーが3回連続で何も入力せずにenterキーを押した場合処理を中断します。
/**
* 質問の入力を受け付ける
* @return string
*/
function askQuestion($resource): string
{
$times = 0;
while (true) {
echo "質問を入力してください。ex)'Better than do nothing'を日本語に翻訳してください。 > ";
$input = trim(fgets($resource, 64));
if ($input === '') {
$times++;
if ($times === 3) {
exit("[error] Too many attempts");
}
continue;
}
return $input;
}
}
実際にユーザーからの質問を受け付けます。
ここでもtrim
を使用した結果をもとに入力値の判定を行い、ユーザーが3回連続で何も入力しなかった場合は処理を中断します。
/**
* ユーザーから受け付けた入力を元にAPIへリクエスト
* @return mixed
*/
function askToAi($inputRole, $question)
{
$openAi = new OpenAi($_ENV['OPENAI_API_KEY']);
$chat = $openAi->chat([
'model' => $_ENV['OPENAI_API_MODEL'],
'messages' => [
[
"role" => "system",
"content" => "あなたは{$inputRole}です。",
],
[
"role" => "user",
"content" => $question,
],
],
'temperature' => 1.0,
'max_tokens' => 500,
'frequency_penalty' => 0,
'presence_penalty' => 0,
]);
$decoded = json_decode($chat);
return $decoded;
}
今回のメインにあたる部分です。ユーザーからの入力を元に実際にOpenAI APIへリクエストを行います。
事前に準備しておいたAPI Keyを、OpenAIクラス(インストール済みのパッケージ)の引数に渡してインスタンス化します。
OpenAIクラスがChat completions API
を扱うためのchatメソッドを提供しているのでそれを使用します。
連想配列で各種設定が可能なため、それぞれ設定を記述します。
model
: 使用したいモデルmessages
: 会話ベースのやり取りを表現した配列temperature
: 回答の安定性を0~1
で指定(より安定した回答を得たい場合は0、同じ質問に対して様々なバリエーションの回答が欲しい場合は1に近い値を指定する)max_tokens
: OpenAI APIはトークン数に応じて課金される料金システムの為、このパラメーターでAIからの回答のトークン数を制御frequency_penalty
: 同じ単語の出現頻度を-2.0~2.0
で指定(-2.0
に近いと同じ単語を繰り返し使い、2.0
に近いと同じ単語を繰り返さなくなる)presence_penalty
: 既に出てきた単語をもう一度使うか否かを-2.0~2.0
で指定(-2.0
に近いと同じ単語を繰り返し使い、2.0
に近いと同じ単語を繰り返さなくなる)- 同じ単語が1度でも出てきたかどうかに対するペナルティが
presence_penalty
、同じ単語がどれだけ出てきたかに対するペナルティがfrequency_penalty
という分けのようです
- 同じ単語が1度でも出てきたかどうかに対するペナルティが
※$_ENV['OPENAI_XXX']
は先述の.env
で定義した環境変数
/**
* APIからの回答を出力
* @return void
*/
function showAnswer($result): void
{
print("---------- Answer ----------\n");
// Get Content
print($result->choices[0]->message->content . "\n");
print("----------------------------\n");
}
最後に、デコード済みのOpenAI APIからのレスポンスをコンソールへ出力します。$result
をデバッグすれば一目瞭然の為細かい説明は割愛しますが、choices
というプロパティが配列になっていて、その0番目の値から回答内容にアクセスしています。
まとめ
最後まで読んで頂きありがとうございました。
PHPを用いてOpenAI APIを実際に使ってみましたが、PHPのアプリケーションに組み込むことに特別ハードルがある印象は受けませんでした。
実際にOpenAI APIのようなサービスを掛け合わせたサービスを世に打ち出していくには、プロンプトが鍵になってくると感じたため、弊社でも引き続きキャッチアップしていきたいです。