PHPでもAIの波に乗る - openai clientを使ってみる

こんにちはみなさん

最近はAIの話がそこらかしこで聞こえます。
何というかこう、バズワードっぽい感じがしますし、まあ、PHPerには関係ないよね、なんて思っていたわけですが。

CTO: やるよ
ワイ: え？
CTO: ウチもAIやるよう

って感じで、我々もAIの波に乗らないとダメみたいです。
そんなわけで、今回はPHPerでもAIの波に乗るために、openai clientを導入して~~遊んで~~検証をしてみようと思います。
# そもそもAIの波に乗るとは
## × AIを作る ○ AIを利用する
AIの波に乗ると言っても、AIを作るわけではないです。作るには圧倒的に自分の知識が足りませんし、あったとしてOpen AIが提供している大規模言語モデルに勝てるようなものを作るのはちょっと現実的ではないです。

そもそも、そんな苦労をしなくても、Open AIが有用なAPIを提供しているわけですから、これを利用するのがいいという話です。

## AIをPHPで利用するには
というわけで、AIを利用して何かいい感じのが作れればいいわけです。
幸いなことに、Open AIはChatGPTなどのAIサービスのWEB APIを提供しているので、普通にこれを叩きにいけば使えちゃうわけですが、その実装をするのも面倒ですよね。

というわけで、openaiのphpクライアントが転がっていたので、使っていくことにしましょう。
https://github.com/openai-php/client

# OpenAI PHPを使ってみる
何はともあれ、使ってみましょう。
まずはインストールから。

```
composer require openai-php/client guzzlehttp/guzzle
```
httpクライアントを手動でインストールせよってことだったので、guzzleを入れてます。

## クライアントの作成
ライブラリを入れたら、実際に使ってみましょう。

```php
$client = OpenAI::client($yourApiKey);
```
個人向けのOpenAIに契約している場合は、こんな感じでクライアントを作ってあげれば十分です。
Httpクライアントはライブラリ側で自動検出してくれるようなので、これで十分みたいです。
factoryチックな作り方もあります

```php
$client = OpenAI::factory()
    ->withApiKey($yourApiKey)
    ->withOrganization('your-organization') // default: null
    ->withHttpClient($client = new \GuzzleHttp\Client([])) // default: HTTP client found using PSR-18 HTTP Client Discovery
    ->make();
```
こんな感じ。

### Azureでのクライアントの作成
現在だと、企業で使用する場合はAzure経由で利用することが多いと思います。
AzureだとAPIの仕様がちょい違うようで、クライアントの作り方が以下のようになります。

```php
$client = OpenAI::factory()
        ->withBaseUri(sprintf('%s/openai/deployments/model-name', getenv('AZURE_OPEN_API_URI')))
        ->withHttpHeader('api-key', getenv('OPEN_AI_API_KEY'))
        ->withQueryParam('api-version', '2023-03-15-preview')
        ->withHttpClient(new \GuzzleHttp\Client([]))
        ->make();
```
`baseUri`は、ドメインの部分が`{resource-name}.openai.azure.com`となっていて、Azure OpenAIのエンドポイントのドメイン部分になります。その後ろに、`/openai/deployments/`とパスを書いて、さらに事前に用意していたデプロイしたモデルの名前を入れる必要があります。
この部分が結構ハマりやすいと感じました。
また、APIキーをヘッダーに渡す必要があるなど、作り方がかなり違います。
もしかしたら、この辺は後で統一される可能性はあります。
pythonのopenaiクライアントはクライアントの作り方自体は統一されています。

## 使ってみる
それでは早速つかてみましょう。
こちらで使いやすいように、ラッパーをかましてみます。

```php
<?php
namespace Niisan\OpenAi;

use OpenAI;
use OpenAI\Client as OpenAIClient;

class Client
{
    public function completions(string $word, float $temperature = 0.5)
    {
        return $this->getClient()->completions()->create([
            'prompt' => $word,
            'temperature' => $temperature
        ]);
    }

    private function getClient(string $model = '/openai/deployments/deployed-model-name'): OpenAIClient
    {
        return OpenAI::factory()
        ->withBaseUri(sprintf('%s%s', getenv('AZURE_OPEN_API_URI'), $model))
        ->withHttpHeader('api-key', getenv('OPEN_AI_API_KEY'))
        ->withQueryParam('api-version', '2023-03-15-preview')
        ->withHttpClient(new \GuzzleHttp\Client([]))
        ->make();
    }
}
```

### 文章の補完
入力補助をするための文章補完を試してみましょう。

```php
<?php
require __DIR__ . '/../vendor/autoload.php';

use Niisan\OpenAi\Client;

$client = new Client;

$response = $client->completions('PHP is');
var_dump($response);
```
結果はこうなりました。

