2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Chat Completions APIの会話は毎回パラレルワールド? Responses APIと比較して理解する会話の継続性

2
Last updated at Posted at 2026-09-14

はじめに

LLMを使ったチャットアプリケーションを作っていると、当たり前のように「前の会話を覚えている」ように見えます。

例えば、

User:
私の名前は田中です。

Assistant:
田中さんですね。

User:
私の名前は何ですか?

Assistant:
田中さんです。

という会話です。

ChatGPTなどのチャットUIを普段使っていると自然に感じますが、LLM APIを利用する場合、

「なぜ2回目の質問で、1回目の内容を知っているのか?」

を理解しておくことは重要です。

今回はOpenAI APIの

  • Chat Completions API
  • Responses API

を実際にPythonから呼び出して、会話の継続性、いわゆる Conversation Continuity がどのように実現されているのか確認してみます。


Chat Completions APIは基本的に「別世界」

まずChat Completions APIについて考えてみます。

1回目に次のメッセージを送ったとします。

私の名前は田中です。

LLMから、

田中さんですね。

と回答されたとします。

続いて2回目のAPIリクエストとして、

私の名前は何ですか?

だけを送ります。

人間同士の会話なら当然「田中さん」と回答できそうですが、Chat Completions APIではそうではありません。

概念的には次のようになります。

1回目のリクエスト

┌──────────────────────┐
│ 世界 A               │
│                      │
│ User: 私の名前は田中 │
│                      │
└──────────────────────┘


2回目のリクエスト

┌──────────────────────┐
│ 世界 B               │
│                      │
│ User: 私の名前は?   │
│                      │
└──────────────────────┘

2回目の世界から1回目の世界は見えません。

世界Bの LLM に 世界A の話をしてもわかるわけがありません。

個人的にはこれを、

「Chat Completions APIの各リクエストは、履歴を渡さなければパラレルワールド」

と考えると、とても分かりやすいと思っています。

同じAPIキーを使っていても、同じモデルを使っていても、それだけで前回の会話をモデルが覚えているわけではありません。


Chat Completions APIで実験する

まずOpenAIのPython SDKをインストールします。

pip install -U openai

APIキーを環境変数に設定しておきます。

Windows PowerShellの場合は、

$env:OPENAI_API_KEY="sk-..."

LinuxやWSLの場合は、

export OPENAI_API_KEY="sk-..."

などで設定できます。

以下のコードで実験します。

from openai import OpenAI

client = OpenAI()

MODEL = "gpt-5"


# ========================================
# 1回目
# ========================================

messages = [
    {
        "role": "user",
        "content": "私の名前は田中です。覚えておいてください。"
    }
]

response1 = client.chat.completions.create(
    model=MODEL,
    messages=messages
)

assistant_message = response1.choices[0].message.content

print("=== 1回目 ===")
print("User: 私の名前は田中です。覚えておいてください。")
print("Assistant:", assistant_message)


# ========================================
# 2回目:履歴を渡さない
# ========================================

response2 = client.chat.completions.create(
    model=MODEL,
    messages=[
        {
            "role": "user",
            "content": "私の名前は何ですか?"
        }
    ]
)

print("\n=== 2回目:履歴なし ===")
print("User: 私の名前は何ですか?")
print("Assistant:", response2.choices[0].message.content)


# ========================================
# 3回目:履歴を渡す
# ========================================

messages.append({
    "role": "assistant",
    "content": assistant_message
})

messages.append({
    "role": "user",
    "content": "私の名前は何ですか?"
})

response3 = client.chat.completions.create(
    model=MODEL,
    messages=messages
)

print("\n=== 3回目:履歴あり ===")
print("User: 私の名前は何ですか?")
print("Assistant:", response3.choices[0].message.content)

実際の回答内容はモデルによって多少変わりますが、おおむね次のような結果になります。

$ python ./chat_completions.py
=== 1回目 ===
User: 私の名前は田中です。覚えておいてください。
Assistant: 承知しました。お名前は田中さんですね。覚えておきます。今後は田中さんとお呼びします。ほかに覚えておいてほしいことがあれば教えてください。

=== 2回目:履歴なし ===
User: 私の名前は何ですか?
Assistant: まだお名前は伺っていません。よろしければ、何とお呼びすればよいですか?

