OpenAI Assistants API チートシート Part 3 [Message 編]

みなさんOpenAIのAPIは使用したことがありますか？？このシリーズのチートシートではOpenAIのAssistantsAPIの紹介をします．これでPart 3です．
従来のCompletion APIは1問1答で答える方式です．つまり，1回聞いて答えが返ってきたらOpenAI CompletionAPIは話していた内容を忘れてしまうのです．つまり，chatgptのように会話履歴を保持してくれません．そのため毎回どのような会話をしたいかを指定しないといけません．そこでAssistants APIを使えば会話を続けたい場合に活用できます！Part 1ではAssistantの作成，Part 2ではThreadの作成の説明をしました．本記事ではOpenAI Assistants API チートシート Part 3としてMessageの作成方法を紹介します．

`参考`
OpenAI APIのドキュメント

https://platform.openai.com/docs/api-reference/assistants

# シリーズ OpenAI AssistantsAPI チートシート
**Part 1 Assistant編**

https://qiita.com/JavaLangRuntimeException/items/1a1abc01e8d7d05dce93

**Part 2 Thread編**

https://qiita.com/JavaLangRuntimeException/items/7ddae318202d3afcb45d

**Part 4 Run編**

https://qiita.com/JavaLangRuntimeException/items/2bb6dea15fa5ddc75871

# 他のチートシート
git/gh コマンド(gitコマンド以外にもgitの概念も書いてあります)

https://qiita.com/JavaLangRuntimeException/items/6b46551f56e0def76eba

lazygit

https://qiita.com/JavaLangRuntimeException/items/42087d09728d5739d73d

Docker コマンド(dockerコマンド以外にもdockerの概念の記事へのリンクもあります)

https://qiita.com/JavaLangRuntimeException/items/21f7c7bf3d143f821697

ステータスコード

https://qiita.com/JavaLangRuntimeException/items/ab1bc7b976ed2dfad91c

TypeScript

https://qiita.com/JavaLangRuntimeException/items/5894391c08e0d8e28389

Go・Gorm

https://qiita.com/JavaLangRuntimeException/items/d388717fc1436bc3ec9d

C#/.NET/Unity

https://qiita.com/JavaLangRuntimeException/items/7849b32bc223d4aa0247

Ruby・Ruby on Rails

https://qiita.com/JavaLangRuntimeException/items/42d935cf92c212f1c7ec

SQL

https://qiita.com/JavaLangRuntimeException/items/f038fbaccdd92fb0308a

Vim

https://qiita.com/JavaLangRuntimeException/items/0c68ab96ea198e0a7294

プルリクエスト・マークダウン記法チートシート

https://qiita.com/JavaLangRuntimeException/items/329eb92a47a07ff4dde8

ファイル操作コマンドチートシート

https://qiita.com/JavaLangRuntimeException/items/16f244606a73f7d106e4

VSCode Github Copilot拡張機能

https://qiita.com/JavaLangRuntimeException/items/be13dc3a346cf6e5ee44

# 他のシリーズ記事
**TypeScriptで学ぶプログラミングの世界**
プログラミング言語を根本的に理解するシリーズです．

https://qiita.com/JavaLangRuntimeException/items/cadf49bb419076819963

**情報処理技術者試験合格への道[IP・SG・FE・AP]**
情報処理技術者試験の単語集です．

https://qiita.com/JavaLangRuntimeException/items/991be402099542ccb936

**IAM AWS User クラウドサービスをフル活用しよう！**
AWSのサービスを例にしてバックエンドとインフラ開発の手法を説明するシリーズです．

https://qiita.com/JavaLangRuntimeException/items/371a334f5a6e07035db5

**Project Gopher: Unlocking Go’s Secrets**
Go言語や標準ライブラリの深掘り調査レポートです．

https://qiita.com/JavaLangRuntimeException/items/dc45b412d3fbd2ccb9e8

**Gopher’s Journey: Exploring TCP Protocol**
Goを用いてTCPプロトコルを探求 & 作成するシリーズです．

https://qiita.com/JavaLangRuntimeException/items/38091220106d86651d2b


# Assistants APIでの実装の流れ
Assistant作成(Part 1で紹介) → Thread作成(Part 2で紹介) → **メッセージ追加(Part 3で紹介)** → Run実行(Part 4で紹介) → 応答取得

