# 异步任务接口变更说明 > **变更日期**:2026-06-05 > > **变更原因**:同步、向量化、出题、批阅等操作耗时较长(数秒到数十秒),改为异步任务模式后接口立即返回 `task_id`,避免后端调用超时。 > > **影响范围**:8 个已有端口的响应格式变更 + 4 个新增任务查询接口 --- ## 一、核心变更 所有受影响的接口从 **同步阻塞返回结果** 改为 **异步任务立即返回 `task_id`**。 **调用流程变更**: ``` 旧流程: POST /sync → 等待 10-30s → 返回完整结果 新流程: POST /sync → 立即返回 {"task_id": "xxx"} GET /tasks/xxx → 轮询(1-2s 间隔)→ status=completed 时取 result ``` --- ## 二、受影响的端口(8 个) ### 1. POST /sync — 触发同步 **旧响应**: ```json { "success": true, "status_code": 2010, "message": "同步完成", "data": { "result": { "documents_processed": 20, "documents_added": 3, "documents_modified": 2, "documents_deleted": 0, "errors": [] } } } ``` **新响应**: ```json { "success": true, "status_code": 2010, "message": "同步任务已启动", "data": { "task_id": "c3d4e5f6a1b2", "message": "同步任务已启动,通过 GET /tasks/c3d4e5f6a1b2 查询进度" } } ``` **冲突检测**:已有同步任务运行时返回 HTTP 409: ```json {"error": "TASK_RUNNING", "message": "同步任务正在执行中 (task_id: xxx),请等待完成"} ``` --- ### 2. POST /documents/sync — 触发文档同步 **旧响应**: ```json { "success": true, "results": [{"collection": "public_kb", "status": "success"}], "synced_count": 1 } ``` **新响应**: ```json { "success": true, "task_id": "xxx", "message": "同步任务已启动,通过 GET /tasks/xxx 查询进度" } ``` --- ### 3. POST /collections/\/reindex — 重建索引 **旧响应**: ```json { "success": true, "message": "重新索引完成: 处理 20 个文档", "documents_processed": 20, "documents_added": 3 } ``` **新响应**: ```json { "success": true, "task_id": "xxx", "message": "重建索引任务已启动: kb_name,通过 GET /tasks/xxx 查询进度" } ``` **冲突检测**:已有重建任务运行时返回 HTTP 409。 --- ### 4. POST /documents/upload — 上传单个文件 **旧响应**: ```json { "success": true, "status_code": 2002, "message": "文件上传成功,已保存并添加到向量库", "data": { "file": { "filename": "test.txt", "collection": "public_kb", "path": "public_kb/test.txt", "size": 1024, "replaced": false }, "sync_status": "已保存并添加到向量库" } } ``` **新响应**(新增 `task_id` 字段): ```json { "success": true, "status_code": 2002, "message": "文件上传成功,已保存,向量化任务已启动", "data": { "file": { "filename": "test.txt", "collection": "public_kb", "path": "public_kb/test.txt", "size": 1024, "replaced": false }, "sync_status": "已保存,向量化任务已启动", "task_id": "a1b2c3d4e5f6" } } ``` > **注意**:文件保存仍为同步操作(毫秒级),仅向量化部分异步执行。`task_id` 为 `null` 表示同步服务不可用,需手动同步。 --- ### 5. POST /documents/batch-upload — 批量上传 **旧响应**: ```json { "success": true, "status_code": 2003, "data": { "results": [...], "success_count": 2, "total": 2 } } ``` **新响应**(新增 `task_id` 字段): ```json { "success": true, "status_code": 2003, "data": { "results": [...], "success_count": 2, "total": 2, "task_id": "b2c3d4e5f6a1" } } ``` > **注意**:批量上传成功后自动触发后台向量化任务。无成功上传时不返回 `task_id`。 --- ### 6. POST /exam/generate — 生成题目 **旧响应**: ```json { "success": true, "status_code": 2020, "message": "出题完成", "data": { "success": true, "total": 10, "questions": [...], "warnings": [] } } ``` **新响应**: ```json { "success": true, "status_code": 2020, "message": "出题任务已启动", "data": { "task_id": "d4e5f6a1b2c3", "message": "出题任务已启动 (10题),通过 GET /tasks/d4e5f6a1b2c3 查询结果" } } ``` **轮询完成后的 `result` 字段**: ```json { "success": true, "total": 10, "source_chunks_used": 15, "requested_types": {"single_choice": 5, "true_false": 3, "fill_blank": 2}, "actual_types": {"single_choice": 5, "true_false": 3, "fill_blank": 2}, "warnings": [], "questions": [...] } ``` --- ### 7. POST /exam/generate-smart — AI 智能出题 **旧响应**: ```json { "success": true, "status_code": 2020, "message": "AI 智能出题成功", "data": { "ai_analysis": {...}, "questions": [...], "total": 16 } } ``` **新响应**: ```json { "success": true, "status_code": 2020, "message": "AI 智能出题任务已启动", "data": { "task_id": "e5f6a1b2c3d4", "message": "AI 智能出题任务已启动,通过 GET /tasks/e5f6a1b2c3d4 查询结果" } } ``` **轮询完成后的 `result` 字段**:与旧 `data` 格式相同(含 `ai_analysis`、`questions`、`total`)。 --- ### 8. POST /exam/grade — 批阅答案 **旧响应**: ```json { "success": true, "status_code": 2021, "message": "批阅完成", "data": { "success": true, "total_score": 12.5, "total_max_score": 22.0, "score_rate": 56.8, "results": [...] } } ``` **新响应**: ```json { "success": true, "status_code": 2021, "message": "批阅任务已启动", "data": { "task_id": "f6a1b2c3d4e5", "message": "批阅任务已启动 (5题),通过 GET /tasks/f6a1b2c3d4e5 查询结果" } } ``` **轮询完成后的 `result` 字段**: ```json { "success": true, "total_score": 12.5, "total_max_score": 22.0, "score_rate": 56.8, "results": [ { "question_id": "uuid-001", "score": 0, "max_score": 2.0, "grading_status": "success", "details": { "correct": false, "student_answer": "A", "correct_answer": "B", "feedback": "正确答案: B" } } ] } ``` --- ## 三、新增接口(4 个) ### GET /tasks 获取任务列表。 **查询参数**:`status`(过滤状态)、`type`(过滤类型)、`limit`(返回数量,默认 50) **响应**: ```json { "success": true, "status_code": 2000, "data": { "tasks": [ { "task_id": "a1b2c3d4e5f6", "type": "sync", "description": "文档同步", "status": "running", "progress": 45.0, "current": 9, "total": 20, "stage": "处理文件", "message": "已处理: 产品手册.pdf", "created_at": "2026-06-05T10:30:00" } ], "total": 1 } } ``` --- ### GET /tasks/\ 获取单个任务状态(**后端组推荐使用的轮询接口**)。 **建议轮询间隔**:1-2 秒,当 `status` 为 `completed` 或 `failed` 时停止。 **响应**: ```json { "success": true, "status_code": 2000, "data": { "task_id": "a1b2c3d4e5f6", "type": "sync", "description": "文档同步", "status": "completed", "progress": 100.0, "current": 20, "total": 20, "stage": "完成", "message": "同步完成", "created_at": "2026-06-05T10:30:00", "started_at": "2026-06-05T10:30:01", "completed_at": "2026-06-05T10:30:15", "duration_ms": 14000, "result": { "documents_processed": 20, "documents_added": 3 } } } ``` **任务状态**: | 状态 | 说明 | |------|------| | `pending` | 已创建,等待执行 | | `running` | 正在执行 | | `completed` | 执行完成,`result` 字段包含完整结果 | | `failed` | 执行失败,`error` 字段包含错误信息 | **任务类型(type)**:`sync` / `reindex` / `upload` / `batch_upload` / `exam_generate` / `exam_grade` --- ### GET /tasks/\/progress SSE 流式任务进度推送(dev-ui 前端推荐使用,后端组通常不需要此接口)。 **Content-Type**:`text/event-stream` --- ### GET /tasks/stats 获取任务统计信息。 **响应**: ```json { "success": true, "status_code": 2000, "data": { "total": 5, "by_status": {"running": 1, "completed": 3, "failed": 1}, "by_type": {"sync": 2, "exam_generate": 2, "upload": 1} } } ``` --- ## 四、后端对接代码参考 ### 通用轮询函数 ```python import time import requests def async_task_poll(task_id, base_url='http://rag-service:5001', interval=2, timeout=300): """ 通用异步任务轮询函数 Args: task_id: 任务 ID(从 POST 响应中获取) base_url: RAG 服务地址 interval: 轮询间隔(秒),建议 1-2 秒 timeout: 超时时间(秒) Returns: 任务结果(result 字段内容) Raises: TimeoutError: 超时 Exception: 任务失败 """ elapsed = 0 while elapsed < timeout: time.sleep(interval) elapsed += interval resp = requests.get(f'{base_url}/tasks/{task_id}') if resp.status_code == 404: raise Exception(f"任务不存在: {task_id}") task = resp.json()['data'] if task['status'] == 'completed': return task.get('result') elif task['status'] == 'failed': raise Exception(f"任务失败: {task.get('error', '未知错误')}") raise TimeoutError(f"任务超时: {task_id} (已等待 {timeout}s)") ``` ### 出题流程示例 ```python def generate_exam(file_path, collection, question_types, difficulty=3): """异步出题流程""" # 1. 提交出题任务 resp = requests.post('http://rag-service:5001/exam/generate', json={ 'file_path': file_path, 'collection': collection, 'question_types': question_types, 'difficulty': difficulty }) task_id = resp.json()['data']['task_id'] # 2. 轮询等待结果 result = async_task_poll(task_id) # 3. result 包含完整的出题结果 questions = result['questions'] return questions ``` ### 批阅流程示例 ```python def grade_exam(answers): """异步批阅流程""" # 1. 提交批阅任务 resp = requests.post('http://rag-service:5001/exam/grade', json={ 'answers': answers }) task_id = resp.json()['data']['task_id'] # 2. 轮询等待结果 result = async_task_poll(task_id) # 3. result 包含完整的批阅结果 return result ``` ### 同步流程示例 ```python def trigger_sync(): """异步同步流程""" # 1. 提交同步任务 resp = requests.post('http://rag-service:5001/sync') if resp.status_code == 409: print("已有同步任务在运行,跳过") return task_id = resp.json()['data']['task_id'] # 2. 轮询等待结果 result = async_task_poll(task_id) print(f"同步完成: 处理 {result['documents_processed']} 个文档") return result ``` --- ## 五、注意事项 1. **超时时间**:异步任务完成后保留 1 小时,超时后自动清理。请在任务完成后及时获取结果。 2. **并发限制**:同一类型的任务(如 `sync`)同一时间只能有一个运行中,重复提交返回 HTTP 409。 3. **向后兼容**:如果 RAG 服务未升级(旧版本),POST 接口仍返回旧的同步结果格式。后端可通过检查响应中是否包含 `task_id` 字段来判断版本。 4. **轮询频率**:建议 1-2 秒间隔,过于频繁的轮询会增加服务器负担。 5. **错误处理**:任务可能因 LLM 超时、文件解析失败等原因失败,`status` 变为 `failed`,`error` 字段包含错误描述。