🔖

Amazon BedrockのCitations機能を使ってテキストを引用してみよう!

に公開

こんにちは👋

2025年7月1日に、Amazon BedrockにおけるCitationsのサポートが発表されました。

https://aws.amazon.com/jp/about-aws/whats-new/2025/06/citations-api-pdf-claude-models-amazon-bedrock/

https://dev.classmethod.jp/articles/amazon-bedrock-claude-citations-api-pdf-support-update/

Citationsとは、生成AIに参考となるドキュメントを与えてテキストを生成させる際に、回答が元のドキュメントのどの部分を引用して生成されたのか?という情報をレスポンスに追加できる機能です。

ClaudeモデルにおけるCitations自体は、2025年の1月頃にAnthropicから発表されていたのですが、半年の期間を経てようやくAmazon Bedrockでも利用可能になりました。

https://www.anthropic.com/news/introducing-citations-api

今回はCitationsの使い方や注意点について簡単に紹介していこうと思います。
(発表されてから急いで書いたので、内容が間違ってたらごめんなさい🙇‍♂️)

Citationsの使い方

まずは、Converse APIにおけるCitationsの使い方を見てみましょう。
Citationsを利用する際のフィールドは、以下のようなイメージになります。

{
    "role": "user",
    "content": [
        {
            "document": {
                "name": "My Document",
                "source": {
                    "text": 生成の参考にしてほしいドキュメント,
                },
                "format": "txt",
                "citations": {
                    "enabled": True,
                },
            }
        },
        {
            "text": "ユーザからの質問",
        },
    ],
}

ドキュメントを指定する部分は従来のコードと同じですが、citationsフィールドにenabled: Trueを渡すことで、レスポンスにドキュメントのどの部分が引用されたのかを含めてくれるようになります。

Citationsを利用できるドキュメントはプレーンテキストおよびPDFのみであり、formatフィールドにtxtpdfのどちらかを指定する必要があります。それ以外のHTMLやMarkdownなどを取り込んでCitationsを利用することは、今のところできないため注意する必要があります。

Amazon Bedrockのユーザガイドやboto3の公式ドキュメントに、各フィールドの意味や使い方、注意点について詳しく記載されているので、こちらも参考にしてみてください。

https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_DocumentBlock.html

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse.html

Citationsを利用できる基盤モデル

Amazon BedrockでCitationsを利用できる基盤モデルは、2025年7月現在だと以下の4つです。

  • Claude 4 Sonnet
  • Claude 4 Opus
  • Claude 3.7 Sonnet
  • Claude 3.5 Sonnet V2 (v1は対応していません)

実際にConverse APIでClaude 3 HaikuやClaude 3 Sonnetを使ってCitationsを利用しようとすると、以下のようなエラーが発生します。

botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the Converse operation: This model doesn't support citations.

また、Anthropic公式のCitationsの紹介には、Claude 3.7 SonnetにおけるCitationsの利用について以下のような警告が記載されているため、こちらにも注意する必要があります。

Claude Sonnet 3.7は、他のClaudeモデルと比較して、ユーザーからのより明確な指示がない限り、引用を行う可能性が低い場合があります。Claude Sonnet 3.7で引用を使用する際は、例えば「回答を裏付けるために引用を使用してください」といった追加の指示をuserに含めることを推奨します。

https://docs.anthropic.com/en/docs/build-with-claude/citations

引用の様子を見てみる

実際にコードを実行してレスポンスを確かめてみましょう。
最近たまごっちにハマっているので、たまごっち公式のハウツーガイドを渡してみます。

https://tamagotchi-official.com/jp/series/connection/howto/

まずは、対象のコンテンツをロードして、タグや属性などの余計な部分を取り除きます。

本来Converse APIには、ドキュメントとして直接HTMLを渡すこともできるのですが、前述の理由からまずはプレーンテキストに変換します(HTML文字列をプレーンテキストだと言い張って読み込むこともできますが、トークン数が無駄に増えるのでオススメはしないです)。

コンテンツをロードする関数(一部)
def load_source():
    source_path = Path("assets", "たまごっち.html")

    html = source_path.read_text()
    soup = BeautifulSoup(html, "html.parser")

    text_maker = HTML2Text()
    text_maker.ignore_links = True
    text_maker.ignore_images = True

    return text_maker.handle(str(soup.find("main")))
