ClaudeのCitations機能をハンズオンで体験しよう:引用の仕組みを徹底理解(Google📒ノートブック付)

AI・機械学習

はじめに

Anthropicの大規模言語モデル Claude には、回答する際に文書からの引用元を自動的に示す「Citations」機能が用意されています。この機能を使うことで、「モデルがどの部分を参照して回答しているのか」を明確に可視化できます。本ハンズオンでは、Google Colab形式のコード例を交えながら、Citationsの仕組みや設定方法をわかりやすく解説します。

対象読者

  • 自前の文書(テキスト・PDFなど)を使ったQAを行いたい方
  • Claudeの引用機能で回答の根拠を明確に示し、ユーザの信頼性を高めたい方
  • 従来のプロンプト制御や埋め込み検索(RAG)と併用し、さらなる精度を目指したい方

到達目標

  • Citations を有効にするためのAPI呼び出し方法を理解する
  • レスポンスに含まれる引用情報の構造(引用テキスト、インデックスなど)を扱えるようになる
  • Plain Text、PDF、Custom Content ドキュメントの違いを理解し、用途に応じて使い分けできるようになる

それでは早速始めていきましょう!

事前準備

Python環境のセットアップ

まずは Google Colab を開き、必要なPythonライブラリをインストールします。
例として requests を使ったAPI呼び出しと、base64 を用いたPDFのエンコードを行う場合を想定します。

!pip install requests

次に、AnthropicのAPIを呼び出すためにAPIキーを設定します。
APIキーは、AnthropicのDeveloper Console等で取得したものを使用してください。

import os
from google.colab import userdata
# os.environ["ANTHROPIC_API_KEY"] = "YOUR_ANTHROPIC_API_KEY_HERE"
os.environ["ANTHROPIC_API_KEY"] = userdata.get('ANTHROPIC_API_KEY')

Note: Colabで実行する場合には、毎回セッションが切れると消えてしまうため、必要に応じて再設定します。

Citations 機能とは?

AnthropicのClaudeに文章やPDFなどのドキュメントを与え、回答の中でその出典(引用箇所)を細かく示してもらう機能です。例えば以下のような特徴があります。

  • 引用箇所のトークン削減
    cited_text はレスポンスの出力トークンに含まれないため、通常のプロンプトベースのソリューションよりもトークン使用量を削減できる可能性があります。

  • 引用の信頼性向上
    モデル内部で標準化されたフォーマットに従い引用を返すため、抽出される引用範囲が文書のどこに存在しているか明確になります。

  • 引用内容の品質向上
    単なるプロンプト制御よりも、関連性の高い箇所をより的確に引用してくれるとレポートされています。

Citations 機能を有効にする手順

AnthropicのAPIにドキュメントを送信する際、JSON形式で citations.enabled = true を設定したドキュメントを含めます。

  1. ドキュメントの形式を指定

    • Plain Text
    • PDF (Base64エンコード)
    • Custom Content (独自に分割単位を指定する場合)
  2. ドキュメントが「チャンク化」される

    • Plain Text/PDF は自動的に文章単位(センテンス単位)でチャンク化されます。
    • Custom Content を使うと、こちらで与えたブロック単位でのみ引用が行われます。
  3. Claudeから引用付きの回答が返ってくる

    • 複数の text ブロックがあり、それぞれに citations リストが含まれる場合があります。
    • PDFならページ番号、テキストなら文字列インデックス、カスタムコンテンツならブロックインデックスで引用箇所が指定されます。

以下で具体的なハンズオンを通して説明していきます。

Plain Text ドキュメントを使った例

サンプル文書の用意

ここでは非常にシンプルなテキストを用意します。

document_text = """The grass is green. The sky is blue."""

このテキストは2文で構成されています。「草は緑、空は青」という内容ですね。

APIリクエスト用のJSON作成

上記テキストを「Plain Text ドキュメント」としてAnthropic APIに送ります。
ここで citations.enabled = true を設定することで引用を有効にしています。

import json

payload = {
    "model": "claude-3-5-sonnet-20241022",  # 適宜更新・変更する
    "max_tokens": 1024,
    "messages": [
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {
                        "type": "text",
                        "media_type": "text/plain",
                        "data": document_text
                    },
                    "title": "My Document",
                    "context": "This is a trustworthy document.",
                    "citations": {"enabled": True}
                },
                {
                    "type": "text",
                    "text": "What color is the grass and sky?"
                }
            ]
        }
    ]
}

json_payload = json.dumps(payload, indent=2)
print(json_payload)

JSONペイロードの中で、ドキュメントが type: "document" として渡されていることや、
citations.enabled = true が設定されていることに注目してください。

APIを呼び出す

次に、requests を使ってAPIリクエストを送ります。
ANTHROPIC_API_KEY は事前に環境変数に設定済みとします)

import requests
import os

api_url = "https://api.anthropic.com/v1/messages"
headers = {
    "content-type": "application/json",
    "x-api-key": os.environ["ANTHROPIC_API_KEY"],
    "anthropic-version": "2023-06-01"
}

response = requests.post(api_url, headers=headers, data=json_payload)
result = response.json()
result

実行後、result にはClaudeからの応答がJSON形式で返ってきます。
Citations 有効の場合、"content" フィールドが以下のような形で返ってくる可能性があります。

{
  "content": [
    {
      "type": "text",
      "text": "According to the document, "
    },
    {
      "type": "text",
      "text": "the grass is green",
      "citations": [
        {
          "type": "char_location",
          "cited_text": "The grass is green.",
          "document_index": 0,
          "document_title": "My Document",
          "start_char_index": 0,
          "end_char_index": 20
        }
      ]
    },
    ...
  ]
}

