📌 内容摘要

  • 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 仍然有其优势。