mcpField
C
chunking-strategies

chunking-strategies

github.com/latestaiagentsデータ・データベース665 回インストール28 週間前に更新
リポジトリ

chunking-strategies について

RAG の検索パイプラインを構築する開発者向けに、テキスト分割方式を比較し、Python の実装例、サイズの指針、評価方法を示すスキルです。

主な機能

  1. 01オーバーラップ付き固定サイズ分割
  2. 02セマンティック分割と親子分割
  3. 03文書コンテキストを保つレイトチャンク化
  4. 04Markdown・コード対応の分割
  5. 05分割サイズと評価の指針

活用シーン

  1. 01技術文書を検索用に分割
  2. 02RAGで関連する文章をまとめて保持
  3. 03内容に応じた分割サイズの選択
  4. 04テストクエリによる分割結果の評価

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/latestaiagents/agent-skills --skill chunking-strategies

インストール後、Claude Code で/chunking-strategiesを入力して読み込まれたか確認します。

詳細

RAG のチャンク分割戦略

最適なチャンク分割は、RAG の性能を良好な水準から優れた水準へと引き上げます。

チャンク分割が重要な理由

不適切なチャンク分割は、次の問題を引き起こします。

  • コンテキストの断片化(回答が複数のチャンクに分割される)
  • 関連性の低い検索結果(チャンクにノイズが多すぎる)
  • 関係性の喪失(親子関係にあるコンテンツが分離される)
  • トークンの無駄(チャンクが大きすぎる、または小さすぎる)

チャンク分割方法の比較

方法最適な用途チャンクの品質実装
固定サイズシンプルなドキュメント、均一なコンテンツ中簡単
再帰的構造化されたドキュメント、マークダウン高中程度
セマンティック複雑なドキュメント、多様なコンテンツ最高複雑
親子階層構造のドキュメント高中程度
レイトチャンク分割コンテキストの保持最高複雑

パターン 1:オーバーラップを使った固定サイズ分割

基本的なアプローチです。シンプルですが効果的です。

from langchain.text_splitter import RecursiveCharacterTextSplitter

def create_fixed_chunks(
    text: str,
    chunk_size: int = 512,
    chunk_overlap: int = 50
) -> list[str]:
    """
    Split text into fixed-size chunks with overlap.

    Guidelines:
    - chunk_size: 256-1024 tokens (512 is solid default)
    - overlap: 10-20% of chunk_size
    """
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        length_function=len,
        separators=["\n\n", "\n", ". ", " ", ""]
    )
    return splitter.split_text(text)

パターン 2:セマンティック分割

任意の境界ではなく、意味に基づいてグループ化します。

from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings

def create_semantic_chunks(text: str) -> list[str]:
    """
    Split text based on semantic similarity between sentences.
    Keeps related content together.
    """
    embeddings = OpenAIEmbeddings()

    splitter = SemanticChunker(
        embeddings=embeddings,
        breakpoint_threshold_type="percentile",
        breakpoint_threshold_amount=95  # Higher = fewer, larger chunks
    )

    return splitter.split_text(text)
カスタムセマンティック分割
import numpy as np
from sentence_transformers import SentenceTransformer

def semantic_chunk(
    sentences: list[str],
    model_name: str = "all-MiniLM-L6-v2",
    threshold: float = 0.5
) -> list[list[str]]:
    """
    Group sentences by semantic similarity.
    """
    model = SentenceTransformer(model_name)
    embeddings = model.encode(sentences)

    chunks = []
    current_chunk = [sentences[0]]

    for i in range(1, len(sentences)):
        # Cosine similarity between consecutive sentences
        sim = np.dot(embeddings[i-1], embeddings[i]) / (
            np.linalg.norm(embeddings[i-1]) * np.linalg.norm(embeddings[i])
        )

        if sim >= threshold:
            current_chunk.append(sentences[i])
        else:
            chunks.append(current_chunk)
            current_chunk = [sentences[i]]

    chunks.append(current_chunk)
    return chunks

パターン 3:親子チャンク分割

小さい単位で検索し、コンテキストとともに返します。

from llama_index.core.node_parser import (
    HierarchicalNodeParser,
    SentenceSplitter,
    get_leaf_nodes
)
from llama_index.core import StorageContext, VectorStoreIndex
from llama_index.core.retrievers import AutoMergingRetriever

