Azure OpenAIのAssistants APIをJavaScriptで実行してみた

## はじめに
Azure OpenAIのAssistants APIをJavaScriptで実行してみました。

## 開発環境  

- **OS**: Windows 11
- **言語**: JavaScript
- **ライブラリ**: 
    - @azure/openai ^1.0.0-beta.12
    - @azure/openai-assistants ^1.0.0-beta.5

## 実装

### 1. ライブラリのインストール
今回のプロジェクトを実行するフォルダに移動します。
ターミナルで以下のコマンドを実行し、必要なライブラリをインストールします。

```terminal
npm i @azure/openai @azure/openai-assistants
```

　　
### 2. アシスタントの作成

`src/createAssistant.js`ファイルを作成し、以下の内容を記述します。
エンドポイントやキーはご自身のものを設定してください。
私はgpt-4oモデルを使用して作成しました。

```src/createAssistant.js
const { AzureKeyCredential } = require("@azure/openai");
const { AssistantsClient } = require("@azure/openai-assistants");

const endpoint = "YOUR_ENDPOINT";
const azureApiKey = "YOUR_API_KEY"; 
const deploymentId = "gpt-4o";

const assistantsClient = new AssistantsClient(endpoint, new AzureKeyCredential(azureApiKey));

async function createAssistant() {
  try {
    const assistant = await assistantsClient.createAssistant({
      model: deploymentId,
      name: "JS Math Tutor", 
      instructions: "You are a personal math tutor...",
      tools: [{ type: "code_interpreter" }]
    });
    console.log('アシスタントの作成に成功しました:', assistant);
    return assistant;
  } catch (error) {
    console.error('アシスタントの作成に失敗しました:', error);
  }
}

createAssistant();
```
　　
ターミナルで以下のコマンドを実行してアシスタントを作成します。

```
node src/createAssistant.js
```
　　
結果は以下のようになります。

```
アシスタントの作成に成功しました: {
  id: 'asst_fvJwgOugzy912KjrsLrv2eI4',
  createdAt: 2024-06-11T07:06:43.000Z,
  name: 'JS Math Tutor',
  description: null,
  model: 'gpt-4o',
  instructions: 'You are a personal math tutor...',
  tools: [ { type: 'code_interpreter' } ],
  fileIds: [],
  metadata: {}
}
```
　　
### 3. スレッドの作成

`src/createThread.js`ファイルを作成し、以下の内容を記述します。  

```src/createThread.js
const { AzureKeyCredential } = require("@azure/openai");
const { AssistantsClient } = require("@azure/openai-assistants");

const endpoint = "YOUR_ENDPOINT";
const azureApiKey = "YOUR_API_KEY"; 

const assistantsClient = new AssistantsClient(endpoint, new AzureKeyCredential(azureApiKey));

async function createThread() {
  try {
    const assistantThread = await assistantsClient.createThread();
    console.log('スレッドの作成に成功しました:', assistantThread.id);
    return assistantThread;
  } catch (error) {
    console.error('スレッドの作成に失敗しました:', error);
  }
}

createThread();
```
　　
ターミナルで以下のコマンドを実行してスレッドを作成します。

```
node src/createThread.js
```
　　
結果は以下のようになります。

```
スレッドの作成に成功しました: thread_MQjdr7VdZC4zCnaiFGJtLOOg
```
　　
### 4. メッセージの追加と質問

`src/addMessageAndAsk.js`ファイルを作成し、以下の内容を記述します。

```src/addMessageAndAsk.js
const { AzureKeyCredential } = require("@azure/openai");
const { AssistantsClient } = require("@azure/openai-assistants");

const endpoint = "YOUR_ENDPOINT";
const azureApiKey = "YOUR_API_KEY";
const threadId = "YOUR_THREAD_ID";
const question = "3x + 11 = 14 を解いてください";

const assistantsClient = new AssistantsClient(endpoint, new AzureKeyCredential(azureApiKey));

async function addMessageAndAsk() {
  try {
    const messageResponse = await assistantsClient.createMessage(threadId, "user", question);
    console.log('メッセージの追加に成功しました:', messageResponse);
  } catch (error) {
    console.error('メッセージの追加に失敗しました:', error);
  }
}

addMessageAndAsk();
```
　　
ターミナルで以下のコマンドを実行してメッセージを追加し、質問します。

```
node src/addMessageAndAsk.js
```
　　
結果は以下のようになります。

```
メッセージの追加に成功しました: {
  id: 'msg_CKI4Re3FYRl8h8YeI5e2WC8o',
  createdAt: 2024-06-11T07:21:08.000Z,
  threadId: 'thread_MQjdr7VdZC4zCnaiFGJtLOOg',
  role: 'user',
  content: [ { type: 'text', text: [Object], imageFile: {} } ],
  assistantId: null,
  runId: null,
  fileIds: [],
  metadata: {}
}
```
　　
### 5. スレッドの実行

`src/runThread.js`ファイルを作成し、以下の内容を記述します。

