AIコーディング 2026.05.14

OpenAI Assistants APIで旅行AIを作る方法|Function Callingと継続スレッドの実装ガイド

タグ:OpenAI API / 生成AI / ワークフロー自動化

このやり方で何ができるか

OpenAIの Assistants API を使うと、単発の質問に答えるだけではなく、ユーザーとの会話を続けたまま、外部のプログラムやデータベースと自動で連携できるAIを作ることができます。

たとえば「10月の京都旅行で3日間のプラン考えて」と聞かれたら、AIが「何人で行く?」と返して、答えが返ってくるのを待ち、その情報をホテル予約サイトと連携して実際の空き状況を確認し、「このホテルが空いてますよ」と提案する。こういう一連の流れが実現できます。

もう一つの大事な点が「会話の記憶」です。通常のChatGPTは「今この瞬間のやり取り」だけを考えて返事をしますが、このやり方なら過去のやり取り全部を覚えたままで、10回目の質問のときも「さっき教えてもらった人数」を参考に答えられるようになります。

準備するもの

・OpenAIのアカウント(ChatGPT有料版でなくOK。APIキーが必要) ・Pythonの基本知識(3年目以上の業務経験があれば十分) ・FastAPIまたはFlask等のWebアプリケーション枠組み(フレームワーク) ・テストの為の簡単なツール(curl コマンドまたはPostman等)

手順(15分程度で概念を理解)

1. Function Calling の概念をつかむ(3分)

通常のAIは「答え」をテキストで返すだけです。しかし Assistants API の Function Calling を使うと、AIが「このデータを持ってきてほしい」「このプログラムを実行してほしい」という「指示」を出せるようになります。

具体的には:

  • ユーザー質問:「10月で空いてるホテルある?」
  • AI の返答:「ホテル検索という関数を実行してほしい。検索条件は『10月、京都、3人』」
  • プログラム(あなたが書いたコード):その指示を受け取り、実際にホテルDB を調べて、結果をAIに返す
  • AI の 2番目の返答:「見つかりました。〇〇ホテルが1泊8000円です」

このようなやり取りが可能になるということです。

2. Persistent Threads の仕組みを理解する(3分)

Persistent Threads(永続スレッド)は「会話の記録」です。

通常の API では:

ユーザー1回目:「旅行計画立ててください」
AI:「何人ですか」
(ここで会話終了)

ユーザー2回目に別の質問:「ホテルもさがして」
AI:「何人ですか」← 前の会話を忘れてる

Persistent Threads を使うと:

ユーザー1回目:「旅行計画立ててください」
AI:「何人ですか」
(スレッド ID: thread_abc123 に記録)

ユーザー2回目:「ホテルも探して」(同じ thread_abc123 を使う)
AI:「さっき聞いた3人ですね。ホテルを探します」← 覚えている

つまり Thread ID という識別番号を持ち続けることで、会話の履歴全体が保存され、AIがそれを参考に返答できるということです。

3. 最小限のコード構造を書く(5分)

実装には以下の 3つの部分が必要です。

(1)Assistants の作成(初回のみ)

from openai import OpenAI

client = OpenAI()

# アシスタント(会話相手のAI)を作成
assistant = client.beta.assistants.create(
    name="旅行プランナーAI",
    instructions="ユーザーの旅行計画をサポートしてください。ホテルや飛行機の検索が必要なら search_hotel 関数を使ってください。",
    model="gpt-4"
)

print(f"Assistant ID: {assistant.id}")

実行するとアシスタント ID が得られます。これ以降はこの ID を指定して使います。

(2)関数定義(Function Calling の仕組み)

# アシスタントに「このような関数が使える」と教える
tools = [
    {
        "type": "function",
        "function": {
            "name": "search_hotel",
            "description": "指定された日付と人数でホテルを検索します",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "検索地域(例:京都)"
                    },
                    "date": {
                        "type": "string",
                        "description": "チェックイン日(YYYY-MM-DD形式)"
                    },
                    "people": {
                        "type": "integer",
                        "description": "人数"
                    }
                },
                "required": ["location", "date", "people"]
            }
        }
    }
]

# アシスタントを更新
client.beta.assistants.update(
    assistant.id,
    tools=tools
)

このコードでアシスタントに「search_hotel という関数が使えるよ」と知らせます。

