今更AzureOpenAIでシンプルにチャットする処理(デプロイ～PHPサンプルまで）

# 概要
まとめる機会があったので今からAzureOpenAIを使ってシンプルにチャットをする処理の概略をまとめてみる

# 内容
基本的には公式リファレンスのとおりです。

https://learn.microsoft.com/ja-jp/azure/ai-services/openai/reference#chat-completions

## 前提処理

Azure OpenAIでリソースを作成します。※ リソース作成までの流れは割愛。
リソースが作成できたら、Azure OpenAI Studioへ移動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/1508b7ca-a496-6578-ca1b-f5e2a7f10825.png)

こんな画面が開けばOKです。利用するためのの準備を進めていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/6a9e04af-ea4a-fc91-0993-76fb3f22611e.png)

## APIエンドポイントの確認

先程の画面の「Azure OpenAI Endpoint」を確認し、メモしておきます。
ここがリクエストするときのエンドポイントになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/e50170f0-4e20-b7af-6ea9-15feaec74e19.png)

## モデルのデプロイ

会話処理をするために会話モデルをデプロイします。
左側のメニューから「デプロイ」を開き、「モデルのデプロイ > 基本モデルをデプロイする」を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/1393a0c7-90f4-0674-89cd-85d3e5e3f9fa.png)

作成するモデルを選択します。今回は「gpt4-o」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/c6ef2963-ceef-aff6-b999-2d8fd07b952c.png)

色々項目はありますが、**「デプロイ名」** を任意に入力して、**「デプロイ」** を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/f1ccac53-c75d-c065-83d6-a0cced2dd2f5.png)

以下のような感じで作成が完了し画面が切り替わります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/93693955-1e73-7898-4694-13205e5b97b2.png)

ここに必要な情報がすべて書かれています。

## サンプルコードから使い方を確認

一応公式にリファレンスもあるんですが、プレイグラウンドのサンプルコードから使い方を確認するほうが直感的にわかりやすいです。モデル詳細画面の **「プレイグラウンドを開く」** を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/2a5470a3-b488-e826-5f74-eff245a76a6a.png)

チャットプレイグラインドが開きますので、画面左上の「コードを表示」を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/a1671a5d-bda9-f9ae-b8f5-06169670d243.png)

サンプルコードが開きます。これをそのまま使うことができます。
今回は **「キー認証」** を使います。Pythonのコードがデフォルトですが、C#やcurlに切り替えることもできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420694/9cfd5334-31fd-79c2-3a6d-93a577e414d3.png)

このコードをベースに処理をコーディングしていくのが良いですが、curl形式のものが最も原始的です。

```shell:curl
curl "https://[APIエンドポイント]/openai/deployments/[利用モデル名]/chat/completions?api-version=2024-02-15-preview" \
  -H "Content-Type: application/json" \
  -H "api-key: YOUR_API_KEY" \
  -d "$payload"
```

ペイロードの内容はJSONで以下のように設定します。
```json:payload
{
  "messages": [
    {
      "role": "system",
      "content": [
        {
          "type": "text",
          "text": "情報を見つけるのに役立つ AI アシスタントです。"
        }
      ]
    }
  ],
  "temperature": 0.7,
  "top_p": 0.95,
  "max_tokens": 800
}
```

**ペイロードの内容について**

| **項目**                     | **内容**                                                                 |
|-----------------------------|-------------------------------------------------------------------------|
| **message[] > role**        | **コンテンツの役割**<br>・system → システムプロンプト         |   
| **message[] > content => type** | **コンテンツのタイプ** <br>テキストの場合「text」                        |
| **message[] > content => text** | **コンテンツの内容**<br>・role = systemのとき → システムプロンプトの内容<br>・role = userのとき → ユーザーの入力テキスト<br>・role = assistantのとき → AIからの応答テキスト                                                 |

**複数会話のやり取りをしている場合のサンプル**

