vault backup: 2025-12-09 10:20:53
This commit is contained in:
parent
e0f02b5d4c
commit
4762403462
@ -1,10 +1,13 @@
|
||||
|
||||
# 产品需求文档:通用文档转换与管理服务 (UDC-M)
|
||||
**Universal Document Converter & Manager - Backend Service**
|
||||
|
||||
| 文档版本 | V2.0 (集成文档管理) |
|
||||
| :--- | :--- |
|
||||
| **最后更新** | 2025-12-09 |
|
||||
| **状态** | 待开发 |
|
||||
| **涉及端** | 后端 API, 异步 Worker, 对象存储, 关系型数据库 |
|
||||
| 文档版本 | V2.0 |
|
||||
| :------- | :------------------------------ |
|
||||
| **最后更新** | 2025-12-09 |
|
||||
| **状态** | 待开发 |
|
||||
| **涉及端** | 后端 API, 异步 Worker, 对象存储, 关系型数据库 |
|
||||
| | |
|
||||
|
||||
---
|
||||
|
||||
226
app_prd/题目解析.md
Normal file
226
app_prd/题目解析.md
Normal file
@ -0,0 +1,226 @@
|
||||
|
||||
# 产品需求文档:题目解析与结构化服务 (QPES)
|
||||
**Question Parsing & Extraction Service - Backend**
|
||||
|
||||
| 文档版本 | V1.0 |
|
||||
| :------- | :--------------------- |
|
||||
| **依赖模块** | UDC-M (通用文档转换服务 V2.0) |
|
||||
| **最后更新** | 2025-12-09 |
|
||||
| **状态** | 待开发 |
|
||||
| **涉及端** | 后端 API, LLM Agent, 数据库 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目背景与目标
|
||||
### 1.1 背景
|
||||
模块一(UDC-M)已经将 PDF/Word/Excel 变成了带有 MinIO 图片链接的标准 Markdown 流。但此时的数据仍然是**非结构化**的“一坨文本”。
|
||||
我们需要将这些文本中的每一道试题(题干、选项、答案、解析)精准切割出来,变成数据库中的一行行记录,以便后续进行知识点打标和组卷搜索。
|
||||
|
||||
### 1.2 目标
|
||||
构建一个**智能分块与语义提取服务**。
|
||||
* **语义切分**:利用 LLM 识别题目边界,解决“跨页中断”、“题目粘连”问题。
|
||||
* **结构化输出**:将非结构化文本转化为标准 JSON 格式(`content`, `answer`, `analysis`)。
|
||||
* **多模态保留**:在切分过程中,**绝对保留** Markdown 中的图片链接(公式截图、几何图)。
|
||||
* **无缝衔接**:通过 `document_id` 与 UDC-M 的资产表深度关联。
|
||||
|
||||
---
|
||||
|
||||
## 2. 系统架构设计
|
||||
|
||||
### 2.1 核心流程
|
||||
1. **Input**: 接收 `document_id` -> 从 UDC-M 获取 Markdown 文本。
|
||||
2. **Chunking Strategy (切片引擎)**:
|
||||
* 由于 LLM 上下文限制(即使 DeepSeek 支持 128k,过长也会导致注意力分散),需采用 **“滑动窗口 (Sliding Window)”** 策略。
|
||||
* *策略*: 设定窗口大小(如 3000 Tokens 或 5 页),重叠大小(如 500 Tokens 或 1 页)。
|
||||
3. **LLM Processor (提取引擎)**:
|
||||
* 构造 Prompt,要求 LLM 输出特定 Schema 的 JSON。
|
||||
* 处理重叠区域的**题目去重 (Deduplication)**。
|
||||
4. **Validation**: 使用 Pydantic 校验 LLM 返回的 JSON 格式,失败则重试。
|
||||
5. **Storage**: 写入 `questions` 表。
|
||||
|
||||
---
|
||||
|
||||
## 3. 功能需求详细说明
|
||||
|
||||
### 3.1 提取任务管理
|
||||
* **异步处理**: 提取过程耗时较长(LLM 生成),必须异步。
|
||||
* **状态机**: `queued` -> `processing` -> `deduplicating` (去重中) -> `success` / `failed`。
|
||||
|
||||
### 3.2 智能分块策略 (Sliding Window)
|
||||
* **场景**: 一道大题可能跨越第 5 页和第 6 页。如果硬切,题目会断裂。
|
||||
* **逻辑**:
|
||||
* **Chunk N**: 读取第 1-1000 行。Prompt: "提取所有完整题目,如果末尾题目被截断,请**不要**提取,并在 JSON 中标记 `truncated: true`"。
|
||||
* **Chunk N+1**: 读取第 800-1800 行(重叠 200 行)。Prompt: "提取所有完整题目。注意:如果是开头被截断的题目(即上一块的末尾),请尝试修复并提取"。
|
||||
* **去重逻辑**:
|
||||
* 对提取出的每一道题计算 `MD5(content_text)`。
|
||||
* 如果后一个 Chunk 提取出的题目 Hash 与前一个 Chunk 已存的题目 Hash 一致,则丢弃,防止重复。
|
||||
|
||||
### 3.3 Prompt Engineering (核心资产)
|
||||
* **输入**: Markdown 片段。
|
||||
* **要求**:
|
||||
1. 识别选择题、填空题、解答题。
|
||||
2. **关键**: 保持文本中的 `` 图片链接不被 LLM 吞掉或篡改。
|
||||
3. **关键**: 数学公式保持 LaTeX 格式(`$` 包裹)。
|
||||
4. 输出 list of objects。
|
||||
|
||||
### 3.4 结构化存储
|
||||
* 将提取结果存入 `questions` 表。
|
||||
* **预留字段**: `knowledges` 和 `methods` 字段初始化为 `NULL`,等待模块三(知识增强)处理。
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据库设计 (PostgreSQL)
|
||||
|
||||
基于 SQLModel 设计,与 UDC-M 的 `documents` 表关联。
|
||||
|
||||
### 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 | |
|
||||
|
||||
### 4.2 Table: `extraction_tasks` (提取任务记录)
|
||||
用于记录每次 LLM 调用的消耗和状态。
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| `id` | UUID (PK) | 任务 ID |
|
||||
| `document_id` | UUID | 关联文档 |
|
||||
| `model_name` | VARCHAR | 使用的模型 (e.g., `deepseek-v3`) |
|
||||
| `token_usage` | INT | 本次任务消耗 Token 数 (用于成本核算) |
|
||||
| `status` | VARCHAR | 状态 |
|
||||
| `error_log` | TEXT | 错误日志 |
|
||||
|
||||
---
|
||||
|
||||
## 5. API 接口定义 (RESTful)
|
||||
|
||||
**Base URL**: `/api/v1/extractions`
|
||||
|
||||
### 5.1 发起提取任务
|
||||
* **Endpoint**: `POST /`
|
||||
* **Description**: 对指定文档进行题目提取。
|
||||
* **Request Body**:
|
||||
```json
|
||||
{
|
||||
"document_id": "uuid-of-document",
|
||||
"chunk_size": 3000, // 可选,Token数
|
||||
"overlap": 500 // 可选,重叠Token数
|
||||
}
|
||||
```
|
||||
* **Logic**:
|
||||
1. 检查 `documents` 表中该文档是否存在且 `parse_result_url` (Markdown) 是否就绪。
|
||||
2. 创建 `extraction_task`。
|
||||
3. 推送到 Redis 队列 `llm-extraction-queue`。
|
||||
* **Response**: `{"task_id": "...", "status": "queued"}`
|
||||
|
||||
### 5.2 查询提取状态
|
||||
* **Endpoint**: `GET /{task_id}`
|
||||
* **Response**:
|
||||
```json
|
||||
{
|
||||
"status": "processing",
|
||||
"progress": "3/10 chunks",
|
||||
"extracted_count": 45
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 获取提取结果 (核心)
|
||||
* **Endpoint**: `GET /documents/{document_id}/questions`
|
||||
* **Description**: 获取某文档下的所有题目(支持分页)。
|
||||
* **Response**:
|
||||
```json
|
||||
{
|
||||
"total": 12,
|
||||
"items": [
|
||||
{
|
||||
"id": 1001,
|
||||
"question_seq": 1,
|
||||
"content_md": "已知函数 $f(x)=x^2$ ... ",
|
||||
"enrich_status": "pending"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 人工修正接口 (可选)
|
||||
* **Endpoint**: `PUT /questions/{question_id}`
|
||||
* **Description**: 人工校对 OCR 错误或切分错误。
|
||||
|
||||
---
|
||||
|
||||
## 6. 技术选型 (Vibe Coding Stack)
|
||||
|
||||
除了沿用模块一的 **FastAPI + Celery + PostgreSQL** 外,本模块的核心在于 **LLM 交互**。
|
||||
|
||||
| 组件 | 选型 | 理由 |
|
||||
| :--- | :--- | :--- |
|
||||
| **LLM Model** | **DeepSeek-V3** | API 成本极低,Context 窗口大,逻辑推理能力强,适合处理长文档。 |
|
||||
| **LLM SDK** | **OpenAI SDK** | DeepSeek 完美兼容 OpenAI 接口协议。不要引入 LangChain (太重),直接用原生 SDK 配合 Pydantic 即可。 |
|
||||
| **Schema Validation** | **Pydantic (Instructor)** | **强烈推荐**使用 `instructor` 库(基于 Pydantic),它可以强制 LLM 输出完全符合 Pydantic 定义的 JSON 结构,极大降低解析错误率。 |
|
||||
| **Markdown Splitter** | **LangChain TextSplitter** | 虽然不用 LangChain 的 Chain,但它的 `MarkdownHeaderTextSplitter` 工具类非常好用,用于预处理 Chunk。 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 开发难点与 Vibe Coding 提示
|
||||
|
||||
在开发此模块时,请注意以下 Prompt 技巧,通过 Cursor/Windsurf 编写代码时由于重要:
|
||||
|
||||
### 7.1 "Instructor" 模式示例
|
||||
不要让 LLM 返回纯文本然后自己 `json.loads()`,这很容易崩。请使用 Schema 驱动模式:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional
|
||||
|
||||
# 1. 定义题目结构
|
||||
class QuestionItem(BaseModel):
|
||||
content: str = Field(..., description="The main text of the question, including LaTeX and image links.")
|
||||
options: Optional[List[str]] = Field(None, description="List of options if it is a multiple choice question.")
|
||||
answer: Optional[str] = Field(None, description="The answer key.")
|
||||
type: str = Field(..., description="Type: choice, fill, or essay")
|
||||
|
||||
class ExtractionResult(BaseModel):
|
||||
questions: List[QuestionItem]
|
||||
|
||||
# 2. Vibe Coding 调用 (伪代码)
|
||||
# client = instructor.patch(OpenAI(...))
|
||||
# resp = client.chat.completions.create(
|
||||
# model="deepseek-chat",
|
||||
# response_model=ExtractionResult, <-- 关键!强制返回对象
|
||||
# messages=[...]
|
||||
# )
|
||||
```
|
||||
|
||||
### 7.2 图片链接保护 Prompt
|
||||
在 System Prompt 中必须加入:
|
||||
> "You are a strictly structural parser. You must NOT simplify, summarize, or remove any content from the original text.
|
||||
> **CRITICAL RULE**: Do NOT remove or modify any image links formatted as ``. They must be preserved exactly as they appear in the text."
|
||||
|
||||
### 7.3 去重逻辑实现
|
||||
建议在 Python 代码中实现去重,而不是让 LLM 去重。
|
||||
* **方法**: 维护一个 `Set`,存储 `hash(content)`。
|
||||
* **流程**: Worker 处理完 Chunk 1,将题目入库并存 Hash 到 Set。处理 Chunk 2 时,先计算 Hash,如果在 Set 里,直接跳过。
|
||||
|
||||
---
|
||||
|
||||
## 8. 与后续模块的接口
|
||||
|
||||
该模块完成后,数据库中的 `questions` 表已经有了干净的题目数据。
|
||||
* **字段状态**: `knowledges` 为 NULL, `enrich_status` 为 `pending`。
|
||||
* **下一步**: 模块三(知识增强服务)只需要 `SELECT * FROM questions WHERE enrich_status = 'pending'`,然后逐条调用 Summary API 进行填空即可。
|
||||
|
||||
这套设计保证了模块的**高内聚、低耦合**。即使模块三挂了,模块二依然可以照常解析入库,不会阻塞业务。
|
||||
Loading…
Reference in New Issue
Block a user