Bedrock Knowledge Bases RetrieveAndGenerateの「複数ターン」の動作確認

記事タイトルとURLをコピーする

はじめに

こんにちは、久保(賢)です。

AWSにおいてRAG(Retrieval-Augmented Generation)を実現する際に必要なナレッジベースをフルマネージドに提供するサービス「Amazon Bedrock Knowledge Bases」には、そのデータを検索するためのAPIとして RetrieveRetrieveAndGenerate(および RetrieveAndGenerateStream) が用意されています。

Retrieveは単純にナレッジベースから関連する情報を検索して返すAPIですが、RetrieveAndGenerateは検索した情報を元に、指定した基盤モデル(LLM)が回答生成までしてくれるAPIです。

Amazon Bedrock Knowledge BasesのRetrieveAndGenerate APIにはsessionIdというパラメータがあり、公式ドキュメントには以下のように記載されています。

sessionId The unique identifier of the session. When you first make a RetrieveAndGenerate request, Amazon Bedrock automatically generates this value. You must reuse this value for all subsequent requests in the same conversational session. This value allows Amazon Bedrock to maintain context and knowledge from previous interactions. You can't explicitly set the sessionId yourself.

「Amazon Bedrockが以前のインタラクションからコンテキストと知識を維持できる」とあります。

これはつまり、会話履歴を元にして、ユーザの質問やモデルの回答を保持し、次回の会話でそれを参照できるということが想定されます。

本記事では、この sessionId を利用した RetrieveAndGenerateStream API が、複数回の呼び出しにおいてどのような回答を生成し、かつ具体的にはどのような仕組み(動作)で実現されているのかを、Bedrockのモデル呼び出しログを確認した結果を共有いたします。
RetrieveAndGenerate APIも同様の結果でした。

対象読者

  • Amazon Bedrock Knowledge Basesを利用している方
  • RetrieveAndGenerate APIのsessionIdの挙動に興味がある方
  • RetrieveAndGenerateの"会話履歴"の処理について知りたい方

確認した際の環境