=== 3回目:履歴あり ===
User: 私の名前は何ですか?
Assistant: 田中さんです。

ポイントは3回目です。

3回目のAPIリクエストでは、実際には次の情報をまとめてLLMに渡しています。

User:
私の名前は田中です。覚えておいてください。

Assistant:
承知しました。お名前は田中さんですね。覚えておきます。今後は田中さんとお呼びします。ほかに覚えておいてほしいことがあれば教えてください。

User:
私の名前は何ですか?

つまりLLM自身が以前のAPIリクエストを思い出したわけではありません。

アプリケーション側が過去の会話をもう一度LLMへ渡している

というのが重要です。

図にすると、

┌────────────────────────────┐
│ アプリケーション           │
│                            │
│ User: 名前は田中           │
│ Assistant: 田中さんですね │
│ User: 名前は?             │
└────────────┬───────────────┘
             │
             │ messages
             ▼
          ┌─────┐
          │ LLM │
          └─────┘

となります。

このようにChat Completions APIでは、会話履歴をアプリケーション側で管理する設計が一般的です。


Responses APIではどうなるのか

次にResponses APIを試してみます。

Responses APIには、

previous_response_id

という仕組みがあります。

OpenAIのAPI Referenceでは、previous_response_id は前回のResponseを指定し、multi-turn conversationを作成するためのものとして定義されています。

これを利用すると、アプリケーション側で毎回すべての会話履歴を messages のような形に組み立て直さなくても、前回のResponseにつながる形で会話を継続できます。


Responses APIで実験する

先ほどと同じ「田中さん」の実験を行います。

from openai import OpenAI

client = OpenAI()

MODEL = "gpt-5"


# ========================================
# 1回目
# ========================================

response1 = client.responses.create(
    model=MODEL,
    input="私の名前は田中です。覚えておいてください。"
)

print("=== 1回目 ===")
print("User: 私の名前は田中です。覚えておいてください。")
print("Assistant:", response1.output_text)
print("Response ID:", response1.id)


# ========================================
# 2回目:previous_response_id なし
# ========================================

response2_without_history = client.responses.create(
    model=MODEL,
    input="私の名前は何ですか?"
)

print("\n=== 2回目:previous_response_id なし ===")
print("User: 私の名前は何ですか?")
print("Assistant:", response2_without_history.output_text)


# ========================================
# 3回目:previous_response_id あり
# ========================================

response2_with_history = client.responses.create(
    model=MODEL,
    previous_response_id=response1.id,
    input="私の名前は何ですか?"
)

print("\n=== 3回目:previous_response_id あり ===")
print("previous_response_id:", response1.id)
print("User: 私の名前は何ですか?")
print("Assistant:", response2_with_history.output_text)

こちらも実際の文章は多少変化しますが、概ね次のようになります。

$ python responses.py
=== 1回目 ===
User: 私の名前は田中です。覚えておいてください。
Assistant: 覚えておきます。田中さん、よろしくお願いします!
呼び方は「田中さん」でよろしいですか?ニックネームなどご希望があれば教えてください。
Response ID: resp_05exxxxxx

=== 2回目:previous_response_id なし ===
User: 私の名前は何ですか?
Assistant: まだお名前を伺っていないので分かりません。よければ、何とお呼びすればいいか教えてください。

=== 3回目:previous_response_id あり ===
previous_response_id: resp_05exxxxxx
User: 私の名前は何ですか?
Assistant: 田中さんです。

ここで重要なのが、

previous_response_id=response1.id

です。

1回目のResponse IDを2回目のリクエストへ渡すことで、前回のResponseにつながる会話として扱うことができます。

Python SDKの output_text は、Response内のテキスト出力を取得するための便利なプロパティとして提供されています。


パラレルワールドではなく「世界がつながる」

先ほどの表現を使うと、Responses APIでは次のように考えることができます。

previous_response_id を指定しなかった場合、

世界 A

┌───────────────────┐
│ 名前は田中です    │
└───────────────────┘


世界 B

┌───────────────────┐
│ 私の名前は?      │
└───────────────────┘

という別々の世界になります。

一方で、

previous_response_id=response1.id

を指定すると、

