
# 产品需求文档：知识增强服务 (KES)
**Knowledge Enrichment Service - Backend**

| 文档版本 | V1.0 |
| :--- | :--- |
| **依赖模块** | UDC-M (模块一), QPES (模块二) |
| **最后更新** | 2025-12-09 |
| **状态** | 待开发 |
| **涉及端** | 后端 API, 异步 Worker, 外部 API 交互 |

---

## 1. 项目背景与目标
### 1.1 背景
模块二（QPES）已经完成了“文档 -> 题目”的物理拆解，数据库中已经有了干净的题干、答案和图片。
但此时的题目是“孤立”的，无法支持“查找所有关于‘勾股定理’的题”或“生成一份‘三角函数’的试卷”等业务场景。我们需要为题目注入**元数据（Metadata）**。

### 1.2 目标
构建一个**异步的知识增强与人工审核服务**。
*   **自动打标**：监控未处理的题目，调用外部 `summary_api` 自动提取知识点和方法。
*   **人工介入 (HITL)**：提供接口，允许教研人员对 AI 提取的标签进行审核、修改和确认。
*   **数据回填**：将处理结果结构化地更新回主数据库。

---

## 2. 系统架构设计

系统设计为 **“生产者-消费者”** 模式，以解耦数据库读取与外部 API 调用（防止外部 API 阻塞系统）。

### 2.1 核心组件
1.  **Enrichment Scheduler (调度器)**:
    *   定时任务 (Celery Beat) 或 事件触发。
    *   扫描 `questions` 表中 `enrich_status = 'pending'` 的记录。
    *   将任务推送到 Redis 队列。
2.  **API Client Worker (执行器)**:
    *   消费队列。
    *   调用外部 `summary_api`。
    *   **关键特性**: 实现流控 (Rate Limiting)、重试 (Retry) 和熔断机制。
3.  **Review API (审核接口)**:
    *   提供给前端/教研后台，用于人工修改知识点。

### 2.2 外部接口定义 (Mock Summary API)
假设外部接口契约如下（开发时需对接真实接口）：
*   **Input**: `{"question_text": "...", "answer": "..."}`
*   **Output**:
    ```json
    {
      "summary": "一道关于利用勾股定理求解直角三角形斜边的应用题",
      "knowledges": ["勾股定理", "直角三角形性质"],
      "methods": ["数形结合", "方程思想"]
    }
    ```

---

## 3. 功能需求详细说明

### 3.1 自动打标流程
1.  **任务获取**: 查询 `questions` 表，条件：`enrich_status='pending'`。
2. Write-Protect (写保护)。IF question.is_reviewed == True: UPDATE ONLY raw_fields; ELSE: UPDATE raw_fields AND updated_fields;
3.  **构建 Payload**: 拼接 `ocr_text` (含公式/图片链接) 和 `answer_md`。
4.  **调用 API**: 发送 HTTP POST 请求给 `summary_api`。
5.  **结果处理**:
    *   **成功**:
	    - **第一步：更新 AI 原始字段**。将 API 返回的 JSON 数据填入 knowledges, methods, problem_summary 字段。这些字段始终反映 AI 的最新判断。
        
	    - **第二步：更新人工审核字段 (写保护逻辑)**。
	        - **检查该题目的 is_reviewed 字段状态**。
	        - **场景 A (未审核, is_reviewed = False)**：将 AI 提取的结果 **复制 (Copy)** 一份给 updated_knowledges 和 updated_methods。此时认为 AI 结果即为当前最佳结果。
	        - **场景 B (已审核, is_reviewed = True)**：**跳过** 对 updated_knowledges 和 updated_methods 的更新。**绝对禁止** 覆盖人工已经校对过的数据，仅更新 AI 原始字段供参考。
	    - **第三步：更新状态**。将 enrich_status 更新为 'done'，清空 enrich_error。
    *   **失败 (5xx/Timeout)**:
        *   触发重试（指数退避，最多 3 次）。
        *   若最终失败，更新状态 `enrich_status = 'failed'`，并记录 `error_msg`。
### 3.2 人工审核 (Human-in-the-Loop)
*   **审核逻辑**: AI 的提取结果并不总是 100% 准确。系统信任链为：**人工数据 > AI 数据**。
*   **数据流**:
    1.  前端展示 `updated_knowledges` (初始值等于 AI 提取值)。
    2.  人工修改（增删改）。
    3.  调用 `PUT /questions/{id}/review` 接口保存。
    4.  系统更新 `updated_at` 和 `reviewer_id`。