抽出されたドキュメント
Tamagotchi Connectionで、たまごっちをお世話しよう!
たまごっちは、お世話の仕方でいろんな姿に成長するよ。
ミニゲームであそんだり、ごはんをあげたり、病気を治したり…
たくさんの遊びをドドーンと紹介!

(中略)

### パスワードを探してアイテムをさらにゲット!

…ひみつのコードを見つけた?
ショップカウンターでAボタンを押してね。
そのあとAボタンを素早く3回押して、おみせやさんが驚いた顔をしたら、
ひみつのコードを入力しよう!

次に、Converse APIに先ほどロードしたドキュメントを渡して、質問に対する回答の内容を引用付きで生成させてみます。

Citationsを実行する
import json
import boto3

client = boto3.client("bedrock-runtime", region_name="us-east-1")
source = load_source()

response = client.converse(
    modelId="us.anthropic.claude-sonnet-4-20250514-v1:0",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "document": {
                        "name": "Tamagotchi Document",
                        "source": {
                            "text": source,
                        },
                        "format": "txt",
                        "citations": {
                            "enabled": True,
                        },
                    }
                },
                {
                    "text": "ひみつのコードの入力方法を教えてー!!",
                },
            ],
        }
    ],
)

print(json.dumps(response, indent=2, ensure_ascii=False))
レスポンスの中身
{

 (中略)

  "output": {
    "message": {
      "role": "assistant",
      "content": [
        {
          "text": "ひみつのコードの入力方法は以下の通りです:\n\n"
        },
        {
          "citationsContent": {
            "content": [
              {
                "text": "ショップカウンターでAボタンを押してね。そのあとAボタンを素早く3回押して、おみせやさんが驚いた顔をしたら、ひみつのコードを入力しよう!"
              }
            ],
            "citations": [
              {
                "title": "Tamagotchi Document",
                "sourceContent": [
                  {
                    "text": "* チェックメーターで  \n状態をチェック!\n\n  * ごはんや  \nおやつをあげよう!\n\n  * うんちを流して…\n  * ときにはしつけも!\n  * 病気になったら  \n治療してね。\n\n  * 夜は電気をオフに!\n\n### 50以上のたまごっちと出会おう!\n\n __\n\nたまごっちは、たまごから成長していくよ!  \nキャラクターは全部で50以上!\n\n  * きちんとお世話しないと  \n死んでしまうことも…  \n大切に育ててね。\n\n  * \n\n### ミニゲームで、ごっちポイントをゲット!\n\n __\n\nたまごっちたちは、遊ぶのが大好き!  \nミニゲームで遊んで、  \nごきげんを  \n満タンにして、  \nごっちポイントも  \nためちゃおー!\n\n  * ♪あつめ\n  * しりずもう\n  * はたあげ\n  * へでぃんぐ\n  * きおく\n  * かけっこ\n  * ふりーすろー\n\n### 150以上のアイテムをあつめよー!\n\n __\n\nおみせに行ったり、  \n訪問販売から買い取ったり、  \nアイテムをたくさんあつめよー!\n\n  *   * \n\n### パスワードを探してアイテムをさらにゲット!\n\n __\n\n10桁の数字のパスワード探して、  \nアイテムをゲットしよう!\n\n  * \n\n…ひみつのコードを見つけた?  \nショップカウンターでAボタンを押してね。  \nそのあとAボタンを素早く3回押して、おみせやさんが驚いた顔をしたら、  \nひみつのコードを入力しよう!\n\n  * \n\n## ____\n\n赤外線を使って、ツーしんしよー!  \nたまごっち同士で友だちになったり、  \nゲームをしたり、  \nプレゼントを交換したりできるよ!\n\n  *   *     * ほうもん\n    * ぷれぜんと\n    * ゲーム\n    * 結婚も…?\n    * 結婚したら、次の世代を  \n育てることもできるよ!\n\n"
                  }
                ],
                "location": {
                  "documentChar": {
                    "documentIndex": 0,
                    "start": 185,
                    "end": 993
                  }
                }
              }
            ]
          }
        },
        {
          "text": "\n\n手順をまとめると:\n1. ショップカウンターでAボタンを1回押す\n2. その後、Aボタンを素早く3回連続で押す\n3. おみせやさんが驚いた顔をしたら成功\n4. その状態でひみつのコード(10桁の数字)を入力する\n\nこの方法で隠されたアイテムを入手できるようですね!"
        }
      ]
    }
  },

 (中略)

}

