引言

Anthropic API 支持引文功能,该功能使 Claude 在回答有关文档的问题时能够提供详细的引文。引文是许多由 LLM 驱动的应用程序中的宝贵功能,可帮助用户跟踪和验证响应中信息的来源。

引文支持以下模型:

  • claude-3-5-sonnet-20241022
  • claude-3-5-haiku-20241022

引文功能是基于提示的引文技术的替代方案。使用此功能具有以下优点:

  • 基于提示的技术通常要求 Claude 输出其打算引用的源文档的完整引用。这会增加输出令牌,从而增加成本。
  • 引文功能不会返回指向未提供为有效来源的文档或位置的引文。
  • 在测试期间,我们发现引文功能生成的引文比基于提示的技术具有更高的召回率和精确度。

引文的文档可以在此处找到。

设置

首先,让我们安装所需的库并初始化我们的 Anthropic 客户端。

!pip install anthropic  --quiet
import anthropic
import os
import json

ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY")
# ANTHROPIC_API_KEY = "" # 在此处放置您的 API 密钥!

client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)

文档类型

引文支持三种不同的文档类型。输出的引文类型取决于所引用的文档类型:

  • 纯文本文档引文 → 字符位置格式
  • PDF 文档引文 → 页面位置格式
  • 自定义内容文档引文 → 内容块位置格式

我们将在下面的示例中探讨如何处理这三种文档类型。

纯文本文档

使用纯文本文档引文时,您将原始文本作为文档提供给模型。您可以提供一个或多个文档。这些文本将被自动分块成句子。模型将根据需要引用这些句子。模型可以一次性引用多个句子,但不会引用小于句子的文本。

除了输出的文本外,API 响应还将包含所有引文的结构化数据。

让我们通过一个虚构公司 PetWorld 的帮助中心客户聊天机器人来了解一个完整的示例。

# 读取所有帮助中心文章并创建一个文档列表
articles_dir = './data/help_center_articles'
documents = []

for filename in sorted(os.listdir(articles_dir)):
    if filename.endswith('.txt'):
        with open(os.path.join(articles_dir, filename), 'r') as f:
            content = f.read()
            # 分割标题和正文
            title_line, body = content.split('\n', 1)
            title = title_line.replace('title: ', '')
            documents.append({
                "type": "document",
                "source": {
                    "type": "text",
                    "media_type": "text/plain",
                    "data": body
                },
                "title": title,
                "citations": {"enabled": True}
            })

QUESTION = "我刚完成结账,我的订单追踪号在哪里?追踪包裹在网站上还不可用。"

# 将问题添加到内容中
content = documents

response = client.messages.create(
    model="claude-3-5-sonnet-latest",
    temperature=0.0,
    max_tokens=1024,
    system='您是 PetWorld 的客服机器人。您的任务是简短、有帮助地回答用户的问题。由于您处于聊天界面,请避免提供额外的细节。您将可以访问 PetWorld 的帮助中心文章来帮助您回答问题。',
    messages=[
        {
            "role": "user",
            "content": documents
        },
        {
            "role": "user",
            "content": [{"type": "text", "text": f'这是用户的问题:{QUESTION}'}]
        },

    ]
)

def visualize_raw_response(response):
    raw_response = {"content": []}

    print("\n" + "="*80 + "\n原始响应:\n" + "="*80)

    for content in response.content:
        if content.type == "text":
            block = {
                "type": "text",
                "text": content.text
            }
            if hasattr(content, 'citations') and content.citations:
                block["citations"] = []
                for citation in content.citations:
                    citation_dict = {
                        "type": citation.type,
                        "cited_text": citation.cited_text,
                        "document_title": citation.document_title,
                    }
                    if citation.type == "page_location":
                        citation_dict.update({
                            "start_page_number": citation.start_page_number,
                            "end_page_number": citation.end_page_number
                        })
                    block["citations"].append(citation_dict)
            raw_response["content"].append(block)

    return json.dumps(raw_response, indent=2)

