Files
kplam a1b80566bb feat: 升级 Ollama 本地模型 qwen2.5:1.5b → qwen3:1.7b
- config.py / .env.example / docker-compose.yml:默认模型改为 qwen3:1.7b(备选 qwen3:4b),compose 同步拉取
- 经 runtime_settings 注入 summarize/query/classify 三用途,改默认值即全局生效
- README / CLAUDE.md / 前端 Settings.vue 占位符同步更新
2026-08-01 00:28:50 +08:00

10 KiB
Raw Permalink Blame History

QMDSearch

面向 AI Agent 的分层信息检索服务,支持多层级知识库检索、向量语义搜索和结构化数据查询。基于 Docker 部署在 NAS 上,为 AI Agent 提供高效、精准的信息检索能力。

特性

  • 分层检索架构:L1 文档定位 → L2 章节大纲 → L3 小节定位 → chunk 原文,逐层收窄,精准召回
  • Hybrid 检索dense 向量 + sparse 稀疏向量 RRF 融合,兼顾语义与关键词匹配
  • 文档三级总结:Ollama 本地模型自动生成文档 L1/L2/L3 总结,支持知识分类
  • 多格式文件入库:支持 .txt .md .html .htm .pdf .docxPDF 扫描件自动 OCR 降级
  • 文本去重:Sha256 文本去重,重复文档自动复用已有结果
  • JWT 认证:Token 鉴权,支持注册开关与默认管理员
  • 管理后台/admin 单页面,含概览、文档管理、检索测试台、类目管理
  • Docker 部署:开箱即用的 Docker Compose 编排,适合 NAS 环境

技术栈

组件 技术
语言 Python 3.12+
Web 框架 FastAPI + Uvicorn
向量数据库 QdrantDocker
缓存 RedisDocker
嵌入模型 OpenAI / Ollama 本地模型(可切换)
本地推理 Ollama + qwen3:1.7b(文档总结)
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 - 小节内容层:两路查询(aL2 命中文档按 section_path 过滤;b:其余文档仅按 doc_id 过滤)后 RRF 融合。
  5. Chunk 层:在 L3 收窄的范围内对原文 chunk 做 hybrid 检索(dense 向量 + sparse 稀疏向量 RRF 融合),返回最终 Top-K。

快速开始

前置要求

  • Docker & Docker Compose
  • OpenAI API Key(或本地 Ollama 模型)

1. 克隆项目

git clone <repo-url>
cd QMDSearch

2. 配置环境变量

cp .env.example .env
# 编辑 .env,至少配置 OPENAI_API_KEY

3. 启动服务

docker compose up -d

4. 拉取 Ollama 模型(首次启动后)

docker exec qmdsearch-ollama ollama pull qwen3:1.7b

5. 验证服务

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 文件上传入库(multipart202 异步)
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 管理后台

统一响应格式

{
  "code": 0,
  "data": { ... },
  "message": "ok"
}

错误码:0 成功,1xxx 客户端错误,2xxx 服务端错误。

检索示例

curl -X POST http://localhost:8000/api/v1/search \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{"query": "如何配置 Redis 缓存", "top_k": 5}'

文件上传示例

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 本地模型 qwen3:1.7b
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

开发

本地开发

# 安装依赖
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/

构建镜像

docker compose build

目录结构约定

  • 类型注解必须(Python 3.12+ 语法)
  • 异步优先(async/await
  • 配置通过环境变量注入(pydantic-settings
  • 日志使用 structlog 结构化日志
  • 测试使用 pytest + pytest-asyncio

部署

NAS 部署

# 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