Metadata-Version: 2.4
Name: searchbox
Version: 0.3.1
Summary: A lightweight local document ingestion and retrieval package.
Author: searchbox
Keywords: search,retrieval,documents,sqlite,pdf,milvus,vector
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: server
Requires-Dist: fastapi>=0.100.0; extra == "server"
Requires-Dist: uvicorn[standard]>=0.22.0; extra == "server"
Requires-Dist: pydantic>=2.0.0; extra == "server"
Requires-Dist: setuptools<82.0.0,>=65.0.0; extra == "server"
Requires-Dist: pymilvus[milvus-lite]<2.6.0,>=2.5.0; extra == "server"
Requires-Dist: pyyaml>=6.0; extra == "server"
Requires-Dist: httpx>=0.24.0; extra == "server"
Provides-Extra: local
Requires-Dist: torch>=2.0.0; extra == "local"
Requires-Dist: transformers>=4.40.0; extra == "local"
Provides-Extra: dashscope
Requires-Dist: dashscope>=1.14.0; extra == "dashscope"
Provides-Extra: vllm
Requires-Dist: httpx>=0.24.0; extra == "vllm"
Provides-Extra: all
Requires-Dist: searchbox[server]; extra == "all"
Requires-Dist: searchbox[local]; extra == "all"
Requires-Dist: searchbox[dashscope]; extra == "all"
Dynamic: license-file

# searchbox

`searchbox` 是一个面向本地知识库 / 文档检索场景的 Python 包，提供：

- 内置 FastAPI 服务端
- 基于 Milvus Lite 的本地向量存储
- 文档抽取、分块、向量化、检索、重排的一体化流程
- 面向多用户、多 collection 的数据隔离能力
- 可切换的 embedding / reranking provider（mock、local、DashScope、vLLM）
- 面向论文摘要库与自定义 collection 的扩展接口

项目定位不是“通用 RAG 全家桶”，而是一个轻量、可本地启动、接口明确的“知识检索底座”。安装后可直接通过 `python -m searchbox start` 启动，不依赖 Docker。

---

## 目录