項目
リージョン ap-northeast-1(東京)
Knowledge Base S3 Vectorを利用したナレッジベースを使用
推論モデル Claude Sonnet 4.6(global.anthropic.claude-sonnet-4-6
Embeddingモデル Amazon Titan Text Embeddings V2
API RetrieveAndGenerateStream

最初に結論

今回のモデル呼び出しログの確認では、sessionId による複数ターンの文脈維持は、主に「会話履歴を用いた検索クエリ生成」で実現されていることが観測されました。
少なくとも今回の観測では、回答生成側の呼び出しには会話履歴そのものは含まれておらず、生成されたクエリと検索結果を元に回答しているように見えます。

実利用上は問題になるケースは少ないかと思いますが、ユースケースによっては、回答生成側にも会話履歴が渡されるようなAPIやプロンプト設計が必要になる可能性があります。

1回目 sessionId なし(初回リクエスト)

シンプルなRAGの流れ。ユーザの質問をそのままベクトル化して検索・回答生成を行う。

U
ユーザの質問
例:「サーバーワークスの2025年度の業績は?」
1
InvokeModel Titan Embed でベクトル化
ユーザの質問テキストをそのまま Embedding モデルに入力し、ベクトルに変換
2
Knowledge Base ベクトル検索
ベクトル化されたクエリで Knowledge Base を検索し、関連ドキュメントを取得
3
ConverseStream Claude で回答生成
system: 回答生成プロンプト + 検索結果(<search_results>)
messages: ユーザの質問テキスト
R
API レスポンス(ストリーム)
モデル出力から <answer> タグを除去し、テキスト部分のみ + sessionId を返却

2回目以降 sessionId あり(複数ターン)

先頭に「クエリ生成処理」が追加される。会話履歴を参照して曖昧な質問を具体的な検索クエリに変換する。

U
ユーザの質問
例:「4つ目の項目について詳しく教えて」(曖昧な指示語を含む質問)
1
Converse クエリ生成処理 ★ 2回目以降のみ
会話履歴 + ユーザの質問から、ベクトル検索用のクエリを生成
例:「4つ目の項目について詳しく教えて」→「サーバーワークス 経常利益 2025年度 詳細」
2
InvokeModel Titan Embed でベクトル化
生成されたクエリを Embedding モデルでベクトルに変換
3
Knowledge Base ベクトル検索
生成されたクエリのベクトルで Knowledge Base を検索し、関連ドキュメントを取得
4
ConverseStream Claude で回答生成
生成されたクエリ + 検索結果を元に回答を生成(会話履歴は含まれない)
R
API レスポンス(ストリーム)
<answer> タグを除去し、テキスト部分のみ + sessionId を返却

観測されたポイント

  • sessionId によるコンテキスト維持は、主に「会話履歴を使ったクエリ生成」で実現されている
  • 会話履歴に蓄積されるのはユーザの元の質問ではなく「生成されたクエリ」と「回答」のペア
  • 回答生成(ConverseStream)には会話履歴が渡されないため、検索結果と生成クエリのみで回答を生成
  • 会話が重なるほど、ユーザの元の表現や意図がセッション上から失われていく可能性がある

1回目と2回目以降の比較

1回目(sessionId なし)

モデル呼び出し: 2回

ユーザの質問 → Embedding → KB検索 → 回答生成

ユーザの質問がそのままベクトル化・回答生成に使用される。

2回目以降(sessionId あり)

モデル呼び出し: 3回(+クエリ生成)

ユーザの質問 → クエリ生成 → Embedding → KB検索 → 回答生成

会話履歴を元にクエリを生成。回答生成自体には会話履歴は渡されない。

検証方法

以下のPythonスクリプトを使い、sessionIdを引き継ぎながら複数回の会話を行い、その際にBedrockのモデル呼び出しログに記録される内容を確認しました。

スクリプトを表示する
#!/usr/bin/env python3
"""
Amazon Bedrock Knowledge Bases - RetrieveAndGenerate ストリーミング検索スクリプト
"""
  
import boto3
  
# -----------------------------------------------------------------------
# 設定
# -----------------------------------------------------------------------
KNOWLEDGE_BASE_ID = "XXXXXXXXXX"
REGION = "ap-northeast-1"
  
# グローバル cross-region inference profile の model ID
MODEL_ARN = "global.anthropic.claude-sonnet-4-6"
  
# 取得する検索結果の件数
NUMBER_OF_RESULTS = 5
# -----------------------------------------------------------------------
  
  
def retrieve_and_generate_stream(client, query: str, session_id: str | None) -> str | None:
    """クエリを送信してストリーミングで回答を表示する。次の session_id を返す。"""
    params = {
        "input": {"text": query},
        "retrieveAndGenerateConfiguration": {
            "type": "KNOWLEDGE_BASE",
            "knowledgeBaseConfiguration": {
                "knowledgeBaseId": KNOWLEDGE_BASE_ID,
                "modelArn": MODEL_ARN,
                "retrievalConfiguration": {
                    "vectorSearchConfiguration": {
                        "numberOfResults": NUMBER_OF_RESULTS,
                    }
                },
            },
        },
    }
    if session_id:
        params["sessionId"] = session_id

    response = client.retrieve_and_generate_stream(**params)

    stream = response.get("stream")
    if stream is None:
        print("[ERROR] ストリームが取得できませんでした。")
        return session_id
  
    citations = []
    next_session_id = session_id
  
    print("\nAssistant: ", end="", flush=True)
    for event in stream:
        if "output" in event:
            print(event["output"].get("text", ""), end="", flush=True)
  
        if "citations" in event:
            citations = event["citations"]
  
        # セッション ID はレスポンスの sessionId フィールドで返される
        if "sessionId" in event:
            next_session_id = event["sessionId"]
  
    print("\n")
  
    # sessionId はレスポンスのトップレベルにも含まれる
    if not next_session_id:
        next_session_id = response.get("sessionId")
  
    return next_session_id
  
  
def main() -> None:
    client = boto3.client("bedrock-agent-runtime", region_name=REGION)
    session_id = None
  
    print("Bedrock Knowledge Base チャット (終了: 'exit' または Ctrl+C)")
    print("=" * 60)
  
    while True:
        try:
            query = input("You: ").strip()
        except (EOFError, KeyboardInterrupt):
            print("\n終了します。")
            break
  
        if not query:
            continue
        if query.lower() in ("exit", "quit", "q"):
            print("終了します。")
            break
  
        session_id = retrieve_and_generate_stream(client, query, session_id)
  
  
if __name__ == "__main__":
    main()

ナレッジベースには、弊社の2025年2月期の業績に関する決算資料を登録しています。

会話は以下の3ターンで実施しました。

ターン ユーザの質問
1回目 「サーバーワークスの2025年度の業績は?」
2回目 「4つ目の項目について詳しく教えて」
3回目 「3つ目の項目については?」

2回目と3回目の質問は、1回目の回答内容を踏まえた曖昧な指示語(「4つ目の項目」「3つ目の項目」)を意図的に使用しています。sessionIdによるコンテキスト維持が機能しなければ、正しく回答できない質問です。

実行結果

以下が実際のスクリプト実行結果全体(API利用者側からの視点)です。sessionIdを引き継ぐことで、2回目以降の曖昧な質問にも正しく回答できていることがわかります。

% python scripts/kb_search.py
Bedrock Knowledge Base チャット (終了: 'exit' または Ctrl+C)
============================================================
You: サーバーワークスの2025年度の業績は?

Assistant:
サーバーワークス(2025年2月期)の連結業績実績は以下の通りです。

- **売上高**:35,717百万円(売上高比100.0%)
- **売上総利益**:4,104百万円(売上高比11.5%)
- **営業利益**:1,072百万円(売上高比3.0%)
- **経常利益**:1,066百万円(売上高比3.0%)
- **親会社株主に帰属する当期純利益**:677百万円(売上高比1.9%)

前期(2024年2月期)と比較して、売上高・営業利益・経常利益などすべての主要科目で大幅に上回る結果となり、通期を通して好調を維持しました。また、修正予想(2025年1月14日発表)に対しても営業利益は107.8%、経常利益は107.0%、当期純利益は116.0%と、いずれも予想を上回って着地しました。


You: 4つ目の項目について詳しく教えて

Assistant:
サーバーワークスグループの2025年2月期(2025年度)の経常利益に関する詳細は以下の通りです。

- **経常利益:1,066百万円**(経常利益率:3.0%)

【前期比較】
- 2024年2月期実績:1,032百万円(経常利益率 3.8%)
- 2025年2月期実績:1,066百万円(経常利益率 3.0%)
- 前期比:**103.2%**

【業績予想との比較】
- 2025年2月期の当初業績予想(修正前):492百万円
- 修正後業績予想:996百万円
- 実績:1,066百万円(予想比 107.0%)

全体として、売上高・営業利益・経常利益などすべての主要科目で前期実績を大幅に上回る結果となりました。

【2026年2月期(翌年度)の業績予想】
参考として、2026年2月期の経常利益予想は **1,171百万円** となっており、前期比での増益が見込まれています。


You: 3つ目の項目については?

Assistant:
サーバーワークスグループの2025年2月期(2025年度)の営業利益に関する詳細は以下の通りです。

**2025年2月期 営業利益:1,072百万円(営業利益率:3.0%)**

### 前期比較
| 項目 | 2024年2月期 | 2025年2月期 | 前期比 |
|------|------------|------------|--------|
| 売上高 | 約35,580百万円 | 35,717百万円 | 100.4% |
| 売上総利益 | 4,034百万円 | 4,104百万円 | 101.1% |
| 営業利益 | 897百万円 | 1,072百万円 | 119.5% |
| 経常利益 | 1,032百万円 | 1,066百万円 | 103.2% |
| 親会社株主に帰属する当期純利益 | 638百万円 | 677百万円 | 106.1% |

### ポイント
- 通期を通して好調を維持し、売上高・営業利益・経常利益などすべての主要科目で前期実績を大幅に上回る結果となった。
- クラウド利用の加速や為替相場の好影響なども利益増加に寄与。
- 人材投資やAWSとの戦略的協業に関するコストなど、将来の成長に資する投資も継続。

### 2026年2月期の業績予想(参考)
- 営業利益:1,140百万円(前期比増加)

スクリプトではストリームのイベントから event["output"]["text"] をそのまま出力しているだけですが、実行結果には <answer><sources> といったタグは含まれていません。後述するモデル呼び出しログを見ると、裏側のConverseStreamという推論APIの出力には <answer><answer_part><text>...</text><sources>...</sources></answer_part></answer> という構造化されたタグが含まれています。
そのため、RetrieveAndGenerate APIがモデルの出力からこれらのタグを除去し、テキスト部分のみをストリームとして返していることがわかります。

補足・注意事項

今回の確認はBedrockのモデル呼び出しログで行っています。 APIとしてはRetrieveAndGenerateStreamを呼び出していますが、モデル呼び出しログには、クエリ生成のためのConverseやEmbeddingのためのInvokeModelなど、複数のモデル呼び出しが記録されています。 RetrieveAndGenerateStreamの裏側では基盤モデルがどのように利用されているのかが確認可能です。

上記のようにログを元にした確認結果であるため、あくまで本記事執筆時点の挙動・内容であることにご留意ください。今後のアップデートで挙動やシステムプロンプト等が変わる可能性があります。

モデル呼び出しログの確認結果

1回目の会話(sessionIdなし)

1回目の会話では、以下の2つのモデル呼び出しが記録されました。

① InvokeModel(Titan Embed) → クエリのベクトル化
② ConverseStream(Claude Sonnet 4.6)   → 検索結果を元に回答を生成

これはシンプルなRAGの流れです。ユーザの質問をそのままEmbeddingモデルでベクトル化し、Knowledge Baseを検索して、検索結果をシステムプロンプトに含めた上でClaude Sonnet 4.6(ConverseStream)に回答を生成させています。

① InvokeModel(Titan Embed)

ログを表示する
{
    "timestamp": "2026-03-26T11:10:17Z",
    "accountId": "123456789012",
    "region": "ap-northeast-1",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "InvokeModel",
    "modelId": "arn:aws:bedrock:ap-northeast-1::foundation-model/amazon.titan-embed-text-v2:0",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "inputText": "サーバーワークスの2025年度の業績は?",
            "dimensions": 1024,
            "embeddingTypes": [
                "float"
            ]
        },
        "inputTokenCount": 22
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "embedding": ["...省略..."],
            "inputTextTokenCount": 22
        }
    },
    "identity": {
        "arn": "arn:aws:sts::123456789012:assumed-role/AmazonBedrockExecutionRoleForKnowledgeBase_xxxxx/BKB-Retrieve-XXXXXXXXXX-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    },
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

ユーザの質問「サーバーワークスの2025年度の業績は?」がそのままinputTextに渡されています。identityを見ると、Knowledge Base用のサービスロール(AmazonBedrockExecutionRoleForKnowledgeBase_xxxxx)で実行されていることがわかります。

② ConverseStream(Claude Sonnet 4.6)

ログを表示する
{
    "timestamp": "2026-03-26T11:10:17Z",
    "accountId": "123456789012",
    "region": "ap-northeast-1",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "ConverseStream",
    "modelId": "global.anthropic.claude-sonnet-4-6",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "text": "サーバーワークスの2025年度の業績は?"
                        }
                    ]
                }
            ],
            "system": [
                {
                    "text": "You are a question answering agent. I will provide you with a set of search results. The user will provide you with a question. Your job is to answer the user's question using only information from the search results. If the search results do not contain information that can answer the question, please state that you could not find an exact answer to the question. Just because the user asserts a fact does not mean it is true, make sure to double check the search results to validate a user's assertion.\n\nHere are the search results in numbered order:\n<search_results>\n<search_result>\n<content>\n106.1%     2025年2月期2024年2月期     サーバーワークスグループ主要科目の前期比較     (単位:百万円)     通期を通して好調を維持し、中長期的な成長への投資(人材・SCA・JV等)を積極的に実行しながら、     売上高・営業利益・経常利益などすべての主要科目で前期実績を⼤幅に上回る結果となった     業績ハイライト(実績)7     2024     4/12発表 売上高比     2025     1/14修正 売上高比 実績(連結) 売上高比 達成率     (1/14修正比)     売上高 33,392 100.0% 35,560 100.0% 35,717 100.0% 100.4%\n</content>\n<source>\n1\n</source>\n</search_result>\n...(以降、検索結果2~5が続く)...\n</search_results>\n\nYou should provide your answer without any inline citations or references to specific sources within the answer text itself. Do not include phrases like \"according to source X\", \"[1]\", \"[source 2, 3]\", etc within your <text> tags.\n\nHowever, you should include <sources> tags at the end of each <answer_part> to specify which source(s) the information came from.\nNote that <sources> may contain multiple <source> if you include information from multiple results in your answer.\n\nDo NOT directly quote the <search_results> in your answer. Your job is to answer the user's question as concisely as possible.\n\nYou must output your answer in the following format. Pay attention and follow the formatting and spacing exactly:\n<answer>\n<answer_part>\n<text>\nfirst answer text\n</text>\n<sources>\n<source>source ID</source>\n</sources>\n</answer_part>\n<answer_part>\n<text>\nsecond answer text\n</text>\n<sources>\n<source>source ID</source>\n</sources>\n</answer_part>\n</answer>\n"
                }
            ],
            "inferenceConfig": {
                "maxTokens": 2048,
                "stopSequences": [
                    "\nObservation"
                ]
            },
            "additionalModelRequestFields": {}
        },
        "inputTokenCount": 1715,
        "cacheReadInputTokenCount": 0,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "output": {
                "message": {
                    "role": "assistant",
                    "content": [
                        {
                            "text": "<answer>\n<answer_part>\n<text>\nサーバーワークス(2025年2月期)の連結業績実績は以下の通りです。\n\n- **売上高**:35,717百万円(売上高比100.0%)\n- **売上総利益**:4,104百万円(売上高比11.5%)\n- **営業利益**:1,072百万円(売上高比3.0%)\n- **経常利益**:1,066百万円(売上高比3.0%)\n- **親会社株主に帰属する当期純利益**:677百万円(売上高比1.9%)\n\n前期(2024年2月期)と比較して、売上高・営業利益・経常利益などすべての主要科目で大幅に上回る結果となり、通期を通して好調を維持しました。また、修正予想(2025年1月14日発表)に対しても営業利益は107.8%、経常利益は107.0%、当期純利益は116.0%と、いずれも予想を上回って着地しました。\n</text>\n<sources>\n<source>1</source>\n<source>2</source>\n<source>4</source>\n</sources>\n</answer_part>\n</answer>"
                        }
                    ]
                }
            },
            "stopReason": "end_turn",
            "metrics": {
                "latencyMs": 7570
            },
            "usage": {
                "inputTokens": 1715,
                "outputTokens": 358,
                "totalTokens": 2073
            }
        },
        "outputTokenCount": 358
    },
    "identity": {
        "arn": "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_AdministratorAccess_xxxxxxxxxxxxxxxx/user@example.com"
    },
    "inferenceRegion": "us-east-2",
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

