The upper quality limit of RAG is determined by the quality of the corpus.无论嵌入和搜索算法多么复杂,如果原始数据不精炼,结果都会很糟糕。在这个项目中,分块管道可以遵循---分隔符和段落边界的原因是因为数据已经在收集阶段被细化为这些结构。
books/
├── 0. 자체 정리/ ← 가장 고품질: 1차 수집 → AI 재분류 → 주제별 MD
│ ├── 01_음양론/
│ ├── 02_오행론/
│ ├── ... (총 20개 주제 폴더 + 부록)
│ └── 20_실전사례/
├── 1. 나의 사주명리-심화편/ ← 고품질: 전자책 추출, 장/절 구조 유지
├── 2. 사주명리 실전 100구문/ ← 고품질: 전자책 추출
└── 99. 온라인 자료 (정리 X)/ ← 2차 수집 원본: PDF/TXT, 구조 불균일
关键观察: 第 0 层是 使用人工智能对主要在线收集的数据按类别进行精确重新分类的结果。将原文划分为主题(阴阳理论、五行理论、极国理论等)并赋予 Markdown 结构(--- 分割线、## 标题),以最大限度地提高分块质量。 99级是第二次在线征集,后来进行,尚未组织。分块质量在 0 到 2 时较高,在 99 时相对较低。换句话说,对于经过以下步骤的数据,RAG 性能越好:收集→基于人工智能的重新分类→结构化。
为本项目收集在线材料时执行的规则:
1.所需来源:
모든 파일에 `출처: URL` 또는 `https://` 포함 필수.
없으면 자동 삭제:
grep -cE '(https?://|출처:)' "$f" → 0이면 rm
没有来源的文档不可靠,因此不包含在语料库中。
2. --- 用分隔线分隔主题:
当一个TXT文件中有多个主题时,请使用---明确区分。这是在分块阶段防止“跨越主题边界的块”的关键设备。
# 좋은 예: 주제가 명확히 분리됨
갑목(甲木)은 양의 목기운으로, 큰 나무를 상징한다...
---
을목(乙木)은 음의 목기운으로, 풀이나 덩굴을 상징한다...
3.没有人工智能生成的内容:
当LLM创建的内容被添加到语料库中时,幻觉就会被再现。所有文档必须是人类撰写的原创文本(学术论文、教科书、专家文章)。
4.自动收集的文件大小限制:
当自动收集代理 (saju-research-collector) 创建新文件时,每个文件的行数限制为 200 行或更少。这是由于代理的稳定性问题(生成超过 400 行时失败),而不是整个语料库的限制。实际语料库通常包含 400 到 1,700 行的文件。但是,如果一个文件中混合了太多主题,则在分块过程中可能会出现主题污染,因此建议尽可能按主题分隔文件。
当 IT 公司将服务计划或知识库构建为 RAG 语料库时,同样的原则也适用:
| 原则 | 沙州项目 | IT公司申请 |
|---|---|---|
| 结构化标记 | --- 分隔线,## 标题 |
按端点分离 Confluence 标题、概念块和 API 文档 |
| 源头追踪 | 网址、页码 | 文档 ID、版本、作者、最后修改日期 |
| 指示是否创建AI | 禁止 | 标记(source: human 与 source: ai-draft) |
| 冗余管理 | dedup.py 余弦相似度 | 文档版本控制+以前版本的存档 |
数据收集管道示例(Confluence → RAG):
Confluence API → 페이지 추출
→ HTML → Markdown 변환 (pandoc / html2text)
→ 메타데이터 추출 (제목, 스페이스, 라벨, 최종수정일)
→ 빈 페이지 / 템플릿 필터링
→ 구조화: 헤딩 기준 섹션 분리
→ 이전 버전 대비 diff → 변경분만 재처리
→ chunks.jsonl에 추가
# chunk.py 핵심 파라미터
--chunk-size 512 # 최대 512자 (글자 단위, 토큰이 아니라 char)
--overlap 64 # 64자 오버랩으로 문맥 단절 방지
重叠意味着: 在下一个块的开头包含上一个块的最后 64 个字符。例如,如果有一个块以“...Gapmok has a Strong Personality.”结尾,则下一个块继续为“Gapmok has a Strong Personality.Therefore, Leadership...”,这确保可以在任一块中检索在块边界处断开的句子以及完整的上下文。
选择512个字符的原因:
与嵌入模型的关系 - 发现的错误和吸取的教训:
在分析这个项目的过程中,发现了密集嵌入的一个性能损失bug。 paraphrase-multilingual-MiniLM-L12-v2 的模型架构支持 512 个 token (max_position_embeddings: 512),但句子转换器库默认为 128 个 token,因此仅嵌入块的第一部分,其余部分被截断。
| 物品 | 价值 |
|---|---|
| 模型架构支持 | 512 个代币 |
| 库默认值(错误,已修复) | 128 个代币 |
| 韩语 512 个字符 → 实际令牌数 | ~337 个代币 |
| 128 个令牌限制反映了实际嵌入 | 前约 200 个字符(约占总数的 39%) |
| 128 vs 512 嵌入余弦相似度 | 0.55(信息显着不同) |
如何修复: model.max_seq_length = 512 添加一行并重建索引。然而,由于该模型可能是基于 128 个 token 进行训练的,因此扩展到 512 个 token 时的质量提升程度需要通过基准来确认。
为什么系统仍然有效:
教训: 不要盲目相信库的默认设置。模型架构和库设置支持的范围可能有所不同。构建嵌入管道时,必须使用max_seq_length检查实际块中的令牌数量。这种类型的配置不匹配很难检测到,因为它会默默地降低性能而不会导致错误。
选择64字符重叠的原因:
它只是不会截断每 512 个字符。 尊重语义单元的分层分区:
Level 1: --- 구분선으로 섹션 분리 (atomic boundary, 절대 넘지 않음)
Level 2: 빈 줄(\n\n)로 단락 분리
Level 3: 문장 단위 분할 (단락이 512자 초과 시)
核心代码(chunk.py:116-137):
def _split_into_paragraphs(text: str) -> list[str]:
# --- 구분선이 있으면 섹션별로 분리, 각 섹션을 atomic 단락으로
if _SECTION_SEPARATOR.search(text):
sections = _SECTION_SEPARATOR.split(text)
paragraphs = []
for section in sections:
section = section.strip()
if not section:
continue
paragraphs.append(section)
return paragraphs
# 없으면 빈 줄로 분리
parts = re.split(r"\n{2,}", text)
return [p.strip() for p in parts if p.strip()]
为什么---分隔线很重要:
---进行主题划分--- 设置为原子边界可以从源头阻止主题污染。IT 服务中的相同结构:
API 文档 (Swagger/OpenAPI) 对于每个端点都有自然的边界,Confluence 页面分为多个部分## 헤딩。对于此类文档,请使用标题 (##) 作为原子边界,而不是 ---。例如,如果GET /api/users和POST /api/users混合在同一个块中,则会在搜索过程中造成混乱,但如果按端点分块,您将获得准确的结果。
此操作的先决条件:
如第 4 章所述,数据在收集阶段必须已包含 --- 分隔符。此策略不适用于没有分隔符的非结构化文档(扫描的 PDF、旧版 Word 文件等),并且需要额外的预处理(基于 LLM 的部分检测、基于规则的标题提取)。
当段落超过 512 个字符时,将考虑韩语结尾,将其分为句子:
sentences = re.split(
r"(?<=[.!?。!?\n])\s*|(?<=[다요음함임됨것]\.)\s+",
para,
)
.!?。!? 从后面分开(通用)다.、요.、음.、함. 等 在韩语最终结尾 + 句号后分割def _detect_lang(text: str) -> str:
"""텍스트 첫 2000자의 문자 유형 비율로 언어 판별"""
# 한글 음절(AC00-D7AF) > 20% → ko
# CJK 통합(4E00-9FFF) > 20% → zh
# ASCII 알파벳이 대다수 → en
语言标签存储在块元数据中,它决定搜索期间标记器的选择和BM25查询扩展方向。