(3)会話ループ(実際の やり取り)

# スレッド作成(初回のみ)
thread = client.beta.threads.create()
thread_id = thread.id

# ユーザーメッセージを追加
message = client.beta.threads.messages.create(
    thread_id=thread_id,
    role="user",
    content="10月に京都で3人の旅行プランを立ててください"
)

# Assistants を実行
run = client.beta.threads.runs.create(
    thread_id=thread_id,
    assistant_id=assistant.id
)

# 実行完了を待つ(ポーリング)
while run.status != "completed":
    run = client.beta.threads.runs.retrieve(
        thread_id=thread_id,
        run_id=run.id
    )
    
    # 関数呼び出しが必要な場合
    if run.status == "requires_action":
        # AI が「search_hotel を実行してほしい」と言った場合の処理
        tool_calls = run.required_action.submit_tool_results.tool_calls
        
        for tool_call in tool_calls:
            # 実際のホテル検索処理(例)
            if tool_call.function.name == "search_hotel":
                result = "〇〇ホテル:1泊8000円"  # 実装例
                
                # 結果をスレッドに送り返す
                client.beta.threads.runs.submit_tool_results(
                    thread_id=thread_id,
                    run_id=run.id,
                    tool_results=[
                        {
                            "tool_call_id": tool_call.id,
                            "output": result
                        }
                    ]
                )

# AIの返答を取得
messages = client.beta.threads.messages.list(thread_id=thread_id)
print(messages.data[0].content[0].text)

4. 実装時の確認ステップ(4分)

次の 4つを順番に試してください:

Step 1:OpenAI API キーをセット

export OPENAI_API_KEY="sk-proj-xxxxxxxx"  # 実際のキーに置き換え

Step 2:最小限のテストコードを実行 アシスタント作成のコードだけ走らせて、ID が得られるか確認。

Step 3:ユーザーメッセージを送る 会話ループのコードで「簡単な質問」(関数呼び出しなし)をテスト。答えが返るか確認。

Step 4:関数呼び出しが発動するメッセージでテスト 「ホテルを探して」など、関数が必要な質問をして、requires_action 状態になるか確認。

つまずきやすいところ

問題 1:「requires_action が返ってこない」

原因:関数の定義がアシスタントに反映されていない可能性があります。

解決方法:assistants.update() で新しいツールを登録した後、必ず新しい run を作り直してください。キャッシュが残っていることがあります。

問題 2:「関数の結果を返したのに次に進まない」

原因:submit_tool_results() のレスポンス後、run.status をすぐに確認する前に一呼吸置く必要があります。

解決方法:submit_tool_results() 後に、小さなスリープ(0.5秒)を入れてから run.retrieve() で状態を確認してください。

import time
client.beta.threads.runs.submit_tool_results(...)
time.sleep(0.5)
run = client.beta.threads.runs.retrieve(...)

問題 3:「複数回の関数呼び出しが連続で必要な場合どうする?」

たとえば「ホテルを探した後、その近くのレストランも探す」みたいなケースです。

解決方法:while ループの中に tool_calls に対するループを入れることで、複数の関数をまとめて処理できます。OpenAI側が「次は requires_action」と判定するまでループが続きます。

慣れてきたら試したいこと

・WebアプリケーションとしてFastAPI で公開する

今までのコードはスクリプトですが、これを Web API にすると、フロントエンド(ブラウザやモバイルアプリ)から呼び出せるようになります。

FastAPI で StreamingResponse を使うと、ホテル検索の結果を「待つ間も少しずつ表示」みたいなリアルタイム表示も可能になります。

・複数の関数を組み合わせる

「ホテル検索」「飛行機検索」「レストラン検索」など、複数の関数をアシスタントに教えることで、「3日間の京都旅行プランを丸ごと立ててください」という複雑な依頼に対応できるようになります。

・AIのシステム指示を工夫する

Assistants の instructions パラメータの書き方を工夫することで、AIの返答スタイルを変えられます。

たとえば:

  • 「敬語で丁寧に返答してください」
  • 「常に予算の観点からコスト・パフォーマンスの良さを優先してください」
  • 「3人用のプランを提案するときは、必ずグループで割った1人当たりの料金も表示してください」

こういった指示を加えることで、より実務的で使いやすいAIになります。


あわせて読みたい

参考ソース