OpenAI Assistants APIをPHPで実装する

- PHP 8.2.12
- "guzzlehttp/guzzle": "^7.9"

OpenAI Assistants APIをPHPにて実装してみます。

HTTPクライアントはguzzleを使用します。Laravelでのシステムにて利用することを想定しています。

APIキーは、**OPENAI_API_KEY**という名前で環境変数に入れてあります。**phpdotenv**を使ってます。

```bash
$ composer require guzzlehttp/guzzle
$ composer require vlucas/phpdotenv
```

### Assistantの作成とファイルのアップロード

Assistantの作成とファイルのアップロードはダッシュボードから行います。

![スライド1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/638071/ef0dd51d-cc88-e487-af8a-a5507048cb9a.jpeg)

![スライド2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/638071/961374ef-c732-7443-c6be-5c7cbe2ac66d.jpeg)


1. Assistantを作成し、名前を決めたりmodelを選択したり、役割を設定したりします
1. Storageの作成画面で、Vecotor storesを選択します
1. Vecotor storeを作成し名前を決めます
1. ファイルをアップロードします。
1. Vector storeのIDをコピーします
1. Assistantの画面に戻ってTools > File Searchを有効にします
1. ファイルの追加画面で、Vecotr storeのIDを入力し追加します
1. AssistantのIDをコピーします > このIDをプログラムで使用します

### 実装：準備

先ほどコピーしたAssistantのIDを変数に入れます。

```php:assistant.php
require __DIR__.'/vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Dotenv\Dotenv;

$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();

$assistantId = 'asst_.....';
```

### 実装：Threadの作成

```php:assistant.php
<?php
$header = [
    'Authorization' => 'Bearer '.$_ENV['OPENAI_API_KEY'],
    'Content-Type'  => 'application/json',
    'OpenAI-Beta' => 'assistants=v2'
];

$url = 'https://api.openai.com/v1/threads';

$client = new Client();
$response = $client->post($url, [
    'headers' => $header,
]);
$thread = json_decode($response->getBody()->getContents(), true);
//print($thread['id']);
```

ThreadはIDを保存しておくと継続した会話ができます。
Threadが長くなるとAPIのコストが高くなるそうなので、今回Threadは一回きりで削除しています。

### 実装：Threadにメッセージを登録

```php:assistant.php
$prompt = 'この資料の目的を教えてください。';

$url = 'https://api.openai.com/v1/threads/'.$thread['id'].'/messages';
$data = [
    'role' => 'user',
    'content' => [['type' => 'text', 'text' => $prompt]]
];
$response = $client->post($url, [
    'headers' => $header,
    'json' => $data,
]);
$message = json_decode($response->getBody()->getContents(), true);
// print($message['id'].PHP_EOL);
```

### 実装：Threadを実行する（Run）

```php:assistant.php
$data = [
    'assistant_id' => $assistantId
];
$url = 'https://api.openai.com/v1/threads/'.$thread['id'].'/runs';
$response = $client->post($url, [
    'headers' => $header,
    'json' => $data,
]);
$run = json_decode($response->getBody()->getContents(), true);
```

### 実装：実行完了を待つ

```php:assistant.php
while($run['status'] != 'completed') {
    print($run['status'].PHP_EOL);
    $url = sprintf('https://api.openai.com/v1/threads/%s/runs/%s', $thread['id'], $run['id']);
    $response = $client->get($url, [
        'headers' => $header,
    ]);
    $run = json_decode($response->getBody()->getContents(), true);
}
```

### 実装：実行結果のメッセージを取得する

実行結果に、注釈(annotations)が含まれているので、削除してます。
ファイルの参照箇所らしきものが書いてあるのですが、解釈の仕方がわかりませんでした。

```php:assistant.php
$url = 'https://api.openai.com/v1/threads/'.$thread['id'].'/messages?order=asc&limit=100';
// $url .= '&after='.$lastMessageId;
$response = $client->get($url, [
    'headers' => $header,
]);
$response = json_decode($response->getBody()->getContents(), true);
$messages = [];
foreach ($response['data'] as $message) {
    $content = $message['content'][0]['text']['value'];
    foreach ($message['content'][0]['text']['annotations'] as $annotation) {
        $content = str_replace($annotation['text'], '', $content);
    }
    print($content.PHP_EOL);
}
```

### 実装：Threadを削除する

Threadはマメに削除する方針ですが、残しておく場合もあると思います。

```php:assistant.php
$url = 'https://api.openai.com/v1/threads/'.$thread['id'];
$response = $client->delete($url, [
    'headers' => $header,
]);
$response = json_decode($response->getBody()->getContents(), true);
if ($response['deleted'] == true) {
    print('deleted');
}
```

### 紹介

OpenAI API, ChatGPTの組み込みサンプルを紹介しています。
ご興味あればみてみてください。

https://develop.sankosc.co.jp/chatgptdemo/