#!/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)')