OpenAI Assistants API チートシート Part 2 [Thread 編]

みなさんOpenAIのAPIは使用したことがありますか？？このシリーズのチートシートではOpenAIのAssistantsAPIの紹介をします．
従来のCompletion APIは1問1答で答える方式でchatgptのように会話履歴を保持してくれません．そのため毎回どんな回答が欲しいか言わないといけません．AssistantsAPIを使えば会話を続けたい場合に活用できます！本記事ではOpenAI Assistants API チートシート Part 2としてThreadの作成方法を紹介します．
これはPart 1で紹介したAsssistantの作成が前提なのでまだPart 1を読んでいない方は以下の記事を参考にしてください．

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

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

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

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

**Part 3 Message編**

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

**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)
```
# Threadとは？
スレッド（Thread）は，OpenAIのAssistants APIにおける会話管理の機能．ユーザーとAIアシスタント間の対話を永続的に保存し，管理するためのコンテナのような役割を果たす．

:::note info
**スレッドの主な目的**
会話の文脈や履歴を維持すること．従来のCompletion APIでは，各リクエストが独立しており，文脈を維持するためには過去の会話履歴を毎回送信する必要があった．一方，スレッドを使用すると，会話の流れや文脈が自動的に保持され、長期的な対話が可能になる．
:::

# Threadの流れ
**1. スレッドは会話を開始する際に作成される**
Assistantを作成したらuserとassistantで会話するためのコンテナであるthreadを作成する．

**2. スレッド内でメッセージのやり取りが行われる**
ユーザーからの入力，ファイルの添付，アシスタントからの応答など，すべてのやり取りがスレッド内に時系列で記録される．これにより，複雑な対話や段階的な問題解決，継続的なプロジェクトサポートなどが可能になる．

**3. スレッド内での実行管理（Run）**
アシスタントによる処理を非同期で実行し，その状態を追跡する機能．ユーザーはRunの状態を確認しながら，処理の進行状況をモニタリングできる．

> このRunに関してはPart 4で説明します．

:::note info
**threadの使用例や実装の特徴**
**長期的なプロジェクトや継続的な学習支援**
まスレッドは複数のセッションにまたがって利用することができ，必要に応じて過去の会話を参照したり，中断した会話を再開したりすることも可能．

**継続的なコミュニケーションが必要なアプリケーション**
スレッドの利用は、チュータリング、カスタマーサポート、プロジェクト管理などに適している．ファイル処理や分析タスクなども，スレッド内で一貫して管理できる．

**実装の特徴**
スレッドの作成からメッセージの追加，実行の管理まで，一連の操作をAPIを通じて行うことができる．ただし，不要なスレッドの管理やエラーハンドリング，セキュリティ考慮など，適切な運用管理も重要である．

このように，ThreadはAssistants APIにおいて、構造化された対話管理を実現する重要な要素として機能していて，より高度で継続的なAIアシスタントとの対話が可能になっている．
:::

# スレッドの作成
スレッドの作成を行う．スレッドの作成時はAssistantの情報は不要で，Part 4で説明するRunで必要になる．
```create_thread.go
package main

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

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

type ThreadRequest struct {
	Messages []Message `json:"messages,omitempty"`
}

type Message struct {
	Role    string      `json:"role"`
	Content interface{} `json:"content"`
}

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()

	// スレッドを作成
	thread, err := client.CreateThread(ctx, openai.ThreadRequest{
		Messages: []openai.ThreadMessage{
			{
				Role:    "user", // userとassistantのどちらかを指定
				Content: "Hello, I'm looking for a tutor to help me with my math homework.", // ユーザーのメッセージ
			},
		},
	})
	if err != nil {
		log.Fatalf("スレッドの作成に失敗: %v", err)
	}

	fmt.Printf("スレッドが作成されました: %v\n", thread)
}
```
実行
```
go run create_thread.go
```
実行に成功すると以下のようなレスポンスが返ってくる．
```bash
スレッドが作成されました:
{
    "id": "thread_abc123xyz789",              # スレッドID
    "object": "thread",                       # オブジェクトタイプ
    "created_at": 1702089600,                 # 作成日時（2024-12-09 00:00:00 UTC）
    "metadata": {},                           # メタデータ
    "headers": {
        "Alt-Svc": "h3=\":443\"; ma=86400",              # HTTP/3設定
        "Cf-Cache-Status": "DYNAMIC",                     # Cloudflareキャッシュ状態
        "Content-Type": "application/json",               # コンテンツタイプ
        "Date": "Mon, 09 Dec 2024 00:00:00 GMT",         # レスポンス日時
        "Openai-Organization": "org-abc123xyz789",        # 組織ID
        "Openai-Processing-Ms": "300",                    # 処理時間(ms)
        "Openai-Version": "2020-10-01",                  # APIバージョン
        "Server": "cloudflare",                          # サーバー
        "Strict-Transport-Security": "max-age=31536000", # HSTSポリシー
        "X-Content-Type-Options": "nosniff",             # セキュリティヘッダー
        "X-Request-Id": "req_abc123xyz789"               # リクエストID
    }
}