レスポンスの中に何やら見慣れないcitationsContentというフィールドが増えていますね。

これが「ドキュメントのどの部分を引用したのか?」という内容になります。citationsContentの中には引用されたテキストや引用箇所の開始位置・終了位置なども含まれています。

ちょっとややこしいですが、各フィールドは以下のような関係になっています。

message
├── content
│   └── text: ドキュメントを引用せずに生成されたテキスト
└── citationsContent
    ├── content
    │   └── text: ドキュメントを引用して生成されたテキスト
    └── citations
        └── sourceContent、locationなど...

Citationsに関するフィールドをスッパリ分離することで、ドキュメントを引用して生成されたテキスト部分とそうでないテキスト部分を明確に区別して取得することができるという訳です。

従来はプロンプトあるいはツールでゴリ押しするしかなかったCitationsですが、APIのフィールド追加だけで簡単に引用箇所を取得できるようになるのは嬉しいですね。

チャンク単位で引用する

RAGでCitationsを利用する場合は、ベクトルDBから取得したチャンクをそのままsourceに含めることもできます(この場合はformatによる指定は不要です)。

{
    "document": {
        "name": "Tamagotchi Document",
        "source": {
            "content": [
                {"text": "たまごっちチャンク その1"},
                {"text": "たまごっちチャンク その2"},
                {"text": "たまごっちチャンク その3"}
            ]
        },
        "citations": {
            "enabled": True
        },
    },
}

この状態でテキスト生成を実行すると、以下のようなレスポンスに変わります。

レスポンスの中身
{

  (中略)

  "output": {
    "message": {
      "role": "assistant",
      "content": [
        {
          "citationsContent": {
            "content": [
              {
                "text": "まめっちは勉強もスポーツも大好きで、何事にも一生懸命なたまごっちです。趣味は発明ですが、ときどき失敗することもあります。また、絵はあまり得意じゃないようです"
              }
            ],
            "citations": [
              {
                "title": "Tamagotchi Document",
                "sourceContent": [
                  {
                    "text": "# まめっち\n\n勉強もスポーツも大好きで、何事にも一生懸命なたまごっち。\n趣味は発明だけど、ときどき失敗することも…。絵はあまり得意じゃないみたい!?\n"
                  }
                ],
                "location": {
                  "documentChunk": {
                    "documentIndex": 0,
                    "start": 2,
                    "end": 3
                  }
                }
              }
            ]
          }
        },
        {
          "text": "!\n\nとても真面目で努力家な性格のたまごっちのようですね。発明が趣味というのも知的で面白い特徴です!"
        }
      ]
    }
  },

  (中略)

}

どこが変わったのかというとdocumentChardocumentChunkに変わっています。
チャンク単位でドキュメントを与えると、自動的にチャンク単位での引用に切り替えてくれるみたいですね。

また、質問と関係ないたまごっちに関するチャンクもたくさん与えていたのですが、関連するチャンクのみを適切に判断してレスポンスを返してくれています。今まで必死にCitations用のプロンプトを調整していたのは何だったのかという気分です。

とはいえ、与えられたチャンクは全て同じドキュメントから派生したチャンクである(あるいは連続したチャンクである)...という前提を感じるので、もしかしたら意図した使い方ではないかもしれません。

しかしながら、RAGにおける悩みどころだった取得したコンテキストをプロンプトへどう組み込むか?を考える手間が減るので、サクッと試してみる分には便利だと思います。

まとめ・感想

AnthropicにCitations機能が搭載されてから、いつAmazon Bedrockに来るのか首を長くして待っていたのですが、ようやく実装されて嬉しいです。

テキストを意図通りに引用してくれると楽しいので、ぜひCitations触ってみてください。

Discussion