print(visualize_raw_response(response))
================================================================================
原始响应:
================================================================================
{
  "content": [
    {
      "type": "text",
      "text": "根据文档,我可以解释为什么您还看不到追踪信息:"
    },
    {
      "type": "text",
      "text": "一旦您的订单发货,您将收到一封包含追踪号码的电子邮件。如果您在订单确认后 48 小时内未收到追踪号码,请联系我们的客户支持团队寻求帮助。",
      "citations": [
        {
          "type": "char_location",
          "cited_text": "一旦您的订单发货,您将收到一封包含追踪号码的电子邮件。 ",
          "document_title": "订单追踪信息"
        },
        {
          "type": "char_location",
          "cited_text": "如果您在订单确认后 48 小时内未收到追踪号码,请联系我们的客户支持团队。",
          "document_title": "订单追踪信息"
        }
      ]
    },
    {
      "type": "text",
      "text": "\n\n由于您刚刚完成结账,您的订单可能还没有发货。一旦发货,您将通过电子邮件收到追踪信息。"
    }
  ]
}

可视化引文

通过利用引文数据,我们可以创建以下 UI:

  1. 精确显示信息来源
  2. 直接链接到源文档
  3. 在上下文中突出显示引用的文本
  4. 通过透明的来源建立信任

下面是一个简单的可视化函数,它将 Claude 的结构化引文转换为带有编号引用的可读格式,类似于学术论文。

该函数接受 Claude 的响应对象并输出:

  • 带有编号引文标记的文本(例如,“答案 [1] 包含此事实 [2]”)
  • 一个编号的参考列表,显示每个引用的文本及其源文档
def visualize_citations(response):
    """
    接受响应对象并返回带有编号引文的字符串。
    示例输出:“这是纯文本答案 [1][2] 这是更多文本 [3]”
    并在下方附带引文列表。
    """
    # 用于存储唯一引文的字典
    citations_dict = {}
    citation_counter = 1

    # 最终格式化的文本
    formatted_text = ""
    citations_list = []

    print("\n" + "="*80 + "\n格式化响应:\n" + "="*80)

    for content in response.content:
        if content.type == "text":
            text = content.text
            if hasattr(content, 'citations') and content.citations:
                # 按引文在文本中出现的顺序排序
                def get_sort_key(citation):
                    if hasattr(citation, 'start_char_index'):
                        return citation.start_char_index
                    elif hasattr(citation, 'start_page_number'):
                        return citation.start_page_number
                    elif hasattr(citation, 'start_block_index'):
                        return citation.start_block_index
                    return 0  # 回退

                sorted_citations = sorted(content.citations, key=get_sort_key)

                # 处理每个引文
                for citation in sorted_citations:
                    doc_title = citation.document_title
                    cited_text = citation.cited_text.replace('\n', ' ').replace('\r', ' ')
                    # 删除可能创建的任何多个空格
                    cited_text = ' '.join(cited_text.split())

                    # 为此引文创建唯一键
                    citation_key = f"{doc_title}:{cited_text}"

                    # 如果这是新引文,请将其添加到我们的字典中
                    if citation_key not in citations_dict:
                        citations_dict[citation_key] = citation_counter
                        citations_list.append(f"[{citation_counter}] \"{cited_text}\" 找到于 \"{doc_title}\"")
                        citation_counter += 1

                    # 将引文编号添加到文本中
                    citation_num = citations_dict[citation_key]
                    text += f" [{citation_num}]"

            formatted_text += text

    # 将格式化文本与引文列表合并
    final_output = formatted_text + "\n\n" + "\n".join(citations_list)
    return final_output

formatted_response = visualize_citations(response)
print(formatted_response)
================================================================================
格式化响应:
================================================================================
根据文档,我可以解释为什么您还看不到追踪信息:您将收到一封包含追踪号码的电子邮件,一旦您的订单发货。如果您在订单确认后 48 小时内未收到追踪号码,请联系我们的客户支持团队寻求帮助。[1][2]

由于您刚刚完成结账,您的订单可能还没有发货。一旦发货,您将通过电子邮件收到追踪信息。

[1] "一旦您的订单发货,您将收到一封包含追踪号码的电子邮件。" 找到于 "订单追踪信息"
[2] "如果您在订单确认后 48 小时内未收到追踪号码,请联系我们的客户支持团队。" 找到于 "订单追踪信息"

PDF 文档