```

:::note info
**roleについて**
`user`か`assistant`を指定できる．
```go
// 使用例
thread, err := client.CreateThread(ctx, openai.ThreadRequest{
    Messages: []openai.ThreadMessage{
        {
            // ユーザーからのメッセージ
            Role: "user",
            Content: "質問です：なぜ空は青いのですか？",
        },
        {
            // アシスタントからの応答
            Role: "assistant",
            Content: "空が青く見える主な理由は光の散乱現象によるものです。",
        },
    },
})
```

userはユーザーからのメッセージを表し，アシスタントに向けてのメッセージである．
assistantを使う用途は
- **会話の文脈を設定する場合**
```go
// 既に進行中の会話の途中からスレッドを作成する場合
thread, err := client.CreateThread(ctx, openai.ThreadRequest{
    Messages: []openai.ThreadMessage{
        {
            Role: "user",
            Content: "Python入門について教えて",
        },
        {
            Role: "assistant",
            Content: "Pythonは初心者に優しい言語です。まず基本を説明しましょう",
        },
        {
            Role: "user",
            Content: "変数の使い方を教えて",
        },
    },
})
```
- **過去の会話を復元する場合**
```go
// データベースなどから過去の会話を復元してスレッドを作成する場合
thread, err := client.CreateThread(ctx, openai.ThreadRequest{
    Messages: []openai.ThreadMessage{
        {
            Role: "user",
            Content: "昨日の続きの説明をお願いします",
        },
        {
            Role: "assistant",
            Content: "昨日は配列の基本まで説明しました",
        },
        {
            Role: "user",
            Content: "そうでした。では次に進みましょう",
        },
    },
})
```
- **特定の文脈から会話を開始する場合**
```go
// 特定の状況や前提条件がある会話を始める場合
thread, err := client.CreateThread(ctx, openai.ThreadRequest{
    Messages: []openai.ThreadMessage{
        {
            Role: "assistant",
            Content: "このレッスンでは中学2年生の数学、特に二次方程式について学習します",
        },
        {
            Role: "user",
            Content: "はい、お願いします",
        },
    },
})
```

である．ただし、多くの場合、新しい会話を開始する際はuserのメッセージだけで十分である．assistantのメッセージは，通常はCreateRun(Part 4で説明)を実行した後に自動的に生成される．

このようにThread，Message(Part 3で説明)，Run(Part 4で説明)を実装すべきである．
```go
// 適切な会話の流れ
thread, err := client.CreateThread(ctx, openai.ThreadRequest{
    Messages: []openai.ThreadMessage{
        {
            Role: "user",
            Content: "質問1、質問2、質問3があります：\n1. ...\n2. ...\n3. ...",
        },
    },
})

// または、一つずつ質問を送る場合
// 1. 最初の質問
thread, err := client.CreateThread(ctx, openai.ThreadRequest{
    Messages: []openai.ThreadMessage{
        {
            Role: "user",
            Content: "質問1",
        },
    },
})
// CreateRunを実行して応答を待つ

// 2. 次の質問（AddMessage使用）
message, err := client.CreateMessage(ctx, thread.ID, openai.Message
Request{
    Role: "user",
    Content: "質問2",
})
// CreateRunを実行して応答を待つ

