チャンク間でコンテキストを維持する
連続性を保つために、オーバーラップ、ローリングコンテキスト、メタデータの挿入を使います。
「チャンク間でコンテキストを維持する」はCoddyKit上の無料AI Prompt Engineeringレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Prompt Engineering学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Prompt Engineeringコースには全4レッスンが含まれています。
チャンク間のコンテキスト問題
ドキュメントをチャンクに分割すると、チャンクNの情報がチャンクN+1を正しく解釈するために必要になることがあります。たとえば、チャンク3で定義された用語がチャンク7で使われる場合です。コンテキストを橋渡ししなければ、チャンク7を処理するモデルはその定義を把握できません。
この問題には、オーバーラップ、ローリング要約の注入、メタデータタグ、ページ番号の参照という4つの方法で対処できます。
戦略1:トークンのオーバーラップ
オーバーラップでは、チャンクNの最後のNトークンをチャンクN+1の先頭で繰り返します。これにより、境界をまたぐ文や論点が、少なくとも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%をオーバーラップさせてください。
- 要約:オーバーラップによって同じ文が2回要約され、重複が生じることがあります。より小さいオーバーラップ(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)戦略の組み合わせ
本番環境では、コンテキストの忠実度を最大限に高めるため、4つの戦略をすべて組み合わせます。
- チャンク分割の前にページマーカーを追加する
- 200トークンのオーバーラップを設けてチャンク分割する
- 各チャンクのプロンプトにメタデータヘッダーを付加する
- 各チャンクの前にローリングサマリーを注入する
この組み合わせにより、モデルは常に、文書内の現在位置、そこまでの内容、そして現在のチャンクが文書全体とどう関係するかを把握できます。
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呼び出しが1回追加され、サマリーが変質したり詳細を失ったりする可能性があります
- メタデータタグ:追加コストなしで付加でき、モデルの状況把握に役立ちますが、コンテンツの代わりにはなりません
- ページマーカー:追跡可能性を高めます。テキストに文字が追加されますが、トークンのオーバーヘッドは最小限です
まずはオーバーラップとメタデータから始めます。テストでチャンク間のコンテキスト欠落が確認された場合にのみ、ローリングサマリーを追加します。
実運用のサイズ設計ガイド
100ページの文書(1ページ平均500トークン、合計50,000トークン)における実用的なサイズの例です。
- チャンクサイズ:800トークン、オーバーラップ150トークン → 約73チャンク
- ローリングサマリー:最大200トークン(各チャンクの処理後に更新)
- メタデータヘッダー:約30トークン/チャンク
- API呼び出し1回あたりの実効入力:800 + 200 + 30 = 1030トークン
- マップ処理のコスト(gpt-4o-mini、$0.15/1M):73 × 1030 × $0.15/1M ≈ $0.011
コンテキスト戦略はコストをほとんど増やさずに、整合性を大幅に高めます。
理解度チェック
チャンク単位で文書を処理する際、ローリングサマリーにはどのような目的がありますか?
まとめ:チャンク間のコンテキスト
チャンク間のコンテキストを維持する4つの戦略です。
- オーバーラップ:チャンクNの最後のN個のトークンを、チャンクN+1の先頭で繰り返す
- ローリングサマリー:短い継続的な要約を各チャンクの前に注入する
- メタデータタグ:文書、章、セクション、ページの情報を含むヘッダー
- ページマーカー:チャンク分割の前にページ番号をテキストへ埋め込む
本番パイプラインでは、4つすべてを組み合わせます。複数のチャンクにまたがる質問で、チャンク間のコンテキスト保持をテストします。これで、長文書の処理戦略に関するコース16を終了します。
よくある質問
「チャンク間でコンテキストを維持する」レッスンは無料ですか?
はい。「チャンク間でコンテキストを維持する」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Prompt Engineeringコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Prompt Engineeringコースには全4レッスンが含まれています。
「チャンク間でコンテキストを維持する」で何を学びますか?
連続性を保つために、オーバーラップ、ローリングコンテキスト、メタデータの挿入を使います。 ブラウザで直接実行するハンズオンコードでAI Prompt Engineeringを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
AI Prompt Engineeringを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAI Prompt Engineeringは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「チャンク間でコンテキストを維持する」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAI Prompt Engineeringレッスンでコードを書いて実行できますか?
はい。すべてのAI Prompt Engineeringレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 長文のチャンク分割戦略
- Map-Reduce 要約パターン
- 階層的要約
- チャンク間でコンテキストを維持する