处理 PDF 时,Claude 可以提供引用特定页码的引文,从而轻松跟踪信息来源。以下是 PDF 引文的工作方式:

  • PDF 文档内容以 base64 编码的数据形式提供
  • 文本会自动分块成句子
  • 引文包含信息所在的页码(从 1 开始计数)
  • 模型可以一次性引用多个句子,但不会引用小于句子的文本
  • 虽然图像会被处理,但目前只有文本内容可以被引用

以下是一个使用 Constitutional AI 论文的示例,以演示 PDF 引文:

import base64
import json

# 读取并编码 PDF
pdf_path = 'data/Constitutional AI.pdf'
with open(pdf_path, "rb") as f:
    pdf_data = base64.b64encode(f.read()).decode()

pdf_response = client.messages.create(
    model="claude-3-5-sonnet-latest",
    temperature=0.0,
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {
                        "type": "base64",
                        "media_type": "application/pdf",
                        "data": pdf_data
                    },
                    "title": "Constitutional AI Paper",
                    "citations": {"enabled": True}
                },
                {
                    "type": "text",
                    "text": "Constitutional AI 的主要思想是什么?"
                }
            ]
        }
    ]
)

print(visualize_raw_response(pdf_response))
print(visualize_citations(pdf_response))
================================================================================
原始响应:
================================================================================
{
  "content": [
    {
      "type": "text",
      "text": "根据论文,以下是 Constitutional AI 的关键方面:\n\n"
    },
    {
      "type": "text",
      "text": "Constitutional AI 是一种通过自我改进来训练无害 AI 助手的无害方法,无需任何人类标签来识别有害输出。唯一的人类监督是通过一组规则或原则提供的,因此得名“Constitutional AI”。",
      "citations": [
        {
          "type": "page_location",
          "cited_text": "我们通过自我改进来训练无害 AI 助手的无害方法,无需任何人类标签来识别有害输出。唯一的人类监督是通过一组规则或原则提供的,因此我们称该方法为“Constitutional AI”。 ",
          "document_title": "Constitutional AI Paper",
          "start_page_number": 1,
          "end_page_number": 2
        }
      ]
    },
    {
      "type": "text",
      "text": "\n\n该过程包括两个主要阶段:\n\n1. 监督学习阶段:\n"
    },
    {
      "type": "text",
      "text": "在此阶段,他们从初始模型中采样,生成自我批评和修订,然后使用修订后的响应对原始模型进行微调。",
      "citations": [
        {
          "type": "page_location",
          "cited_text": "在监督阶段,我们从初始模型中采样,然后生成自我批评和修订,然后使用修订后的响应对原始模型进行微调。 ",
          "document_title": "Constitutional AI Paper",
          "start_page_number": 1,
          "end_page_number": 2
        }
      ]
    },
    {
      "type": "text",
      "text": "\n\n2. 强化学习阶段:\n"
    },
    {
      "type": "text",
      "text": "在此阶段,他们:\n- 从微调模型中采样\n- 使用模型评估两个样本中的哪个更好\n- 从这个 AI 偏好数据集中训练一个偏好模型\n- 使用“RL 来自 AI 反馈”(RLAIF)",
      "citations": [
        {
          "type": "page_location",
          "cited_text": "在 RL 阶段,我们从微调模型中采样,使用模型评估两个样本中的哪个更好,然后从这个 AI 偏好数据集中训练一个偏好模型。然后我们使用偏好模型作为奖励信号进行 RL 训练,即我们使用“RL 来自 AI 反馈”(RLAIF)。 ",
          "document_title": "Constitutional AI Paper",
          "start_page_number": 1,
          "end_page_number": 2
        }
      ]
    },
    {
      "type": "text",
      "text": "\n\n主要成果是:\n\n"
    },
    {
      "type": "text",
      "text": "- 他们能够训练一个无害但不过于回避的 AI 助手,该助手通过解释其反对意见来处理有害查询\n- SL 和 RL 方法都可以利用思维链式推理来提高人类判断的性能和 AI 决策的透明度\n- 这些方法使得能够更精确地控制 AI 行为,并且所需的人类标签更少",
      "citations": [
        {
          "type": "page_location",
          "cited_text": "结果,我们能够训练一个无害但不过于回避的 AI 助手,该助手通过解释其反对意见来处理有害查询。SL 和 RL 方法都可以利用思维链式推理来提高人类判断的性能和 AI 决策的透明度。这些方法使得能够更精确地控制 AI 行为,并且所需的人类标签更少。",
          "document_title": "Constitutional AI Paper",
          "start_page_number": 1,
          "end_page_number": 2
        }
      ]
    },
    {
      "type": "text",
      "text": "\n\n"
    },
    {
      "type": "text",
      "text": "最终目标不是完全消除人类监督,而是使其更有效、更透明和更有针对性。虽然这项工作减少了对人类监督以确保无害性的依赖,但他们仍然依赖于有帮助性标签形式的人类监督。研究人员预计,从预训练的 LM 和广泛的提示开始,有可能在没有人为反馈的情况下实现有帮助性和指令遵循性,但这留给未来的工作。",
      "citations": [
        {
          "type": "page_location",
          "cited_text": "通过删除无害性的人类反馈标签,我们已经远离了对人类监督的依赖,并更接近于实现对齐的自我监督方法。然而,在这项工作中,我们仍然依赖于有帮助性标签形式的人类监督。我们预计,从预训练的 LM 和广泛的提示开始,有可能在没有人为反馈的情况下实现有帮助性和指令遵循性,但这留给未来的工作。我们的最终目标不是完全消除人类监督,而是使其更有效、更透明和更有针对性。",
          "document_title": "Constitutional AI Paper",
          "start_page_number": 15,
          "end_page_number": 16
        }
      ]
    }
  ]
}