```json
{
  "messages": [
    {
      "role": "system",
      "content": [
        {
          "type": "text",
          "text": "情報を見つけるのに役立つ AI アシスタントです。"
        }
      ]
    },
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "こんにちは"
        }
      ]
    },
    {
      "role": "assistant",
      "content": [
        {
          "type": "text",
          "text": "こんにちは！今日はどんなお手伝いができますか？"
        }
      ]
    },
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "桃太郎の登場人物をサイバーパンク風にアレンジして"
        }
      ]
    },
    {
      "role": "assistant",
      "content": [
        {
          "type": "text",
          "text": "もちろんです！桃太郎の登場人物をサイバーパンク風にアレンジしてみましょう。\n\n### 桃太郎\n**名前:** 桃太郎（コードネーム: Momo-01）\n**背景:** 高度なサイバネティクス技術を駆使して作られたバイオロイド。彼は桃の形をしたカプセルから生まれ、特別なミッションを持っている。強化された身体能力と高度なハッキングスキルを持つ。\n\n### おじいさん\n**名前:** オールド・ハッカー（Old Hacker）\n**背景:** かつては伝説的なハッカーだったが、今は引退して静かに暮らしている。桃太郎を見つけ、彼にサイバーパンクの世界で生き抜くための知識と技術を教える。\n\n### おばあさん\n**名前:** メカニック・グラン（Mechanic Gran）\n**背景:** おじいさんのパートナーであり、優れたメカニック。彼女は桃太郎の身体をメンテナンスし、必要なアップグレードを施す。彼女の工房は最新のテクノロジーで溢れている。\n\n### 犬\n**名前:** サイバー・ドッグ（Cyber Dog）\n**背景:** 高度なAIを搭載したロボット犬。桃太郎の忠実なパートナーであり、戦闘や情報収集において重要な役割を果たす。内蔵された武器システムとセンサーで敵を感知し、攻撃する。\n\n### 猿\n**名前:** ハッキング・モンキー（Hacking Monkey）\n**背景:** 小型で俊敏なサイボーグ猿。優れたハッキング能力を持ち、敵のシステムに侵入して情報を盗む。桃太郎のチームの一員として、情報戦で活躍する。\n\n### 雉\n**名前:** ドローン・フェニックス（Drone Phoenix）\n**背景:** 高性能なドローンで、空中からの偵察や攻撃を担当する。AIによって自律的に動き、桃太郎の指示に従う。敵の動きを監視し、戦略的な情報を提供する。\n\n### 鬼\n**名前:** オーガ・コーポレーション（Ogre Corporation）\n**背景:** 巨大な企業であり、サイバーパンク都市を支配する悪の組織。非人道的な実験や違法な取引を行い、都市の人々を苦しめている。桃太郎のミッションは、この企業を打倒し、都市に平和を取り戻すこと。\n\nこのように、桃太郎の物語をサイバーパンク風にアレンジしてみました。いかがでしょうか？"
        }
      ]
    }
  ],
  "temperature": 0.7,
  "top_p": 0.43,
  "max_tokens": 2931
}
```



## 応答サンプル

応答はJSON形式で、応答メッセージの内容やコンテンツフィルターの状態、トークンの使用量を取得することが可能です。（使っているモデルによって応答のフォーマットは異なる可能性があります）

```json
{
        "choices": [
            {
                "content_filter_results": {
                    "hate": {
                        "filtered": false,
                        "severity": "safe"
                    },
                    "self_harm": {
                        "filtered": false,
                        "severity": "safe"
                    },
                    "sexual": {
                        "filtered": false,
                        "severity": "safe"
                    },
                    "violence": {
                        "filtered": false,
                        "severity": "safe"
                    }
                },
                "finish_reason": "stop",
                "index": 0,
                "logprobs": null,
                "message": {
                    "content": "こんにちは！今日はどんなお手伝いをしましょうか？",
                    "role": "assistant"
                }
            }
        ],
        "created": 1732784407,
        "id": "",
        "model": "",
        "object": "chat.completion",
        "prompt_filter_results": [
            {
                "prompt_index": 0,
                "content_filter_results": {
                    "hate": {
                        "filtered": false,
                        "severity": "safe"
                    },
                    "self_harm": {
                        "filtered": false,
                        "severity": "safe"
                    },
                    "sexual": {
                        "filtered": false,
                        "severity": "safe"
                    },
                    "violence": {
                        "filtered": false,
                        "severity": "safe"
                    }
                }
            }
        ],
        "system_fingerprint": "",
        "usage": {
            "completion_tokens": 14,
            "prompt_tokens": 40,
            "total_tokens": 54
        }
    }
```

### 応答項目について（一部抜粋）

| **項目**                          | **内容**                                                                                             |
|----------------------------------|-----------------------------------------------------------------------------------------------------|
| **choices[] > content_filter_results** | コンテンツフィルターの結果                                                                           |
| **choices[] > finish_reason**    | 終了理由。通常は「stop」、コンテンツフィルターによる遮断の場合は「content_filter」となる                  |
| **choices[] > message => content** | 生成された応答本文                                                                                   |
| **choices[] > message => role**  | 応答の役割。通常は「assistant」                                                                       |
| **usage => completion_tokens**   | 今回のAPIリクエストで使った出力トークン数                                                              |
| **usage => prompt_tokens**       | 今回のAPIリクエストで使った入力トークン数                                                              |
| **usage => total_tokens**        | 今回のAPIリクエストで使った総トークン数                                                                |


基本的にはchoisesの内容を取得して利用するのがシンプルです。

## チャット処理を実行する

これらを使ってチャット処理を実行する場合、ユーザー入力を使ってペイロードを構成し、APIエンドポイントにリクエストするだけでOKです。以下にサンプルコード（PHP）を書きます。