ログからわかるポイントは以下の通りです。

  • messagesにはユーザの質問がそのまま含まれている
  • systemプロンプトには、You are a question answering agent.から始まる回答生成用のプロンプトと、Knowledge Baseから取得した検索結果(<search_results>)が含まれている
  • 出力は<answer>タグで構造化されており、回答テキストと引用元(<sources>)が含まれている。先述の通り、これらのタグはRetrieveAndGenerate APIにより除去され、ユーザにはテキスト部分のみがストリームで返される
  • identityはAPIを呼び出したユーザ自身のロールであり、①のInvokeModelとは異なるロールで実行されている
  • inferenceRegionus-east-2となっており、クロスリージョン推論が利用されていることがわかる

2回目の会話(sessionIdあり)

2回目の会話で「4つ目の項目について詳しく教えて」と質問すると、モデル呼び出しログには3つのログが記録されました。

① Converse(Claude Sonnet 4.6)         → クエリの生成(★新たに追加)
② InvokeModel(Titan Embed) → 生成されたクエリのベクトル化
③ ConverseStream(Claude Sonnet 4.6)   → 検索結果を元に回答を生成

1回目と比べて、先頭にConverseによる呼び出しが追加されています。

① Converse(クエリ生成処理)

ログを表示する
{
    "timestamp": "2026-03-26T11:10:36Z",
    "accountId": "123456789012",
    "region": "ap-northeast-1",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "Converse",
    "modelId": "global.anthropic.claude-sonnet-4-6",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "text": "4つ目の項目について詳しく教えて"
                        }
                    ]
                }
            ],
            "system": [
                {
                    "text": "You are a query creation agent. You will be provided with a function and a description of what it searches over. The user will provide you a question, and your job is to determine the optimal query to use based on the user's question.\n\nHere are a few examples of queries formed by other search function selection and query creation agents:\n<examples>\n<example>\n<question>What if my vehicle is totaled in an accident?</question>\n<generated_query>\"what happens if my vehicle is totaled\"</generated_query>\n</example>\n<example>\n<question>I am relocating within the same state. Can I keep my current agent?</question>\n<generated_query>\"can I keep my current agent when moving in state\"</generated_query>\n</example>\n</examples>\n\nYou should also pay attention to the conversation history between the user and the search engine in order to gain the context necessary to create the query.\nHere's another example that shows how you should reference the conversation history when generating a query:\n\n<example>\nInput:\n<conversation_history>\n<conversation>\n<question>How many vehicles can I include in a quote in Kansas</question>\n<answer>You can include 5 vehicles in a quote if you live in Kansas</answer>\n</conversation>\n</conversation_history>\n\nUser: What about texas?\n\nOutput:\n<generated_query>\nHow many vehicles can be included in a quote in Texas?\n</generated_query>\n</example>\n\nIMPORTANT: the elements in the <example> tags should not be assumed to have been provided to you to use UNLESS they are also explicitly given to you below. All of the values and information within the examples (the questions, answers, and function calls) are strictly part of the examples and have not been provided to you.\n\nHere is the current conversation history:\n<conversation_history>\n<conversation>\n<question>サーバーワークスの2025年度の業績は?</question>\n<answer>\nサーバーワークス(2025年2月期)の連結業績実績は以下の通りです。\n\n- **売上高**:35,717百万円(売上高比100.0%)\n- **売上総利益**:4,104百万円(売上高比11.5%)\n- **営業利益**:1,072百万円(売上高比3.0%)\n- **経常利益**:1,066百万円(売上高比3.0%)\n- **親会社株主に帰属する当期純利益**:677百万円(売上高比1.9%)\n\n前期(2024年2月期)と比較して、売上高・営業利益・経常利益などすべての主要科目で大幅に上回る結果となり、通期を通して好調を維持しました。また、修正予想(2025年1月14日発表)に対しても営業利益は107.8%、経常利益は107.0%、当期純利益は116.0%と、いずれも予想を上回って着地しました。\n</answer>\n</conversation>\n\n</conversation_history>\n\nThe format for the <generated_query> MUST be: <generated_query>$GENERATED_QUERY_VALUE</generated_query>\n\nIf you are unable to determine which function to call or if you are unable to generate a query, respond with 'Sorry, I am unable to assist you with this request.'\n"
                }
            ],
            "inferenceConfig": {
                "maxTokens": 2048,
                "stopSequences": [
                    "\nObservation"
                ]
            },
            "additionalModelRequestFields": {}
        },
        "inputTokenCount": 846,
        "cacheReadInputTokenCount": 0,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "output": {
                "message": {
                    "role": "assistant",
                    "content": [
                        {
                            "text": "<generated_query>サーバーワークス 経常利益 2025年度 詳細</generated_query>"
                        }
                    ]
                }
            },
            "stopReason": "end_turn",
            "metrics": {
                "latencyMs": 3708
            },
            "usage": {
                "inputTokens": 846,
                "cacheReadInputTokens": 0,
                "cacheWriteInputTokens": 0,
                "outputTokens": 34,
                "totalTokens": 880
            }
        },
        "outputTokenCount": 34
    },
    "identity": {
        "arn": "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_AdministratorAccess_xxxxxxxxxxxxxxxx/user@example.com"
    },
    "inferenceRegion": "us-east-2",
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

