【AGI】OpenAI APIをPHP環境で試してみる

media thumbnail

こんにちは、株式会社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を取得します。
以下のリンク(公式ページ)から取得して保存しておきます。

API keys | 公式ページ

簡易アプリ用のプロジェクトを作成

先に、こちらが今回の最終的なフォルダ構成です。

./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という分けのようです

$_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のようなサービスを掛け合わせたサービスを世に打ち出していくには、プロンプトが鍵になってくると感じたため、弊社でも引き続きキャッチアップしていきたいです。

参考文献

少しでも開発にお困りの方は
相談しやすいスペシャリストにお問い合わせください

お問い合わせ
  1. breadcrumb-logo
  2. メディア
  3. 【AGI】OpenAI APIをPHP環境で試してみる