### 3.3 知识库一致性 (Optional Advanced)
*   *需求*: 虽然初期可以存自由文本的 Tag，但为了检索准确，建议引入“标准知识点树”。
*   *逻辑*: 在保存 Tag 前，检查 Tag 是否在系统的 `knowledge_tree` 表中。如果不在，标记为“新词”，提示管理员审核。

---

## 4. 数据库设计 (PostgreSQL)

沿用模块二的 `questions` 表，本模块主要负责 **UPDATE** 操作。
### **4.1 Table: questions (字段更新)**

本模块负责该表的 **更新 (UPDATE)** 操作。

- **读取条件**: WHERE enrich_status = 'pending'
    
- **更新字段 (AI)**: knowledges, methods, problem_summary, enrich_status。
    
- **更新字段 (人工)**: updated_knowledges, updated_methods, is_reviewed。

-  (完整表结构定义请参考“统一数据库设计规范”文档)
---

## 5. API 接口定义 (RESTful)

**Base URL**: `/api/v1/enrichments`

### 5.1 触发/重试打标
*   **POST** `/trigger`
*   **Description**: 手动触发一次批量打标（通常由定时任务调用，也可以人工触发重试失败任务）。
*   **Body**: `{"retry_failed": true}`
*   **Response**: `{"triggered_count": 50}`

### 5.2 提交人工审核结果
*   **PUT** `/questions/{question_id}/review`
*   **Description**: 教研人员提交修正后的知识点。
*   **Body**:
    ```json
    {
      "knowledges": ["修正后的知识点1", "修正后的知识点2"],
      "methods": ["方法1"],
      "confirm": true // 标记 is_reviewed = true
    }
    ```
*   **Response**: `200 OK`

### 5.3 获取待审核题目列表
*   **GET** `/questions/review-queue`
*   **Query**:
    *   `status`: `done` (已AI打标，等待人工)
    *   `is_reviewed`: `false`
*   **Response**: 题目列表 (含 AI 提取的 Tag)。

---

## 6. 技术选型 (Vibe Coding Stack)

### 6.1 核心技术
*   **HTTP Client**: **`httpx`** (异步) 或 **`aiohttp`**。
    *   *理由*: 相比 `requests`，异步客户端在 Celery Worker 中能支持更高的并发（Event Loop），适合 I/O 密集型任务。
*   **Resilience (弹性)**: **`tenacity`**。
    *   *理由*: Python 最好的重试库。用装饰器 `@retry` 就能实现指数退避，代码极简。
*   **Rate Limiting**: **`Redis` + `Lua Script`** 或 Celery 自带的 rate limit。
    *   *理由*: 防止把外部 `summary_api` 打挂。

### 6.2 代码结构示例 (Service Layer)

```python
# services/enrichment_service.py
import httpx
from tenacity import retry, stop_after_attempt, wait_exponential

class SummaryAPIClient:
    def __init__(self, base_url: str, api_key: str):
        self.client = httpx.AsyncClient(base_url=base_url, headers={"Authorization": api_key})

    @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
    async def get_summary(self, text: str):
        response = await self.client.post("/summarize", json={"text": text})
        response.raise_for_status()
        return response.json()
```

---

## 7. 模块间联动总结

到此为止，三个模块已经形成了一个完美的闭环：

1.  **UDC-M (文档与转换)**:
    *   **Input**: 用户上传 PDF/Docx。
    *   **Output**: 永久存储原始文件 + 标准 Markdown (含 MinIO 图片链)。
    *   **Data**: `documents` 表。

2.  **QPES (题目解析)**:
    *   **Input**: 读取 `documents` 里的 Markdown。
    *   **Action**: 调用 DeepSeek 切分题目。
    *   **Output**: 结构化题目。
    *   **Data**: `questions` 表 (基础字段)。

3.  **KES (知识增强)**:
    *   **Input**: 读取 `questions` (Pending 状态)。
    *   **Action**: 调用 Summary API + 人工审核。
    *   **Output**: 完整的、带标签的题库。
    *   **Data**: `questions` 表 (Tag 字段)。

**部署建议**：
这三个模块可以部署在**同一个代码仓库 (Monorepo)** 中，共享数据库 Model 定义，但通过**Docker Compose** 拆分为不同的服务容器运行，以便根据负载独立扩容（例如：UDC 扩容 GPU 节点，KES 扩容 IO 密集型节点）。