典型的な使用パターンは以下のようである．
```go
// 1. アシスタント作成
assistant, _ := client.CreateAssistant(ctx, openai.AssistantRequest{
    Name: "Math Tutor",
    Model: "gpt-4",
})

// 2. スレッド作成
thread, _ := client.CreateThread(ctx, openai.ThreadRequest{})

// 3. メッセージ追加
message, _ := client.CreateMessage(ctx, thread.ID, openai.MessageRequest{
    Content: "Help with math",
})

// 4. スレッド実行
run, _ := client.CreateRun(ctx, thread.ID, openai.RunRequest{
    AssistantID: assistant.ID,
})

// 5. 結果取得
messages, _ := client.ListMessages(ctx, thread.ID, nil)
```

# MessageとAssistantとThreadの関係
- **Assistant（アシスタント）**
AIアシスタントの設定や性格を定義
特定の目的や機能に特化させることが可能
複数のThreadで同じAssistantを使用可能
- **Thread（スレッド）**
会話の文脈を保持するコンテナ
一連の会話をグループ化
1つのThreadに1つ以上のMessageが含まれる
同じAssistantと複数の会話を分けて管理可能
- **Message（メッセージ）**
Thread内での各発言を表現
ユーザーとアシスタントの発言を記録
必ず特定のThreadに属する

**会話作成までの流れ**
**Assistant作成(Part 1で紹介)**
Assistantの作成

**Thread作成(Part 2で紹介)**
新しい会話の器（Thread）を作成
↓
ThreadIDが発行される

**Message追加(本記事Part 3で紹介)**
ThreadIDに対してユーザーメッセージを追加
↓
MessageIDが発行される

> ここから下はPart 4で紹介します

**Assistant実行**
ThreadIDに対してAssistantを実行（Run作成）
↓
RunIDが発行される
↓
Runのステータスを監視
↓
完了を待機

**結果取得**
ThreadIDに対してメッセージ一覧を取得
↓
Assistantの応答を含む全メッセージを取得

# Messageの作成
メッセージを作成する方法は以下のコードの通りである．
```create_message.go
package main

import (
    "context"
    "log"
    "os"

    "github.com/sashabaranov/go-openai"
)

func main() {
    client := openai.NewClient(os.Getenv("OPENAI_API_KEY"))
    ctx := context.Background()

    threadId := "thread_abc123" // thread_idを適宜変更

    message, err := client.CreateMessage(
        ctx,
        threadId, 
        openai.MessageRequest{
            Role:    "user",
            Content: "How does AI work? Explain it in simple terms.",
        },
    )
    if err != nil {
        log.Fatal(err)
    }

    jsonData, err := json.MarshalIndent(message, "", "    ")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Message created:\n%s\n", string(jsonData))
}
```
実行
```
go run create_message.go
```
成功すると以下のようなレスポンスになる．
```bash
{
    "id": "msg_abc123",                    # メッセージの一意のID
    "object": "thread.message",            # オブジェクトタイプを示す固定値 
    "created_at": 1713226573,             # 作成日時（UNIXタイムスタンプ）
    "thread_id": "thread_abc123",         # 所属するスレッドID
    "role": "user",                       # メッセージの送信者（user/assistant）
    "content": [
        {
            "type": "text",               # コンテンツのタイプ
            "text": {                     # テキストメッセージの内容
                "value": "Hello, world!", # 実際のメッセージテキスト
                "annotations": []         # テキストの注釈（空配列）
            }
        }
    ],
    "attachments": [],                    # 添付ファイル（空配列）
    "assistant_id": null,                 # アシスタントID（ない場合はnull）
    "run_id": null,                       # 実行ID（ない場合はnull）
    "metadata": {}                        # カスタムメタデータ（空オブジェクト）
}
```

