vault backup: 2025-12-09 10:28:34

This commit is contained in:
imac-maxwell 2025-12-09 10:28:34 +08:00
parent 4762403462
commit d4369e8219
2 changed files with 207 additions and 15 deletions

View File

@ -0,0 +1,192 @@
# 产品需求文档:知识增强服务 (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. **构建 Payload**: 拼接 `ocr_text` (含公式/图片链接) 和 `answer_md`
3. **调用 API**: 发送 HTTP POST 请求给 `summary_api`
4. **结果处理**:
* **成功**:
* 将返回的 JSON 填入 `knowledges`, `methods`, `problem_summary` 字段。
* 同时将这些值 **Copy** 一份给 `updated_knowledges`, `updated_methods` (作为人工审核的默认值)。
* 更新状态 `enrich_status = 'done'`
* **失败 (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` (字段补充)
| 字段名 | 类型 | 说明 |
| :--- | :--- | :--- |
| ... | ... | (模块二已有的基础字段) |
| `problem_summary` | TEXT | 题目一句话摘要 (AI生成) |
| `knowledges` | JSONB | AI提取: `["k1", "k2"]` |
| `methods` | JSONB | AI提取: `["m1"]` |
| `updated_knowledges`| JSONB | **人工审核版** (应用层展示以此为准) |
| `updated_methods` | JSONB | **人工审核版** |
| `enrich_status` | VARCHAR | `pending` (待处理), `processing`, `done`, `failed` |
| `enrich_error` | TEXT | 调用外部API失败的错误日志 |
| `is_reviewed` | BOOLEAN | 是否经过人工确认 (默认 False) |
| `reviewer_id` | INT | 审核人ID |
| `reviewed_at` | TIMESTAMP | 审核时间 |
---
## 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 密集型节点)。

View File

@ -75,21 +75,21 @@
### 4.1 Table: `questions` (题库核心表)
| 字段名 | 类型 | 说明 |
| :--- | :--- | :--- |
| `id` | BIGINT (PK) | 题目唯一主键 |
| `document_id` | UUID (FK) | **关联 UDC-M 的 documents 表** |
| `batch_id` | UUID | 关联本次提取任务 ID |
| `question_seq` | INT | 在原文档中的顺序号 (1, 2, 3...) |
| `question_type` | VARCHAR | `choice` (选择), `fill` (填空), `essay` (解答) |
| `content_md` | TEXT | **题干内容** (含 Markdown 图片和 Latex) |
| `options_json` | JSONB | 选择题选项 `[{"label":"A","text":"..."}, ...]` |
| `answer_md` | TEXT | 参考答案 |
| `analysis_md` | TEXT | 解析 (如果有) |
| `image_urls` | JSONB | 提取出的图片链接列表 `["http...", "http..."]` (冗余字段,方便索引) |
| `content_hash` | VARCHAR(64) | 用于去重 |
| `enrich_status` | VARCHAR | `pending` (待打标), `done` (已打标) |
| `created_at` | TIMESTAMP | |
| 字段名 | 类型 | 说明 |
| :-------------- | :---------- | :---------------------------------------------- |
| `id` | BIGINT (PK) | 题目唯一主键 |
| `document_id` | UUID (FK) | **关联 UDC-M 的 documents 表** |
| `batch_id` | UUID | 关联本次提取任务 ID |
| `question_seq` | INT | 在原文档中的顺序号 (1, 2, 3...) |
| `question_type` | VARCHAR | `choice` (选择), `fill` (填空), `essay` (解答) |
| `content_md` | TEXT | **题干内容** (含 Markdown 图片和 Latex) |
| `options_json` | JSONB | 选择题选项 `[{"label":"A","text":"..."}, ...]` |
| `answer_md` | TEXT | 参考答案 |
| `analysis_md` | TEXT | 解析 (如果有) |
| `image_urls` | JSONB | 提取出的图片链接列表 `["http...", "http..."]` (冗余字段,方便索引) |
| `content_hash` | VARCHAR(64) | 用于去重 |
| `enrich_status` | VARCHAR | `pending` (待打标), `done` (已打标) |
| `created_at` | TIMESTAMP | |
### 4.2 Table: `extraction_tasks` (提取任务记录)
用于记录每次 LLM 调用的消耗和状态。