0Pricing
AI Prompt Engineering · レッスン

Map-Reduce 要約パターン

各チャンクを個別に要約してから、要約を統合します。

「Map-Reduce 要約パターン」はCoddyKit上の無料AI Prompt Engineeringレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Prompt Engineering学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Prompt Engineeringコースには全4レッスンが含まれています。

Map-Reduceパターン

ドキュメントがLLMのコンテキストウィンドウを超える場合、すべてを一度に渡すことはできません。map-reduceパターンを使うと、この問題を解決できます。

  • Map:各チャンクを個別に要約します
  • Reduce:すべてのチャンクの要約を1つの最終要約に統合します

これは分散システムにおける従来のMapReduceに対応するもので、分割統治の考え方を言語タスクに適用しています。

ステップ1:Mapフェーズ

mapフェーズでは、各チャンクを要約用プロンプトとともにLLMへ送ります。モデルは、そのチャンクだけを短く要約して返します。これらの要約をリストに集めます。

各要約は元のチャンクより大幅に短くする必要があり、通常は元の長さの10~20%程度にします。この圧縮によって、reduceステップが実行可能になります。

import openai

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

def summarize_chunk(chunk, model='gpt-4o'):
    resp = client.chat.completions.create(
        model=model,
        messages=[
            {'role': 'system', 'content': 'Summarize the following text concisely in 3-5 sentences.'},
            {'role': 'user', 'content': chunk}
        ]
    )
    return resp.choices[0].message.content

def map_phase(chunks):
    return [summarize_chunk(c) for c in chunks]

ステップ2:Reduceフェーズ

reduceフェーズでは、すべてのチャンクの要約を連結し、統合用プロンプトとともにLLMへ送ります。モデルは、一貫性のある1つの最終要約を生成します。

チャンクの要約を連結した結果がまだ1つのコンテキストウィンドウに収まらない場合は、reduceを再帰的に適用します。まず要約をグループごとにまとめてから、それらを統合してください。

def reduce_phase(chunk_summaries, model='gpt-4o'):
    combined = '\n\n'.join(
        f'Section {i+1}:\n{s}'
        for i, s in enumerate(chunk_summaries)
    )
    resp = client.chat.completions.create(
        model=model,
        messages=[
            {'role': 'system', 'content': 'You are given summaries of consecutive sections of a document. Write a single coherent summary of the entire document.'},
            {'role': 'user', 'content': combined}
        ]
    )
    return resp.choices[0].message.content

まとめて実装:Raw API

ここでは、フレームワークを使わず、OpenAI APIの直接呼び出しだけでmap-reduceパイプライン全体を実装します。各フェーズのプロンプトとパラメーターを完全に制御できます。

def map_reduce_summarize(document, chunk_size=1000):
    chunks = fixed_chunk(document, max_tokens=chunk_size)
    print(f'Chunks: {len(chunks)}')

    chunk_summaries = map_phase(chunks)
    print(f'Map phase complete. Summaries: {len(chunk_summaries)}')

    final_summary = reduce_phase(chunk_summaries)
    return final_summary

with open('long_report.txt') as f:
    doc = f.read()

result = map_reduce_summarize(doc)
print(result)

LangChain MapReduceDocumentsChain

LangChainには、チャンク分割、並列map呼び出し、reduceステップを処理する既製のMapReduceDocumentsChainが用意されています。便利な一方で、Raw APIの呼び出しより柔軟性は低くなります。

from langchain_openai import ChatOpenAI
from langchain.chains.summarize import load_summarize_chain
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.docstore.document import Document

llm = ChatOpenAI(model='gpt-4o', openai_api_key='sk-...')
splitter = RecursiveCharacterTextSplitter(chunk_size=3000, chunk_overlap=200)

with open('long_report.txt') as f:
    text = f.read()

docs = splitter.create_documents([text])
chain = load_summarize_chain(llm, chain_type='map_reduce')
result = chain.invoke(docs)
print(result['output_text'])

Mapフェーズの並列化

各チャンクの要約は独立しているため、mapフェーズは並列化できます。PythonのThreadPoolExecutorを使うと、すべてのチャンクに対するAPI呼び出しを同時に送信でき、実時間を大幅に短縮できます。

from concurrent.futures import ThreadPoolExecutor, as_completed

def map_phase_parallel(chunks, max_workers=10):
    summaries = [None] * len(chunks)
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {
            executor.submit(summarize_chunk, chunk): i
            for i, chunk in enumerate(chunks)
        }
        for future in as_completed(futures):
            idx = futures[future]
            summaries[idx] = future.result()
    return summaries

再帰的なReduce

ドキュメントが非常に長い場合、チャンクの要約自体がコンテキストウィンドウを超えることがあります。その場合は再帰的にreduceを適用します。要約をバッチに分け、各バッチをreduceしてから、バッチの要約をさらにreduceします。