================================================================================
格式化响应:
================================================================================
根据论文,以下是 Constitutional AI 的关键方面:

Constitutional AI 是一种通过自我改进来训练无害 AI 助手的无害方法,无需任何人类标签来识别有害输出。唯一的人类监督是通过一组规则或原则提供的,因此得名“Constitutional AI”。[1]

该过程包括两个主要阶段:

1. 监督学习阶段:
在此阶段,他们从初始模型中采样,生成自我批评和修订,然后使用修订后的响应对原始模型进行微调。[2]

2. 强化学习阶段:
在此阶段,他们:

- 从微调模型中采样
- 使用模型评估两个样本中的哪个更好
- 从这个 AI 偏好数据集中训练一个偏好模型
- 使用“RL 来自 AI 反馈”(RLAIF)[3]

主要成果是:

- 他们能够训练一个无害但不过于回避的 AI 助手,该助手通过解释其反对意见来处理有害查询
- SL 和 RL 方法都可以利用思维链式推理来提高人类判断的性能和 AI 决策的透明度
- 这些方法使得能够更精确地控制 AI 行为,并且所需的人类标签更少[4]

最终目标不是完全消除人类监督,而是使其更有效、更透明和更有针对性。虽然这项工作减少了对人类监督以确保无害性的依赖,但他们仍然依赖于有帮助性标签形式的人类监督。研究人员预计,从预训练的 LM 和广泛的提示开始,有可能在没有人为反馈的情况下实现有帮助性和指令遵循性,但这留给未来的工作。[5]

[1] "我们通过自我改进来训练无害 AI 助手的无害方法,无需任何人类标签来识别有害输出。唯一的人类监督是通过一组规则或原则提供的,因此我们称该方法为“Constitutional AI”。" 找到于 "Constitutional AI Paper"
[2] "在监督阶段,我们从初始模型中采样,然后生成自我批评和修订,然后使用修订后的响应对原始模型进行微调。" 找到于 "Constitutional AI Paper"
[3] "在 RL 阶段,我们从微调模型中采样,使用模型评估两个样本中的哪个更好,然后从这个 AI 偏好数据集中训练一个偏好模型。然后我们使用偏好模型作为奖励信号进行 RL 训练,即我们使用“RL 来自 AI 反馈”(RLAIF)。" 找到于 "Constitutional AI Paper"
[4] "结果,我们能够训练一个无害但不过于回避的 AI 助手,该助手通过解释其反对意见来处理有害查询。SL 和 RL 方法都可以利用思维链式推理来提高人类判断的性能和 AI 决策的透明度。这些方法使得能够更精确地控制 AI 行为,并且所需的人类标签更少。" 找到于 "Constitutional AI Paper"
[5] "通过删除无害性的人类反馈标签,我们已经远离了对人类监督的依赖,并更接近于实现对齐的自我监督方法。然而,在这项工作中,我们仍然依赖于有帮助性标签形式的人类监督。我们预计,从预训练的 LM 和广泛的提示开始,有可能在没有人为反馈的情况下实现有帮助性和指令遵循性,但这留给未来的工作。我们的最终目标不是完全消除人类监督,而是使其更有效、更透明和更有针对性。" 找到于 "Constitutional AI Paper"