このログから、以下のことがわかります。

  • operationConverse(非ストリーミング)であり、1回目にはなかった新しい呼び出し
  • systemプロンプトにYou are a query creation agent.と記載されており、会話履歴を元にベクトル検索用のクエリを生成する専用の処理が実行されている
  • <conversation_history>に1回目の質問と回答のペアが含まれている
  • messagesにはユーザの新しい質問「4つ目の項目について詳しく教えて」がそのまま入っている
  • 出力として <generated_query>サーバーワークス 経常利益 2025年度 詳細</generated_query> が生成された → 「4つ目の項目」という相対的な指定と会話履歴から、「経常利益」であると正しく解釈されている

② InvokeModel(Titan Embed)

ログを表示する
{
    "timestamp": "2026-03-26T11:10:40Z",
    "accountId": "123456789012",
    "region": "ap-northeast-1",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "InvokeModel",
    "modelId": "arn:aws:bedrock:ap-northeast-1::foundation-model/amazon.titan-embed-text-v2:0",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "inputText": "サーバーワークス 経常利益 2025年度 詳細",
            "dimensions": 1024,
            "embeddingTypes": [
                "float"
            ]
        },
        "inputTokenCount": 26
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "embedding": ["...省略..."],
            "inputTextTokenCount": 26
        }
    },
    "identity": {
        "arn": "arn:aws:sts::123456789012:assumed-role/AmazonBedrockExecutionRoleForKnowledgeBase_xxxxx/BKB-Retrieve-XXXXXXXXXX-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    },
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

inputTextがユーザの元の質問ではなく、 クエリ生成処理が生成した「サーバーワークス 経常利益 2025年度 詳細」 になっています。

③ ConverseStream(Claude Sonnet 4.6)

ログを表示する
{
    "timestamp": "2026-03-26T11:10:40Z",
    "accountId": "123456789012",
    "region": "ap-northeast-1",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "ConverseStream",
    "modelId": "global.anthropic.claude-sonnet-4-6",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "text": "サーバーワークス 経常利益 2025年度 詳細"
                        }
                    ]
                }
            ],
            "system": [
                {
                    "text": "You are a question answering agent. ...(回答フォーマットの指示は1回目と同様)...\n\nHere are the search results in numbered order:\n<search_results>\n...(生成されたクエリに基づく新しい検索結果5件)...\n</search_results>\n"
                }
            ],
            "inferenceConfig": {
                "maxTokens": 2048,
                "stopSequences": [
                    "\nObservation"
                ]
            },
            "additionalModelRequestFields": {}
        },
        "inputTokenCount": 1714,
        "cacheReadInputTokenCount": 0,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "output": {
                "message": {
                    "role": "assistant",
                    "content": [
                        {
                            "text": "<answer>\n<answer_part>\n<text>\nサーバーワークスグループの2025年2月期(2025年度)の経常利益に関する詳細は以下の通りです。\n\n- **経常利益:1,066百万円**(経常利益率:3.0%)\n\n【前期比較】\n- 2024年2月期実績:1,032百万円(経常利益率 3.8%)\n- 2025年2月期実績:1,066百万円(経常利益率 3.0%)\n- 前期比:**103.2%**\n\n【業績予想との比較】\n- 2025年2月期の当初業績予想(修正前):492百万円\n- 修正後業績予想:996百万円\n- 実績:1,066百万円(予想比 107.0%)\n\n全体として、売上高・営業利益・経常利益などすべての主要科目で前期実績を大幅に上回る結果となりました。\n</text>\n<sources>\n<source>4</source>\n<source>5</source>\n</sources>\n</answer_part>\n<answer_part>\n<text>\n【2026年2月期(翌年度)の業績予想】\n参考として、2026年2月期の経常利益予想は **1,171百万円** となっており、前期比での増益が見込まれています。\n</text>\n<sources>\n<source>1</source>\n<source>3</source>\n</sources>\n</answer_part>\n</answer>"
                        }
                    ]
                }
            },
            "stopReason": "end_turn",
            "metrics": {
                "latencyMs": 8042
            },
            "usage": {
                "inputTokens": 1714,
                "outputTokens": 429,
                "totalTokens": 2143
            }
        },
        "outputTokenCount": 429
    },
    "identity": {
        "arn": "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_AdministratorAccess_xxxxxxxxxxxxxxxx/user@example.com"
    },
    "inferenceRegion": "us-east-2",
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