def recursive_reduce(summaries, batch_size=10):
    while len(summaries) > 1:
        batches = [
            summaries[i:i + batch_size]
            for i in range(0, len(summaries), batch_size)
        ]
        summaries = [reduce_phase(batch) for batch in batches]
        print(f'Reduced to {len(summaries)} summaries')
    return summaries[0]

重要な詳細の保持

単純なmap-reduceでよく起こる問題は、圧縮の過程で重要な詳細が失われることです。対策は次のとおりです。

  • mapステップで名前、数値、日付を保持するようプロンプトで指示します
  • reduceステップでセクション間の矛盾を確認するよう求めます
  • チャンクサイズを大きくして、mapのコンテキストをより豊かにします
  • 検証パスを実行し、元のドキュメントに含まれる重要なエンティティが最終要約に現れているかモデルに確認させます
MAP_PROMPT = '''Summarize the following section in 5 sentences.
Preserve all key names, numbers, dates, and conclusions.

Section:
{chunk}'''

Refine Chain:別のパターン

refine chainはmap-reduceに代わる方法です。チャンクを順番に処理し、チャンクNの要約をチャンクN+1とともに渡して、モデルが進行中の要約を更新します。これにより、より一貫性のある出力を生成できますが、並列化できず、処理も遅くなります。

物語としての一貫性が重要な場合(例:法的契約書)はrefineを使用してください。速度が重要な場合(例:ニュース記事のバッチ処理)はmap-reduceを使用してください。

def refine_summarize(chunks):
    current_summary = summarize_chunk(chunks[0])
    for chunk in chunks[1:]:
        prompt = (
            f'Existing summary:\n{current_summary}\n\n'
            f'New section:\n{chunk}\n\n'
            'Update the summary to incorporate the new section.'
        )
        resp = client.chat.completions.create(
            model='gpt-4o',
            messages=[{'role': 'user', 'content': prompt}]
        )
        current_summary = resp.choices[0].message.content
    return current_summary

コストとトークンの管理

map-reduceでは、多数のAPI呼び出しが発生します。gpt-4oを入力トークン100万個あたり5ドルで使用して、100チャンクのドキュメントを処理する場合は次のようになります。

  • Map:100チャンク × 1000トークン = 入力トークン10万個 ≈ 0.50ドル
  • Reduce:約1万トークン(要約) ≈ 0.05ドル
  • 合計:ドキュメント1件あたり約0.55ドル

コストを下げるには、mapフェーズでgpt-4o-mini(100万トークンあたり0.15ドル)を使い、reduceでのみgpt-4oを使用します。このハイブリッド方式なら、品質をほとんど損なわずにコストを70%削減できます。

def map_phase_cheap(chunks):
    # Use mini model for map — cheaper, sufficient for chunk summaries
    return [
        summarize_chunk(c, model='gpt-4o-mini')
        for c in chunks
    ]

def reduce_phase_quality(summaries):
    # Use full model for final synthesis
    return reduce_phase(summaries, model='gpt-4o')

Map-Reduceを使う場面

map-reduceによる要約は、次のような場合に適しています。

  • ドキュメントがモデルのコンテキストウィンドウより長い場合
  • 多数のドキュメントを一括要約する場合(ドキュメント単位でも並列化できます)
  • 各ステップのプロンプトを細かく制御する必要がある場合

一方、特定の事実を抽出する場合(代わりに検索を使用してください)や、ドキュメントがコンテキストに収まるほど短い場合(そのまま直接要約してください)には、あまり適していません。

理解度チェック

map-reduceによる要約パターンでは、reduceフェーズで何が行われますか?

振り返り:Map-Reduce要約

map-reduceパターンは、1回のLLM呼び出しでは長すぎるドキュメントを処理します。

  • Map:各チャンクを個別に要約します — 並列化できます
  • Reduce:チャンクの要約を1つの最終要約に統合します
  • 再帰的なreduce:要約自体が長すぎる場合に適用します
  • コスト削減のポイント:mapには安価なモデルを、reduceには高品質なモデルを使用します

LangChainのMapReduceDocumentsChainには、既製の実装が用意されています。次のレッスンでは、書籍や研究論文を対象とした階層的要約を扱います。

よくある質問

「Map-Reduce 要約パターン」レッスンは無料ですか?

はい。「Map-Reduce 要約パターン」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Prompt Engineeringコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Prompt Engineeringコースには全4レッスンが含まれています。

「Map-Reduce 要約パターン」で何を学びますか?

各チャンクを個別に要約してから、要約を統合します。 ブラウザで直接実行するハンズオンコードでAI Prompt Engineeringを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

AI Prompt Engineeringを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのAI Prompt Engineeringは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「Map-Reduce 要約パターン」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このAI Prompt Engineeringレッスンでコードを書いて実行できますか?

はい。すべてのAI Prompt Engineeringレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 長文のチャンク分割戦略
  2. Map-Reduce 要約パターン
  3. 階層的要約
  4. チャンク間でコンテキストを維持する
← AI Prompt Engineeringに戻る