自定义内容文档

虽然纯文本文档会自动分块成句子,但自定义内容文档可让您完全控制引文的粒度。此 API 形状允许您:

  • 定义自己的任意大小的块
  • 控制最小引文单元
  • 优化不适合句子分块的文档

在下面的示例中,我们使用与纯文本示例相同的帮助中心文章,但我们不使用句子级引文,而是将每篇文章视为一个单独的块。这演示了文档类型选择如何影响引文行为和粒度。您会注意到 cited_text 是整篇文章,而不是源文章中的一个句子。

# 读取所有帮助中心文章并创建一个自定义内容文档列表
articles_dir = './data/help_center_articles'
documents = []

for filename in sorted(os.listdir(articles_dir)):
    if filename.endswith('.txt'):
        with open(os.path.join(articles_dir, filename), 'r') as f:
            content = f.read()
            # 分割标题和正文
            title_line, body = content.split('\n', 1)
            title = title_line.replace('title: ', '')

            documents.append({
                "type": "document",
                "source": {
                    "type": "content",
                    "content": [
                        {"type": "text", "text": body}
                    ]
                },
                "title": title,
                "citations": {"enabled": True}
            })

QUESTION = "我刚完成结账,我的订单追踪号在哪里?追踪包裹在网站上还不可用。"

custom_content_response = client.messages.create(
    model="claude-3-5-sonnet-latest",
    temperature=0.0,
    max_tokens=1024,
    system='您是 PetWorld 的客服机器人。您的任务是简短、有帮助地回答用户的问题。由于您处于聊天界面,请避免提供额外的细节。您将可以访问 PetWorld 的帮助中心文章来帮助您回答问题。',
    messages=[
        {
            "role": "user",
            "content": documents
        },
        {
            "role": "user",
            "content": [{"type": "text", "text": f'这是用户的问题:{QUESTION}'}]
        }
    ]
)

print(visualize_raw_response(custom_content_response))
print(visualize_citations(custom_content_response))
================================================================================
原始响应:
================================================================================
{
  "content": [
    {
      "type": "text",
      "text": "一旦您的订单发货,您将收到一封包含追踪号码的电子邮件。如果您在订单确认后 48 小时内未收到追踪号码,请联系我们的客户支持团队寻求帮助。",
      "citations": [
        {
          "type": "content_block_location",
          "cited_text": "一旦您的订单发货,您将收到一封包含追踪号码的电子邮件。要追踪您的包裹,请登录您的 PetWorld 帐户并转到“订单历史记录”。点击您要追踪的订单,然后选择“追踪包裹”。这将显示您当前的订单状态和预计送达日期。您也可以在我们的运输合作伙伴的网站上直接输入追踪号码以获取更详细的信息。如果您在订单确认后 48 小时内未收到追踪号码,请联系我们的客户支持团队。",
          "document_title": "订单追踪信息"
        }
      ]
    }
  ]
}

================================================================================
格式化响应:
================================================================================
一旦您的订单发货,您将收到一封包含追踪号码的电子邮件。如果您在订单确认后 48 小时内未收到追踪号码,请联系我们的客户支持团队寻求帮助。[1]

[1] "一旦您的订单发货,您将收到一封包含追踪号码的电子邮件。要追踪您的包裹,请登录您的 PetWorld 帐户并转到“订单历史记录”。点击您要追踪的订单,然后选择“追踪包裹”。这将显示您当前的订单状态和预计送达日期。您也可以在我们的运输合作伙伴的网站上直接输入追踪号码以获取更详细的信息。如果您在订单确认后 48 小时内未收到追踪号码,请联系我们的客户支持团队。" 找到于 "订单追踪信息"

使用 Context 字段

