| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- #!/usr/bin/env python3
- """
- 构建 RAG 索引: 文本分块 → JSON 索引文件
- 简单但实用的 BM25 风格索引(无需向量数据库)
- """
- import json
- import os
- import re
- from pathlib import Path
- PROCESSED_DIR = Path('/home/67/knowledge/maritime/processed')
- INDEX_FILE = Path('/home/67/knowledge/maritime/rag_index.json')
- CHUNK_SIZE = 512 # 每块字符数
- CHUNK_OVERLAP = 50 # 重叠字符数
- def split_chunks(text, source_file, chunk_size=CHUNK_SIZE, overlap=CHUNK_OVERLAP):
- """将文本分割为带重叠的块"""
- chunks = []
- # 按段落优先分割
- paragraphs = re.split(r'\n\s*\n', text)
-
- current_chunk = ''
- chunk_id = 0
-
- for para in paragraphs:
- para = para.strip()
- if not para:
- continue
-
- if len(current_chunk) + len(para) <= chunk_size:
- current_chunk += '\n' + para if current_chunk else para
- else:
- if current_chunk:
- chunks.append({
- 'id': f'{source_file}#chunk{chunk_id}',
- 'source': source_file,
- 'chunk_id': chunk_id,
- 'text': current_chunk,
- 'length': len(current_chunk),
- })
- chunk_id += 1
- # 重叠: 保留上一块末尾
- overlap_text = current_chunk[-overlap:] if len(current_chunk) > overlap else ''
- current_chunk = overlap_text + '\n' + para
- else:
- # 单段落超长,按字符切
- for i in range(0, len(para), chunk_size - overlap):
- chunk_text = para[i:i+chunk_size]
- chunks.append({
- 'id': f'{source_file}#chunk{chunk_id}',
- 'source': source_file,
- 'chunk_id': chunk_id,
- 'text': chunk_text,
- 'length': len(chunk_text),
- })
- chunk_id += 1
- current_chunk = ''
-
- # 最后一块
- if current_chunk.strip():
- chunks.append({
- 'id': f'{source_file}#chunk{chunk_id}',
- 'source': source_file,
- 'chunk_id': chunk_id,
- 'text': current_chunk,
- 'length': len(current_chunk),
- })
-
- return chunks
- # 构建索引
- all_chunks = []
- txt_files = sorted(PROCESSED_DIR.glob('*.txt'))
- print(f'处理 {len(txt_files)} 个文本文件...')
- for i, txt_path in enumerate(txt_files):
- text = txt_path.read_text(encoding='utf-8')
- if '[扫描版PDF' in text:
- continue
-
- chunks = split_chunks(text, txt_path.stem)
- all_chunks.extend(chunks)
-
- if (i+1) % 200 == 0:
- print(f' 进度: {i+1}/{len(txt_files)}, 累计块数: {len(all_chunks)}')
- # 构建关键词倒排索引(简单 BM25 近似)
- print(f'构建倒排索引... ({len(all_chunks)} 个文本块)')
- keyword_index = {}
- for chunk in all_chunks:
- # 分词: 中文按字符 bigram + 英文按空格
- words = set()
- text = chunk['text'].lower()
- # 中文 bigram
- chinese_chars = re.findall(r'[\u4e00-\u9fff]+', text)
- for cc in chinese_chars:
- for j in range(len(cc) - 1):
- words.add(cc[j:j+2])
- if len(cc) >= 3:
- for j in range(len(cc) - 2):
- words.add(cc[j:j+3])
- # 英文单词
- eng_words = re.findall(r'[a-z]+', text)
- words.update(eng_words)
-
- for w in words:
- if w not in keyword_index:
- keyword_index[w] = []
- keyword_index[w].append(chunk['id'])
- # 保存索引
- index_data = {
- 'total_chunks': len(all_chunks),
- 'total_files': len(txt_files),
- 'chunk_size': CHUNK_SIZE,
- 'chunks': all_chunks,
- 'keyword_index_size': len(keyword_index),
- }
- with open(INDEX_FILE, 'w', encoding='utf-8') as f:
- json.dump(index_data, f, ensure_ascii=False)
- # 单独保存倒排索引
- with open(INDEX_FILE.with_suffix('.keywords.json'), 'w', encoding='utf-8') as f:
- json.dump(keyword_index, f, ensure_ascii=False)
- print(f'完成!')
- print(f' 文本块: {len(all_chunks)}')
- print(f' 关键词: {len(keyword_index)}')
- print(f' 索引文件: {INDEX_FILE} ({INDEX_FILE.stat().st_size / 1024 / 1024:.1f} MB)')
|