// 3. さらに次の質問
message, err := client.CreateMessage(ctx, thread.ID, openai.MessageRequest{
    Role: "user",
    Content: "質問3",
})
// CreateRunを実行して応答を待つ
```
:::

スレッドは以下のような情報を持っている．メッセージなどの情報はこれから説明するスレッドの詳細情報取得や，スレッドの情報更新では扱えないので，Part 3のmessage編で説明します．
```go
// Thread構造体の詳細説明
type Thread struct {
    // 基本情報
    ID        string         `json:"id"`            // スレッドの一意のID
    Object    string         `json:"object"`        // オブジェクトタイプ（"thread"）
    CreatedAt int64         `json:"created_at"`    // 作成時刻（Unixタイム）
    
    // メタデータ（interface{}型なので様々な型のデータを格納可能）
    Metadata  map[string]any `json:"metadata"`      
    
    // ツールリソース情報
    ToolResources ToolResources `json:"tool_resources,omitempty"`
    
    // HTTP関連情報
    httpHeader
}

// ツールリソース構造体
type ToolResources struct {
    // コードインタープリタツール用のリソース
    CodeInterpreter *CodeInterpreterToolResources `json:"code_interpreter,omitempty"`
    
    // ファイル検索ツール用のリソース
    FileSearch      *FileSearchToolResources      `json:"file_search,omitempty"`
}

```
# スレッドの取得
スレッドの詳細情報を取得するコードは以下の通りである．メッセージの取得はここではできないので別で行う必要がある．
```retrieve_thread.go
package main

import (
    "context"
    "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()

    // スレッドIDを指定して取得
    threadID := "thread_abc123"
    thread, err := client.RetrieveThread(ctx, threadID)
    if err != nil {
        log.Fatal(err)
    }

    // 取得したスレッド情報を使用
    log.Printf("Thread ID: %s", thread.ID)
	log.Printf("Object Type: %s", thread.Object)
	log.Printf("Created At: %d", thread.CreatedAt)
	log.Printf("Metadata: %v", thread.Metadata)
	log.Printf("ToolResourcs: %v", thread.ToolResources)
}
```
実行
```
go run retrieve_thread.go
```
成功すると以下のようなレスポンスが返ってくる．
```bash
2024/12/09 13:22:55 Thread ID: thread_abc123xyz789
2024/12/09 13:22:55 Object Type: thread
2024/12/09 13:22:55 Created At: 1733711242
2024/12/09 13:22:55 Metadata: map[]
2024/12/09 13:22:55 ToolResourcs: {0xc000098120 <nil>}
```
## スレッドの詳細情報を修正する
以下のようなコードでスレッドの詳細情報を更新できる．

```modify_thread.go
package main

import (
    "context"
    "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"

    // 例1: ユーザー情報を追加
    thread1, err := client.ModifyThread(ctx, threadID, openai.ThreadRequest{
        Metadata: map[string]string{
            "user_id": "user_123",
            "status": "active",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    // 例2: カテゴリとタグを設定
    thread2, err := client.ModifyThread(ctx, threadID, openai.ThreadRequest{
        Metadata: map[string]string{
            "category": "support",
            "tags": "python,beginner",
            "priority": "high",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    // 例3: 状態管理
    thread3, err := client.ModifyThread(ctx, threadID, openai.ThreadRequest{
        Metadata: map[string]string{
            "status": "completed",
            "completed_at": "2024-12-09",
            "resolved_by": "agent_789",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    // 例4: 既存のメタデータを全て削除（空のマップを設定）
    thread4, err := client.ModifyThread(ctx, threadID, openai.ThreadRequest{
        Metadata: map[string]string{},
    })
    if err != nil {
        log.Fatal(err)
    }
}
```
実行
```bash
go run modify_thread.go
```

# スレッドの削除
スレッドを削除するコードは以下のようなコードで実装できる．
```remove_thread.go
package main

import (
    "context"
    "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"

    // スレッドの削除
    deletedThread, err := client.DeleteThread(ctx, threadID)
    if err != nil {
        log.Fatal(err)
    }

    // 削除結果の確認
    log.Printf("Thread ID: %s", deletedThread.ID)
    log.Printf("Object: %s", deletedThread.Object)
    log.Printf("Deleted: %v", deletedThread.Deleted)
}
```
実行
```bash
go run remove_thread.go
```
実行結果は以下の通りである
```bash
Thread ID: thread_abc123
Object: thread.deleted
Deleted: true
```

これでスレッドを作成したり，作成したスレッドの情報を取得することができる．次のpartでメッセージを作成してスレッドに追加し，会話をするための直前の準備をしましょう．

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

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

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

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

![Wondershare Uniconverter 15_000005.GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3757442/a69a568b-c471-60d2-3845-ec0b2b1e765b.gif)