```src/runThread.js
const { AzureKeyCredential } = require("@azure/openai");
const { AssistantsClient } = require("@azure/openai-assistants");

const endpoint = "YOUR_ENDPOINT";
const azureApiKey = "YOUR_API_KEY";
const threadId = "YOUR_THREAD_ID";
const assistantId = "YOUR_ASSISTANT_ID";

const assistantsClient = new AssistantsClient(endpoint, new AzureKeyCredential(azureApiKey));

async function runThread() {
  try {
    let runResponse = await assistantsClient.createRun(threadId, { assistantId });
    console.log('スレッドの実行を開始しました:', runResponse);

    do {
      await new Promise(resolve => setTimeout(resolve, 800));
      runResponse = await assistantsClient.getRun(threadId, runResponse.id);
    } while (runResponse.status === "queued" || runResponse.status === "in_progress")

    const runMessages = await assistantsClient.listMessages(threadId);
    console.log('アシスタントの回答:');
    for (const message of runMessages.data) {
      for (const item of message.content) {
        if (item.type === "text") {
          console.log(item.text.value);
        }
      }
    }
  } catch (error) {
    console.error('スレッドの実行に失敗しました:', error);
  }
}

runThread();
```
　　
ターミナルで以下のコマンドを実行してスレッドを実行し、アシスタントに回答させます。

```
node src/runThread.js
```
　　
結果は以下のようになります。

```
スレッドの実行を開始しました: {
  id: 'run_apdojJ2UCwvNsLWZmvwmfUNE',
  object: 'thread.run',
  status: 'queued',
  model: 'gpt-4o',
  instructions: 'You are a personal math tutor...',
  tools: [ { type: 'code_interpreter' } ],
  metadata: {},
  temperature: 1,
  usage: null,
  assistantId: 'asst_fvJwgOugzy912KjrsLrv2eI4',
  threadId: 'thread_MQjdr7VdZC4zCnaiFGJtLOOg',
  fileIds: [],
  topP: 1,
  maxCompletionTokens: null,
  maxPromptTokens: null,
  truncationStrategy: { type: 'auto', last_messages: null },
  incompleteDetails: null,
  responseFormat: 'auto',
  toolChoice: 'auto',
  requiredAction: undefined,
  lastError: undefined,
  createdAt: 2024-06-11T07:23:37.000Z,
  expiresAt: 2024-06-11T07:33:37.000Z,
  startedAt: null,
  completedAt: null,
  cancelledAt: null,
  failedAt: null
}
アシスタントの回答:
方程式 \( 3x + 11 = 14 \) を解いてみましょう。

1. まず、方程式から11を引いて、xの項を残します：
\[ 3x + 11 - 11 = 14 - 11 \]
\[ 3x = 3 \]

2. 次に、xの係数である3で両辺を割って、xを求めます：
\[ x = \frac{3}{3} \]
\[ x = 1 \]

つまり、この方程式の解は \( x = 1 \) です。
3x + 11 = 14 を解いてください
```
　　
### 6. ファイルのアップロードと参照

`src/uploadFileAndUpdate.js`ファイルを作成し、以下の内容を記述します。

```src/uploadFileAndUpdate.js
const fs = require('fs').promises;
const { AzureKeyCredential } = require("@azure/openai");
const { AssistantsClient } = require("@azure/openai-assistants");

const endpoint = "YOUR_ENDPOINT";
const azureApiKey = "YOUR_API_KEY";
const assistantId = "YOUR_ASSISTANT_ID";
const filename = "sample.txt";

const assistantsClient = new AssistantsClient(endpoint, new AzureKeyCredential(azureApiKey));

async function uploadFileAndUpdate() {
  try {
    const uint8array = await fs.readFile(filename, 'utf-8');
    const uploadAssistantFile = await assistantsClient.uploadFile(uint8array, "assistants", { filename });
    console.log('ファイルのアップロードに成功しました:', uploadAssistantFile);

    const updateAssistant = await assistantsClient.updateAssistant(assistantId, { fileIds: [uploadAssistantFile.id] });
    console.log('アシスタントへのファイル関連付けに成功しました:', updateAssistant);
  } catch (error) {
    console.error('ファイルの操作に失敗しました:', error);
  }
}

uploadFileAndUpdate();
```
　　
sample.txtの中身は以下のとおりです。
```sample.txt
Technology has rapidly evolved, impacting communication, work, and entertainment. The internet and social media connect billions, while automation and AI reshape the workplace. Streaming services and VR change how we entertain. Smart homes and IoT enhance convenience but raise privacy concerns. In healthcare, telemedicine and precision medicine improve accessibility and outcomes. Yet, challenges like AI ethics and digital inequality persist. Navigating these challenges responsibly is key to harnessing technology for a better world.
```
　　
ターミナルで以下のコマンドを実行してファイルをアップロードし、アシスタントに関連付けます。

```
node src/uploadFileAndUpdate.js
```
　　
結果は以下のようになります。

