0Pricing
AI Prompt Engineering · 课时

在文本块之间保持上下文

使用重叠、滚动上下文和元数据注入保持连贯性

在文本块之间保持上下文 是 CoddyKit 上的免费 AI Prompt Engineering 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Prompt Engineering 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Prompt Engineering 课程共包含 4 节课。

跨文本块的上下文问题

当文档被拆分为多个文本块时,文本块 N 中的信息可能是正确理解文本块 N+1 所必需的。例如,文本块 3 中定义的术语在文本块 7 中被使用。如果没有上下文衔接,处理文本块 7 的模型就不知道该术语的定义。

有四种策略可以解决这个问题:重叠、注入滚动摘要、元数据标签和页码引用。

策略 1:词元重叠

重叠会将文本块 N 的最后 N 个词元重复放在文本块 N+1 的开头。这样可以确保跨越边界的句子或论点至少在一个文本块中完整出现。

典型的重叠量为100–200 个词元(大约 75–150 个词)。重叠过多会增加冗余和成本,过少则会留下边界信息缺口。

import tiktoken

enc = tiktoken.get_encoding('cl100k_base')

def chunk_with_overlap(text, max_tokens=1000, overlap=200):
    tokens = enc.encode(text)
    chunks = []
    step = max_tokens - overlap
    i = 0
    while i < len(tokens):
        chunk = tokens[i:i + max_tokens]
        chunks.append(enc.decode(chunk))
        i += step
    return chunks

chunks = chunk_with_overlap(document, max_tokens=1000, overlap=200)
print(f'{len(chunks)} chunks with 200-token overlap')

用于检索与摘要的重叠

重叠在不同任务中的表现不同:

  • 检索:重叠有帮助——查询可以在相邻两个文本块中的任意一个匹配重叠区域,从而提高召回率。请使用文本块大小 10–20% 的重叠。
  • 摘要:重叠可能导致重复——同一句话会被摘要两次。请使用较小的重叠(5–10%),或在生成摘要前去除重叠部分。
def strip_overlap(chunks, overlap_tokens=200):
    '''Remove the leading overlap from each chunk (except the first).'''
    cleaned = [chunks[0]]
    for chunk in chunks[1:]:
        tokens = enc.encode(chunk)
        trimmed = enc.decode(tokens[overlap_tokens:])
        cleaned.append(trimmed)
    return cleaned

策略 2:注入滚动摘要

滚动摘要是对截至目前已处理的所有分块进行持续汇总而成的概括。在处理第 N 个分块之前,将滚动摘要作为上下文注入提示词。这样既能让模型了解先前的内容,又不会超出上下文窗口。

import openai
client = openai.OpenAI(api_key='sk-...')

def process_with_rolling_summary(chunks):
    rolling_summary = ''
    results = []

    for i, chunk in enumerate(chunks):
        context = ''
        if rolling_summary:
            context = f'Summary of previous sections:\n{rolling_summary}\n\n'

        prompt = context + f'Current section:\n{chunk}'
        resp = client.chat.completions.create(
            model='gpt-4o',
            messages=[
                {'role': 'system', 'content': 'Answer questions or summarize, using prior context.'},
                {'role': 'user', 'content': prompt}
            ]
        )
        result = resp.choices[0].message.content
        results.append(result)

        # Update rolling summary
        rolling_summary = update_rolling_summary(rolling_summary, chunk)

    return results

更新滚动摘要

滚动摘要应当逐步增长。处理完每个分块后,请让 LLM 通过纳入该分块中的新信息来更新摘要。请将滚动摘要保持简短——控制在 200–400 个词元以内,以便为当前分块留出空间。

def update_rolling_summary(current_summary, new_chunk, max_words=150):
    if not current_summary:
        prompt = f'Summarize the following in under {max_words} words:\n\n{new_chunk}'
    else:
        prompt = (
            f'Current running summary (under {max_words} words):\n{current_summary}\n\n'
            f'New section to incorporate:\n{new_chunk}\n\n'
            f'Update the summary to include the new section. Stay under {max_words} words.'
        )
    resp = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': prompt}]
    )
    return resp.choices[0].message.content

策略 3:元数据标签

元数据标签会为每个分块附加结构化信息,让 LLM 知道自己正在处理什么内容,并保持逻辑连贯性。

常见标签包括:document_title、chapter、section、page、chunk_index、total_chunks。请将这些标签作为提示词中的标头注入,而不要放入分块文本中,以便将内容与元数据分开。

def build_prompt_with_metadata(chunk, metadata):
    header = (
        f'Document: {metadata["title"]}\n'
        f'Chapter: {metadata["chapter"]}\n'
        f'Section: {metadata["section"]}\n'
        f'Page: {metadata["page"]}\n'
        f'Chunk: {metadata["chunk_index"] + 1} of {metadata["total_chunks"]}\n'
        '---\n'
    )
    return header + chunk

prompt = build_prompt_with_metadata(
    chunk=chunks[5],
    metadata={
        'title': 'Annual Report 2024',
        'chapter': '3. Financial Results',
        'section': '3.2 Revenue Breakdown',
        'page': 42,
        'chunk_index': 5,
        'total_chunks': 120
    }
)

策略 4:页码引用

如果分块引用了前一页的内容,那么在嵌入页码后,模型就可以引用该内容。在分块之前将分页作为标记注入文本,这样它们就能保留在各个分块中:

def inject_page_markers(pages):
    '''pages: list of strings, one per page.'''
    marked = []
    for i, page_text in enumerate(pages):
        marked.append(f'[PAGE {i+1}]\n{page_text}')
    return '\n\n'.join(marked)

# When the model sees [PAGE 12] in context, it can say
# 'As defined on page 12...' in its output, enabling traceability.
document_with_markers = inject_page_markers(pdf_pages)
chunks = chunk_with_overlap(document_with_markers)

组合策略

在生产环境中,请组合使用全部四种策略,以最大限度地保持上下文准确性:

  1. 在分块之前添加页面标记
  2. 使用 200 个词元的重叠进行分块
  3. 为每个分块的提示词附加元数据标头
  4. 在每个分块之前注入滚动摘要

组合使用这些方法可以确保模型始终知道:自己位于文档中的什么位置、之前出现了什么内容,以及当前分块与整体内容之间的关系。

def process_document(pages, doc_metadata):
    # Step 1: inject page markers
    full_text = inject_page_markers(pages)
    # Step 2: chunk with overlap
    chunks = chunk_with_overlap(full_text, max_tokens=900, overlap=150)
    total = len(chunks)
    rolling_summary = ''
    results = []

    for i, chunk in enumerate(chunks):
        meta = {**doc_metadata, 'chunk_index': i, 'total_chunks': total}
        prompt = build_prompt_with_metadata(chunk, meta)
        if rolling_summary:
            prompt = 'Prior context:\n' + rolling_summary + '\n\n' + prompt
        result = call_llm(prompt)
        results.append(result)
        rolling_summary = update_rolling_summary(rolling_summary, chunk)

    return results

评估上下文保留

要验证您的上下文策略是否有效,请创建需要从多个分块中获取信息的测试问题:

  • 定义第 2 个分块中介绍的术语,并在第 8 个分块中提问
  • 计算跨越多个页面的总数
  • 找出第 1 章与第 5 章之间的矛盾

运行处理流程,并检查答案是否正确引用了先前的内容。如果答案不正确,请增加重叠部分或滚动摘要的长度。

test_questions = [
    {
        'question': 'What is the definition of "net recurring revenue" used in this report?',
        'defined_in_chunk': 2,
        'asked_in_chunk': 9,
        'expected_keywords': ['net recurring revenue', 'subscription', 'exclude']
    }
]

def evaluate_context_retention(pipeline_results, test_questions):
    for test in test_questions:
        answer = pipeline_results[test['asked_in_chunk']]
        for kw in test['expected_keywords']:
            if kw.lower() not in answer.lower():
                print(f'FAIL: missing "{kw}" in answer to chunk {test["asked_in_chunk"]}')

权衡总结

每种策略都有相应的成本和收益:

  • 重叠:简单易用,会使词元数量增加重叠部分的比例,但可能导致摘要重复
  • 滚动摘要:功能强大,每个分块都会额外增加一次 LLM 调用,且摘要可能逐渐偏离原意或丢失细节
  • 元数据标签:添加时无需额外成本,有助于模型定位,但不能替代内容本身
  • 页面标记:支持追溯,会增加文本中的字符数,但几乎不会增加词元开销

请先使用重叠和元数据。只有在测试中发现跨分块上下文失败时,才添加滚动摘要。

实际规模指南

对于一份 100 页的文档,以下是实用的规模参考(平均每页 500 个词元,总计 50,000 个词元):

  • 分块大小:800 个词元,重叠 150 个词元 → 约 73 个分块
  • 滚动摘要:最多 200 个词元(每个分块处理后更新)
  • 元数据标头:每个分块约 30 个词元
  • 每次应用程序接口调用的有效输入:800 + 200 + 30 = 1030 个词元
  • 映射成本(gpt-4o-mini,价格为每 100 万个词元 0.15 美元):73 × 1030 × 0.15 美元/100 万 ≈ 0.011 美元

上下文策略只会增加极少的成本,却能大幅提升连贯性。

知识检查

在逐个分块处理文档时,使用滚动摘要的目的是什么?

回顾:跨分块上下文

维持跨分块上下文的四种策略:

  • 重叠:在第 N+1 个分块的开头重复第 N 个分块最后的 N 个词元
  • 滚动摘要:在每个分块之前注入一份简短的持续摘要
  • 元数据标签:包含文档、章节、节和页码信息的标头
  • 页面标记:在分块之前将页码嵌入文本

在生产流程中组合使用全部四种策略。使用跨多个分块的问题测试跨分块上下文的保留情况。至此,第 16 课《长文档处理策略》结束。

常见问题解答

「在文本块之间保持上下文」课时是免费的吗?

是的 — 「在文本块之间保持上下文」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Prompt Engineering 课程的其余内容,请升级到 CoddyKit PRO。 AI Prompt Engineering 课程共包含 4 节课。

「在文本块之间保持上下文」这节课中我会学到什么?

使用重叠、滚动上下文和元数据注入保持连贯性 你通过在浏览器中直接运行的动手代码来练习 AI Prompt Engineering,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 AI Prompt Engineering 需要有经验吗?

无需任何先前经验。CoddyKit 上的 AI Prompt Engineering 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「在文本块之间保持上下文」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 AI Prompt Engineering 课中编写并运行代码吗?

能。每节 AI Prompt Engineering 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 长文本分块策略
  2. Map-Reduce 摘要模式
  3. 层次化摘要
  4. 在文本块之间保持上下文
← 返回 AI Prompt Engineering