Initial commit: QMDSearch 分层信息检索服务

- FastAPI + Qdrant + Redis + Ollama 技术栈
- L1→L2→L3→chunk 四层分层检索(dense + sparse RRF 融合)
- 文档三级总结与 2.5 级回退
- query 解析路由与分类
- /admin 管理页面
This commit is contained in:
2026-07-29 21:24:40 +08:00
commit 51dc8dc4f6
83 changed files with 10794 additions and 0 deletions
@@ -0,0 +1,41 @@
# Checklist
## 文档管理 API
- [x] GET /api/v1/documents 分页返回 itemsdoc_id/title/category/tags/summary)与 next_offset,空库返回空列表
- [x] GET /api/v1/documents/{doc_id} 返回 L1 全部字段 + l2_nodes/l3_nodes + chunks_count
- [x] 不存在 doc_id 详情返回 code=1004
- [x] DELETE /api/v1/documents/{doc_id} 后四层集合该文档点全部清除,再查详情返回 1004
- [x] DELETE 不存在 doc_id 幂等成功且 deleted 统计为 0
## 统计 API
- [x] GET /api/v1/knowledge/stats 返回四层集合点数、类目分布、uncategorized_count
- [x] stats 数值与内存 Qdrant 实际写入一致(测试验证)
## 管理页面
- [x] GET /admin 返回 200 HTML
- [x] 页面含 概览/文档管理/入库/检索测试台/类目 五个区块
- [x] 页面无外部 CDN/外链资源(无 http(s):// src 或 link
- [x] 所有 fetch 指向 /api/v1/ 路径且与后端路由契约一致(路径、方法、字段名)
- [x] 删除操作有二次确认逻辑(confirm 或等价交互)
- [x] code≠0 时页面有错误提示处理
## 单测补全
- [x] judge.py:正常幻觉率计算、部分断言不支持、解析失败返回 0.0
- [x] response.pyok/error/ApiError 结构与 code
- [x] ollama.pygenerate 正常/json_mode payload 含 format:is_available 可达与异常分支
- [x] run_eval.py:门槛判定(✅/❌)与数值格式化纯函数
## 部署与文档
- [x] Dockerfile 含 COPY scripts/ scripts/
- [x] CLAUDE.md 项目结构、API 清单(含 documents 管理端点/stats//admin)与实际一致
## 集成
- [x] 内存 Qdrant 管理闭环冒烟通过:入库→列表→详情→检索→删除→统计
- [x] `uv run pytest` 全部通过(含新增测试)
- [x] `uv run ruff check app tests scripts` 无错误
@@ -0,0 +1,83 @@
# 项目完整性补全:管理 API + 管理页面 + 单测补全 Spec
## Why
分层 RAG 全链路已验收(149 测试全绿),但系统对外闭环不完整:已入库文档无法查看/管理(无列表/详情/删除 API),运维人员无 UI 可手工验证检索与入库,judge/response/ollama 客户端等模块无单测覆盖,Dockerfile 未打包评测脚本,CLAUDE.md 项目结构已过时。本变更补齐管理闭环与测试覆盖,使系统达到可运维状态。
## What Changes
- **文档管理 API(新增)**`GET /api/v1/documents`(分页列表,源自 doc_l1 scroll)、`GET /api/v1/documents/{doc_id}`(详情:L1 总结 + L2/L3 节点 + chunk 数)、`DELETE /api/v1/documents/{doc_id}`(按 doc_id 过滤删除四层集合所有点,幂等)
- **统计 API(新增)**`GET /api/v1/knowledge/stats`(四层集合点数、类目分布、uncategorized 数量)
- **管理页面(新增)**:FastAPI 托管静态单页 `/admin`,原生 JS + fetch 单文件实现(无 Node 构建链、镜像零新增依赖),含 概览 / 文档管理 / 文档入库 / 检索测试台 / 类目列表 五个区块;删除操作前端二次确认
- **单元测试补全**scripts/eval/judge.py、app/api/response.py、app/services/ollama.pyHTTP 行为)、scripts/eval/run_eval.py 可测纯函数、新增管理 API 端点测试、/admin 页面存在性测试
- **完整性修补**Dockerfile 增加 `COPY scripts/ scripts/`CLAUDE.md 项目结构与 API 清单更新至当前实现
无破坏性变更(现有 API 路径与响应结构不变,仅新增)。
## Impact
- Affected specs: 文档管理(新增)、统计概览(新增)、管理页面(新增)、测试覆盖、部署
- Affected code:
- 修改:[qdrant.py](file:///Users/kplam/coding/QMDSearch/app/services/qdrant.py)scroll/delete/count 管理操作)、[main.py](file:///Users/kplam/coding/QMDSearch/app/main.py)(挂载 /admin 静态页)、[document.py](file:///Users/kplam/coding/QMDSearch/app/api/v1/document.py)(新增端点)、[knowledge.py](file:///Users/kplam/coding/QMDSearch/app/api/v1/knowledge.py)stats)、[Dockerfile](file:///Users/kplam/coding/QMDSearch/Dockerfile)、[CLAUDE.md](file:///Users/kplam/coding/QMDSearch/CLAUDE.md)
- 新增:`app/static/admin.html``tests/test_judge.py``tests/test_response.py``tests/test_ollama_client.py``tests/test_run_eval.py``tests/test_document_admin_api.py``tests/test_admin_page.py`
## ADDED Requirements
### Requirement: 文档列表与详情
系统 SHALL 提供 `GET /api/v1/documents`:基于 doc_l1 集合 scroll 分页返回文档摘要列表(doc_id、title、category、tags、总结层级推断、L1 总结摘要),支持 `limit`/`offset` 游标分页;并提供 `GET /api/v1/documents/{doc_id}` 返回单文档详情:L1 payload 全部字段、L2/L3 节点列表、chunk 数量。doc_id 不存在时返回 code=1004。
#### Scenario: 分页列表
- **WHEN** 请求 `GET /api/v1/documents?limit=20`
- **THEN** 返回 code=0data 含 items(每项 doc_id/title/category/tags/summary)与 next_offset(无更多为 null
#### Scenario: 详情与不存在
- **WHEN** 请求已入库 doc_id 的详情
- **THEN** 返回 L1 字段 + l2_nodes/l3_nodes 列表 + chunks_count;请求不存在的 doc_id 返回 code=1004
### Requirement: 文档删除
系统 SHALL 提供 `DELETE /api/v1/documents/{doc_id}`:按 doc_id payload 过滤删除 doc_l1/doc_l2/doc_l3/chunks 四个集合中的全部点;删除不存在 doc_id 幂等返回成功(deleted_points=0);返回各集合删除点数统计。
#### Scenario: 删除已入库文档
- **WHEN** 对已入库 doc_id 执行 DELETE
- **THEN** 四层集合该 doc_id 的点全部被清除,再次 GET 详情返回 1004
### Requirement: 统计概览
系统 SHALL 提供 `GET /api/v1/knowledge/stats`:返回四层集合各自点数、按 category 的文档分布、uncategorized 文档数。统计基于 Qdrant count 与 doc_l1 轻量 scroll 聚合(仅取 category 字段),属管理端低频接口。
#### Scenario: 概览数据
- **WHEN** 请求 stats
- **THEN** data 含 collections(四层点数)、categories(类目→文档数)、uncategorized_count
### Requirement: 管理页面
系统 SHALL 在 `/admin` 提供静态单页管理界面(单 HTML 文件,内联 CSS/JS,无外部 CDN 依赖),包含五个区块:概览(stats 展示)、文档管理(列表 + 详情查看 + 删除,删除需二次确认)、文档入库(表单提交 text/title/source,展示分类与总结结果)、检索测试台(输入 query 展示 hits/routed_categories/fallback)、类目列表。页面调用同-origin `/api/v1/*`,统一处理 code≠0 的错误提示。
#### Scenario: 页面可用性
- **WHEN** 浏览器访问 `/admin`
- **THEN** 返回 200 HTML,包含五个功能区块与全部 fetch 调用指向 `/api/v1/` 路径,无外部资源引用
### Requirement: 单元测试补全
系统 SHALL 为以下模块补齐单测:judge.pymock Ollama:断言抽取/支持判定/解析失败返回 0.0)、response.pyok/error/ApiError 结构)、ollama.pymock httpxgenerate/json_mode payload/is_available 可达与异常分支)、run_eval.py 可测纯函数(格式化与门槛判定)、新增管理 API 与 /admin 页面存在性(TestClient 200 + 关键区块标记)。
### Requirement: 部署与文档完整性
Dockerfile SHALL 打包 scripts/ 目录(镜像内可执行评测);CLAUDE.md SHALL 更新项目结构、API 清单与管理页面说明至当前实现。
## MODIFIED Requirements
### Requirement: QdrantService(新增管理操作)
QdrantService SHALL 新增:`scroll_l1(limit, offset) -> (items, next_offset)`(仅取列表所需 payload 字段)、`get_doc_detail(doc_id) -> dict | None`L1 + L2/L3 节点 + chunk 计数)、`delete_by_doc_id(doc_id) -> dict[str, int]`(四集合按过滤删除,返回各集合删除数)、`count(collection) -> int`。均复用现有客户端与集合常量,不改变既有 upsert/查询行为。
## REMOVED Requirements
无。
@@ -0,0 +1,24 @@
# Tasks
- [x] Task 1: QdrantService 管理操作扩展:scroll_l1 分页(仅取 doc_id/title/category/tags/text/level 所需字段)、get_doc_detailL1+L2/L3 节点+chunk 计数)、delete_by_doc_id(四集合过滤删除返回各集合删除数)、count;内存 Qdrant 单测覆盖
- [x] SubTask 1.1: scroll_l1 与 count 实现 + 单测(分页游标、空集合)
- [x] SubTask 1.2: get_doc_detail 与 delete_by_doc_id 实现 + 单测(存在/不存在/删除后四层清空)
- [x] Task 2: 文档管理 API`GET /api/v1/documents`limit/offset 分页)、`GET /api/v1/documents/{doc_id}`(详情,不存在 code=1004)、`DELETE /api/v1/documents/{doc_id}`(幂等,返回各集合删除数);沿用统一响应与懒加载单例模式;TestClient 测试(mock QdrantService
- [x] Task 3: 统计 API`GET /api/v1/knowledge/stats`(四层点数 + 类目分布 + uncategorized 数);TestClient 测试
- [x] Task 4: 管理页面 `app/static/admin.html` 单文件(内联 CSS/JS,零外部依赖)+ main.py 挂载 /admin:概览/文档管理(列表/详情/删除二次确认)/入库表单/检索测试台/类目列表五区块,统一 code≠0 错误提示;TestClient 存在性测试(200 + 五区块标记 + 无 http(s) 外链资源)
- [x] Task 5: 单测补全:tests/test_judge.pymock Ollama:正常判定/部分断言不支持/解析失败返回 0.0)、tests/test_response.py、tests/test_ollama_client.pymock httpxgenerate/json_mode/is_available 分支)、tests/test_run_eval.py(门槛判定与格式化纯函数)
- [x] Task 6: 部署与文档修补:Dockerfile 增加 `COPY scripts/ scripts/`CLAUDE.md 更新项目结构(scripts/eval、static)、API 清单(documents 管理端点、stats、/admin)与管理页面使用说明
- [x] Task 7: 集成验证:`uv run pytest` 全绿 + `uv run ruff check app tests scripts` 通过;内存 Qdrant 走通 入库→列表→详情→检索→删除→统计 管理闭环冒烟;确认页面 fetch 路径与实际 API 契约一致
- [x] Task 8: 修复验收发现:CLAUDE.md 项目结构中 tests 目录注释「18 个测试文件」与实际 26 个不符,改为不写死数量防止再次漂移
# Task Dependencies
- [Task 2] depends on [Task 1]
- [Task 3] depends on [Task 1]
- [Task 4] depends on [Task 2, Task 3]
- [Task 7] depends on [Task 4, Task 5, Task 6]
# Parallelizable
- Task 1 / Task 5 / Task 6 互相独立,可并行
- Task 2 与 Task 3 可并行(Task 1 完成后)