以下のことがわかります。

  • ConverseStreamのmessagesに含まれているのは、ユーザが入力した元の質問(「4つ目の項目について詳しく教えて」)ではなく、 クエリ生成処理が生成したクエリ「サーバーワークス 経常利益 2025年度 詳細」 である
  • ConverseStreamのログには 会話履歴は含まれていないsystemプロンプトの構造は1回目と同一であり、新しい検索結果のみが含まれている
  • つまり回答生成モデルは、過去の会話を知らない状態で、生成されたクエリと検索結果だけで回答を生成している

3回目の会話(sessionIdあり)

3回目も2回目と同様に3つのモデル呼び出しが記録されました。

① Converse(クエリ生成処理)

ログを表示する
{
    "timestamp": "2026-03-26T11:13:59Z",
    "accountId": "123456789012",
    "region": "ap-northeast-1",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "Converse",
    "modelId": "global.anthropic.claude-sonnet-4-6",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "text": "3つ目の項目については?"
                        }
                    ]
                }
            ],
            "system": [
                {
                    "text": "You are a query creation agent. ...(examplesなどの指示は2回目と同様)...\n\nHere is the current conversation history:\n<conversation_history>\n<conversation>\n<question>サーバーワークスの2025年度の業績は?</question>\n<answer>\nサーバーワークス(2025年2月期)の連結業績実績は以下の通りです。\n\n- **売上高**:35,717百万円(売上高比100.0%)\n- **売上総利益**:4,104百万円(売上高比11.5%)\n- **営業利益**:1,072百万円(売上高比3.0%)\n- **経常利益**:1,066百万円(売上高比3.0%)\n- **親会社株主に帰属する当期純利益**:677百万円(売上高比1.9%)\n\n前期(2024年2月期)と比較して、売上高・営業利益・経常利益などすべての主要科目で大幅に上回る結果となり、通期を通して好調を維持しました。また、修正予想(2025年1月14日発表)に対しても営業利益は107.8%、経常利益は107.0%、当期純利益は116.0%と、いずれも予想を上回って着地しました。\n</answer>\n</conversation>\n<conversation>\n<question>サーバーワークス 経常利益 2025年度 詳細</question>\n<answer>\nサーバーワークスグループの2025年2月期(2025年度)の経常利益に関する詳細は以下の通りです。\n\n- **経常利益:1,066百万円**(経常利益率:3.0%)\n\n【前期比較】\n- 2024年2月期実績:1,032百万円(経常利益率 3.8%)\n- 2025年2月期実績:1,066百万円(経常利益率 3.0%)\n- 前期比:**103.2%**\n\n【業績予想との比較】\n- 2025年2月期の当初業績予想(修正前):492百万円\n- 修正後業績予想:996百万円\n- 実績:1,066百万円(予想比 107.0%)\n\n全体として、売上高・営業利益・経常利益などすべての主要科目で前期実績を大幅に上回る結果となりました。\n\n【2026年2月期(翌年度)の業績予想】\n参考として、2026年2月期の経常利益予想は **1,171百万円** となっており、前期比での増益が見込まれています。\n</answer>\n</conversation>\n\n</conversation_history>\n"
                }
            ],
            "inferenceConfig": {
                "maxTokens": 2048,
                "stopSequences": [
                    "\nObservation"
                ]
            },
            "additionalModelRequestFields": {}
        },
        "inputTokenCount": 1215,
        "cacheReadInputTokenCount": 0,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "output": {
                "message": {
                    "role": "assistant",
                    "content": [
                        {
                            "text": "会話履歴から、前回の質問では経常利益の詳細(2番目の項目)について確認しており、最初の回答では5つの主要項目(売上高、売上総利益、営業利益、経常利益、当期純利益)が挙げられていました。3つ目の項目は「営業利益」です。\n\n<generated_query>サーバーワークス 営業利益 2025年度 詳細</generated_query>"
                        }
                    ]
                }
            },
            "stopReason": "end_turn",
            "metrics": {
                "latencyMs": 23083
            },
            "usage": {
                "inputTokens": 1215,
                "cacheReadInputTokens": 0,
                "cacheWriteInputTokens": 0,
                "outputTokens": 135,
                "totalTokens": 1350
            }
        },
        "outputTokenCount": 135
    },
    "identity": {
        "arn": "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_AdministratorAccess_xxxxxxxxxxxxxxxx/user@example.com"
    },
    "inferenceRegion": "us-east-2",
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