context 字段允许您提供有关 Claude 可以使用的文档的附加信息,但这些信息不会被引用。这对于以下方面很有用:

  • 提供文档元数据(例如,发布日期、作者)
  • 上下文检索
  • 包含不应直接引用的使用说明或上下文

在下面的示例中,我们提供了一篇带有上下文字段的忠诚度计划文章。请注意 Claude 如何使用上下文中的信息来指导其响应,但上下文字段的内容不能用于引用。

import json

# 创建一个带有上下文字段的文档
document = {
    "type": "document",
    "source": {
        "type": "text",
        "media_type": "text/plain",
        "data": "PetWorld 提供忠诚度计划,客户每花费一美元可赚取 1 个积分。累积 100 个积分后,您将获得 5 美元的奖励,可用于下次购买。积分在赚取后 12 个月内到期。您可以在您的帐户仪表板中或通过咨询客服来查看您的积分余额。"
    },
    "title": "忠诚度计划详情",
    "context": "警告:本文档已 12 个月未更新。内容可能已过时。在提供指导后,请务必告知用户此内容可能不正确。",
    "citations": {"enabled": True}
}

QUESTION = "PetWorld 的忠诚度计划是如何运作的?积分何时到期?"

context_response = client.messages.create(
    model="claude-3-5-sonnet-latest",
    temperature=0.0,
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": [
                document,
                {
                    "type": "text",
                    "text": QUESTION
                }
            ]
        }
    ]
)

print(visualize_raw_response(context_response))
print(visualize_citations(context_response))
================================================================================
原始响应:
================================================================================
{
  "content": [
    {
      "type": "text",
      "text": "让我根据提供的信息解释一下 PetWorld 的忠诚度计划:\n\n"
    },
    {
      "type": "text",
      "text": "PetWorld 的忠诚度计划很简单——您每花费一美元可赚取 1 个积分。累积 100 个积分后,您将获得 5 美元的奖励,可用于下次购买。",
      "citations": [
        {
          "type": "char_location",
          "cited_text": "PetWorld 提供忠诚度计划,客户每花费一美元可赚取 1 个积分。累积 100 个积分后,您将获得 5 美元的奖励,可用于下次购买。 ",
          "document_title": "忠诚度计划详情"
        }
      ]
    },
    {
      "type": "text",
      "text": "\n\n"
    },
    {
      "type": "text",
      "text": "积分在赚取后 12 个月内到期。",
      "citations": [
        {
          "type": "char_location",
          "cited_text": "积分在赚取后 12 个月内到期。 ",
          "document_title": "忠诚度计划详情"
        }
      ]
    },
    {
      "type": "text",
      "text": "\n\n"
    },
    {
      "type": "text",
      "text": "您可以通过查看您的帐户仪表板或联系客服来轻松跟踪您的积分。",
      "citations": [
        {
          "type": "char_location",
          "cited_text": "您可以在您的帐户仪表板中或通过咨询客服来查看您的积分余额。",
          "document_title": "忠诚度计划详情"
        }
      ]
    },
    {
      "type": "text",
      "text": "\n\n请注意,由于此信息来自一篇已 12 个月未更新的文章,因此计划中的某些详细信息可能已更改。最好直接向 PetWorld 核实当前条款。"
    }
  ]
}

================================================================================
格式化响应:
================================================================================
让我根据提供的信息解释一下 PetWorld 的忠诚度计划:

PetWorld 的忠诚度计划很简单——您每花费一美元可赚取 1 个积分。累积 100 个积分后,您将获得 5 美元的奖励,可用于下次购买。[1]

积分在赚取后 12 个月内到期。[2]

您可以通过查看您的帐户仪表板或联系客服来轻松跟踪您的积分。[3]

请注意,由于此信息来自一篇已 12 个月未更新的文章,因此计划中的某些详细信息可能已更改。最好直接向 PetWorld 核实当前条款。

[1] "PetWorld 提供忠诚度计划,客户每花费一美元可赚取 1 个积分。累积 100 个积分后,您将获得 5 美元的奖励,可用于下次购买。" 找到于 "忠诚度计划详情"
[2] "积分在赚取后 12 个月内到期。" 找到于 "忠诚度计划详情"
[3] "您可以在您的帐户仪表板中或通过咨询客服来查看您的积分余额。" 找到于 "忠诚度计划详情"

