7.8 KiB
7.8 KiB
档案导入 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 补全并更新数据库。
当前版本会把导入结果保存为运行时快照文件:
runtime/proofdb/imports/{import_uid}.json
后续接入 MySQL、OpenSearch、Vector DB 时,会沿用当前返回结构中的 UID。
请求方式
POST /api/articles/import
支持三种调用方式:
multipart/form-data上传 Markdown 文件。text/markdown或text/plain直接发送 Markdown 原文。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 | 是 |
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 分页规则
系统会优先识别以下分页格式:
<!-- DOCMASTER:PAGE 0001 -->
## Page 1
第一页正文...
---
<!-- DOCMASTER:PAGE 0002 -->
## Page 2
第二页正文...
如果没有 DOCMASTER:PAGE 标记,系统会把整份 Markdown 当作单页处理。
分段与 Chunk 切分策略
当前 import 使用 Markdown 预处理 + 页内向量 chunk 切分:
- 先根据 Markdown 分页标记拆成 page。
- 每页内部去掉分页标题、水平分隔线等结构性标记。
- 页眉、页脚、密级、纯页码等噪声块会被过滤,不作为向量 chunk 的正文。
- 检索证据只定位到页码,因此 chunk 可以跨段落、跨列表项,但不会跨页。
- 每页内部会按段落/句子/自然停顿形成文本单元,并尽量打包到接近
chunk_size。 - 如果单个文本单元超过
chunk_size,才退回固定字符窗口硬切,并使用chunk_overlap保留上下文。
请求示例
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 原文:
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 调用示例:
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 正文..."
}'
成功响应
状态码:
201 Created
响应示例:
{
"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 格式错误
状态码:
400 Bad Request
{
"code": 400,
"message": "Invalid JSON body.",
"errors": {
"body": "Syntax error"
}
}
参数校验失败
状态码:
422 Unprocessable Entity
{
"code": 422,
"message": "Archive import validation failed.",
"errors": {
"source": [
"source is required."
],
"content": [
"content, file, pages, or paragraphs is required."
]
}
}
快照保存失败
状态码:
500 Internal Server Error
{
"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 的稳定内容哈希生成,方便人工查看和引用。