```
ファイルのアップロードに成功しました: {
  id: 'assistant-STWS4rx4ZqHIKAHi4txkRlYL',
  bytes: 537,
  filename: 'sample.txt',
  createdAt: 2024-06-11T07:34:31.000Z,
  purpose: 'assistants'
}
アシスタントへのファイル関連付けに成功しました: {
  id: 'asst_fvJwgOugzy912KjrsLrv2eI4',
  createdAt: 2024-06-11T07:06:43.000Z,
  name: 'JS Math Tutor',
  description: null,
  model: 'gpt-4o',
  instructions: 'You are a personal math tutor...',
  tools: [ { type: 'code_interpreter' } ],
  fileIds: [ 'assistant-STWS4rx4ZqHIKAHi4txkRlYL' ],
  metadata: {}
}
```
　　
先ほどのaddMessageAndAsk.jsのquestionの値を変更して、アップロードしたファイルを日本語訳するメッセージを追加します。

```addMessageAndAsk.js
const { AzureKeyCredential } = require("@azure/openai");
const { AssistantsClient } = require("@azure/openai-assistants");

const endpoint = "YOUR_ENDPOINT";
const azureApiKey = "YOUR_API_KEY";
const threadId = "YOUR_THREAD_ID";
const question = "sample.txtを日本語訳してください。"; //ここを変更

const assistantsClient = new AssistantsClient(endpoint, new AzureKeyCredential(azureApiKey));

async function addMessageAndAsk() {
  try {
    const messageResponse = await assistantsClient.createMessage(threadId, "user", question);
    console.log('メッセージの追加に成功しました:', messageResponse);
  } catch (error) {
    console.error('メッセージの追加に失敗しました:', error);
  }
}

addMessageAndAsk();
```
　　
ターミナルで以下のコマンドを実行してメッセージを追加し、質問します。

```
node src/addMessageAndAsk.js
```
　　
ターミナルで以下のコマンドを実行し、追加したメッセージを含むスレッドを実行し、アシスタントに回答させます。

```
node src/runThread.js
```
　　
結果は以下のようになります。

```
アシスタントの回答:
以下がファイル「sample.txt」の内容です：

Technology has rapidly evolved, impacting communication, work, and entertainment. The internet and social media connect billions, while automation and AI reshape the workplace. Streaming services and VR change how we entertain. Smart homes and IoT enhance convenience but raise privacy concerns. In healthcare, telemedicine and precision medicine improve accessibility and outcomes. Yet, challenges like AI ethics and digital inequality persist. Navigating these challenges responsibly is key to harnessing technology for a better world.

この文章を日本語に訳します。

技術は急速に進化し、コミュニケーション、仕事、そしてエンターテインメントに影響を与えています。インターネットとソーシャルメディアは数十億人をつな げる一方で、自動化とAIは職場を再構築しています。ストリーミングサービスやVRは私たちのエンターテインメントの方法を変えてくれます。スマートホームやIoTは便利さを向上させますが、プライバシーの懸念も引き起こします。医療においては、遠隔医療や精密医療がアクセシビリティと成果を改善しています。それ でも、AI倫理やデジタル格差といった課題が残っています。これらの課題に責任を持って対応することが、より良い世界のために技術を活用する鍵です。       

ファイルのパスが正しいかどうかを確認し、修正して再度内容を読み取ります。
sample.txtを日本語訳してください。
方程式 \( 3x + 11 = 14 \) を解いてみましょう。

1. まず、方程式から11を引いて、xの項を残します：
\[ 3x + 11 - 11 = 14 - 11 \]
\[ 3x = 3 \]

2. 次に、xの係数である3で両辺を割って、xを求めます：
\[ x = \frac{3}{3} \]
\[ x = 1 \]

つまり、この方程式の解は \( x = 1 \) です。
3x + 11 = 14 を解いてください
```

数学の問題と同じスレッドにメッセージを追加したので、数学の会話履歴が存在しますが、日本語訳された結果も表示されていることが確認できます。

　　
### 7. アシスタントの削除

`src/deleteAssistant.js`ファイルを作成し、以下の内容を記述します。

```deleteAssistant.js
const { AzureKeyCredential } = require("@azure/openai");
const { AssistantsClient } = require("@azure/openai-assistants");

const endpoint = "YOUR_ENDPOINT";
const azureApiKey = "YOUR_API_KEY";
const assistantId = "YOUR_ASSISTANT_ID";

const assistantsClient = new AssistantsClient(endpoint, new AzureKeyCredential(azureApiKey));

async function deleteAssistant() {
  try {
    const deleteResponse = await assistantsClient.deleteAssistant(assistantId);
    console.log('アシスタントの削除に成功しました:', deleteResponse);
  } catch (error) {
    console.error('アシスタントの削除に失敗しました:', error);
  }
}

deleteAssistant();
```
　　
ターミナルで以下のコマンドを実行して作成したアシスタントを削除します。

```
node src/deleteAssistant.js
```
　　
結果は以下のようになります。

```
アシスタントの削除に成功しました: { id: 'asst_fvJwgOugzy912KjrsLrv2eI4', deleted: true }
```

　　
以上です！
最後まで読んでいただき、ありがとうございました！

## 参考資料
https://learn.microsoft.com/ja-jp/javascript/api/overview/azure/openai-assistants-readme?view=azure-node-preview