PDF 高亮显示

PDF 引文的一个限制是只返回页码。您可以使用第三方库将返回的引文文本与页面内容匹配,以吸引对引文内容的关注。此单元格演示了使用 Claude 和 PyMuPDF 的 PDF 引文高亮显示,创建了一个新的带注释的 PDF:

import fitz  # PyMuPDF

# 设置路径并读取 PDF
pdf_path = 'data/Amazon-com-Inc-2023-Shareholder-Letter.pdf'
output_pdf_path = 'data/Amazon-com-Inc-2023-Shareholder-Letter-highlighted.pdf'

# 读取并编码 PDF
with open(pdf_path, "rb") as f:
    pdf_data = base64.b64encode(f.read()).decode()

response = client.messages.create(
    model="claude-3-5-sonnet-latest",
    max_tokens=1024,
    temperature=0,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {
                        "type": "base64",
                        "media_type": "application/pdf",
                        "data": pdf_data
                    },
                    "title": "Amazon 2023 Shareholder Letter",
                    "citations": {"enabled": True}
                },
                {
                    "type": "text",
                    "text": "亚马逊 2023 年总收入是多少?与去年相比增长了多少?"
                }
            ]
        }
    ]
)

print(visualize_raw_response(response))

# 收集 PDF 引文
pdf_citations = []
for content in response.content:
    if hasattr(content, 'citations') and content.citations:
        for citation in content.citations:
            if citation.type == "page_location":
                pdf_citations.append(citation)

doc = fitz.open(pdf_path)

# 处理每个引文
for citation in pdf_citations:
    if citation.type == "page_location":
        text_to_find = citation.cited_text.replace('\u0002', '')
        start_page = citation.start_page_number - 1  # 转换为 0 索引
        end_page = citation.end_page_number - 2

        # 处理引文范围内的每一页
        for page_num in range(start_page, end_page + 1):
            page = doc[page_num]

            text_instances = page.search_for(text_to_find.strip())

            if text_instances:
                print(f"在第 {page_num + 1} 页找到引用的文本")
                for inst in text_instances:
                    highlight = page.add_highlight_annot(inst)
                    highlight.set_colors({"stroke":(1, 1, 0)})  # 黄色高亮
                    highlight.update()
            else:
                print(f"在第 {page_num + 1} 页未找到 {text_to_find}")

# 保存新 PDF
doc.save(output_pdf_path)
doc.close()

print(f"\n已在以下位置创建高亮显示的 PDF:{output_pdf_path}")
================================================================================
原始响应:
================================================================================
{
  "content": [
    {
      "type": "text",
      "text": "根据信函,"
    },
    {
      "type": "text",
      "text": "亚马逊 2023 年总收入同比增长 12%(“YoY”),从 5140 亿美元增至 5750 亿美元",
      "citations": [
        {
          "type": "page_location",
          "cited_text": "2023 年,亚马逊的总收入同比增长 12%(“YoY”),从 5140 亿美元增至 5750 亿美元。 ",
          "document_title": "Amazon 2023 Shareholder Letter",
          "start_page_number": 1,
          "end_page_number": 2
        }
      ]
    },
    {
      "type": "text",
      "text": "。\n\n细分如下:\n"
    },
    {
      "type": "text",
      "text": "\n- 北美收入同比增长 12%,从 3160 亿美元增至 3530 亿美元\n- 国际收入同比增长 11%,从 1180 亿美元增至 1310 亿美元\n- AWS 收入同比增长 13%,从 800 亿美元增至 910 亿美元",
      "citations": [
        {
          "type": "page_location",
          "cited_text": "按部门划分,北美收入同比增长 12%,从 3160 亿美元增至 3530 亿美元,国际收入同比增长 11%,从 1180 亿美元增至 1310 亿美元,AWS 收入同比增长 13%,从 800 亿美元增至 910 亿美元。\r\n",
          "document_title": "Amazon 2023 Shareholder Letter",
          "start_page_number": 1,
          "end_page_number": 2
        }
      ]
    }
  ]
}
在第 1 页找到引用的文本
在第 1 页找到引用的文本

已在以下位置创建高亮显示的 PDF:data/Amazon-com-Inc-2023-Shareholder-Letter-highlighted.pdf