Response 1
┌───────────────────┐
│ 名前は田中です    │
└─────────┬─────────┘
          │
          │ previous_response_id
          ▼
Response 2
┌───────────────────┐
│ 私の名前は?      │
└───────────────────┘

というように、前の世界と次の世界をつなぐことができます。


Chat CompletionsとResponses APIの違い

今回の実験だけに絞って比較すると、次のようになります。

API 会話履歴の継続方法 アプリから送るもの
Chat Completions 過去の会話を再送 messages
Responses 前回のResponseを参照可能 previous_response_id
どちらも何も指定しない 過去の会話を認識できない 現在の入力のみ

Chat Completionsの場合は、

messages = [
    {
        "role": "user",
        "content": "私の名前は田中です"
    },
    {
        "role": "assistant",
        "content": "田中さんですね"
    },
    {
        "role": "user",
        "content": "私の名前は何ですか?"
    }
]

のように、アプリケーション側で会話履歴を持つことになります。

一方Responses APIでは、

response1 = client.responses.create(
    model=MODEL,
    input="私の名前は田中です"
)

response2 = client.responses.create(
    model=MODEL,
    previous_response_id=response1.id,
    input="私の名前は何ですか?"
)

という形で会話をつなげられます。

概念的には、

Chat Completions
──────────────────────────────

Application

  過去のUser発言
        +
  過去のAssistant発言
        +
  今回のUser発言
        │
        ▼
       LLM


Responses API
──────────────────────────────

Response 1
    │
    │ Response ID
    ▼
Response 2
    │
    │ Response ID
    ▼
Response 3

という違いになります。


では、LLMが会話を「記憶」しているのか?

ここは少し注意が必要です。

previous_response_id を使うと、

AIが前回の会話を覚えた

ように感じます。

しかし、

LLMそのものに人間のような長期記憶が追加された

と考えるのは少し違います。

Chat Completionsではアプリケーション側が会話履歴を渡します。

Responses APIでは、OpenAIのAPI側が管理するResponseを参照することで、過去の文脈につながる形で次の推論を行います。

つまり、

LLMが勝手に思い出している

のではなく、

次の推論で必要となる過去のコンテキストが
何らかの方法でLLMへ与えられている

と理解した方がよいと思います。


previous_response_idにも注意点がある

previous_response_id は便利ですが、

前回のResponseに関係するすべての設定が自動的に永久に引き継がれる

という意味ではありません。

例えばOpenAIのAPI Referenceでは、previous_response_id を使用しても、前回Responseの instructions は次のResponseへ自動的には引き継がれないと説明されています。

したがって、

response1 = client.responses.create(
    model=MODEL,
    instructions="必ず日本語で回答してください。",
    input="私の名前は田中です"
)

としたからといって、

response2 = client.responses.create(
    model=MODEL,
    previous_response_id=response1.id,
    input="私の名前は何ですか?"
)

で、前回の instructions そのものまで継承されると考えるべきではありません。

システムとして必要な指示は、各リクエストで適切に管理する必要があります。

また、Responses APIで管理されるResponseには保存期間やデータ保持ポリシーがあります。OpenAIの現在のデータコントロール資料では、Responses APIのApplication Stateはデフォルトで一定期間保持される仕様になっています。Zero Data Retentionなどを利用する場合は挙動が異なるため、本番システムではこの点も確認が必要です。

そのため previous_response_id は、

ユーザーの情報を永久に覚えておく長期メモリ

とは別の仕組みとして考えた方がよいでしょう。


Conversation Continuityという考え方

今回扱っているのは、英語では Conversation Continuity や Context Continuity と表現できます。

単純なLLM APIの呼び出しでは、

今回の入力
    ↓
   LLM
    ↓
今回の出力

だけです。

しかしチャットアプリケーションになると、

過去の会話
    +
現在の入力
    ↓
   LLM
    ↓
現在の回答

という形で文脈を継続する必要があります。

さらに実際のAIシステムでは、「会話履歴」だけで十分とは限りません。

例えば、

会話履歴
    │
    ├── ユーザーがすでに伝えた情報
    │
    ├── 現在どの処理まで進んでいるか
    │
    ├── どのエージェントが対応しているか
    │
    ├── 外部システムから取得した情報
    │
    └── 過去のセッションから引き継ぐ情報