```php
object(OpenAI\Responses\Completions\CreateResponse)#58 (6) {
  ["id"]=>
  string(34) "returned-uuid-name"
  ["object"]=>
  string(15) "text_completion"
  ["created"]=>
  int(1682900778)
  ["model"]=>
  string(12) "gpt-35-turbo"
  ["choices"]=>
  array(1) {
    [0]=>
    object(OpenAI\Responses\Completions\CreateResponseChoice)#55 (4) {
      ["text"]=>
      string(87) " an open source, server-side web scripting language that is used to develop dynamic web"
      ["index"]=>
      int(0)
      ["logprobs"]=>
      NULL
      ["finishReason"]=>
      string(6) "length"
    }
  }
  ["usage"]=>
  object(OpenAI\Responses\Completions\CreateResponseUsage)#57 (3) {
    ["promptTokens"]=>
    int(2)
    ["completionTokens"]=>
    int(16)
    ["totalTokens"]=>
    int(18)
  }
}
```
欲しいのはこの中のテキストだけなので、var_dumpを

```php
$response = $client->completions('PHP is');
echo $response->choices[0]->text;
```
と書き換えてやれば、
```php
a server-side scripting language that is used to create dynamic web pages that can interact
```
こんなメッセージが取り出せます。

### Chatしてみる
次に、会話をしてみましょう。
まず、クライアントにコードを追加します。

```Client.php
public function chat(string $content, float $temperature = 0.5)
{
    return $this->getClient()->chat()->create([
        'messages' => [
            ['role' => 'user', 'content' => $content]
        ],
        'temperature' => $temperature
    ]);
}
```
これを使ってみましょう。

```php
<?php
require __DIR__ . '/../vendor/autoload.php';

use Niisan\OpenAi\Client;

$client = new Client;

$response = $client->chat('Qiitaというメディアについて、いいところを3つ挙げてください。');

foreach ($response->choices as $choice) {
    echo $choice->message->role . "\n";
    echo $choice->message->content . "\n";
}

print_r($response->toArray());
```
さあ、こいつの結果を見てみましょう。

```
# php tests/chat.php 
assistant
1. コンテンツの質が高い：Qiitaは技術系の記事が多く、その中でも特にプログラミングに関する記事が充実しています。そのため、技術的な知識を深めたい人にとって非常に有用な情報源となっています。

2. コミュニティが活発：Qiitaはユーザー同士が交流しやすい環境が整っており、コメントやいいね機能を通じて、記事の投稿者と読者がやり取りすることができます。また、Qiita内でのコミュニティ活動も盛んで、イベントやミートアップなどが開催されることもあります。

3. 利用が無料：Qiitaは誰でも無料で利用することができます。そのため、技術的な情報を得るためにお金をかける必要がなく、誰でも気軽にアクセスできることが魅力の一つです。また、記事の投稿も自由に行うことができるため、技術的な知見を持っている人が自由に情報を発信できるという点も大きなメリットとなっています。
```
おお！いいじゃんいいじゃん。
近頃Qiitaはあれやこれやと言われますが、こういうことなんですよ。
わかっているじゃないですか。
そのほかのオブジェクトも見ておきましょう。

```
Array
(
    [id] => returned-chat-id
    [object] => chat.completion
    [created] => 1682902023
    [model] => gpt-35-turbo
    [choices] => Array
        (
            [0] => Array
                (
                    [index] => 0
                    [message] => Array
                        (
                            [role] => assistant
                            [content] => 1. コンテンツの質が高い：Qiitaは技術系の記事が多く、その中でも特にプログラミングに関する記事が充実しています。そのため、技術的な知識を深めたい人にとって非常に有用な情報源となっています。

2. コミュニティが活発：Qiitaはユーザー同士が交流しやすい環境が整っており、コメントやいいね機能を通じて、記事の投稿者と読者がやり取りすることができます。また、Qiita内でのコミュニティ活動も盛んで、イベントやミートアップなどが開催されることもあります。

3. 利用が無料：Qiitaは誰でも無料で利用することができます。そのため、技術的な情報を得るためにお金をかける必要がなく、誰でも気軽にアクセスできることが魅力の一つです。また、記事の投稿も自由に行うことができるため、技術的な知見を持っている人が自由に情報を発信できるという点も大きなメリットとなっています。
                        )

                    [finish_reason] => stop
                )

        )

    [usage] => Array
        (
            [prompt_tokens] => 36
            [completion_tokens] => 356
            [total_tokens] => 392
        )

)
```
単純に楽しむだけなら、choicesだけ持ってくればよく、課金額の計算とかまでするなら、usageの数値を見ていく必要がありそうですね。

# まとめ
ということで、簡単にですがopenai clientをPHPで使う方法を紹介しました。
すでに使えるAIがそこにあるという状況ですから、これを利用しない手はないわけです。
また、別のところで紹介する予定ですが、AIによって検証できる幅が非常に増えるのも確かなわけで、これを実現するには自分たちがどうすればいいのかを今のうちに考えておくのは重要かなと思います。
AIがただのバズワードであれば杞憂で終わったでいいのですが、乗り遅れてついていけなくなっちゃったは悲しいので。

何はともあれ、~~遊んでて~~使ってて面白いなぁって思ったので、皆様もぜひつかてみてください。