# Messageの内容をリスト表示
あるthread内でのmessageをリストにして表示する方法は以下のコードの通りである．
```list_message.go
package main

import (
	"context"
	"github.com/joho/godotenv"
	"log"
	"os"

	"github.com/sashabaranov/go-openai"
)

func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal("Error loading .env file")
	}

	apiKey := os.Getenv("OPENAI_API_KEY")
	if apiKey == "" {
		log.Fatal("環境変数 OPENAI_API_KEY が設定されていません")
	}

	client := openai.NewClient(apiKey)
	ctx := context.Background()

	threadId := "thread_abc123" // threadIdを適宜変更

	messages, err := client.ListMessage(
		ctx,
		threadId, //threadIdは必須
		nil, // オプションパラメータはnil
		nil,
		nil,
		nil,
		nil,
	)
	if err != nil {
		log.Fatal(err)
	}

	// メッセージ内容を表示
	for _, msg := range messages.Messages {
		for _, content := range msg.Content {
			if content.Type == "text" {
				log.Printf("Message ID: %s, Role: %s, Content: %s",
					msg.ID,
					msg.Role,
					content.Text)
			}
		}
	}
}

```
実行
```
go run list_message.go
```
成功すると以下のレスポンスになる
```bash
2024/12/10 09:32:59 Message ID: msg_abc123, Role: user, Content: &{Integrate x^2 []}
2024/12/10 09:32:59 Message ID: msg_abc123, Role: user, Content: &{Hello, I'm looking for a tutor to help me with my math homework. []}
```

# Messageの詳細情報取得
Messageの詳細情報は以下の通りである．
```retrieve_message.go
package main

import (
	"context"
	"github.com/joho/godotenv"
	"log"
	"os"

	"github.com/sashabaranov/go-openai"
)

func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal("Error loading .env file")
	}

	apiKey := os.Getenv("OPENAI_API_KEY")
	if apiKey == "" {
		log.Fatal("環境変数 OPENAI_API_KEY が設定されていません")
	}

	client := openai.NewClient(apiKey)
	ctx := context.Background()

	threadId := "thread_abc123" // 必須パラメータ：スレッドID
	messageId := "msg_abc123"   // 必須パラメータ：メッセージID

	message, err := client.RetrieveMessage(
		ctx,
		threadId,
		messageId,
	)
	if err != nil {
		log.Fatal(err)
	}

    jsonData, err := json.MarshalIndent(message, "", "    ")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Retrieved message:\n%s\n", string(jsonData))
}

```
実行
```
go run retrieve_message.go
```
成功すると以下のようなレスポンスになる
```bash
{
    "id": "msg_abc123",                    # メッセージの一意のID
    "object": "thread.message",            # オブジェクトタイプ
    "created_at": 1733790190,             # 作成日時（UNIXタイムスタンプ）
    "thread_id": "thread_xyz789",         # メッセージが属するスレッドID
    "role": "user",                       # 送信者の役割（user/assistant）
    "content": [
        {
            "type": "text",               # コンテンツタイプ
            "text": {                     # テキストコンテンツ
                "value": "Hello world",   # メッセージの実際の内容
                "annotations": []         # テキストの注釈
            }
        }
    ],
    "attachments": [],                    # 添付ファイル
    "assistant_id": null,                 # アシスタントID
    "run_id": null,                       # 実行ID
    "metadata": {}                        # メタデータ
}
```
:::note info
**Messageの構造体はどうなっているのか？**
```go
type Message struct {
    // メッセージの一意識別子
    ID          string           `json:"id"`
    
    // オブジェクトタイプ（通常は "thread.message"）
    Object      string           `json:"object"`
    
    // メッセージ作成時のUNIXタイムスタンプ
    CreatedAt   int              `json:"created_at"`
    
    // このメッセージが属するスレッドのID
    ThreadID    string           `json:"thread_id"`
    
    // メッセージの送信者の役割（"user" または "assistant"）
    Role        string           `json:"role"`
    
    // メッセージの内容（テキストや他の型のコンテンツを含む配列）
    Content     []MessageContent `json:"content"`
    
    // 添付ファイルのID配列
    // nolintコメントは後方互換性のために維持されていることを示す
    FileIds     []string         `json:"file_ids"`
    
    // メッセージに関連付けられたアシスタントのID（オプション）
    // ポインタ型なのでnullable
    AssistantID *string          `json:"assistant_id,omitempty"`
    
    // メッセージに関連付けられた実行ID（オプション）
    // ポインタ型なのでnullable
    RunID       *string          `json:"run_id,omitempty"`
    
    // カスタムメタデータを格納できるマップ
    Metadata    map[string]any   `json:"metadata"`

    // HTTPレスポンスヘッダー情報を含む埋め込みフィールド
    httpHeader
}
```
:::