など、LLM以外の状態も含めて管理する必要が出てきます。

つまり、実際のサービスにおけるConversation Continuityは、

LLMのコンテキスト

だけの問題ではなく、

システム全体として
何を「会話の状態」として管理するのか

というアーキテクチャの問題になってきます。


APIの会話継続から、AI Platformの会話継続へ

ここまでの話を整理すると、

Chat Completions
        │
        │
        │ アプリケーション側で
        │ 会話履歴を管理
        ▼
Conversation Continuity
        ▲
        │
        │ previous_response_id
        │
Responses API

となります。

しかし実際のAIアプリケーションでは、さらにその上に、

┌─────────────────────────┐
│ AI Application / Platform│
│                         │
│ ・Session               │
│ ・User State            │
│ ・Business State        │
│ ・Conversation History  │
│ ・Long-term Context     │
└────────────┬────────────┘
             │
             ▼
        LLM / LLM API

というレイヤーが存在します。

ここまで来ると「会話を継続する」という言葉の意味も、

単に直前の発言を覚えていること

から、

ユーザーとの対話状態をシステム全体として継続すること

へ変わってきます。


STORM PlatformにおけるConversation Continuity

弊社 Sionic AI が提供している STORM Platform は、LLMと Chat Completions 系の API で接続しています。

Chat Completions API vs Responses API という観点で見ると Responses API の方が新しいアーキテクチャで機能も豊富です。OpenAI も最新モデルでは Responses API の利用を強く推しています。

観点 Responses API Chat Completions API
位置づけ 新しい統合API 従来型のチャットAPI
会話継続 previous_response_id や conversation 基本的に messages を渡して管理
新規開発 推奨 既存互換用途
Conversation Continuity previous_response_id や conversation で継続可能 アプリ側で過去の messages を再送
履歴管理の主体 OpenAI側の仕組みも利用可能 基本的にアプリケーション側
履歴なしで再リクエスト 独立した会話 独立した会話

しかしながら、現時点(2026年9月)では、複数社(Anthropic, Google 等)のLLM のサポートの互換性を重要視しており、Chat Completions API系を採用しています。
これも、今後の LLM の進化をキャッチアップするため、徐々に Responses API 系に移行・拡張していきます。

STORM Platform での会話継続性

先ほど書いたように、STORM Platform は、Chat Completions API 系を採用しているため履歴を渡さない限り会話の継続性はありません。

image.png

この状況で、「私の名前は何ですか?」と聞いても覚えていないはずです。

image.png

ところが、STORM Platformでは『田中さん』と回答できます。→なぜでしょうか?

これは、STORM Platform 側で履歴を自動で管理してその履歴を LLM に渡しているため会話の継続性が維持されます。

image.png

ただし、LLM Node の 「ヒストリーターン数」を 0 に設定すると、パラレルワールドに戻ります。

ヒストリーターンを0にした場合

image.png

このように、STORM Platform は、AI アプリケーションを構築するために必要・便利な機能をプラットフォーム側で提供しております。
ご興味があれば、1か月の無料トライアルも可能ですので、お試しください。

まとめ

今回、Chat Completions APIとResponses APIを使って、「会話の継続性」がどのように実現されているのかを確認しました。

Chat Completions APIでは、過去の会話を messages として再度渡さなければ、それぞれのAPIリクエストは基本的に独立した世界として考えることができます。

つまり、

履歴を渡さないChat Completionsは
毎回パラレルワールド

です。

一方Responses APIでは、

previous_response_id

を使用することで、前回のResponseにつながる形でmulti-turn conversationを構成できます。

ただし、どちらの場合も重要なのは、

LLMそのものが人間のように過去を思い出している

わけではないということです。

何らかの形で過去のコンテキストを次の推論へつなげる仕組みが存在しています。

そして実際のAIシステムでは、Conversation ContinuityはLLMの会話履歴だけでは完結しません。

セッション、ユーザー情報、業務状態、外部システムの情報、さらに必要であれば過去のセッションまで含め、

「何を会話の状態として、どこまで継続させるのか」

を設計する必要があります。

この観点で考えると、Chat Completions APIとResponses APIの違いは、単なるAPI仕様の違いではなく、

AIシステムにおける会話状態の管理を考えるための入り口

として見ることができると思います。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?