このログから、いくつかの点が確認できます。

  • <conversation_history>に1回目と2回目の質問と回答のペアが両方含まれている
  • ただし、2回目のユーザ自身の元々の質問「4つ目の項目について詳しく教えて」は含まれていない
  • 2回目の会話履歴の<question>には、ユーザが実際に入力した「4つ目の項目について詳しく教えて」ではなく、クエリ生成処理が生成した「サーバーワークス 経常利益 2025年度 詳細」が使用されている
    • つまり、会話履歴に蓄積されるのは「ユーザの元の質問」ではなく「生成されたクエリ」である。これは会話が重なるほど、ユーザの元の表現や意図がセッション上から失われていくことを意味する
  • クエリ生成処理の出力を見ると、思考過程が出力に含まれている → 「3つ目の項目は「営業利益」です」と推論した上で<generated_query>サーバーワークス 営業利益 2025年度 詳細</generated_query>を生成している

② InvokeModel(Titan Embed)

ログを表示する
{
    "timestamp": "2026-03-26T11:14:22Z",
    "accountId": "123456789012",
    "region": "ap-northeast-1",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "InvokeModel",
    "modelId": "arn:aws:bedrock:ap-northeast-1::foundation-model/amazon.titan-embed-text-v2:0",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "inputText": "サーバーワークス 営業利益 2025年度 詳細",
            "dimensions": 1024,
            "embeddingTypes": [
                "float"
            ]
        },
        "inputTokenCount": 27
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "embedding": ["...省略..."],
            "inputTextTokenCount": 27
        }
    },
    "identity": {
        "arn": "arn:aws:sts::123456789012:assumed-role/AmazonBedrockExecutionRoleForKnowledgeBase_xxxxx/BKB-Retrieve-XXXXXXXXXX-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    },
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

2回目のInvokeModelも、クエリ生成処理が生成したクエリ「サーバーワークス 営業利益 2025年度 詳細」がinputTextになっています。

③ ConverseStream(Claude Sonnet 4.6)

ログを表示する
{
    "timestamp": "2026-03-26T11:14:22Z",
    "accountId": "123456789012",
    "region": "ap-northeast-1",
    "requestId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "operation": "ConverseStream",
    "modelId": "global.anthropic.claude-sonnet-4-6",
    "input": {
        "inputContentType": "application/json",
        "inputBodyJson": {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "text": "サーバーワークス 営業利益 2025年度 詳細"
                        }
                    ]
                }
            ],
            "system": [
                {
                    "text": "You are a question answering agent. ...(1回目・2回目と同様の回答生成プロンプト)...\n\nHere are the search results in numbered order:\n<search_results>\n...(「営業利益」に関する新しい検索結果5件)...\n</search_results>\n"
                }
            ],
            "inferenceConfig": {
                "maxTokens": 2048,
                "stopSequences": [
                    "\nObservation"
                ]
            },
            "additionalModelRequestFields": {}
        },
        "inputTokenCount": 1714,
        "cacheReadInputTokenCount": 0,
        "cacheWriteInputTokenCount": 0
    },
    "output": {
        "outputContentType": "application/json",
        "outputBodyJson": {
            "output": {
                "message": {
                    "role": "assistant",
                    "content": [
                        {
                            "text": "<answer>\n<answer_part>\n<text>\nサーバーワークスグループの2025年2月期(2025年度)の営業利益に関する詳細は以下の通りです。\n\n**2025年2月期 営業利益:1,072百万円(営業利益率:3.0%)**\n\n### 前期比較\n| 項目 | 2024年2月期 | 2025年2月期 | 前期比 |\n|------|------------|------------|--------|\n| 売上高 | 約35,580百万円 | 35,717百万円 | 100.4% |\n| 売上総利益 | 4,034百万円 | 4,104百万円 | 101.1% |\n| 営業利益 | 897百万円 | 1,072百万円 | 119.5% |\n| 経常利益 | 1,032百万円 | 1,066百万円 | 103.2% |\n| 親会社株主に帰属する当期純利益 | 638百万円 | 677百万円 | 106.1% |\n\n### ポイント\n- 通期を通して好調を維持し、売上高・営業利益・経常利益などすべての主要科目で前期実績を大幅に上回る結果となった。\n- クラウド利用の加速や為替相場の好影響なども利益増加に寄与。\n- 人材投資やAWSとの戦略的協業に関するコストなど、将来の成長に資する投資も継続。\n\n### 2026年2月期の業績予想(参考)\n- 営業利益:1,140百万円(前期比増加)\n</text>\n<sources>\n<source>3</source>\n<source>4</source>\n<source>5</source>\n<source>1</source>\n</sources>\n</answer_part>\n</answer>"
                        }
                    ]
                }
            },
            "stopReason": "end_turn",
            "metrics": {
                "latencyMs": 6694
            },
            "usage": {
                "inputTokens": 1714,
                "outputTokens": 534,
                "totalTokens": 2248
            }
        },
        "outputTokenCount": 534
    },
    "identity": {
        "arn": "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_AdministratorAccess_xxxxxxxxxxxxxxxx/user@example.com"
    },
    "inferenceRegion": "ap-southeast-4",
    "schemaType": "ModelInvocationLog",
    "schemaVersion": "1.0"
}