# Messageのmetadataの変更
メッセージの修正は実質的にメタデータの更新のみが可能で，メッセージの本文やその他の属性は変更できない．
```modify_message.go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/joho/godotenv"
	"log"
	"os"

	"github.com/sashabaranov/go-openai"
)

func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal("Error loading .env file")
	}

	apiKey := os.Getenv("OPENAI_API_KEY")
	if apiKey == "" {
		log.Fatal("環境変数 OPENAI_API_KEY が設定されていません")
	}

	client := openai.NewClient(apiKey)
	ctx := context.Background()

	threadId := "thread_puC1Mp2TYUuvZYtMc7sNT5Az" // 必須パラメータ：スレッドID
	messageId := "msg_5Oqh2yIsBCu6mE66Rav7nS4K"   // 必須パラメータ：メッセージID

	// メッセージ修正のためのリクエスト作成
	request := map[string]string{
		"modified":  "true",
		"timestamp": "2024-12-10",
	}

	modifiedMessage, err := client.ModifyMessage(
		ctx,
		threadId,
		messageId,
		request, // 修正内容を含むリクエストを追加
	)
	if err != nil {
		log.Fatal(err)
	}

	// 修正されたメッセージを整形して表示
	jsonData, err := json.MarshalIndent(modifiedMessage, "", "    ")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Modified message:\n%s\n", string(jsonData))
}

```
実行
```
go run modify_messae.go
```
成功するとこのようなレスポンスになる．
```bash
{
    "id": "msg_abc123",                    # メッセージID
    "object": "thread.message",            # オブジェクトタイプ
    "created_at": 1733790190,             # 作成日時（UNIXタイムスタンプ）
    "thread_id": "thread_xyz789",         # スレッドID
    "role": "user",                       # ユーザーロール（user/assistant）
    "content": [
        {
            "type": "text",
            "text": {
                "value": "Sample message text",  # メッセージ内容
                "annotations": []                # 注釈（空配列）
            }
        }
    ],
    "file_ids": [],                       # 添付ファイルID（空配列）
    "metadata": {                         # 更新されたメタデータ
        "modified": "true",               # 修正フラグ
        "timestamp": "2024-12-10"         # タイムスタンプ
    }
}
```

# Messageの削除
MessageをThreadから削除するコードは以下の通りである．
```delete_message.go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/joho/godotenv"
	"log"
	"os"

	"github.com/sashabaranov/go-openai"
)

func main() {
	err := godotenv.Load()
	if err != nil {
		log.Fatal("Error loading .env file")
	}

	apiKey := os.Getenv("OPENAI_API_KEY")
	if apiKey == "" {
		log.Fatal("環境変数 OPENAI_API_KEY が設定されていません")
	}

	client := openai.NewClient(apiKey)
	ctx := context.Background()

	threadId := "thread_abc123" // 必須パラメータ：スレッドID
	messageId := "msg_abc123"   // 必須パラメータ：メッセージID

	deletionStatus, err := client.DeleteMessage(
		ctx,
		threadId,
		messageId,
	)
	if err != nil {
		log.Fatal(err)
	}

	jsonData, err := json.MarshalIndent(deletionStatus, "", "    ")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Deletion status:\n%s\n", string(jsonData))
}
```

実行 & レスポンス
```bash
❯ go run delete_message.go
Deletion status:
{
    "id": "msg_5Oqh2yIsBCu6mE66Rav7nS4K",
    "object": "thread.message.deleted",
    "deleted": true
}

❯ go run list_message.go    #実際に削除されたことがわかりますね
2024/12/10 10:21:09 Message ID: msg_Iswpuv6raCtPFHwf9PGi55WO, Role: user, Content: &{Hello, I'm looking for a tutor to help me with my math homework. []}
```
これでメッセージを作成したり，作成したメッセージの情報を取得することができる．次のpartで作成したスレッドで，会話をする準備をしましょう．

**Part 4**　OpenAI Assistants APIチートシート [Run 編]

https://qiita.com/JavaLangRuntimeException/items/2bb6dea15fa5ddc75871

それではQiitaアドカレ.24企画の今日のクリスマスツリーです．

> 詳しくは[こちら](https://qiita.com/JavaLangRuntimeException/items/1f4a6febf957f522ba45)の記事から

![Wondershare Uniconverter 15_000006.GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/25c5db6a-479e-b5c4-36d9-9bdc65b97eed.gif)