Claude API 引用功能详解:提升文档问答的准确性与可追溯性
技术背景
在大语言模型(LLM)应用中,文档问答(Document Q&A)是核心场景之一。然而,模型生成的回答往往缺乏可验证性,用户难以判断信息是否准确、来源何处。传统的基于提示词的引用方法要求模型在输出中嵌入完整引文,这不仅增加了输出令牌和成本,还可能导致模型生成不存在的引用。Claude API 的引用功能通过结构化方式返回引用,确保引用真实存在于提供的文档中,且召回率和精确度更高。该功能适用于客服机器人、企业知识库、法律/医疗文档分析等需要高可信度回答的场景。主要限制包括:仅支持特定模型(claude-sonnet-4-6 和 claude-3-5-haiku-20241022),且 PDF 引用仅返回页码,需额外处理才能高亮。
核心原理
引用功能的核心原理是:在 API 请求中,将文档作为特殊类型的 content block 传入,并设置 citations.enabled 为 true。模型在生成回答时,会基于这些文档内容进行推理,并在输出中自动附加结构化引用对象。引用对象包含被引用的文本(cited_text)、源文档标题(document_title)以及位置信息(如字符索引、页码或块索引)。位置格式取决于文档类型:纯文本返回字符位置(start_char_index, end_char_index),PDF 返回页码(start_page_number, end_page_number),自定义内容返回块索引(start_block_index, end_block_index)。模型在生成时,会先对文档进行分句或分块处理,然后根据回答内容匹配相关片段,确保引用精确且可追溯。
架构分析
系统架构设计上,引用功能将文档处理与生成过程解耦。客户端将文档编码为 base64 或直接提供文本,通过 API 传递给模型。模型内部对文档进行分句/分块,并在生成时动态引用。开发者可设计如下架构:1) 文档预处理模块:负责读取、清洗、分块文档,并构建符合 API 要求的 document 对象。2) 问答服务:调用 Claude API,传入文档和用户问题,接收响应。3) 引用解析模块:解析响应中的 citations 字段,提取结构化数据。4) 可视化层:将引用渲染为编号标记、高亮或链接。关键设计决策包括:选择文档类型(纯文本适合句子级引用,自定义内容适合块级控制),利用 context 字段传递元数据而不被引用,以及使用 PyMuPDF 等库实现 PDF 高亮。
完整实战教程
引用
Claude API 支持引用功能,使 Claude 在回答文档相关问题时能够提供详细的引用。在许多基于大语言模型的应用中,引用功能是一种有价值的辅助手段,帮助用户追踪和验证回答中的信息来源。
引用功能支持以下模型:
* claude-sonnet-4-6
* claude-3-5-haiku-20241022
引用功能是基于提示词的引用技术的一种替代方案。使用该功能具有以下优势:
- 基于提示词的技术通常要求 Claude 输出其打算引用的源文档中的完整引文。这会增加输出令牌,从而增加成本。
- 引用功能不会返回指向未作为有效来源提供的文档或位置的引用。
- 在测试中,我们发现引用功能生成的引用在召回率和精确度上均高于基于提示词的技术。
引用功能的文档可以在此处找到。
设置
首先,让我们安装所需的库并初始化我们的 Anthropic 客户端。
!pip install anthropic --quiet
import json
import os
import anthropic
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY")
# ANTHROPIC_API_KEY = "" # Put your API key here!
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
文档类型
引用支持三种不同的文档类型。输出的引用类型取决于被引用文档的类型:
* 纯文本文档引用 → 字符位置格式
* PDF 文档引用 → 页码位置格式
* 自定义内容文档引用 → 内容块位置格式
我们将在下面的示例中探讨如何使用这些类型。
纯文本文档
使用纯文本文档引用时,您将文档作为原始文本提供给模型。您可以提供一个或多个文档。这些文本将自动分句。模型将根据需要引用这些句子。模型能够在单个引用中同时引用多个句子,但不会引用小于句子的文本。
除了输出的文本外,API 响应还将包含所有引用的结构化数据。
让我们看一个完整的示例,使用虚构公司 PetWorld 的帮助中心客户聊天机器人。
# Read all help center articles and create a list of documents
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)) as f:
content = f.read()
# Split into title and body
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 = "I just checked out, where is my order tracking number? Track package is not available on the website yet for my order."
# Add the question to the content
content = documents
response = client.messages.create(
model="claude-sonnet-4-6",
temperature=0.0,
max_tokens=1024,
system="You are a customer support bot working for PetWorld. Your task is to provide short, helpful answers to user questions. Since you are in a chat interface avoid providing extra details. You will be given access to PetWorld's help center articles to help you answer questions.",
messages=[
{"role": "user", "content": documents},
{
"role": "user",
"content": [{"type": "text", "text": f"Here is the user's question: {QUESTION}"}],
},
],
)
def visualize_raw_response(response):
raw_response = {"content": []}
print("\n" + "=" * 80 + "\nRaw response:\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))
可视化引用
通过利用引用数据,我们可以创建用户界面,实现:
- 精确展示信息的来源
- 直接链接到源文档
- 在上下文中高亮显示引用的文本
- 通过透明的来源建立信任
下面是一个简单的可视化函数,它将Claude的结构化引用转换为带有编号引用的可读格式,类似于学术论文。
该函数接收Claude的响应对象,并输出:
- 带有编号引用标记的文本(例如,“答案[1]包含此事实[2]”)
- 一个编号的引用列表,显示每个引用的文本及其源文档
def visualize_citations(response):
"""
Takes a response object and returns a string with numbered citations.
Example output: "here is the plain text answer [1][2] here is some more text [3]"
with a list of citations below.
"""
# Dictionary to store unique citations
citations_dict = {}
citation_counter = 1
# Final formatted text
formatted_text = ""
citations_list = []
print("\n" + "=" * 80 + "\nFormatted response:\n" + "=" * 80)
for content in response.content:
if content.type == "text":
text = content.text
if hasattr(content, "citations") and content.citations:
# Sort citations by their appearance in the text
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 # fallback
sorted_citations = sorted(content.citations, key=get_sort_key)
# Process each citation
for citation in sorted_citations:
doc_title = citation.document_title
cited_text = citation.cited_text.replace("\n", " ").replace("\r", " ")
# Remove any multiple spaces that might have been created
cited_text = " ".join(cited_text.split())
# Create a unique key for this citation
citation_key = f"{doc_title}:{cited_text}"
# If this is a new citation, add it to our dictionary
if citation_key not in citations_dict:
citations_dict[citation_key] = citation_counter
citations_list.append(
f'[{citation_counter}] "{cited_text}" found in "{doc_title}"'
)
citation_counter += 1
# Add the citation number to the text
citation_num = citations_dict[citation_key]
text += f" [{citation_num}]"
formatted_text += text
# Combine the formatted text with the citations list
final_output = formatted_text + "\n\n" + "\n".join(citations_list)
return final_output
formatted_response = visualize_citations(response)
print(formatted_response)
PDF 文档
在处理 PDF 文件时,Claude 可以提供引用,引用中会包含具体的页码,方便追踪信息来源。以下是 PDF 引用的工作方式:
- PDF 文档内容以 base64 编码数据提供
- 文本自动按句子分块
- 引用包含找到信息的页码(从 1 开始)
- 模型可以在单个引用中同时引用多个句子,但不会引用小于句子的文本
- 虽然会处理图像,但目前只能引用文本内容
下面是一个使用 Constitutional AI 论文演示 PDF 引用的示例:
import base64
import json
# Read and encode the 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-sonnet-4-6",
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": "What is the main idea of Constitutional AI?"},
],
}
],
)
print(visualize_raw_response(pdf_response))
print(visualize_citations(pdf_response))
自定义内容文档
虽然纯文本文档会自动按句子分块,但自定义内容文档让您完全控制引用的粒度。这种 API 形态允许您:
* 定义任意大小的自定义块
* 控制最小引用单元
* 针对不适合按句子分块的文档进行优化
在下面的示例中,我们使用与上述纯文本示例相同的帮助中心文章,但不再允许句子级引用,而是将每篇文章视为一个单独的块。这演示了文档类型的选择如何影响引用行为和粒度。您会注意到,cited_text 是整篇文章,而不是源文章中的某个句子。
# Read all help center articles and create a list of custom content documents
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)) as f:
content = f.read()
# Split into title and body
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 = "I just checked out, where is my order tracking number? Track package is not available on the website yet for my order."
custom_content_response = client.messages.create(
model="claude-sonnet-4-6",
temperature=0.0,
max_tokens=1024,
system="You are a customer support bot working for PetWorld. Your task is to provide short, helpful answers to user questions. Since you are in a chat interface avoid providing extra details. You will be given access to PetWorld's help center articles to help you answer questions.",
messages=[
{"role": "user", "content": documents},
{
"role": "user",
"content": [{"type": "text", "text": f"Here is the user's question: {QUESTION}"}],
},
],
)
print(visualize_raw_response(custom_content_response))
print(visualize_citations(custom_content_response))
使用上下文字段
context 字段允许你提供关于文档的附加信息,Claude 在生成回复时可以使用这些信息,但不会引用这些信息。这在以下场景中非常有用:
* 提供文档的元数据(例如,发布日期、作者)
* 上下文检索
* 包含使用说明或不应直接引用的上下文
在下面的示例中,我们为忠诚度计划文章在上下文字段中提供了一个警告。注意 Claude 如何使用上下文中的信息来指导其回复,但上下文字段的内容不可用于引用。
import json
# Create a document with context field
document = {
"type": "document",
"source": {
"type": "text",
"media_type": "text/plain",
"data": "PetWorld offers a loyalty program where customers earn 1 point for every dollar spent. Once you accumulate 100 points, you'll receive a $5 reward that can be used on your next purchase. Points expire 12 months after they are earned. You can check your point balance in your account dashboard or by asking customer service.",
},
"title": "Loyalty Program Details",
"context": "WARNING: This article has not been updated in 12 months. Content may be out of date. Be sure to inform the user this content may be incorrect after providing guidance.",
"citations": {"enabled": True},
}
QUESTION = "How does PetWorld's loyalty program work? When do points expire?"
context_response = client.messages.create(
model="claude-sonnet-4-6",
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))
PDF 高亮
PDF 引用的一个限制是只返回页码。你可以使用第三方库将返回的引用文本与页面内容进行匹配,以突出显示引用的内容。此单元格演示了使用 Claude 和 PyMuPDF 进行 PDF 引用高亮,创建新的带注释的 PDF:
import fitz # PyMuPDF
# Setup paths and read PDF
pdf_path = "data/Amazon-com-Inc-2023-Shareholder-Letter.pdf"
output_pdf_path = "data/Amazon-com-Inc-2023-Shareholder-Letter-highlighted.pdf"
# Read and encode the PDF
with open(pdf_path, "rb") as f:
pdf_data = base64.b64encode(f.read()).decode()
response = client.messages.create(
model="claude-sonnet-4-6",
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": "What was Amazon's total revenue in 2023 and how much did it grow year-over-year?",
},
],
}
],
)
print(visualize_raw_response(response))
# Collect PDF citations
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)
# Process each citation
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 # Convert to 0-based index
end_page = citation.end_page_number - 2
# Process each page in the citation range
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"Found cited text on page {page_num + 1}")
for inst in text_instances:
highlight = page.add_highlight_annot(inst)
highlight.set_colors({"stroke": (1, 1, 0)}) # Yellow highlight
highlight.update()
else:
print(f"{text_to_find} not found on page {page_num + 1}")
# Save the new PDF
doc.save(output_pdf_path)
doc.close()
print(f"\nCreated highlighted PDF at: {output_pdf_path}")
常见问题
常见错误与排查:1) 模型不支持引用:确保使用 claude-sonnet-4-6 或 claude-3-5-haiku-20241022。2) 文档格式错误:检查 source.type 是否正确(text/base64/content),media_type 是否匹配。3) 引用为空:确认文档内容是否足够相关,或 citations.enabled 是否设置为 true。4) PDF 高亮失败:由于 PDF 文本提取可能包含特殊字符,需清理 cited_text(如去除 ),并注意页码索引从 1 开始,而 PyMuPDF 从 0 开始。5) 上下文信息被引用:确保 context 字段内容不被模型引用,仅作为指导。6) 成本控制:引用功能可能增加输出令牌,建议设置 max_tokens 并监控使用量。
企业应用扩展
生产环境优化方案:1) 缓存文档处理结果,避免重复编码。2) 使用异步调用或批量处理提高吞吐量。3) 结合上下文检索(Contextual Retrieval)技术,为文档块添加上下文信息,提升引用准确性。4) 实现引用验证机制,确保 cited_text 与源文档一致。5) 设计用户友好的引用展示界面,支持点击跳转。6) 监控引用召回率与精确度,持续优化文档分块策略。7) 考虑使用自定义内容文档类型,对非句子结构文档(如表格、代码)进行块级控制。
总结
本文深入解析 Claude API 的引用功能,该功能使模型在回答文档相关问题时能提供精确的来源引用,显著提升回答的可信度与可追溯性。文章详细介绍了三种文档类型(纯文本、PDF、自定义内容)的引用格式与实现方式,并通过完整代码示例演示了如何构建带引用的问答系统。同时,文章探讨了 context 字段的用法、PDF 高亮实现,以及引用数据的可视化方法。读者将掌握如何利用引用功能优化 RAG 应用,减少幻觉,并提升用户体验。
- --
内容来源与验证
- 内容等级:PREMIUM_A
- 质量等级:A
- 难度:中级
- 验证等级:V1
- 原文作者:Anthropic
- 原文日期:2024-03-14
- 来源许可:MIT
- 来源版本:
e8c46f3a0ef6df874609f24e0000bfbcd41a333e - 查看原文
站内还有更多完整实战教程
加入会员,解锁全部教程、完整代码与持续更新内容。
加入会员