3回目のConverseStreamでも2回目と同様に、messagesにはユーザの元の質問ではなく生成されたクエリが含まれ、会話履歴は含まれていません。

まとめ:sessionIdによるコンテキスト維持の動作(執筆時点)

確認結果をまとめると以下のようになります。

※あくまで執筆時点(2026年3月末)での動作確認結果であり、今後のアップデートで変更される可能性がある点はご了承ください。

1回目(sessionIdなし)

ユーザの質問
  → [InvokeModel] Embeddingでベクトル化
  → Knowledge Base検索
  → [ConverseStream] 検索結果 + ユーザの質問 → 回答生成

2回目以降(sessionIdあり)

ユーザの質問
  → [Converse] クエリ生成処理(会話履歴 + ユーザの質問 → 検索クエリ生成)
  → [InvokeModel] 生成されたクエリをEmbeddingでベクトル化
  → Knowledge Base検索
  → [ConverseStream] 検索結果 + 生成されたクエリ → 回答生成

つまり、今回確認した範囲では、sessionId による「コンテキストと知識の維持」は、会話履歴を直接そのまま回答生成へ渡すというより、会話履歴を元に検索クエリを補完・具体化することで実現されているように見えました。

  1. 会話履歴はクエリ生成処理のシステムプロンプトに渡される
    • 過去の質問と回答のペアが<conversation_history>として蓄積される
  2. クエリ生成処理が会話履歴を元に、ベクトル検索に適したクエリを生成する
    • 「4つ目の項目」のような曖昧な指示語を、会話履歴から具体的なキーワード(「経常利益」)に変換する
  3. 回答生成(ConverseStream)自体には会話履歴は渡されない
    • 回答生成に使われるのは、生成されたクエリと新しい検索結果のみ
  4. 会話履歴に残るのはログ上ではユーザの元の質問ではなく、生成されたクエリと回答生成の出力のみとなっていた
    • ユーザの元の表現や意図は、クエリ生成処理によって変換された形で会話履歴に蓄積される