シンプルにユーザー入力（`$argv[1]`）を使ってペイロードを構成します。

### PHP

```php
<?php

// Configuration
$apiKey = "[APIキー]";

// Set headers
$headers = [
    "Content-Type: application/json",
    "api-key: $apiKey",
];

$userInput = $argv[1];

// Payload for the request
$payload = [
    "messages" => [
        [
            "role" => "system",
            "content" => [
                [
                    "type" => "text",
                    "text" => "情報を見つけるのに役立つ AI アシスタントです。"
                ]
            ]
        ],
        [
            "role" => "user",
            "content" => [
                [
                    "type" => "text",
                    "text" => $userInput
                ]
            ]
        ]
    ],
    "temperature" => 0.7,
    "top_p" => 0.43,
    "max_tokens" => 2931,
];

// Endpoint URL
$endpoint = "[APIエンドポイント]";

// Initialize cURL session
$ch = curl_init();

// cURL options
curl_setopt($ch, CURLOPT_URL, $endpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));

// Execute the request and handle errors
$response = curl_exec($ch);

if (curl_errno($ch)) {
    $error_msg = curl_error($ch);
    curl_close($ch);
    die("Failed to make the request. Error: $error_msg");
}

curl_close($ch);

// Decode and print the response
$responseData = json_decode($response, true);

echo $responseData["choices"][0]["message"]["content"];
```

このコードを実行すると以下の感じになります。

```
# php chat.php サイバーパンク風のポムポムプリンのプロフィール
もちろん、サイバーパンク風のポムポムプリンのプロフィールを作成しますね！

---

**名前:** ポムポムプリン

**別名:** ネオ・プリン

**種族:** サイボーグ・ゴールデンレトリバー

**年齢:** 不明（サイボーグ化により年齢不詳）

**出身地:** ネオ・サンリオシティ

**職業:** 情報ブローカー兼ハッカー

**外見:**
ポムポムプリンは、サイバーパンクの世界で一新された姿を持っています。彼の体は金属とバイオニックパーツで強化され、片目にはデジタルインターフェースが装備されています。耳には通信デバイスが埋め込まれ、背中にはデータケーブルが垂れ下がっています。トレードマークのベレー帽は、LEDライトで光るように改造されています。

**性格:**
ネオ・プリンは、かつての愛らしい性格を保ちながらも、サイバーパンクの世界で生き抜くために鋭い知性と冷静な判断力を持つようになりました。彼は仲間を大切にし、困っている人々を助けるために自分のスキルを駆使します。

**スキル:**
- 高度なハッキング技術
- 情報収集と解析
- サイバネティック戦闘能力
- 高速データ転送

**装備:**
- サイバネティックアーム（多機能ツール内蔵）
- デジタルインターフェース付きゴーグル
- データケーブル（ネットワーク接続用）
- ステルスモード機能

**背景:**
ネオ・サンリオシティは、テクノロジーが支配する未来都市。ポムポムプリンは、ある日突然サイボーグ化され、目覚めたときには自分の過去の記憶がほとんど失われていました。彼は自分の過去を探るため、そしてこの混沌とした都市で正義を貫くために、情報ブローカーとして活動を始めました。

**目標:**
ポムポムプリンは、自分の過去を取り戻すことと、ネオ・サンリオシティに平和をもたらすことを目指しています。彼は仲間たちと共に、巨大企業や犯罪組織と戦いながら、都市の闇を照らし出す存在となっています。

---

このような設定で、サイバーパンク風のポムポムプリンのプロフィールを作成しました。いかがでしょうか？
```

### 会話を継続する場合

会話の流れを維持する場合、APIリクエストの際のペイロードに過去の会話履歴をもたせます。単純にペイロード部にやり取りの履歴を持たせるだけで大丈夫です。以下のような形になります。

```text
[
    "role" => "system",
    "content" => [
        [
            "type" => "text",
            "text" => "[システムプロンプト]"
        ]
    ]
],
[
    "role" => "user",
    "content" => [
        [
            "type" => "text",
            "text" => [ユーザー入力①]
        ]
    ]
],
[
    "role" => "assistant",
    "content" => [
        [
            "type" => "text",
            "text" => [会話出力①]
        ]
    ]
],
[
    "role" => "user",
    "content" => [
        [
            "type" => "text",
            "text" => [ユーザー入力②]
        ]
    ]
],
[
    "role" => "assistant",
    "content" => [
        [
            "type" => "text",
            "text" => [会話出力②]
        ]
    ]
]
```

# 終わり

curlベースで動くので、どんな環境でも割と簡単に実装できます。ただ、これを実際にどう使っていくかとか、会話履歴をどう管理するかとか、安定性を保つ工夫とか、周りの仕組みについて考える余地はまだまだありますね。