def create_hierarchical_index(documents):
    """
    Create parent-child chunk hierarchy.
    Small chunks for retrieval, auto-merge to parents for context.
    """
    # Define chunk sizes for each level
    node_parser = HierarchicalNodeParser.from_defaults(
        chunk_sizes=[2048, 512, 128]  # Parent → Child → Leaf
    )

    nodes = node_parser.get_nodes_from_documents(documents)
    leaf_nodes = get_leaf_nodes(nodes)

    # Store all nodes
    storage_context = StorageContext.from_defaults()
    storage_context.docstore.add_documents(nodes)

    # Index only leaf nodes
    index = VectorStoreIndex(
        leaf_nodes,
        storage_context=storage_context
    )

    # Retriever auto-merges to parents when siblings retrieved
    retriever = AutoMergingRetriever(
        index.as_retriever(similarity_top_k=12),
        storage_context=storage_context,
        simple_ratio_thresh=0.3  # Merge if 30%+ siblings retrieved
    )

    return retriever

パターン 4:レイトチャンク分割(2026 年の手法)

まずドキュメント全体を埋め込み、その後チャンクに分割します。これにより、グローバルコンテキストが保持されます。

def late_chunking(
    document: str,
    model,
    chunk_size: int = 512
) -> list[dict]:
    """
    Late chunking: embed document, then split embeddings.
    Preserves document-level context in chunk embeddings.

    Reference: Jina AI Late Chunking (2024)
    """
    # 1. Get token-level embeddings for full document
    tokens = model.tokenize(document)
    token_embeddings = model.encode_tokens(tokens)

    # 2. Split into chunks
    chunks = []
    for i in range(0, len(tokens), chunk_size):
        chunk_tokens = tokens[i:i + chunk_size]
        chunk_embeddings = token_embeddings[i:i + chunk_size]

        # 3. Pool chunk embeddings (mean pooling)
        chunk_vector = np.mean(chunk_embeddings, axis=0)

        chunks.append({
            "text": model.decode(chunk_tokens),
            "embedding": chunk_vector
        })

    return chunks

パターン 5:マークダウン/コードを考慮したチャンク分割

from langchain.text_splitter import (
    MarkdownHeaderTextSplitter,
    Language,
    RecursiveCharacterTextSplitter
)

def chunk_markdown(text: str) -> list[dict]:
    """Split markdown by headers, preserving structure."""
    headers_to_split_on = [
        ("#", "h1"),
        ("##", "h2"),
        ("###", "h3"),
    ]

    splitter = MarkdownHeaderTextSplitter(
        headers_to_split_on=headers_to_split_on
    )

    return splitter.split_text(text)


def chunk_code(code: str, language: str = "python") -> list[str]:
    """Split code respecting language syntax."""
    lang_map = {
        "python": Language.PYTHON,
        "javascript": Language.JS,
        "typescript": Language.TS,
    }

    splitter = RecursiveCharacterTextSplitter.from_language(
        language=lang_map.get(language, Language.PYTHON),
        chunk_size=1000,
        chunk_overlap=100
    )

    return splitter.split_text(code)

チャンクサイズのガイドライン

コンテンツの種類推奨サイズオーバーラップ
Q&A/FAQ256-51225-50
技術ドキュメント512-102450-100
法的文書1024-2048100-200
コード500-100050-100
会話256-51250-100

評価:チャンク分割の品質を確認する方法

def evaluate_chunking(chunks: list[str], test_queries: list[dict]):
    """
    Evaluate chunk quality with test queries.

    test_queries format:
    [{"query": "What is X?", "expected_chunk_contains": "X is..."}]
    """
    results = {
        "avg_chunk_size": np.mean([len(c) for c in chunks]),
        "chunk_size_std": np.std([len(c) for c in chunks]),
        "total_chunks": len(chunks),
        "retrieval_hits": 0
    }

    for tq in test_queries:
        # Check if expected content is in a single chunk
        for chunk in chunks:
            if tq["expected_chunk_contains"] in chunk:
                results["retrieval_hits"] += 1
                break

    results["hit_rate"] = results["retrieval_hits"] / len(test_queries)
    return results

ベストプラクティス

  1. チャンクサイズをクエリの長さに合わせる - チャンクのサイズは、想定されるクエリと同程度にします
  2. 意味のまとまりを保つ - 文の途中や段落の途中で分割しないでください
  3. メタデータを含める - 各チャンクにソース、ページ、セクションの情報を追加します
  4. 実際のクエリでテストする - 実際のユースケースで評価します
  5. 検索モデルを考慮する - 特定の埋め込みモデルは、特定のチャンクサイズを好む場合があります

クイック判断ツリー

What type of content?
├─ Structured (headers, sections)
│   └─ Use: Markdown/recursive splitter + hierarchy
├─ Unstructured (prose, articles)
│   └─ Use: Semantic chunking
├─ Code
│   └─ Use: Language-aware splitter
└─ Mixed
    └─ Use: Parent-child with semantic leaves

コメント