この仕組みにより、ユーザが曖昧な表現で質問しても文脈に沿った適切な検索と回答が可能になっています。
一方で、回答生成(ConverseStream)自体には会話履歴が渡されないため、ユースケースによってはRetrieveAndGenerate APIでは対応が難しい場合があります。例えば、過去の回答内容を踏まえた追加の考察や比較を求めるような質問は、回答生成モデルが過去の質問そのものは参照できないため、期待通りの結果にならない可能性があります。
主に情報の検索が目的であり数回程度のやりとりで済むケースであれば、RetrieveAndGenerate APIの仕組みで十分にコンテキストを維持した会話が可能かと思います。
より複雑な対話や長期的な会話履歴の参照が必要なユースケースでは、別途プロンプト設計やAPI選択を検討する必要があるかと思います。
この点を把握しておくことで、ユースケースに応じた適切なAPIの選択やプロンプト設計に役立つかと思います。

おわりに

RetrieveAndGenerate APIのsessionIdによる「コンテキストと知識の維持」は、裏側ではクエリ生成処理と会話履歴の組み合わせによって実現されているように観測されました。

RetrieveAndGenerate の内部動作はAPIドキュメントだけでは見えにくい部分がありますが、モデル呼び出しログを確認することでsessionId が複数ターン会話でどのように効いているのかを具体的に把握できました。特に、曖昧な指示語を含む質問でも継続的に回答できる背景に、会話履歴を使ったクエリ生成処理があると考えられる点は、実運用での設計やトラブルシュートにも役立ちそうです。

なお、本記事では最低限のパラメータのみ指定してAPIを利用していますが、 APIドキュメントのとおりRetrieveAndGenerateでは回答生成時のプロンプト指定やガードレールの指定なども可能です。これらを活用することで、よりユースケースに適した回答生成が可能になりますので、ぜひご確認ください。

久保 賢二(執筆記事の一覧)

猫とAWSが好きです。