開發日誌 #1:專案初始化與智能分片引擎
專案背景
公司需要建立內部招標文件知識庫,但市面上的向量資料庫分片效果並不理想。特別是對於中文招標文件這種高度結構化的文檔,傳統的固定大小分片或語義分片都會破壞文檔結構,導致:
- 目錄被拆散
- 章節標題與內容分離
- 階層關係遺失
因此,我決定從零開始打造一套完整的 RAG 系統,核心重點是開發專門針對中文招標文件的智能分片引擎。
系統架構設計
在動手寫程式之前,我先規劃了整個系統的架構:
rag-chunking-engine/
├── src/
│ ├── parsers/ # 文檔解析器
│ │ ├── pdf_parser.py
│ │ ├── word_parser.py
│ │ └── text_parser.py
│ ├── chunkers/ # 分片策略
│ │ ├── fixed_size_chunker.py
│ │ ├── semantic_chunker.py
│ │ ├── structure_aware_chunker.py
│ │ ├── hybrid_chunker.py
│ │ └── proposal_chunker.py ⭐ 核心
│ └── utils/ # 工具模組
│ ├── models.py
│ ├── evaluator.py
│ └── config.py
├── config/
│ └── chunking_config.yaml
└── test_document.py # 文檔測試腳本
設計理念
模組化設計:將解析、分片、評估分離,每個模組都可以獨立測試和替換。
可擴展性:預留多種分片策略接口,雖然目前主要使用 ProposalChunker,但未來可以輕鬆加入其他策略。
統一數據模型:定義 Chunk、Document、Metadata 等標準資料結構,確保各模組間的數據流轉順暢。
ProposalChunker 開發
核心需求分析
中文招標文件(服務建議書)有以下特點:
- 特殊區域:封面、評選表、目次等需要完整保留
- 階層式標題:使用「壹貳參」→「一二三」→「(一)(二)」→「1.2.3.」的多層標題
- 語義完整性:每個章節應該保持完整,不能隨意切斷
- 長度控制:有些章節過長,需要智能分割但不能破壞上下文
實作策略
1. 特殊區域識別
使用正則表達式識別並完整保留特殊區域:
def _is_special_section(self, text: str) -> tuple[bool, str]:
"""識別特殊區域"""
special_patterns = {
'封面': r'服務建議書',
'評選表': r'評選項目對照表',
'目次': r'目\s*次|目\s*錄',
'圖目錄': r'圖\s*目\s*錄',
'表目錄': r'表\s*目\s*錄'
}
for section_type, pattern in special_patterns.items():
if re.search(pattern, text[:200]):
return True, section_type
return False, ''2. 階層式標題解析
支援四層中文標題格式:
# 層級 1: 壹、貳、參、肆、伍、陸、柒、捌、玖、拾
level1_pattern = r'^([壹貳參肆伍陸柒捌玖拾]+)、\s*(.+)$'
# 層級 2: 一、二、三、四、五、六、七、八、九、十
level2_pattern = r'^([一二三四五六七八九十百]+)[\.\、]\s*(.+)$'
# 層級 3: (一)、(二)、(三)
level3_pattern = r'^\(([一二三四五六七八九十]+)\)[\.\、]?\s*(.+)$'
# 層級 4: 1.、2.、3.
level4_pattern = r'^(\d+)[\.\、]\s*(.+)$'3. 章節路徑追蹤
維護完整的章節階層路徑:
def _update_section_path(self, level: int, title: str):
"""更新章節路徑"""
# 截斷當前層級之後的路徑
self.current_section_path = self.current_section_path[:level]
# 添加新的層級
self.current_section_path.append(title)
return ' > '.join(self.current_section_path)這樣就能生成像這樣的完整路徑:
參、專案需求規劃 > 二. 數位雙生智能火場鑑識輔助系統 > 1. 使用者介面層
4. 智能長度控制
當章節內容超過設定的最大長度(預設 3000 字符)時,自動分割但保留上下文:
def _split_large_section(self, content: str, metadata: dict) -> list:
"""分割過長的章節"""
chunks = []
max_chunk = self.config.get('max_chunk_size', 3000)
# 以段落為單位分割
paragraphs = content.split('\n\n')
current_chunk = ""
for para in paragraphs:
if len(current_chunk) + len(para) <= max_chunk:
current_chunk += para + "\n\n"
else:
if current_chunk:
chunks.append(self._create_chunk(current_chunk, metadata))
current_chunk = para + "\n\n"
if current_chunk:
chunks.append(self._create_chunk(current_chunk, metadata))
return chunks實測效果
測試文檔:「華電聯網服務建議書.pdf」(130 頁,約 65,000 字符)
分片結果
✅ 文檔解析成功
📄 總頁數: 130
📝 總字符數: 65,127
✅ 智能分片完成
📦 總分片數: 360
├─ 特殊區域: 5 個
│ ├─ 封面: 1
│ ├─ 評選表: 1
│ ├─ 目次: 1
│ ├─ 圖目錄: 1
│ └─ 表目錄: 1
└─ 正文分片: 355 個
⏱️ 處理時間: 4 秒
分片品質檢查
隨機抽查幾個分片查看效果:
分片 #15(章節開頭):
章節路徑: 壹、專案說明 > 一. 本專案規劃重點摘要
標題層級: 2
內容預覽: (一) 運用數位雙生(Digital Twin)技術建置火場智能鑑識輔助系統...
分片 #87(多層路徑):
章節路徑: 參、專案需求規劃 > 二. 數位雙生智能火場鑑識輔助系統 > 1. 使用者介面層
標題層級: 4
內容: 本層提供友善的 Web-based 操作介面...
可以看到,ProposalChunker 完美地保留了文檔結構和階層關係!
技術難題與解決
難題 1:Windows 編碼問題
問題描述:
UnicodeEncodeError: 'cp950' codec can't encode character '\u2460'
Windows 命令列預設使用 CP950 編碼,無法顯示部分中文字符和符號。
解決方案:
在程式開頭強制使用 UTF-8:
import sys
import io
if sys.platform == 'win32':
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')難題 2:返回類型不一致
問題描述:
AttributeError: 'list' object has no attribute 'text'_split_large_section() 有時返回 list[Chunk],有時返回單個 Chunk,導致後續處理出錯。
解決方案:
統一處理返回值,使用 isinstance() 判斷類型:
result = self._create_section_chunk(...)
if result:
if isinstance(result, list):
chunks.extend(result) # 是 list,用 extend
else:
chunks.append(result) # 是單個對象,用 append數據輸出設計
為了方便後續使用和除錯,設計了完整的輸出結構:
rag/01 華電聯網服務建議書_20251012_124248/
├── chunks.json # 所有分片的 JSON 數據
├── report.txt # 統計報告
├── preview.txt # 前 5 個分片預覽
└── chunks/ # 每個分片的獨立文字檔
├── chunk_0001.txt
├── chunk_0002.txt
└── ...
chunks.json 結構
{
"chunk_id": "chunk_0087",
"text": "本層提供友善的 Web-based 操作介面...",
"metadata": {
"source_file": "doc/01 華電聯網服務建議書.pdf",
"section": "參、專案需求規劃 > 二. 數位雙生智能火場鑑識輔助系統 > 1. 使用者介面層",
"heading": "1. 使用者介面層",
"heading_level": 4,
"page_number": 45,
"start_char": 15234,
"end_char": 17891,
"is_special": false,
"chunk_type": "正文"
}
}完整的元數據讓每個分片都可追溯,為後續的檢索和問答提供了堅實基礎。
階段總結
第一階段完成了專案的核心基礎:
✅ 完成項目:
- 模組化專案結構
- 多格式文檔解析器(PDF/Word/Text)
- ProposalChunker 智能分片引擎
- 完整的數據輸出與報告
✅ 技術突破:
- 四層中文標題識別
- 章節路徑追蹤
- 智能長度控制
- Windows UTF-8 編碼處理
🎯 下一步: 將分片數據向量化並存入 ChromaDB,實現語義檢索功能。