# BM25 中文分词问题修复

## 问题描述

BM25 检索器无法正确处理中文查询，所有检索结果的分数都是 0。

### 根本原因

1. **默认的 `bm25s.tokenize` 不支持中文分词**
   - 使用正则表达式 `r"(?u)\b\w\w+\b"` 进行分词
   - 将"什么是RAG"当作一个整体 token，而不是分成"什么"、"是"、"RAG"
   - 导致查询词无法在文档中找到匹配

2. **`tokenizer` 参数已被弃用**
   - LlamaIndex 的 `BM25Retriever.from_defaults()` 中的 `tokenizer` 参数已废弃
   - 即使传递了 jieba tokenizer，也不会被使用
   - 索引和检索都使用 `bm25s.tokenize` 的默认分词

## 解决方案

### 创建自定义中文 BM25 检索器

在 `src/retrievers/chinese_bm25_retriever.py` 中实现了 `ChineseBM25Retriever` 类：

```python
class ChineseBM25Retriever(BaseRetriever):
    """支持中文的 BM25 检索器"""
    
    def __init__(
        self,
        nodes: List[BaseNode],
        tokenizer: Callable[[str], List[str]],  # jieba tokenizer
        similarity_top_k: int = DEFAULT_SIMILARITY_TOP_K,
        ...
    ):
        # 使用 jieba 对文档进行分词
        corpus_tokens = [self._tokenizer(text) for text in corpus_texts]
        
        # 构建 BM25 索引
        self.bm25 = bm25s.BM25()
        self.bm25.index(corpus_tokens)
    
    def _retrieve(self, query_bundle: QueryBundle):
        # 使用 jieba 对查询进行分词
        query_tokens = self._tokenizer(query)
        
        # 执行 BM25 检索
        results, scores = self.bm25.retrieve([query_tokens], k=self.similarity_top_k)
        ...
```

### 核心改进

1. **索引阶段**：使用 jieba 对所有文档进行中文分词
2. **检索阶段**：使用 jieba 对查询进行中文分词
3. **一致性**：确保索引和检索使用相同的分词器

## 测试结果

### 查询："什么是RAG"

```
[1] 分数: 1.3977
    内容: 检索增强生成(RAG)技术详解
    RAG是一种结合了信息检索和生成式AI的技术框架...
```

✅ **成功匹配到 RAG 技术文档，分数从 0.0 提升到 1.3977**

### 查询："向量数据库"

```
[1] 分数: 0.7952
    内容: 人工智能与机器学习入门...
```

✅ **成功检索到相关文档**

## 使用方法

### 重建索引（必需！）

旧索引是用错误的分词器构建的，必须重建：

```bash
python rebuild_bm25_index.py
# 或
python cli.py build-index --data-dir ./data --force-rebuild
```

### 测试检索

```bash
python test_chinese_bm25.py
```

### CLI 查询

```bash
python cli.py query "什么是RAG" --mode bm25
```

## 技术细节

### jieba 分词示例

```python
查询: "什么是RAG"
分词结果: ['什么', 'rag']  # ✅ 正确分词

默认 bm25s.tokenize:
分词结果: ['什么是rag']     # ❌ 当作一个词
```

### 分词器配置

```python
from src.tokenizers import create_tokenizer

tokenizer = create_tokenizer(
    language="chinese",
    use_stemmer=False  # 中文不需要词干提取
)

tokens = tokenizer("什么是RAG")
# 输出: ['什么', 'rag']
```

## 文件修改

1. **新增文件**
   - `src/retrievers/__init__.py`
   - `src/retrievers/chinese_bm25_retriever.py`

2. **修改文件**
   - `src/storage/bm25_store.py`：使用 `ChineseBM25Retriever` 替代默认的 `BM25Retriever`

## 注意事项

1. **必须重建索引**：旧索引使用错误的分词器，无法兼容
2. **持久化功能**：暂时不支持从持久化文件加载，需要在启动时重建
3. **性能**：jieba 首次加载需要几百毫秒，后续查询速度正常

## 未来改进

1. **实现持久化**：保存分词后的索引，避免每次重建
2. **自定义停用词**：根据业务需求调整停用词列表
3. **混合检索优化**：调整 BM25 和向量检索的权重比例

## 相关文档

- [BM25 功能文档](./BM25_FEATURE_README.md)
- [测试报告](./测试报告.md)
