はじめに
RAGシステム構築において、検索と生成結果の精度を向上させるための核心は、構造化されたChunksの設計にあります。
1. 一般的なChunks分割方法
1. 固定サイズChunk分割(Fixed-size Chunking)
from langchain.text_splitter import CharacterTextSplitter
text = "これはLangChainを使って固定サイズチャンクをテストするサンプルテキストです。chunk_sizeとchunk_overlapを設定することで、チャンクのサイズと内容をうまくコントロールできます。"
text_splitter = CharacterTextSplitter(
separator="", # 特定の文字で分割しない
chunk_size=512, # 各チャンクの文字数
chunk_overlap=3, # 重複する文字数
length_function=len,
)
chunks = text_splitter.split_text(text)
print(chunks)
2. 文や段落、特定の句読点などに基づく分割
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
loader = TextLoader("../../data/xxx.txt")
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", "。", ",", " ", ""], # 区切り文字の優先順位
chunk_size=200,
chunk_overlap=10,
)
chunks = text_splitter.split_text(docs)
3. セマンティックベースの分割
https://zhuanlan.zhihu.com/p/1924496550433919103
2. 構造化Chunks分割方法(推奨)
1. すでに構造化されている文書向け
例:タイトル、序論、第1章、第2章、結論などのタグに基づく直接的な分割。
https://zhuanlan.zhihu.com/p/1987591795891269696
2. 非構造化・半構造化文書向け
検索と生成結果の精度向上のため、私は全ての非構造化・半構造化文書を構造化Chunksに変換することを推奨します。異なる構造の文書には、それぞれ異なる構造化Chunks設計が必要です。
例:数百件の履歴書を分割し、ベクトル化してベクトルDBに保存する場合。各履歴書は異なるテンプレートを使用しています。従来のChunks分割方法では、検索・生成の精度が低下します。
私のアプローチ:
まず、人間の視点で考えてみます。テンプレートが異なっても、この部分が個人情報、あの部分がプロジェクト経験、その部分が資格情報と簡単に判断できます。これをコードでどう判別するか?
ステップ1:物理的な段落分割(意味は問わず、単に段落単位に分割)
blocks = re.split(r'\n\s*\n',text)
ステップ2:段落のタイプ分類。
分類は以下のように区別します:
基本情報(氏名、性別、生年月日、学歴など)
スキル(python, C++ など)
自己評価
資格情報
プロジェクト経験(1プロジェクト毎に1チャンク)
全ての履歴書の構造は以下のようになります:
resume_chunks = [
basic_chunk,
skills_chunk,
introduction_chunk,
certification_chunk,
project_chunk_1,
project_chunk_2,
project_chunk_3,
......
other
]
(1) キーワード頻度アルゴリズムで判定。
type_keywords = {
"basic_chunk" : ["姓名", "年龄", "年纪", "出生年月", "大学", "学院", ...],
"skills_chunk" : ["python", "C++", "iOS", "Android", "Java", "PHP", ...],
"introduction_chunk" : ["学習好き", "ストレス耐性", "高効率", "アジャイル開発", ...],
"certification_chunk" : ["コンピューター二級", "大学英語四級", "大学英語六級", "IELTS", "AWS", ...],
"project_chunk" : ["プロジェクト経験", "担当", "期間", ...]
}
スコア = ヒットしたキーワード数 / 段落長さ
(2) 日付パターン頻度で判定
(19|20)\d{2}年\s*\d{1,2}月
2回以上出現はプロジェクトまたは職務経験を示す
ステップ3:隣接段落の意味的統合(ソフトマージ)
1つのプロジェクトが3つの段落に分かれている問題を解決
cos_sim(block[i], block[i+1]) > 0.85
これら3ステップを経て構造化Chunksが得られ、データをさらにクリーンアップ後、意味的に整った段落をベクトル化してベクトルDBに保存します。
利点:意味の連続性を最大限保持し、LLMを使わずコストを削減;検索と生成の精度が10倍向上。
project_chunk_1のjson例:
chunk = {
"chunk_id": "resume_Louis_project_01",
"resume_id": "resume_Louis",
"section_type": "project",
"title": "感情支援AIエージェント",
"period":{
"from": "2026-01",
"to": "2026-02"
},
"role": ["設計", "開発", "テスト"],
"skills": ["python", "js"],
"content": "プロジェクト内容:感情支援AIエージェントを開発し、多くのプログラマーにサービスを提供...",
"source":{
"file": "履歴書(Louis).pdf",
"page": [2,3]
}
}