Initial commit: QMDSearch 分层信息检索服务
- FastAPI + Qdrant + Redis + Ollama 技术栈 - L1→L2→L3→chunk 四层分层检索(dense + sparse RRF 融合) - 文档三级总结与 2.5 级回退 - query 解析路由与分类 - /admin 管理页面
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
# Checklist
|
||||
|
||||
## 配置与任务管理器
|
||||
|
||||
- [x] config 含 ingest_max_concurrency(默认 2)/ingest_task_ttl_done(默认 86400)/ingest_task_ttl_failed(默认 604800),.env.example 同步
|
||||
- [x] submit 返回 task_id 且状态初始 pending;后台任务按信号量限流(超额保持 pending)
|
||||
- [x] 状态机按流水线阶段推进:pending→summarizing→classifying→embedding→writing→done
|
||||
- [x] 成功后 result 为完整 IngestionResult;失败 error 含 stage 与 message,partial_summary(已产出总结)保留
|
||||
- [x] 状态写 Redis:done TTL 24h、failed TTL 7 天;Redis 故障降级内存字典且接口不报错
|
||||
|
||||
## API
|
||||
|
||||
- [x] POST /api/v1/documents 合法请求返回 HTTP 202 + {task_id, status:"pending"},不再同步等待流水线
|
||||
- [x] POST 空文本等校验失败仍同步返回 code=1001(不进任务队列)
|
||||
- [x] GET /api/v1/documents/tasks/{task_id} 返回状态/时间戳;done 附 result;failed 附 error.stage/message
|
||||
- [x] 查询不存在 task_id 返回 code=1004
|
||||
- [x] 既有入库相关测试全部适配新契约且通过
|
||||
|
||||
## 管理页面
|
||||
|
||||
- [x] 入库提交后展示 task_id 与状态,2s 轮询直至 done/failed
|
||||
- [x] done 展示 category/置信度/tags/总结层级/chunks_count;failed 展示 stage 与 message
|
||||
- [x] 轮询期间禁止重复提交;5 分钟超时停止轮询并提示
|
||||
- [x] 页面测试覆盖 tasks 轮询路径与新交互标记
|
||||
|
||||
## 集成与文档
|
||||
|
||||
- [x] 内存闭环:提交→轮询 done→GET /documents/{doc_id} 可查→检索命中→删除 全通
|
||||
- [x] 失败路径:FakeOllama 抛错 → 任务 failed、stage 正确、failed TTL=7 天
|
||||
- [x] CLAUDE.md API 清单反映 POST /documents 202 异步与任务查询端点
|
||||
- [x] `uv run pytest` 全部通过;`uv run ruff check app tests scripts` 无错误
|
||||
@@ -0,0 +1,72 @@
|
||||
# 入库异步任务化 Spec
|
||||
|
||||
## Why
|
||||
|
||||
当前 `POST /api/v1/documents` 同步等待整条入库流水线(总结→分类→切分→向量化→写库),真实 Ollama 冒烟单篇耗时 39s+,长文档分钟级,客户端/网关易超时,批量导入不可用。改为后台任务模式:提交即返回 task_id,状态可查,失败任务长期保留便于归因。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING**:`POST /api/v1/documents` 由同步返回 IngestionResult(200)改为立即返回 `{task_id, status}`(HTTP 202),流水线在后台 asyncio 任务中执行
|
||||
- **新增** `GET /api/v1/documents/tasks/{task_id}`:查询任务状态(pending/各阶段/done/failed)、完成后的 IngestionResult、失败时的 stage 与错误信息
|
||||
- **新增** `app/core/ingest_tasks.py` IngestTaskManager:提交登记 → 信号量限流并发 → 后台执行 → 状态机推进 → 结果/错误落储
|
||||
- **状态存储**:Redis 为主(key `ingest_task:{task_id}`,JSON);done TTL 24h,**failed TTL 7 天**(便于失败归因);Redis 不可用时降级进程内字典(重启丢失,日志告警),服务不因此拒绝入库
|
||||
- **管理页**:入库区块改为「提交 → 轮询任务状态 → 展示进度/结果/失败详情」
|
||||
- **配置新增**:`ingest_max_concurrency`(默认 2)、`ingest_task_ttl_done`(默认 86400s)、`ingest_task_ttl_failed`(默认 604800s,7 天)
|
||||
- CLAUDE.md API 清单同步更新(标注 POST /documents 行为变更)
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: 文档入库 API、管理页面、任务状态存储
|
||||
- Affected code:
|
||||
- 修改:[document.py](file:///Users/kplam/coding/QMDSearch/app/api/v1/document.py)(POST 改 202 + 新增任务查询端点)、[config.py](file:///Users/kplam/coding/QMDSearch/app/config.py)、[.env.example](file:///Users/kplam/coding/QMDSearch/.env.example)、[admin.html](file:///Users/kplam/coding/QMDSearch/app/static/admin.html)(入库区块轮询)、[CLAUDE.md](file:///Users/kplam/coding/QMDSearch/CLAUDE.md)
|
||||
- 新增:`app/core/ingest_tasks.py`、`tests/test_ingest_tasks.py`、`tests/test_ingest_task_api.py`
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 异步入库提交
|
||||
|
||||
系统 SHALL 将 `POST /api/v1/documents` 改为异步任务模式:校验通过后立即返回 HTTP 202 与 `{task_id, status: "pending"}`,入库流水线在后台任务执行;并发后台任务数受 `ingest_max_concurrency` 信号量限制,超限任务保持 pending 排队。请求体与校验规则(text 非空等)与现状一致,校验失败仍同步返回 1001。
|
||||
|
||||
#### Scenario: 提交即返回
|
||||
|
||||
- **WHEN** 提交合法文档
|
||||
- **THEN** 响应 202 + task_id,无需等待流水线完成;后台任务按并发额度开始执行
|
||||
|
||||
### Requirement: 任务状态查询
|
||||
|
||||
系统 SHALL 提供 `GET /api/v1/documents/tasks/{task_id}`:返回 `{task_id, status, created_at, updated_at}`;status 取值 `pending | summarizing | classifying | embedding | writing | done | failed`(与流水线阶段一致推进);done 时附 `result`(完整 IngestionResult:document_id/category/tags/总结层级/chunks_count);failed 时附 `error: {stage, message}`(stage 来自 IngestionError,已产出总结不丢失一并附在 error.partial_summary)。task_id 不存在返回 code=1004。
|
||||
|
||||
#### Scenario: 生命周期
|
||||
|
||||
- **WHEN** 提交后轮询 task_id
|
||||
- **THEN** 依次观察到 pending→阶段状态→done(result 可查,文档已可检索);失败任务观察到 failed + error.stage
|
||||
|
||||
### Requirement: 状态存储与保留
|
||||
|
||||
任务状态 SHALL 写入 Redis(`ingest_task:{task_id}`,JSON):done TTL=ingest_task_ttl_done(默认 24h),failed TTL=ingest_task_ttl_failed(默认 7 天),进行中状态 TTL 取 done 值。Redis 不可用时降级进程内字典并日志告警,入库与查询照常(重启后历史任务丢失可接受,接口语义不变)。
|
||||
|
||||
#### Scenario: Redis 宕机降级
|
||||
|
||||
- **WHEN** Redis 连接失败
|
||||
- **THEN** 提交与查询仍正常,仅日志 warning;进程重启后任务状态丢失
|
||||
|
||||
### Requirement: 管理页入库区块
|
||||
|
||||
管理页入库表单 SHALL 改为:提交后展示 task_id 与状态进度条/文本,每 2s 轮询任务接口,done 展示分类/标签/总结层级/chunks_count,failed 展示 error.stage 与 message;轮询期间禁止重复提交,超时(5 分钟)停止轮询并提示可稍后手动查询。
|
||||
|
||||
### Requirement: 并发控制
|
||||
|
||||
后台入库 SHALL 用 asyncio 信号量限制并发(默认 2),避免多任务同时打满 Ollama/embedding;排队任务状态保持 pending。
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: POST /api/v1/documents(行为变更)
|
||||
|
||||
原同步返回 IngestionResult 的行为废弃,改为 202 + task_id。所有既有调用方(管理页、既有测试)同步适配;响应体统一响应包装不变(`{code:0, data:{task_id,status}, message:"ok"}`,HTTP 状态码 202)。
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: 同步入库等待
|
||||
|
||||
**Reason**:长耗时流水线导致客户端超时、无法批量导入。
|
||||
**Migration**:调用方改为提交后轮询 `GET /api/v1/documents/tasks/{task_id}`;管理页已内置轮询。
|
||||
@@ -0,0 +1,18 @@
|
||||
# Tasks
|
||||
|
||||
- [x] Task 1: 配置 + IngestTaskManager:config 新增 ingest_max_concurrency/ingest_task_ttl_done/ingest_task_ttl_failed 与 .env.example 同步;新建 app/core/ingest_tasks.py——submit(doc)→task_id、asyncio 信号量限流后台执行、状态机(pending→summarizing→classifying→embedding→writing→done/failed)、Redis 主存储(failed TTL 7 天)+ Redis 故障降级进程内字典、get(task_id) 查询;单测(FakeIngester 成功/失败/IngestionError.stage 透传/partial_summary 保留/并发限流/Redis 降级)
|
||||
- [x] SubTask 1.1: 配置项 + .env.example
|
||||
- [x] SubTask 1.2: IngestTaskManager 实现 + 单测
|
||||
- [x] Task 2: API 改造:POST /api/v1/documents 改 202 返回 {task_id,status}(校验失败仍同步 1001);新增 GET /api/v1/documents/tasks/{task_id}(不存在 1004);适配既有 test_document_api.py 与 test_e2e_integration.py 中入库断言为新契约;新增 tests/test_ingest_task_api.py
|
||||
- [x] Task 3: 管理页入库区块改造:提交→展示 task_id 与状态→2s 轮询→done 展示结果/failed 展示 stage+message→轮询期禁重复提交→5 分钟超时提示;更新 test_admin_page.py 断言(含 tasks 轮询路径)
|
||||
- [x] Task 4: 集成验证 + 文档:内存 Qdrant + FakeOllama 走通 提交→轮询至 done→文档可检索→删除 闭环;失败路径(FakeOllama 抛错)状态 failed 且 stage 正确;Redis 降级路径可用;CLAUDE.md API 清单更新(POST /documents 标注 202 异步、新增任务查询端点);`uv run pytest` 全绿 + ruff 通过
|
||||
|
||||
# Task Dependencies
|
||||
|
||||
- [Task 2] depends on [Task 1]
|
||||
- [Task 3] depends on [Task 2]
|
||||
- [Task 4] depends on [Task 3]
|
||||
|
||||
# Parallelizable
|
||||
|
||||
- 各任务串行依赖,无并行项
|
||||
Reference in New Issue
Block a user