refactor: 统一代码格式,调整多行代码换行风格
对多个文件进行代码格式化调整,将长行参数拆分为多行书写,提升代码可读性,包括: - 调整函数定义、调用的多行换行格式 - 优化列表、元组、字典的多行排版 - 新增README.md项目说明文档
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
# QMDSearch
|
||||
|
||||
面向 AI Agent 的分层信息检索服务,支持多层级知识库检索、向量语义搜索和结构化数据查询。基于 Docker 部署在 NAS 上,为 AI Agent 提供高效、精准的信息检索能力。
|
||||
|
||||
## 特性
|
||||
|
||||
- **分层检索架构**:L1 文档定位 → L2 章节大纲 → L3 小节定位 → chunk 原文,逐层收窄,精准召回
|
||||
- **Hybrid 检索**:dense 向量 + sparse 稀疏向量 RRF 融合,兼顾语义与关键词匹配
|
||||
- **文档三级总结**:Ollama 本地模型自动生成文档 L1/L2/L3 总结,支持知识分类
|
||||
- **多格式文件入库**:支持 `.txt` `.md` `.html` `.htm` `.pdf` `.docx`,PDF 扫描件自动 OCR 降级
|
||||
- **文本去重**:Sha256 文本去重,重复文档自动复用已有结果
|
||||
- **JWT 认证**:Token 鉴权,支持注册开关与默认管理员
|
||||
- **管理后台**:`/admin` 单页面,含概览、文档管理、检索测试台、类目管理
|
||||
- **Docker 部署**:开箱即用的 Docker Compose 编排,适合 NAS 环境
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 组件 | 技术 |
|
||||
|------|------|
|
||||
| 语言 | Python 3.12+ |
|
||||
| Web 框架 | FastAPI + Uvicorn |
|
||||
| 向量数据库 | Qdrant(Docker) |
|
||||
| 缓存 | Redis(Docker) |
|
||||
| 嵌入模型 | OpenAI / Ollama 本地模型(可切换) |
|
||||
| 本地推理 | Ollama + qwen2.5:1.5b(文档总结) |
|
||||
| OCR | rapidocr-onnxruntime + pypdfium2(扫描件 PDF) |
|
||||
| 部署 | Docker Compose on NAS |
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
QMDSearch/
|
||||
├── app/ # 应用主目录
|
||||
│ ├── main.py # FastAPI 入口(含 /admin 管理页面)
|
||||
│ ├── config.py # 配置管理(pydantic-settings / 环境变量)
|
||||
│ ├── api/ # API 路由层
|
||||
│ │ ├── response.py # 统一响应格式
|
||||
│ │ └── v1/ # API v1
|
||||
│ │ ├── search.py # 检索接口
|
||||
│ │ ├── document.py # 文档入库/管理/上传
|
||||
│ │ ├── knowledge.py # 知识库接口
|
||||
│ │ └── auth.py # 认证接口
|
||||
│ ├── core/ # 核心业务逻辑
|
||||
│ │ ├── retriever.py # 分层检索引擎
|
||||
│ │ ├── query_parser.py # Query 解析与分类路由
|
||||
│ │ ├── embeddings.py # 向量嵌入
|
||||
│ │ ├── sparse.py # 稀疏向量编码
|
||||
│ │ ├── ranker.py # RRF 融合与结果截断
|
||||
│ │ ├── summarizer.py # 文档三级总结
|
||||
│ │ ├── headings.py # 原生标题树解析
|
||||
│ │ ├── classifier.py # 文档分类
|
||||
│ │ ├── chunker.py # 标题树感知 chunk 切分
|
||||
│ │ ├── ingestion.py # 文档入库流水线
|
||||
│ │ ├── ingest_tasks.py # 异步任务管理(含文本去重)
|
||||
│ │ ├── file_parser.py # 多格式文件解析(含 PDF OCR)
|
||||
│ │ └── auth.py # JWT 认证
|
||||
│ ├── models/ # Pydantic 数据模型
|
||||
│ ├── services/ # 外部服务客户端
|
||||
│ │ ├── qdrant.py # Qdrant 客户端
|
||||
│ │ ├── redis.py # Redis 缓存客户端
|
||||
│ │ └── ollama.py # Ollama 客户端
|
||||
│ └── static/ # 静态资源
|
||||
│ └── admin.html # 管理后台(单文件)
|
||||
├── tests/ # 测试
|
||||
├── scripts/ # 运维与评测脚本
|
||||
│ ├── eval/ # 回归评测
|
||||
│ └── smoke_live.py # 在线冒烟
|
||||
├── docker-compose.yml # Docker 编排
|
||||
├── Dockerfile # 应用镜像
|
||||
├── .env.example # 环境变量模板
|
||||
└── pyproject.toml # 项目配置
|
||||
```
|
||||
|
||||
## 分层检索架构
|
||||
|
||||
```
|
||||
Query → 解析路由 → L1(文档总结,候选文档集)
|
||||
↓
|
||||
L2(章节大纲,按 section 剪枝)
|
||||
↓
|
||||
L3(小节定位,两路 RRF 融合)
|
||||
↓
|
||||
Chunk(原文 hybrid 检索,dense + sparse)
|
||||
↓
|
||||
Top-K 结果
|
||||
```
|
||||
|
||||
### 检索链路详解
|
||||
|
||||
1. **Query 解析路由**:Ollama 小模型将 query 解析为结构化结果(rewrite、关键词、命中类目+置信度)。高置信且类目数不超上限时按类目过滤,否则全库兜底。
|
||||
2. **L1 - 文档定位层**:在文档 L1 总结集合中检索,产出候选文档集合。无命中时直接全库 chunk 兜底。
|
||||
3. **L2 - 章节大纲层**:在候选文档内检索 L2 章节大纲,按 section 剪枝定位。未命中的文档落入 L3 b 路。
|
||||
4. **L3 - 小节内容层**:两路查询(a:L2 命中文档按 section_path 过滤;b:其余文档仅按 doc_id 过滤)后 RRF 融合。
|
||||
5. **Chunk 层**:在 L3 收窄的范围内对原文 chunk 做 hybrid 检索(dense 向量 + sparse 稀疏向量 RRF 融合),返回最终 Top-K。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 前置要求
|
||||
|
||||
- Docker & Docker Compose
|
||||
- OpenAI API Key(或本地 Ollama 模型)
|
||||
|
||||
### 1. 克隆项目
|
||||
|
||||
```bash
|
||||
git clone <repo-url>
|
||||
cd QMDSearch
|
||||
```
|
||||
|
||||
### 2. 配置环境变量
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# 编辑 .env,至少配置 OPENAI_API_KEY
|
||||
```
|
||||
|
||||
### 3. 启动服务
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 4. 拉取 Ollama 模型(首次启动后)
|
||||
|
||||
```bash
|
||||
docker exec qmdsearch-ollama ollama pull qwen2.5:1.5b
|
||||
```
|
||||
|
||||
### 5. 验证服务
|
||||
|
||||
```bash
|
||||
curl http://localhost:8000/api/v1/health
|
||||
# {"status": "ok"}
|
||||
```
|
||||
|
||||
管理后台:浏览器访问 `http://localhost:8000/admin`
|
||||
|
||||
## API 文档
|
||||
|
||||
| 方法 | 路径 | 说明 | 认证 |
|
||||
|------|------|------|------|
|
||||
| `GET` | `/api/v1/health` | 健康检查 | 否 |
|
||||
| `POST` | `/api/v1/auth/register` | 用户注册 | 否(可关闭) |
|
||||
| `POST` | `/api/v1/auth/login` | 用户登录,返回 JWT token | 否 |
|
||||
| `GET` | `/api/v1/auth/me` | 获取当前用户信息 | 是 |
|
||||
| `POST` | `/api/v1/search` | 分层检索 | 是 |
|
||||
| `POST` | `/api/v1/documents` | 文档入库(JSON 文本,202 异步入库) | 是 |
|
||||
| `POST` | `/api/v1/documents/upload` | 文件上传入库(multipart,202 异步) | 是 |
|
||||
| `GET` | `/api/v1/documents/tasks/{task_id}` | 入库任务状态查询 | 是 |
|
||||
| `GET` | `/api/v1/documents` | 文档列表(分页) | 是 |
|
||||
| `GET` | `/api/v1/documents/{doc_id}` | 文档详情 | 是 |
|
||||
| `DELETE` | `/api/v1/documents/{doc_id}` | 删除文档(幂等) | 是 |
|
||||
| `GET` | `/api/v1/knowledge/categories` | 知识分类类目集 | 是 |
|
||||
| `GET` | `/api/v1/knowledge/stats` | 统计(四层点数 + 类目分布) | 是 |
|
||||
| `GET` | `/admin` | 管理后台 | 否 |
|
||||
|
||||
### 统一响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": { ... },
|
||||
"message": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
错误码:`0` 成功,`1xxx` 客户端错误,`2xxx` 服务端错误。
|
||||
|
||||
### 检索示例
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/search \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-d '{"query": "如何配置 Redis 缓存", "top_k": 5}'
|
||||
```
|
||||
|
||||
### 文件上传示例
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/v1/documents/upload \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-F "file=@document.pdf"
|
||||
```
|
||||
|
||||
## 文档入库流程
|
||||
|
||||
```
|
||||
文件上传 → 文本提取 → 三级总结(Ollama)→ 分类判定(L1 总结)→ 向量化 → 写入 Qdrant
|
||||
↓
|
||||
不足三级 → 2.5 级回退
|
||||
```
|
||||
|
||||
### 三级总结
|
||||
|
||||
| 层级 | 名称 | 说明 |
|
||||
|------|------|------|
|
||||
| L1 | 总结 | 对整篇文档的一句话高度概括 |
|
||||
| L2 | 大纲 | 文档主要章节和关键主题 |
|
||||
| L3 | 内容大纲 | 每个章节的详细内容摘要 |
|
||||
|
||||
**2.5 级回退**:短文本(< 500 字符)自动降级为二级总结,跳过大纲层。
|
||||
|
||||
### 文件格式支持
|
||||
|
||||
| 格式 | 解析方式 |
|
||||
|------|---------|
|
||||
| `.txt` `.md` | UTF-8 解码 |
|
||||
| `.html` `.htm` | HTML 标签剥离 |
|
||||
| `.pdf` | pypdf 提取文本层;扫描件自动 OCR 降级 |
|
||||
| `.docx` | python-docx 段落提取 |
|
||||
|
||||
### PDF OCR 降级
|
||||
|
||||
图片型/扫描件 PDF 在文本层为空时,自动触发 OCR:
|
||||
1. pypdfium2 渲染每页为图片
|
||||
2. rapidocr-onnxruntime 识别文字
|
||||
3. 拼接返回结果
|
||||
|
||||
可通过环境变量 `PDF_OCR_ENABLED=false` 关闭,或通过 `PDF_OCR_MAX_PAGES` / `PDF_OCR_DPI` 控制行为。
|
||||
|
||||
### 文本去重
|
||||
|
||||
入库时自动计算 `sha256(doc.text)`,相同文本重复提交不重跑流水线,直接复用已有文档 ID,结果中 `deduplicated=true` 标记。
|
||||
|
||||
## 配置项
|
||||
|
||||
所有配置通过环境变量注入,完整列表见 `.env.example`。主要配置项:
|
||||
|
||||
| 变量 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `EMBEDDING_PROVIDER` | 嵌入模型提供商(openai / local) | `openai` |
|
||||
| `OPENAI_API_KEY` | OpenAI API Key | - |
|
||||
| `EMBEDDING_MODEL` | 嵌入模型名称 | `text-embedding-3-small` |
|
||||
| `OLLAMA_MODEL` | Ollama 本地模型 | `qwen2.5:1.5b` |
|
||||
| `RETRIEVAL_TOP_K` | 检索召回数 | `20` |
|
||||
| `RETRIEVAL_FINAL_K` | 最终返回数 | `5` |
|
||||
| `INGEST_MAX_CONCURRENCY` | 入库并发上限 | `2` |
|
||||
| `JWT_SECRET_KEY` | JWT 签名密钥(生产必填) | 自动生成(仅开发) |
|
||||
| `AUTH_REGISTER_ENABLED` | 是否开放注册 | `true` |
|
||||
| `UPLOAD_MAX_SIZE_MB` | 上传文件大小上限 | `20` |
|
||||
| `PDF_OCR_ENABLED` | 是否启用 PDF OCR | `true` |
|
||||
| `PDF_OCR_MAX_PAGES` | OCR 最大页数 | `30` |
|
||||
| `PDF_OCR_DPI` | OCR 渲染 DPI | `200` |
|
||||
|
||||
## 开发
|
||||
|
||||
### 本地开发
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
uv sync --extra dev
|
||||
|
||||
# 启动开发服务器
|
||||
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||
|
||||
# 运行测试
|
||||
uv run pytest
|
||||
|
||||
# 代码检查
|
||||
uv run ruff check app/ tests/
|
||||
```
|
||||
|
||||
### 构建镜像
|
||||
|
||||
```bash
|
||||
docker compose build
|
||||
```
|
||||
|
||||
### 目录结构约定
|
||||
|
||||
- 类型注解必须(Python 3.12+ 语法)
|
||||
- 异步优先(async/await)
|
||||
- 配置通过环境变量注入(pydantic-settings)
|
||||
- 日志使用 structlog 结构化日志
|
||||
- 测试使用 pytest + pytest-asyncio
|
||||
|
||||
## 部署
|
||||
|
||||
### NAS 部署
|
||||
|
||||
```bash
|
||||
# 1. 创建数据目录
|
||||
mkdir -p /path/to/nas/data/{qdrant,redis,ollama}
|
||||
|
||||
# 2. 配置 .env,设置 NAS_DATA_DIR 和 API Key
|
||||
NAS_DATA_DIR=/path/to/nas/data
|
||||
OPENAI_API_KEY=sk-xxx
|
||||
|
||||
# 3. 启动
|
||||
docker compose up -d
|
||||
|
||||
# 4. 查看日志
|
||||
docker compose logs -f app
|
||||
```
|
||||
|
||||
### 端口映射
|
||||
|
||||
| 服务 | 端口 |
|
||||
|------|------|
|
||||
| API | 8000 |
|
||||
| Qdrant | 6333 |
|
||||
| Qdrant Dashboard | 6334 |
|
||||
| Redis | 6379 |
|
||||
| Ollama | 11434 |
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
Reference in New Issue
Block a user