上記はあくまでも構造の一例です。実際には回答内容に応じて動的に変わる点に注意してください。

PDF ドキュメントを使った例

次にPDFファイルを渡す場合です。PDFの文字データをBase64エンコードしてAPIに送る方法になります。
(スキャン画像PDFのように文字データが無いものは引用できません)

PDFをBase64エンコード

ColabにサンプルPDFをアップロードしたり、ローカル環境にあるPDFファイルを想定します。

import base64

with open("/content/真実の水面.pdf", "rb") as f:
    pdf_data = f.read()

encoded_pdf = base64.b64encode(pdf_data).decode("utf-8")

PDFドキュメントとしてリクエストJSONを作成

pdf_payload = {
    "model": "claude-3-5-sonnet-20241022",
    "max_tokens": 1024,
    "messages": [
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {
                        "type": "base64",
                        "media_type": "application/pdf",
                        "data": encoded_pdf
                    },
                    "title": "PDF Document Title",
                    "context": "Any additional context if needed",
                    "citations": {"enabled": True}
                },
                {
                    "type": "text",
                    "text": "Please summarize the document with citations."
                }
            ]
        }
    ]
}

APIを呼び出す

pdf_json_payload = json.dumps(pdf_payload, indent=2)
pdf_response = requests.post(api_url, headers=headers, data=pdf_json_payload)
pdf_result = pdf_response.json()
pdf_result

PDFの場合は、引用が以下のようにページ番号で表される可能性があります(1-indexed):

{
  "type": "page_location",
  "cited_text": "Some text from page 1.",
  "document_index": 0,
  "document_title": "PDF Document Title",
  "start_page_number": 1,
  "end_page_number": 2
}

Custom Content ドキュメントを使った例

文章を任意のブロック単位で分割しておき、その粒度で引用をしてもらいたい場合に使います。
自動チャンク化(文章単位など)を避けたい場合に便利です。

カスタムブロックの用意

例として、2つの文章をあえて独立ブロックとして定義します。

custom_content = [
    {"type": "text", "text": "First chunk of text."},
    {"type": "text", "text": "Second chunk of text."}
]

Custom Content のリクエストJSON作成

custom_payload = {
    "model": "claude-3-5-sonnet-20241022",
    "max_tokens": 1024,
    "messages": [
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {
                        "type": "content",
                        "content": custom_content
                    },
                    "title": "Document Title",
                    "context": "Additional metadata for the model",
                    "citations": {"enabled": True}
                },
                {
                    "type": "text",
                    "text": "Please cite which block contains the text 'Second chunk of text'."
                }
            ]
        }
    ]
}

API呼び出し

custom_json_payload = json.dumps(custom_payload, indent=2)
custom_response = requests.post(api_url, headers=headers, data=custom_json_payload)
custom_result = custom_response.json()
custom_result

レスポンスに引用がある場合、例えば以下のように content_block_location が使われます:

{
  "type": "content_block_location",
  "cited_text": "Second chunk of text.",
  "document_index": 0,
  "document_title": "Document Title",
  "start_block_index": 1,
  "end_block_index": 2
}

start_block_index ~ end_block_index は0-indexed、かつ end は排他的)

レスポンスの解析

Citations 有効時のレスポンスは、基本的に以下のような大枠の構造をとります。

{
  "completion": "...",
  "stop_reason": "...",
  "content": [
    {
      "type": "text",
      "text": "この文章は~"
    },
    {
      "type": "text",
      "text": "引用部分",
      "citations": [
        {
          "type": "char_location" or "page_location" or "content_block_location",
          "cited_text": "...",
          "document_index": 0,
          ...
        }
      ]
    }
  ],
  ...
}

content 配列内には複数の要素がありますが、citations が付いているのはその一部になります。
引用情報は、たとえば result["content"] を走査して取り出すことが可能です。

引用されたテキストと元のドキュメント位置を抽出

def extract_citations(content_blocks):
    extracted = []
    for block in content_blocks:
        if block.get("type") == "text":
            citations = block.get("citations", [])
            for c in citations:
                # ここでは type, cited_text, document_index などを取得
                extracted.append({
                    "block_text": block["text"],
                    "citation_type": c["type"],
                    "cited_text": c["cited_text"],
                    "document_index": c["document_index"],
                    "document_title": c.get("document_title"),
                })
    return extracted

citations_data = extract_citations(result.get("content", []))
for c in citations_data:
    print(c)

ここで得られる cited_text は、トークン課金には含まれない部分です(追加でお得、といったイメージ)。

機能の互換性と留意点

  1. Token費用
    Citations を有効にすると、ドキュメント解析のためにわずかに追加トークンを消費しますが、引用部分そのものは出力トークンに数えられません。

  2. バッチ処理やキャッシュ
    他のAPI機能(Prompt Caching、Batch APIなど)と組み合わせても動作します。

  3. 画像PDFは非対応
    テキスト抽出ができないPDFは引用の対象にならない点に注意してください。

  4. タイトルやコンテキストは引用対象にならない
    titlecontext フィールドはモデルへのヒントとしては利用されますが、そこからの文字列は引用されません。

📒ノートブック

Google Colab

まとめ

Citations 機能を使うことで、Claudeが回答の根拠となる原文を明確に示し、また引用テキスト部分のトークンコストを節約できます。
運用時は以下を意識してください。

  • ドキュメントは適切な形式(Plain Text、PDF、Custom Content)を選ぶ。
  • citations.enabled = true を忘れずに設定。
  • レスポンスで引用部分をJSONパースして必要な箇所を取得。

ぜひ自身のアプリケーションやワークフローに応用してみてください。

コメント

タイトルとURLをコピーしました