Files
kplam 51dc8dc4f6 Initial commit: QMDSearch 分层信息检索服务
- FastAPI + Qdrant + Redis + Ollama 技术栈
- L1→L2→L3→chunk 四层分层检索(dense + sparse RRF 融合)
- 文档三级总结与 2.5 级回退
- query 解析路由与分类
- /admin 管理页面
2026-07-29 21:24:40 +08:00

73 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 入库异步任务化 Spec
## Why
当前 `POST /api/v1/documents` 同步等待整条入库流水线(总结→分类→切分→向量化→写库),真实 Ollama 冒烟单篇耗时 39s+,长文档分钟级,客户端/网关易超时,批量导入不可用。改为后台任务模式:提交即返回 task_id,状态可查,失败任务长期保留便于归因。
## What Changes
- **BREAKING**`POST /api/v1/documents` 由同步返回 IngestionResult200)改为立即返回 `{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`(默认 604800s7 天)
- 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`(完整 IngestionResultdocument_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_countfailed 展示 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}`;管理页已内置轮询。