📌 内容摘要
- Claude Sonnet 4.6 和 Opus 4.6 均支持 100 万 token 上下文,约等于 75 万汉字或整个中型代码库。
- 超大上下文不等于无限制——内容位置、token 成本、”迷失在中间”问题需要针对性处理。
- 本文给出 6 类高频场景的实战代码:长文档问答、代码库分析、多文档对比、合同审查、会议记录处理、书籍摘要。
- 最后讨论什么时候用大上下文、什么时候用 RAG,帮你做出正确的架构选择。
一、100万 Token 意味着什么?
Claude Sonnet 4.6 和 Opus 4.6 的上下文窗口已经达到 100 万 token(Claude Haiku 4.5 为 20 万 token)。换算成实际内容量:
| 内容类型 | 100万 token 约等于 |
|---|---|
| 中文文章 | 约 60-70 万汉字(《红楼梦》全本约 73 万字) |
| 英文文章 | 约 75 万单词(约 10 本普通小说) |
| Python 代码 | 约 3-5 万行代码(中等规模项目) |
| PDF 文档 | 约 1000-2000 页(取决于排版密度) |
| 对话历史 | 约 2000-5000 轮对话(每轮平均200 token) |
这意味着大量之前需要分片处理、建向量数据库、或者写复杂检索逻辑的场景,现在可以直接把所有内容塞进去一次性处理。
二、使用大上下文的注意事项
超大上下文不是银弹,有几个重要特性需要理解:
注意一:”迷失在中间”效应
研究表明,语言模型对文档开头和结尾的内容注意力最强,对中间部分的关注度会下降。如果你的关键信息恰好在一份超长文档的中间,Claude 可能会忽略或遗漏它。
应对策略:把最重要的指令和关键信息放在 System Prompt(开头)或最后一条用户消息(结尾),而不是埋在文档中间。
注意二:成本随长度线性增长
输入 token 数直接决定费用。一次 50 万 token 的请求(Sonnet 4.6),仅输入就要 $1.5。在确认架构前,先评估实际业务的调用频率和成本预算。
注意三:响应时间更长
上下文越长,首字延迟(TTFT)越长。100 万 token 的请求可能需要 10-30 秒才开始输出第一个字,对实时交互场景不友好。建议配合流式输出使用。
三、读取和处理大型文件
读取本地文件并计算 Token 数
import anthropic
from pathlib import Path
client = anthropic.Anthropic()
def load_text_file(path: str) -> str:
"""读取文本文件,自动处理编码"""
return Path(path).read_text(encoding="utf-8")
def estimate_tokens(text: str, model: str = "claude-sonnet-4-6") -> int:
"""精确估算 token 数(不实际生成内容)"""
response = client.messages.count_tokens(
model=model,
messages=[{"role": "user", "content": text}]
)
return response.input_tokens
def check_fits_in_context(text: str, max_tokens: int = 900_000) -> bool:
"""检查文本是否在上下文限制内(留 10 万 token 给输出)"""
tokens = estimate_tokens(text)
print(f"文本 token 数:{tokens:,}(上限:{max_tokens:,})")
return tokens <= max_tokens
# 使用示例
content = load_text_file("annual_report.txt")
if check_fits_in_context(content):
print("✅ 文件可以直接放入上下文")
else:
print("⚠️ 文件过大,需要分片处理")
读取 PDF 文件
pip install pymupdf # 或 pip install pdfplumber
import fitz # pymupdf
def extract_pdf_text(pdf_path: str) -> str:
"""提取 PDF 所有页面的文本"""
doc = fitz.open(pdf_path)
pages = []
for i, page in enumerate(doc):
text = page.get_text()
if text.strip():
pages.append(f"[第{i+1}页]\n{text}")
return "\n\n".join(pages)
def extract_pdf_by_page(pdf_path: str) -> list[dict]:
"""按页面返回,便于精确引用"""
doc = fitz.open(pdf_path)
return [
{"page": i + 1, "content": page.get_text()}
for i, page in enumerate(doc)
if page.get_text().strip()
]
# 提取后直接传入 Claude
pdf_content = extract_pdf_text("contract.pdf")
print(f"PDF 内容长度:{len(pdf_content)} 字")
四、场景一:长文档深度问答
import anthropic
from pathlib import Path
client = anthropic.Anthropic()
class LongDocumentQA:
"""基于完整文档的问答系统,不做任何分片"""
def __init__(self, document_path: str):
self.document = Path(document_path).read_text(encoding="utf-8")
self.conversation = []
print(f"文档已加载:{len(self.document)} 字符")
def ask(self, question: str) -> str:
self.conversation.append({"role": "user", "content": question})
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
system=f"""你是一个专业的文档分析助手。
以下是需要分析的完整文档:
{self.document}
回答要求:
- 基于文档内容回答,引用具体段落或页码
- 如果文档中没有相关信息,明确说明
- 回答简洁准确,避免猜测""",
messages=self.conversation,
)
answer = response.content[0].text
self.conversation.append({"role": "assistant", "content": answer})
return answer
def extract_structured(self, extraction_prompt: str) -> str:
"""结构化信息提取"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
system=f"以下是需要分析的文档:\n\n{self.document}",
messages=[{"role": "user", "content": extraction_prompt}],
)
return response.content[0].text
# 使用示例:分析一份年度报告
qa = LongDocumentQA("annual_report_2025.txt")
# 多轮对话,Claude 始终基于完整文档回答
print(qa.ask("公司2025年的总营收是多少?同比增长了多少?"))
print(qa.ask("主要风险因素有哪些?"))
print(qa.ask("管理层对2026年的展望是什么?"))
# 结构化提取
result = qa.extract_structured("""
请从文档中提取以下信息,以 JSON 格式返回:
{
"revenue": "总营收",
"growth_rate": "同比增长率",
"main_risks": ["风险1", "风险2"],
"key_highlights": ["亮点1", "亮点2", "亮点3"]
}
""")
五、场景二:大型代码库分析
import os
from pathlib import Path
import anthropic
client = anthropic.Anthropic()
def load_codebase(
root_dir: str,
extensions: list[str] = [".py", ".js", ".ts", ".go", ".java"],
exclude_dirs: list[str] = ["node_modules", ".git", "__pycache__", "venv", "dist"],
max_file_size_kb: int = 500,
) -> tuple[str, dict]:
"""递归加载代码库,返回合并文本和文件索引"""
files_content = []
file_index = {}
total_chars = 0
for path in sorted(Path(root_dir).rglob("*")):
# 跳过排除目录
if any(ex in path.parts for ex in exclude_dirs):
continue
# 只处理指定扩展名
if path.suffix not in extensions:
continue
# 跳过过大文件
if path.stat().st_size > max_file_size_kb * 1024:
continue
try:
content = path.read_text(encoding="utf-8", errors="ignore")
rel_path = str(path.relative_to(root_dir))
file_entry = f"### 文件:{rel_path}\n```{path.suffix[1:]}\n{content}\n```"
files_content.append(file_entry)
file_index[rel_path] = len(content)
total_chars += len(content)
except Exception:
continue
combined = "\n\n".join(files_content)
print(f"加载了 {len(files_content)} 个文件,共 {total_chars:,} 字符")
return combined, file_index
def analyze_codebase(root_dir: str, question: str) -> str:
"""分析整个代码库"""
codebase, index = load_codebase(root_dir)
# 生成文件结构摘要
file_summary = "\n".join(
f"- {path} ({size:,} chars)"
for path, size in sorted(index.items())
)
response = client.messages.create(
model="claude-opus-4-6", # 代码分析用 Opus 效果更好
max_tokens=8192,
system=f"""你是一名资深软件架构师,正在分析以下代码库。
项目文件结构:
{file_summary}
完整代码内容:
{codebase}
分析要求:
- 基于实际代码内容回答,引用具体文件和行号
- 识别潜在的 Bug、安全漏洞和性能问题
- 评估代码质量和可维护性""",
messages=[{"role": "user", "content": question}],
)
return response.content[0].text
# 使用示例
result = analyze_codebase(
"./my-project",
"分析这个项目的整体架构,指出最主要的3个潜在问题,并给出改进建议"
)
print(result)
六、场景三:多文档对比分析
def compare_documents(docs: dict[str, str], comparison_prompt: str) -> str:
"""
对比多份文档
docs: {"文档A": "内容...", "文档B": "内容...", ...}
"""
# 构建多文档上下文
context_parts = []
for name, content in docs.items():
context_parts.append(f"""
{content}
""")
full_context = "\n\n".join(context_parts)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
system=f"""你是一名专业分析师,需要对比分析以下 {len(docs)} 份文档:
{full_context}
分析原则:
- 客观对比,不偏向任何一份文档
- 引用具体内容支撑观点
- 指出文档间的关键差异和共同点""",
messages=[{"role": "user", "content": comparison_prompt}],
)
return response.content[0].text
# 使用示例:对比三家供应商的投标文件
from pathlib import Path
docs = {
"供应商A投标书": Path("vendor_a.txt").read_text(encoding="utf-8"),
"供应商B投标书": Path("vendor_b.txt").read_text(encoding="utf-8"),
"供应商C投标书": Path("vendor_c.txt").read_text(encoding="utf-8"),
}
result = compare_documents(docs, """
请从以下维度对比三家供应商:
1. 报价总额和付款条件
2. 交付时间和里程碑
3. 技术方案优劣
4. 售后服务承诺
5. 综合推荐排序
""")
print(result)
七、场景四:合同与法律文件审查
CONTRACT_REVIEW_SYSTEM = """你是一名经验丰富的合同律师助理。
审查要求:
- 识别不公平或模糊的条款
- 标注高风险条款(用⚠️标记)
- 指出缺失的重要条款
- 使用专业但易懂的语言
- 免责声明:本分析仅供参考,不构成法律意见"""
def review_contract(contract_text: str) -> str:
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=8192,
system=CONTRACT_REVIEW_SYSTEM + f"\n\n合同全文:\n\n{contract_text}",
messages=[{
"role": "user",
"content": """请完成以下审查任务:
1. 合同摘要(100字以内)
2. 关键条款清单(甲方义务/乙方义务/付款条件/违约责任/争议解决)
3. 风险条款(⚠️标记,说明风险)
4. 缺失条款建议
5. 综合评级(低风险/中风险/高风险)及理由"""
}],
)
return response.content[0].text
八、场景五:超长会议记录处理
def process_meeting_transcript(transcript: str) -> dict:
"""处理会议记录,提取结构化信息"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
system=f"以下是完整会议记录:\n\n{transcript}",
messages=[{
"role": "user",
"content": """请提取以下信息,以 JSON 格式返回:
{
"summary": "会议摘要(200字以内)",
"attendees": ["参会人员列表"],
"decisions": [
{"decision": "决定内容", "owner": "负责人", "deadline": "截止时间"}
],
"action_items": [
{"task": "任务描述", "assignee": "责任人", "due_date": "截止日期"}
],
"key_discussions": ["关键讨论点1", "关键讨论点2"],
"next_meeting": "下次会议时间(如有)"
}
只返回 JSON,不要其他内容。"""
}],
)
import json
text = response.content[0].text.strip()
# 去除可能的 markdown 代码块标记
if text.startswith("```"):
text = text.split("```")[1]
if text.startswith("json"):
text = text[4:]
return json.loads(text.strip())
九、何时用大上下文,何时用 RAG?
并非所有场景都适合把所有内容塞入上下文,理性选择架构更重要:
| 维度 | 直接使用大上下文 | RAG(检索增强生成) |
|---|---|---|
| 文档数量 | 少(1-10份) | 多(数百到数万份) |
| 文档总大小 | 在 100 万 token 以内 | 超出上下文限制 |
| 调用频率 | 低频(每次分析一份) | 高频(知识库频繁查询) |
| 分析深度 | 需要全局理解、跨段落推理 | 精确查找特定信息 |
| 成本敏感度 | 可接受较高单次成本 | 需要控制每次查询成本 |
| 实时性要求 | 可接受较长响应时间 | 需要快速实时响应 |
| 典型场景 | 合同审查、代码审计、研究报告分析 | 客服问答、企业知识库、文档搜索 |
如果你的文档总量在 100 万 token 以内、每次需要全局理解,直接用大上下文是最简单有效的方案——不需要向量数据库、不需要 embedding、不需要检索逻辑,代码量减少 80%。反之,如果文档库持续增长且需要高频查询,RAG 的成本优势会越来越明显。
十、配合 Prompt Caching 降低大上下文成本
def qa_with_cached_document(document: str, questions: list[str]) -> list[str]:
"""
对同一份文档问多个问题时,使用缓存大幅降低成本
文档只在首次请求时计费(1.25倍),后续读取只需 0.1 倍费用
"""
answers = []
for question in questions:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[
{
"type": "text",
"text": f"你是文档分析专家。以下是完整文档:\n\n{document}",
"cache_control": {"type": "ephemeral"}, # 缓存文档内容
}
],
messages=[{"role": "user", "content": question}],
)
answers.append(response.content[0].text)
# 打印缓存命中情况
usage = response.usage
cache_read = getattr(usage, "cache_read_input_tokens", 0)
cache_write = getattr(usage, "cache_creation_input_tokens", 0)
if cache_read:
print(f"✅ 缓存命中 {cache_read:,} tokens(节省约90%输入费用)")
elif cache_write:
print(f"📝 写入缓存 {cache_write:,} tokens(首次,1.25倍费用)")
return answers
# 使用示例:对一份 50 页报告问 10 个问题
report = Path("research_report.txt").read_text(encoding="utf-8")
questions = [
"报告的核心结论是什么?",
"主要的数据来源有哪些?",
"有哪些局限性和不足?",
"对行业的影响预测是什么?",
]
answers = qa_with_cached_document(report, questions)
# 第1次:写入缓存(1.25倍费用)
# 第2-4次:读取缓存(0.1倍费用,节省约90%)
常见问题
Q:100 万 token 上下文是 Sonnet 4.6 还是只有 Opus 4.6 才有?
Claude Sonnet 4.6 和 Opus 4.6 均支持 100 万 token 上下文,Haiku 4.5 为 20 万 token。100 万 token 是2026年3月正式转为标准定价(无溢价)的功能,不需要额外费用。
Q:把整本书塞进去,Claude 真的能全部”理解”吗?
Claude 能处理上下文内的所有内容,但”理解”的深度会随内容增多而分散。对于需要精确定位的任务(找某个具体数字),效果很好;对于需要跨越全文综合推理的复杂任务,建议把关键信息显式提示出来,而不是期待模型自动发现。
Q:文档中有表格和图片怎么处理?
纯文本表格(Markdown 格式)可以直接传入,Claude 能理解表格结构。图片需要单独传入(base64 编码),不计入文字 token,但图片有自己的 token 计费规则(约 1000-5000 token 每张,取决于尺寸)。
Q:上下文窗口和 max_tokens 有什么关系?
上下文窗口 = 输入 token + 输出 token 的总上限。max_tokens 是你设定的最大输出 token 数。举例:上下文窗口 100 万 token,你的输入是 90 万 token,那么 max_tokens 最多只能设到 10 万。实际使用中建议输入不超过 90 万 token,留足输出空间。
总结
Claude 的 100 万 token 上下文是处理长文档任务的利器,核心使用原则是:把不变的大文档放在 System Prompt 并开启缓存(节省 90% 费用);对同一份文档连续提问时成本会随问题数量摊薄;需要跨文档分析时用 XML 标签清晰分隔每份文档。多数之前需要复杂 RAG 架构的场景,现在可以用更简单的大上下文方案替代,但文档库超大或高频查询时 RAG 仍然有其优势。