proofdb/apidoc/importapi.md
2026-05-01 23:40:14 +08:00

278 lines
7.8 KiB
Markdown
Raw 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.

# 档案导入 API
## 接口说明
导入一份 Markdown 档案文本,将其规范化为 Archive、Page、Chunk 三层结构,并为每个 chunk 生成全局唯一的 `chunk_uid`
正式使用时,推荐直接上传原始 Markdown 文件,或者把 Markdown 原文作为请求体发送。系统会根据 Markdown 中的分页标记自动分页、分段和切 chunk。
导入会先写入 PostgreSQL。如果 `title/year/author/tags/summary` 缺失,系统会把 `archive_uid` 推入 Redis 队列,由独立的 `ai_metadata` process 异步请求 AI 补全并更新数据库。
当前版本会把导入结果保存为运行时快照文件:
```text
runtime/proofdb/imports/{import_uid}.json
```
后续接入 MySQL、OpenSearch、Vector DB 时,会沿用当前返回结构中的 UID。
## 请求方式
```http
POST /api/articles/import
```
支持三种调用方式:
1. `multipart/form-data` 上传 Markdown 文件。
2. `text/markdown``text/plain` 直接发送 Markdown 原文。
3. `application/json` 发送 Markdown 字符串,适合程序内部调用。
## 请求字段
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `file` | file | 否 | Markdown 文件。使用 `multipart/form-data` 时推荐传此字段 |
| `archive_uid` | string | 否 | 档案级 ULID。不传时系统自动生成 |
| `title` | string | 否 | 档案标题。不传时,系统会优先请求 AI 生成,失败后从 Markdown 标题或文件名推断 |
| `year` | int | 否 | 档案年份。不传时,系统会请求 AI 从文本中抽取或推断 |
| `author` | string | 否 | 作者或签发人。不传时,系统会请求 AI 从文本中抽取或推断 |
| `summary` | string | 否 | 档案摘要。不传时,系统会请求 AI 生成 |
| `source` | string | 否 | 来源标识。不传时,系统会使用上传文件名或 `raw-markdown` |
| `series` | string | 否 | 所属系列集 |
| `tags` | array | 否 | 标签数组。不传时,系统会请求 AI 生成 |
| `metadata` | object | 否 | 档案级元数据,例如年份、作者、馆藏信息 |
| `content` | string | 否 | Markdown 原文。JSON 调用时使用 |
| `pages` | array | 否 | 页数组。兼容字段,一般不需要调用方传入 |
| `pages[].page_number` | int|string | 是 | 页码,可传数字或原始页码字符串,例如 `12`、`12a`、`封面` |
| `pages[].content` | string | 是 | 当前页 OCR 或转录文本 |
| `pages[].metadata` | object | 否 | 页级元数据 |
| `paragraphs` | array | 否 | 段落数组。兼容字段,一般不需要调用方传入 |
| `paragraphs[].page_number` | int|string | 否 | 当前段落所在页码 |
| `paragraphs[].content` | string | 是 | 段落正文 |
| `paragraphs[].metadata` | object | 否 | 段落级元数据 |
| `chunk_size` | int | 否 | 每个 chunk 的目标最大字符数,默认 `800`,范围 `100-4000` |
| `chunk_overlap` | int | 否 | 超长文本硬切时的重叠字符数,默认 `120`,必须小于 `chunk_size` |
## Markdown 分页规则
系统会优先识别以下分页格式:
```markdown
<!-- DOCMASTER:PAGE 0001 -->
## Page 1
第一页正文...
---
<!-- DOCMASTER:PAGE 0002 -->
## Page 2
第二页正文...
```
如果没有 `DOCMASTER:PAGE` 标记,系统会把整份 Markdown 当作单页处理。
## 分段与 Chunk 切分策略
当前 import 使用 Markdown 预处理 + 页内向量 chunk 切分:
1. 先根据 Markdown 分页标记拆成 page。
2. 每页内部去掉分页标题、水平分隔线等结构性标记。
3. 页眉、页脚、密级、纯页码等噪声块会被过滤,不作为向量 chunk 的正文。
4. 检索证据只定位到页码,因此 chunk 可以跨段落、跨列表项,但不会跨页。
5. 每页内部会按段落/句子/自然停顿形成文本单元,并尽量打包到接近 `chunk_size`
6. 如果单个文本单元超过 `chunk_size`,才退回固定字符窗口硬切,并使用 `chunk_overlap` 保留上下文。
## 请求示例
```bash
curl -X POST http://127.0.0.1:8787/api/articles/import \
-F 'title=NSD 76 Disposition of NSC Policy Documents' \
-F 'source=archive://nsc/nsd-76' \
-F 'chunk_size=800' \
-F 'chunk_overlap=120' \
-F 'file=@test/1.test.md;type=text/markdown'
```
也可以直接发送 Markdown 原文:
```bash
curl -X POST 'http://127.0.0.1:8787/api/articles/import?title=NSD%2076&source=archive://nsc/nsd-76' \
-H 'Content-Type: text/markdown' \
--data-binary '@test/1.test.md'
```
JSON 调用示例:
```bash
curl -X POST http://127.0.0.1:8787/api/articles/import \
-H 'Content-Type: application/json' \
--data '{
"title": "NSD 76 Disposition of NSC Policy Documents",
"source": "archive://nsc/nsd-76",
"content": "<!-- DOCMASTER:PAGE 0001 -->\n\n## Page 1\n\nMarkdown 正文..."
}'
```
## 成功响应
状态码:
```http
201 Created
```
响应示例:
```json
{
"code": 0,
"message": "Archive imported.",
"data": {
"import_uid": "01HX8K7V9Y3QF2Z6M4A1B8C9D0",
"archive": {
"archive_uid": "01HX8K7V9Y3QF2Z6M4A1B8C9D0",
"title": "NSD 76 Disposition of NSC Policy Documents",
"year": 1992,
"author": "Brent Scowcroft",
"source": "archive://nsc/nsd-76",
"series": null,
"tags": [
"美国国家安全委员会",
"政策文件",
"NSD 76"
],
"summary": "这份档案是 1992 年关于 NSC 政策文件处置的国家安全指令,列明已被取代或已完成、不再有效的政策文件。",
"metadata": {
"ai_enrichment": {
"enabled": true,
"attempted": true,
"filled": [
"year",
"author",
"tags",
"summary"
],
"missing": [],
"model": "glm-4.7-flash"
}
}
},
"pages": [
{
"page_number": 1,
"block_count": 8,
"chunk_count": 2,
"content_length": 1042,
"chunk_uids": [
"01HX8K7V9Y3QF2Z6M4A1B8C9D0_1_28172",
"01HX8K7V9Y3QF2Z6M4A1B8C9D0_2_19304"
]
}
],
"chunks": [
{
"chunk_uid": "01HX8K7V9Y3QF2Z6M4A1B8C9D0_1_28172",
"chunk_index": 1,
"page_start": 1,
"page_end": 1,
"pages": [
1
],
"text": "NSD 45 20 AUG 90 U.S. Policy in Response to the Iraqi Invasion of Kuwait (C)\n* *COMMENT** OBE by operations Desert Shield and Desert Storm.",
"length": 142,
"embedding_ref": null
}
],
"stats": {
"page_count": 8,
"page_block_count": 86,
"chunk_count": 14,
"chunk_size": 800,
"chunk_overlap": 120
},
"queue": {
"ai_metadata_enqueued": true,
"needs_ai_metadata": true
}
}
}
```
## 错误响应
### JSON 格式错误
状态码:
```http
400 Bad Request
```
```json
{
"code": 400,
"message": "Invalid JSON body.",
"errors": {
"body": "Syntax error"
}
}
```
### 参数校验失败
状态码:
```http
422 Unprocessable Entity
```
```json
{
"code": 422,
"message": "Archive import validation failed.",
"errors": {
"source": [
"source is required."
],
"content": [
"content, file, pages, or paragraphs is required."
]
}
}
```
### 快照保存失败
状态码:
```http
500 Internal Server Error
```
```json
{
"code": 500,
"message": "Archive import snapshot could not be saved.",
"errors": {
"storage": "具体错误信息"
}
}
```
## UID 说明
| UID | 说明 |
| --- | --- |
| `import_uid` | 本次导入任务 ID目前等同于档案级 ULID |
| `archive_uid` | 档案级 ULID例如 `01HX8K7V9Y3QF2Z6M4A1B8C9D0` |
| `chunk_uid` | chunk 级核心 ID格式为 `{archive_uid}_{chunk_index}_{short_uid}` |
`chunk_uid` 是后续 MySQL、OpenSearch、Vector DB 之间关联的核心字段。
`chunk_index``1` 开始递增。`short_uid` 是 5 位数字短码,由 chunk 的稳定内容哈希生成,方便人工查看和引用。