1. [项目概览](#项目概览)
2. [核心能力](#核心能力)
3. [项目结构](#项目结构)
4. [工作流程](#工作流程)
5. [安装](#安装)
6. [快速开始](#快速开始)
7. [配置文件说明](#配置文件说明)
8. [Python API 使用说明](#python-api-使用说明)
9. [服务端 REST API](#服务端-rest-api)
10. [Provider 说明](#provider-说明)
11. [文件解析与分块策略](#文件解析与分块策略)
12. [多用户、多 collection 与论文库](#多用户多-collection-与论文库)
13. [自定义 collection](#自定义-collection)
14. [测试与示例脚本](#测试与示例脚本)
15. [开发备注与已知事项](#开发备注与已知事项)
16. [许可证](#许可证)

---

## 项目概览

`searchbox` 当前实现由两部分组成：

1. 客户端封装：提供 `MilvusCorpusSearch`、`MilvusDocumentSearch`、`MilvusClient` 等 Python 接口。
2. 内置服务端：位于 `searchbox/server/`，负责文本入库、向量检索、重排、Milvus 管理与 REST API 暴露。

默认运行模式如下：

- 服务端启动后，使用本地 Milvus Lite `.db` 文件作为向量数据库。
- 客户端通过 HTTP 调用内置服务端。
- 文档入库时先抽取文本，再按字符数切块，再调用 embedding provider 生成向量。
- 查询时先向量召回 chunk，再通过 reranker 做重排，最后按文件级聚合返回结果。

默认数据库文件名是 `./milvus_local.db`，服务端还会维护一个 `.searchbox_server_state.json` 用于记录临时 collection 的 TTL 和论文卷册状态。

---

## 核心能力

### 1. 文档知识库检索

针对普通文档（txt / md / json / html / pdf 等）：

- 支持文件或目录递归入库
- 支持按 `user_id` 做数据隔离
- 支持文件级检索：返回最相关文档
- 支持单文档 chunk 级检索：返回文档内最相关片段
- 支持按 `file_path` 过滤检索范围
- 同一路径重复入库时会先删除旧内容，再写入新版本，避免重复数据

### 2. 多 provider 推理适配

服务端可切换以下 embedding / reranking provider：

- `mock`：测试用，使用哈希向量与伪重排
- `local`：本地 HuggingFace 模型
- `dashscope`：阿里云 DashScope API
- `vllm`：外部 vLLM HTTP 服务

### 3. 多 collection 管理

服务端支持：

- 默认 collection
- 手动创建临时 / 永久 collection
- TTL 自动过期清理
- 按 collection 统计用户与文件

### 4. 论文摘要库

单独的 paper volume 机制，适合维护“论文摘要向量库”：

- 按 volume 建立 collection
- 按摘要向量检索论文
- 支持删除论文、删除整个 volume

### 5. 自定义 collection

支持通过 REST API 创建带指定 schema 的 Milvus collection，包括：

- 标量字段
- dense vector 字段
- BM25 sparse 字段

当前仓库对自定义 collection 暴露的是“schema 创建 / 描述 / 删除”接口，不包含通用插入与搜索封装。

---

## 项目结构

```text
searchbox/
├── pyproject.toml                # 包配置与 optional dependencies
├── README.md                     # 项目说明
├── smoke_test.py                 # 端到端 smoke test
├── all_methods_demo.py           # 历史示例，和当前接口不完全一致
├── tests/
│   ├── test_milvus_document_search.py
│   └── test_server_fixes.py
│
├── searchbox/
│   ├── __init__.py               # 对外导出 API
│   ├── __main__.py               # CLI 入口：init / start / check
│   ├── config.py                 # 客户端配置加载与模板生成
│   ├── milvus.py                 # HTTP client + 高层检索封装
│   ├── extractors.py             # 文件文本抽取
│   ├── factory.py                # SearchFactory
│   ├── base.py                   # 通用搜索基类
│   ├── search_types.py           # SearchCandidate 数据结构
│   ├── sources.py                # 候选源抽象
│   ├── strategies.py             # 搜索策略抽象
│   ├── milvus_config.yaml        # 内置默认配置模板
│   └── server/
│       ├── __init__.py           # start_server / start_server_from_yaml
│       ├── app.py                # FastAPI 路由与服务拼装
│       ├── service.py            # KnowledgeService / PaperService
│       ├── chunking.py           # 文本切块逻辑
│       ├── store.py              # MilvusStore / PaperStore / CustomStore
│       └── providers/
│           ├── base.py
│           ├── mock.py
│           ├── local_qwen.py
│           ├── dashscope_qwen.py
│           └── vllm_qwen.py
```

---

## 工作流程

### 文档入库流程

```text
add_path / add_paths / add_text
        ↓
FileTextExtractor 抽取文本
        ↓
split_text 按段落边界分块
        ↓
Embedder 生成向量
        ↓
MilvusStore 写入 collection
```

### 文件级检索流程

```text
query
  ↓
Embedder(query)
  ↓
Milvus 向量召回 top-p chunks
  ↓
Reranker 对 chunks 重排
  ↓
按 file_path 做 max-pooling 聚合
  ↓
返回 top-k 文件
```

### 单文档片段检索流程

```text
query + file_path
        ↓
限定该文件内的 chunks 做向量召回
        ↓
Reranker 重排
        ↓
返回 top-k chunk 命中
```

---

## 安装

### 1. 基础安装

```bash
pip install .
```

这会安装包本体，但如果你要启动服务端，通常还需要额外依赖。

### 2. 服务端依赖

```bash
pip install "searchbox[server]"
```

包含：

- `fastapi`
- `uvicorn[standard]`
- `pydantic`
- `pymilvus`
- `milvus`
- `pyyaml`
- `httpx`

### 3. 本地模型依赖

```bash
pip install "searchbox[server,local]"
```

额外包含：

- `torch`
- `transformers`

### 4. DashScope 依赖

```bash
pip install "searchbox[server,dashscope]"
```

### 5. 全量安装

```bash
pip install "searchbox[all]"
```

---

## 快速开始

### 方式一：直接用默认 mock 配置体验流程

这是当前最推荐的首次使用方式：

- 无需额外模型服务
- 无需 GPU
- 最适合先验证安装、配置、入库、检索链路是否正常

注意：`mock` 只适合功能验证，不代表真实语义检索质量。

生成配置文件：

```bash
python -m searchbox init
```

建议先把数据库路径改到一个明确可写的子目录，例如：

```yaml
milvus_db:
  uri: "./data/milvus_local.db"
```

然后创建目录：

```bash
mkdir -p data
```

启动服务：

```bash
python -m searchbox start
```

健康检查：

```bash
python -m searchbox check
# 或
curl http://localhost:18765/health
```

在 Python 中使用：

```python
from searchbox import MilvusCorpusSearch

search = MilvusCorpusSearch.from_config("milvus_config.yaml", user_id="alice")

search.add_text(
    text="检索增强生成系统通常需要文档切块、召回与重排。",
    file_path="docs/intro.txt",
    title="Intro",
)

results = search.search_pretty("什么是重排", top_k=3)
for item in results:
    print(item["score"], item["path"], item["snippet"])
```

### 方式二：使用 vLLM 模型服务

如果你希望得到更真实的语义检索效果，推荐使用 `vllm` provider。当前项目的 `vllm` 方案要求：

- 一个 embedding 服务
- 一个 reranking 服务
- `searchbox` 服务再去调用它们

#### 方案 A：联网直接加载 Hugging Face 模型

强烈建议把 `vllm` 放到单独的 Python / Conda 环境里启动，而不是和 `searchbox` 主环境混用。原因是 `vllm` 依赖较重，常见问题包括：

- `libstdc++` / `CXXABI` 版本冲突
- CUDA / PyTorch / Triton 版本不匹配
- 与当前业务环境中的 `sqlite3`、`icu`、`transformers` 等依赖互相干扰

一个更稳的做法是新建独立环境，例如：

```bash
conda create -n vllm_env python=3.12 -y
conda activate vllm_env
conda install -c conda-forge libstdcxx-ng libgcc-ng -y
pip install vllm
```

如果需要联网从 Hugging Face 拉模型，再按需执行：

```bash
huggingface-cli login
```

先分别启动两个 vLLM 服务：

```bash
# embedding 服务
vllm serve Qwen/Qwen3-Embedding-0.6B --port 8806

# rerank 服务
vllm serve Qwen/Qwen3-Reranker-0.6B \
  --port 8807 \
  --hf_overrides '{"architectures": ["Qwen3ForSequenceClassification"],"classifier_from_token": ["no", "yes"],"is_original_qwen3_reranker": true}'
```

然后把 `milvus_config.yaml` 改成：

```yaml
embedding:
  provider: "vllm"
  dim: 1024
  metric_type: "IP"
  model: "Qwen/Qwen3-Embedding-0.6B"

reranking:
  provider: "vllm"
  model: "Qwen/Qwen3-Reranker-0.6B"

vllm:
  embed_base_url: "http://127.0.0.1:8806"
  rerank_base_url: "http://127.0.0.1:8807"
  embed_api_key: "EMPTY"
  rerank_api_key: "EMPTY"
  timeout: 60.0
```

最后启动 `searchbox`：
```bash
python -m searchbox start
```

#### 方案 B：本地模型目录方式

如果你不想让 `vllm serve` 直接联网拉模型，可以先把模型下载到本地目录，再用目录路径启动：

```bash
huggingface-cli download Qwen/Qwen3-Embedding-0.6B --local-dir /path/to/models/Qwen3-Embedding-0.6B
huggingface-cli download Qwen/Qwen3-Reranker-0.6B --local-dir /path/to/models/Qwen3-Reranker-0.6B
```

然后启动：

```bash
vllm serve /path/to/models/Qwen3-Embedding-0.6B --port 8806

vllm serve /path/to/models/Qwen3-Reranker-0.6B \
  --port 8807 \
  --hf_overrides '{"architectures": ["Qwen3ForSequenceClassification"],"classifier_from_token": ["no", "yes"],"is_original_qwen3_reranker": true}'
```

这里要特别注意：

- `Qwen/Qwen3-Embedding-0.6B` 这样的字符串默认是 Hugging Face 模型名，不是要求你手动在当前目录创建同名目录
- 如果你传的是本地路径，那就用真实存在的模型目录路径
- 当前 `searchbox` 的 `vllm` provider 期望的是服务根地址，不要把 `embed_base_url` / `rerank_base_url` 写成带 `/v1` 的地址

#### 完整 vLLM 配置示例

如果你希望直接把 `milvus_config.yaml` 改成可运行的 `vllm` 版本，可以参考下面的完整配置片段：

```yaml
server:
  host: "127.0.0.1"
  port: 18765

milvus_db:
  uri: "./data/milvus_local.db"
  token: ""
  db_name: "default"
  collection: "knowledge"

embedding:
  provider: "vllm"
  dim: 1024
  metric_type: "IP"
  model: "Qwen/Qwen3-Embedding-0.6B"   # 或本地目录路径

reranking:
  provider: "vllm"
  model: "Qwen/Qwen3-Reranker-0.6B"    # 或本地目录路径

vllm:
  embed_base_url: "http://127.0.0.1:8806"
  rerank_base_url: "http://127.0.0.1:8807"
  embed_api_key: "EMPTY"
  rerank_api_key: "EMPTY"
  timeout: 60.0

milvus_service:
  base_url: "http://localhost:18765"
  timeout: 60.0

search:
  recall_top_p: 20
  default_top_k: 5
  score_threshold: null
  collection: null

ingest:
  chunk_max_chars: 2000
  chunk_overlap: 200
```

推荐启动顺序：

```bash
# 1) 启动 embedding vLLM
vllm serve Qwen/Qwen3-Embedding-0.6B --port 8806

# 2) 启动 rerank vLLM
vllm serve Qwen/Qwen3-Reranker-0.6B \
  --port 8807 \
  --hf_overrides '{"architectures": ["Qwen3ForSequenceClassification"],"classifier_from_token": ["no", "yes"],"is_original_qwen3_reranker": true}'

# 3) 启动 searchbox
mkdir -p data
python -m searchbox start
```

如果你使用本地模型目录，把上面两个 `vllm serve` 后面的模型名替换成真实路径即可。

#### 如何选择

- 只想先验证功能是否可用：优先 `mock`
- 想要更真实的检索效果，并且有可用 GPU / 推理服务：用 `vllm`
- 本地模型太重、又没有现成 vLLM 服务：再考虑 `local` 或其他 provider

### 方式三：在代码里后台启动服务

```python
from searchbox import start_server, MilvusCorpusSearch

thread = start_server(
    config={
        "milvus_uri": "./data/milvus_local.db",
        "embed_provider": "mock",
        "rerank_provider": "mock",
        "embed_dim": 1024,
    },
    host="127.0.0.1",
    port=18765,
    background=True,
)

search = MilvusCorpusSearch(user_id="alice", base_url="http://127.0.0.1:18765")
search.add_text("hello retrieval", file_path="demo.txt", title="Demo")
print(search.search("retrieval"))
```

### 方式四：通过 YAML 启动

```python
from searchbox import start_server_from_yaml

start_server_from_yaml("milvus_config.yaml")
```

---

## 配置文件说明

默认模板文件位于 `searchbox/milvus_config.yaml`，执行 `python -m searchbox init` 会复制到当前目录。

### 配置结构

```yaml
server:
  host: "127.0.0.1"
  port: 18765

milvus_db:
  uri: "./milvus_local.db"
  token: ""
  db_name: "default"
  collection: "knowledge"

embedding:
  provider: "mock"
  dim: 1024
  metric_type: "IP"
  model: "Qwen/Qwen3-Embedding-0.6B"
  batch_size: 32
  max_length: 8192

reranking:
  provider: "mock"
  model: "Qwen/Qwen3-Reranker-4B"

vllm:
  embed_base_url: "http://127.0.0.1:8806"
  rerank_base_url: "http://127.0.0.1:8807"
  embed_api_key: ""
  rerank_api_key: ""
  timeout: 60.0

dashscope:
  api_key: ""

milvus_service:
  base_url: "http://localhost:18765"
  timeout: 60.0

search:
  recall_top_p: 20
  default_top_k: 5
  score_threshold: null
  collection: null

ingest:
  chunk_max_chars: 2000
  chunk_overlap: 200
```

### 配置项分工

#### `server`

服务端监听地址与端口，只在启动服务端时使用。

#### `milvus_db`

Milvus 连接与默认 collection：

- `uri`：本地 Milvus Lite 数据库路径，建议使用明确可写的子目录，例如 `./data/milvus_local.db`
- `token`：远程 Milvus 鉴权时使用
- `db_name`：逻辑数据库名
- `collection`：默认知识库 collection 名称

注意：

- Milvus Lite 除了数据库文件，还会在同目录创建 lock 文件
- 因此 `milvus_db.uri` 所在目录必须可写
- 如果把数据库直接放到只读目录，通常会在首次入库或检索时报错

#### `embedding`

控制 embedding provider：

- `provider`：`mock` / `local` / `dashscope` / `vllm`
- `dim`：向量维度，必须与模型输出一致
- `metric_type`：Milvus 向量检索度量方式，默认 `IP`
- `model`：provider 对应模型名或路径
- `batch_size` / `max_length`：仅 `local` 模式使用

#### `reranking`

控制 reranker provider 和模型名。

#### `vllm`

仅 `provider=vllm` 时生效：

- embedding 与 reranking 走不同 base URL
- 支持独立 API key
- 支持请求超时设置

#### `dashscope`

仅 `provider=dashscope` 时需要填写 `api_key`。

#### `milvus_service`

客户端访问服务端时使用：

- `base_url`
- `timeout`

#### `search`

客户端默认检索参数：

- `recall_top_p`：向量召回 chunk 数
- `default_top_k`：最终返回结果数
- `score_threshold`：结果过滤阈值
- `collection`：默认请求的 collection

#### `ingest`

客户端默认入库分块参数：

- `chunk_max_chars`
- `chunk_overlap`

---

## Python API 使用说明

### 1. `MilvusCorpusSearch`

多文件语料检索入口。

### 创建实例

```python
from searchbox import MilvusCorpusSearch

# 推荐：从 YAML 读取客户端配置
search = MilvusCorpusSearch.from_config("milvus_config.yaml", user_id="alice")

# 使用默认配置（base_url=http://localhost:18765）
search = MilvusCorpusSearch.from_config(None, user_id="alice")

# 直接传参
search = MilvusCorpusSearch(
    user_id="alice",
    base_url="http://localhost:18765",
    collection=None,
    recall_top_p=20,
    default_top_k=5,
    score_threshold=0.3,
    chunk_max_chars=2000,
    chunk_overlap=200,
    timeout=60.0,
)
```

### 入库文件 / 目录

```python
# 单文件
search.add_path("/data/report.pdf")

# 目录递归入库
search.add_path("/data/docs/")

# 禁止递归
search.add_path("/data/docs/", recursive=False)

# 包含隐藏文件
search.add_path("/data/docs/", include_hidden=True)

# 批量路径
search.add_paths(["/data/docs/", "/data/notes/a.md"])
```

### 直接入库文本

```python
result = search.add_text(
    text="这是一段不会落地到本地文件的文本。",
    file_path="virtual/doc-001",
    title="虚拟文档",
)
```

### 文件级检索

```python
results = search.search("量子计算", top_k=5)
```

返回格式：

```python
[
    {
        "id": "/data/paper1.pdf",
        "title": "paper1.pdf",
        "content": "最相关的 chunk 文本...",
        "score": 0.91,
        "metadata": {
            "file_path": "/data/paper1.pdf",
            "milvus_score": 0.91,
        },
    }
]
```

### 便于展示的结果

```python
pretty = search.search_pretty("量子计算", top_k=5, snippet_length=280)
```

返回格式：

```python
[
    {
        "path": "/data/paper1.pdf",
        "file_name": "paper1.pdf",
        "score": 0.91,
        "snippet": "截断后的摘要文本...",
        "metadata": {...},
    }
]
```

### 文件管理

```python
files = search.list_files()
deleted = search.delete_file("/data/paper1.pdf")
health = search.health()
```

### 支持的扩展名

```python
exts = search.supported_extensions()
```

当前支持：

- `.txt`
- `.md`
- `.rst`
- `.py`
- `.toml`
- `.yaml`
- `.yml`
- `.csv`
- `.tsv`
- `.html`
- `.htm`
- `.json`
- `.pdf`

### 2. `MilvusDocumentSearch`

单文档 chunk 级检索入口，适合回答“某一篇文档里，哪一段最相关”。

### 创建实例

```python
from searchbox import MilvusDocumentSearch

doc = MilvusDocumentSearch.from_config(
    config="milvus_config.yaml",
    user_id="alice",
    file_path="/data/book.pdf",
)
```

### 检索片段

```python
results = doc.search("实验设置在哪一节", top_k=3)
```

返回格式：

```python
[
    {
        "id": "/data/book.pdf#3",
        "title": "book.pdf",
        "content": "最相关 chunk 文本...",
        "score": 0.84,
        "metadata": {
            "file_path": "/data/book.pdf",
            "chunk_id": 3,
            "start_offset": 1200,
            "end_offset": 1680,
            "milvus_score": 0.84,
        },
    }
]
```

### 展示友好结果

```python
pretty = doc.search_pretty("实验设置在哪一节", top_k=3)
```

字段包含：

- `path`
- `file_name`
- `chunk_id`
- `start_offset`
- `end_offset`
- `score`
- `snippet`

### 3. `MilvusClient`

底层 HTTP 客户端，适合你自己封装 SDK 或服务调用。

```python
from searchbox import MilvusClient

client = MilvusClient(base_url="http://localhost:18765", timeout=60.0)

client.ingest(
    user_id="alice",
    files=[{"file_path": "a.txt", "text": "hello world"}],
)

print(client.search(user_id="alice", query="hello"))
print(client.search_chunks(user_id="alice", file_path="a.txt", query="hello"))
print(client.list_files(user_id="alice"))
print(client.health())
```

### 4. `start_server` / `start_server_from_yaml`

```python
from searchbox import start_server, start_server_from_yaml
```

- `start_server(config=..., background=True)`：编程式启动
- `start_server_from_yaml("milvus_config.yaml")`：从 YAML 启动

### 5. `MilvusConfig` 与配置工具

```python
from searchbox import (
    MilvusConfig,
    load_milvus_config,
    generate_config_template,
    check_service,
)
```

可用于：

- 读取 YAML
- 生成配置模板
- 检查服务是否存活

---

## 服务端 REST API

以下接口来自 `searchbox/server/app.py`。

### 1. 健康检查

- `GET /health`

示例返回：

```json
{
  "ok": true,
  "version": "1.0.0",
  "default_collection": "knowledge",
  "embed_provider": "mock",
  "rerank_provider": "mock",
  "active_collections": 1,
  "active_volumes": 0
}
```

### 2. 文档入库与检索

- `POST /ingest`
- `POST /search`
- `POST /search/chunks`
- `GET /files`
- `DELETE /files`
- `GET /chunks`

#### `POST /ingest`

请求体：

```json
{
  "user_id": "alice",
  "collection": "knowledge",
  "chunk": {"max_chars": 2000, "overlap": 200},
  "files": [
    {
      "file_path": "docs/a.txt",
      "text": "hello world",
      "metadata": {"title": "A"}
    }
  ]
}
```

#### `POST /search`

请求体：

```json
{
  "user_id": "alice",
  "query": "hello",
  "recall_top_p": 20,
  "top_k": 5,
  "include_content": true,
  "score_threshold": 0.3,
  "file_paths": ["docs/a.txt"],
  "collection": "knowledge"
}
```

#### `POST /search/chunks`

请求体：

```json
{
  "user_id": "alice",
  "file_path": "docs/a.txt",
  "query": "hello",
  "recall_top_p": 20,
  "top_k": 5,
  "score_threshold": 0.3,
  "collection": "knowledge"
}
```

### 3. collection 管理

- `POST /collections`
- `GET /collections`
- `GET /collections/{name}`
- `PATCH /collections/{name}`
- `DELETE /collections/{name}`
- `DELETE /collection`（兼容旧接口）
- `GET /collections/{name}/users`
- `DELETE /collections/{name}/users/{user_id}`

#### 特性

- 创建时可指定 `temporary=true`
- 临时 collection 支持 `ttl_hours`
- 过期 collection 会在请求触发时被清理

创建请求示例：

```json
{
  "name": "tmp_demo",
  "temporary": true,
  "ttl_hours": 24
}
```

### 4. 论文摘要库 API

- `POST /papers/collections`
- `GET /papers/collections`
- `GET /papers/collections/{volume}`
- `DELETE /papers/collections/{volume}`
- `POST /papers/ingest`
- `DELETE /papers/files`
- `POST /papers/search`

#### 论文入库示例

```json
{
  "volume": "acl_2025",
  "papers": [
    {
      "file_path": "papers/001.pdf",
      "title": "A Retrieval Paper",
      "abstract": "This paper studies...",
      "metadata": {"authors": ["A", "B"]}
    }
  ]
}
```

### 5. 管理接口

- `GET /admin/collections`
- `GET /admin/collection/stats`
- `GET /admin/files`
- `GET /admin/papers`

适合做管理后台或调试脚本。

### 6. 自定义 collection 接口

- `POST /custom-collections`
- `GET /custom-collections/{name}`
- `DELETE /custom-collections/{name}`

可定义字段类型：

- `int8` / `int16` / `int32` / `int64`
- `float` / `double`
- `bool`
- `varchar`
- `json`

额外支持索引类型：

- `DENSE`
- `BM25`

---

## Provider 说明

### 1. `mock`

特点：

- 无需模型与外网
- 最适合测试流程与 API
- 是当前推荐的默认起步方式
- 检索精度非常有限

实现方式：

- embedding：基于文本 md5 派生伪向量
- reranking：再次用 mock 向量做相似度排序

### 2. `local`

特点：

- 本地加载 HuggingFace 模型
- 不依赖远程 API
- 更适合有 GPU 的机器

当前默认模型名：

- embedding：`Qwen/Qwen3-Embedding-0.6B`
- reranking：`Qwen/Qwen3-Reranker-4B`

注意：

- embedding 维度必须与配置中的 `embedding.dim` 一致
- `LocalQwenReranker` 使用生成式 yes/no 概率近似相关性得分
- CPU 也能运行，但速度可能较慢

### 3. `dashscope`

特点：

- 调用阿里云 DashScope API
- 无需本地 GPU
- 需要配置 `dashscope.api_key`

默认模型名：

- embedding：`text-embedding-v3`
- reranking：`gte-rerank`

### 4. `vllm`

特点：

- 适合已有独立推理服务的场景
- embedding 与 reranking 分别连接不同 HTTP 服务
- 比 `mock` 更接近真实语义检索效果

推荐使用方式：

- embedding：`Qwen/Qwen3-Embedding-0.6B`
- reranking：`Qwen/Qwen3-Reranker-0.6B`
- 初次使用可以直接让 `vllm serve` 联网拉模型
- 如果要更稳定可控，可以提前下载到本地，再用本地目录启动
- 最好在独立环境中运行 `vllm`

约定接口：

- embedding：`POST /pooling`
- reranking：`POST /v1/rerank`

配置示例：

```yaml
embedding:
  provider: "vllm"
  dim: 1024
  metric_type: "IP"
  model: "Qwen/Qwen3-Embedding-0.6B"

reranking:
  provider: "vllm"
  model: "Qwen/Qwen3-Reranker-0.6B"

vllm:
  embed_base_url: "http://127.0.0.1:8806"
  rerank_base_url: "http://127.0.0.1:8807"
  embed_api_key: "EMPTY"
  rerank_api_key: "EMPTY"
  timeout: 60.0
```

注意：

- 当前项目的 `vllm` provider 不是直接用 OpenAI Python SDK
- 它内部调用的是：
  - embedding：`/pooling`
  - reranking：`/v1/rerank`
- 因此 `base_url` 应填写服务根地址，例如 `http://127.0.0.1:8806`，不要额外带 `/v1`

---

## 运行建议

### 1. 本地 Lite 模式的索引类型

当前项目默认面向 `Milvus Lite` 本地模式，dense vector 索引使用 `FLAT`。

原因是：

- `Milvus Lite` local mode 不支持 `HNSW`
- 本地模式常见支持的是 `FLAT`、`IVF_FLAT`、`AUTOINDEX`

影响是：

- `FLAT` 在小规模本地知识库里更稳、更兼容
- 大规模数据下搜索速度可能不如 `HNSW`
- 但检索结果本身不会因为 `FLAT` 而“更差”，它是精确搜索

对于当前项目的典型场景（少量 PDF、本地实验、功能验证），`FLAT` 是更合适的默认值。

### 2. 开发安装方式

本地开发建议使用 editable install：

```bash
pip install -e ".[server]"
```

这样修改 `searchbox/` 下源码后，无需重复打包安装，重启脚本或服务即可生效。

### 3. 用户目录使用方式

项目支持在“非仓库目录”直接使用，典型流程如下：

```bash
cd /path/to/your/workdir
python -m searchbox init
python -m searchbox start
```

然后在同目录编写脚本：

```python
from searchbox import MilvusCorpusSearch

search = MilvusCorpusSearch.from_config("milvus_config.yaml", user_id="alice")
```

这也是推荐的终端用户使用方式。

---

## 文件解析与分块策略

### 1. 文件解析

`FileTextExtractor` 当前支持：

- 纯文本类：`.txt`、`.md`、`.rst`、`.py`、`.toml`、`.yaml`、`.yml`、`.csv`、`.tsv`
- HTML：`.html`、`.htm`
- JSON：`.json`
- PDF：`.pdf`

处理方式：

- 文本文件：按 UTF-8 读取，忽略非法字符
- HTML：正则去标签后压缩空白
- JSON：递归展开字典 / 列表为文本
- PDF：优先调用 `pdftotext -layout`，失败时回退到简单字节级文本提取

注意：PDF fallback 仅为兜底方案，复杂版式文档可能效果一般。若系统安装了 `pdftotext`，效果通常更可靠。

### 2. 分块逻辑

分块函数位于 `searchbox/server/chunking.py`。

规则：

- 基于字符数切块，不是基于 token
- `max_chars` 下限 200
- `overlap` 会限制在 `max_chars // 2` 以内
- 优先尝试在段落边界 `\n\n` 处分割
- 其次尝试在换行 `\n` 处分割
- 保存 `chunk_id`、`start_offset`、`end_offset`

这使得 chunk 检索结果可以直接回定位到原文偏移区间。

---

## 多用户、多 collection 与论文库

### 1. 多用户隔离

普通知识库数据在 `MilvusStore` 中带有 `user_id` 字段。

搜索表达式会自动附加：

```text
user_id == "your_user_id"
```

因此：

- 同一 collection 内不同用户数据相互隔离
- `list_files`、`search`、`delete_file` 等接口都基于 `user_id`

### 2. 多 collection

服务端默认 collection 名称由配置中的 `milvus_db.collection` 指定，默认是 `knowledge`。

你也可以在请求中传 `collection`：

- 让不同业务线使用不同知识库
- 用临时 collection 做实验
- 用永久 collection 做正式环境

### 3. 临时 collection TTL

`POST /collections` 可创建临时 collection，服务端会记录过期时间。后续任意请求触发 `_evict_expired()` 时，过期 collection 会被删除。

适合：

- 临时会话知识库
- 实验性数据集
- 每任务独立索引

### 4. 论文库 volume

论文库使用独立的 `PaperStore`，与普通 chunk 库不同：

- 入库对象是论文摘要，不是 chunk
- 不区分 `user_id`
- 每个 volume 对应一个 collection，命名规则为 `paper_<规范化后的volume>`

---

## 自定义 collection

`CustomStore` 支持创建带 schema 的 Milvus collection。

约束与特性：

- 必须且只能有一个主键字段
- 最多允许 `4` 个索引字段
- `BM25` 索引仅适用于 `varchar`
- `DENSE` 会额外创建对应的 float vector 字段
- schema 开启 `enable_dynamic_field=True`

示例请求：

```json
{
  "name": "hybrid_docs",
  "fields": [
    {"name": "doc_id", "field_type": "varchar", "is_primary": true, "max_length": 128},
    {"name": "title", "field_type": "varchar", "max_length": 512},
    {"name": "body", "field_type": "varchar", "max_length": 65535, "index_type": "BM25"},
    {"name": "embedding", "field_type": "varchar", "max_length": 128, "index_type": "DENSE", "dim": 1024}
  ]
}
```

说明：当前项目只提供 schema 生命周期接口；如果你要在这个 collection 上做完整数据读写，需要自行补充插入 / 查询逻辑。

---

## 测试与示例脚本

### 1. `smoke_test.py`

这是当前仓库里最贴近现实现状的端到端示例，覆盖：

- 后台启动服务
- 文本入库
- 文件级检索
- chunk 级检索
- 同路径重复入库替换旧数据

运行：

```bash
python smoke_test.py
```

### 2. `tests/test_server_fixes.py`

关注点：

- 重复入库同一 `file_path` 时会先删旧数据，再插入新数据

### 3. `tests/test_milvus_document_search.py`

意图是验证单文档搜索封装；但从当前源码看，测试与 `MilvusDocumentSearch` 的最新构造参数不完全同步，若后续维护测试，建议按当前实现更新。

### 4. `all_methods_demo.py`

该文件引用了当前仓库中已不存在的旧接口（如 `docsearch`、`SqliteDocumentRepository` 等），属于历史遗留示例，不能直接作为当前版本使用文档。

---

## 开发备注与已知事项

### 1. 当前主线接口是 Milvus 版

从源码看，当前对外稳定接口主要是：

- `MilvusCorpusSearch`
- `MilvusDocumentSearch`
- `MilvusClient`
- `start_server` / `start_server_from_yaml`

`base.py`、`sources.py`、`strategies.py` 等保留了通用搜索抽象，但当前项目主流程已经围绕 Milvus 服务端展开。

### 2. 文件级检索的聚合策略

服务端会：

1. 先召回 chunk
2. rerank 每个 chunk
3. 对同一文件只保留最高分 chunk 作为该文件得分

所以最终结果是“文件级 max-pooling 聚合”，不是多 chunk 得分求和。

### 3. 重复入库行为

对同一个 `user_id + file_path` 再次入库时，旧 chunk 会被删掉再重建。这是源码里的明确设计，不会累计重复版本。

### 4. PDF 解析依赖系统工具

如果环境中没有 `pdftotext`，会退回到一个非常轻量的 PDF 文本提取逻辑；对于扫描版 PDF 或复杂布局 PDF，建议自行接入更强抽取方案。

### 5. 搜索分数含义

最终返回的 `score` 来自 reranker 分数，不一定等于 Milvus 原始向量分数。客户端结果中的 `metadata.milvus_score` 当前也复用了最终返回值，而不是单独暴露向量召回原始得分。

### 6. 关闭客户端连接

`MilvusCorpusSearch`、`MilvusDocumentSearch`、`MilvusClient` 都提供 `close()` 与上下文管理能力，长生命周期服务中建议显式关闭。

### 7. setuptools / pymilvus 兼容性

当前项目的服务端依赖对以下组合较敏感：

- `pymilvus`
- `milvus-lite`
- `setuptools`

原因是部分 `pymilvus` 版本在导入时仍依赖 `pkg_resources`，而较新的 `setuptools` 已经移除了它。

因此项目当前在 `pyproject.toml` 中显式限制了：

- `setuptools>=65.0.0,<82.0.0`
- `pymilvus[milvus-lite]>=2.5.0,<2.6.0`

如果你手动升级了这些包，启动服务时报 `pkg_resources`、`Function` / `FunctionType`、或 Milvus Lite 相关错误，优先检查是否偏离了这组版本约束。

### 8. Milvus Lite 可写目录要求

健康检查成功并不代表底层 Milvus Lite 一定已经正确打开数据库。

常见现象是：

- `GET /health` 正常
- 但第一次 `/ingest` 或 `/search` 才报错

这通常是因为：

- `milvus_db.uri` 指向的目录不可写
- 或其旁边的 lock 文件无法创建

推荐始终使用类似下面的配置：

```yaml
milvus_db:
  uri: "./data/milvus_local.db"
```

并确保：

```bash
mkdir -p data
```

---

## 许可证

本项目使用 MIT License，见 `LICENSE`。
