知识库文件摄入与 RAG 检索技术方案¶
- 版本:v1.0
- 日期:2026-05-21
- 适用项目:
Tender_Documents
1. 背景¶
当前知识库模块已经具备文件上传、OSS 存储和 knowledge_files 基础记录能力,但还没有形成完整的知识摄入链路。
目标不是把知识库简单做成向量数据库,而是建立一条从“用户上传文件”到“可被标书生成引用”的完整流水线:
- 原始文件进入 OSS。
- 文件元数据进入 PostgreSQL。
- 解析后的 Markdown 与切块正文进入 PostgreSQL。
- chunk embedding 进入向量检索服务。
- 标书生成时按知识库、文件、权限过滤后召回 chunk 正文。
这样做可以同时满足:
- 文件列表、权限、状态、重试等业务管理。
- 章节生成时的语义召回。
- 返回引用来源、文件名、页码、标题路径。
- 后续从自建向量库切换到云托管向量库。
2. 方案结论¶
采用“OSS + PostgreSQL + ARQ worker + 向量库”的混合架构。
推荐落地顺序:
- 第一阶段先自建
Qdrant,跑通知识摄入与检索闭环。 - 向量服务层做 provider 抽象,后续可切到阿里云
DashVector。 - 不继续使用 Cloudflare Vectorize 作为国内主链路向量库。
- 向量库只存向量和轻量 metadata,chunk 全文仍然存 PostgreSQL。
- 知识库文件摄入不要复用
ingestion_tasks,应使用知识库专用任务表。
原因:
- Cloudflare Vectorize 国内延迟较高,不适合作为章节生成的同步依赖。
- Qdrant 部署简单,适合先把功能闭环跑通。
- DashVector 更适合后续生产托管,但一开始接入会增加调试成本。
- PostgreSQL 管理权限、状态、全文、引用来源更稳。
- 向量库主键以
chunk_id为核心,避免后续换库时推翻业务表。
3. 总体流程¶
用户上传文件
-> OSS 保存原文
-> knowledge_files 创建记录 status=pending
-> knowledge_file_parse_jobs 创建任务
-> ARQ 入队 knowledge_file_ingest
-> worker 下载 OSS 文件
-> document_parse_service 解析成 Markdown
-> 写 knowledge_file_contents
-> 切块写 knowledge_file_chunks
-> embedding 后写向量库(Qdrant / DashVector)
-> 回写 KnowledgeFile status=success / failed
sequenceDiagram
participant U as Browser
participant API as FastAPI
participant OSS as OSS
participant DB as PostgreSQL
participant Q as ARQ/Redis
participant W as KnowledgeWorker
participant E as Embedding API
participant V as Vector Store
U->>API: get presigned-url
API-->>U: upload_url + file_key
U->>OSS: PUT 原始文件
U->>API: confirm-upload(file_key)
API->>OSS: HeadObject 校验
API->>DB: create knowledge_files(status=pending)
API->>DB: create knowledge_file_parse_jobs
API->>Q: enqueue knowledge_file_ingest
API-->>U: 返回文件记录
Q->>W: consume knowledge_file_ingest
W->>DB: job/status -> processing
W->>OSS: download file bytes
W->>W: document_parse_service.extract_full_markdown
W->>DB: upsert knowledge_file_contents
W->>DB: replace knowledge_file_chunks
W->>E: batch embeddings
W->>V: upsert chunk vectors
W->>DB: file/job -> success
4. 当前代码落点¶
现有可复用模块:
- 知识库文件接口:
app/routes/v1/knowledges/file.py - 知识库文件服务:
app/services/knowledge/file_service.py - 知识库模型:
app/models/knowledge.py - OSS 能力:
app/services/oss_service.py - ARQ 入队:
app/services/arq_queue_service.py - worker 注册:
app/worker/parser_jobs.py - 文档解析:
app/services/parse/document/service.py - embedding 配置:
app/core/config.py
当前需要调整的点:
KnowledgeFile只保存基础文件信息,不够支撑解析和索引状态。confirm-upload只落库,不会创建解析任务或入队。- 现有
vector_service绑定 Cloudflare Vectorize,且以task_id为主,不适合知识库长期索引。 - 标书生成上下文目前只拿到知识库文件名,没有检索知识库正文。
5. 数据模型设计¶
5.1 扩展 knowledge_files¶
建议补充字段:
file_key: str
content_type: str | None
ext: str | None
page_count: int | None
parse_error: str | None
parsed_at: datetime | None
parser_version: str | None
chunk_count: int
embedding_status: str
status 继续表示文件整体摄入状态:
embedding_status 表示向量索引状态:
5.2 新增 knowledge_file_parse_jobs¶
用于跟踪每次文件摄入任务,避免把知识库逻辑塞进 ingestion_tasks。
建议字段:
id
knowledge_file_id
knowledge_id
directory_id
user_id
file_key
file_name
file_size
status
stage
progress
retry_count
max_retries
error_message
parser_version
started_at
finished_at
result_summary
created_at
updated_at
任务状态:
任务阶段:
5.3 新增 knowledge_file_contents¶
保存解析后的整文内容。
建议字段:
id
knowledge_file_id
knowledge_id
user_id
source_markdown
plain_text
page_count
content_sha256
parser_version
created_at
updated_at
说明:
source_markdown是后续切块、引用、重建索引的主来源。plain_text可选,用于关键词搜索或摘要。content_sha256用于幂等和判断是否需要重建索引。
5.4 新增 knowledge_file_chunks¶
保存 chunk 全文和引用信息。
建议字段:
id
knowledge_file_id
knowledge_id
directory_id
user_id
chunk_index
chunk_text
heading_path
page_start
page_end
token_count
vector_id
content_sha256
parser_version
created_at
updated_at
推荐索引:
6. 向量库设计¶
6.1 Provider 抽象¶
新增向量存储抽象,不让业务代码绑定具体向量库:
统一接口:
class VectorStore:
async def upsert_chunks(self, chunks: list[VectorChunk]) -> None: ...
async def search(self, query_vector: list[float], filters: VectorFilters, top_k: int) -> list[VectorMatch]: ...
async def delete_by_file_id(self, file_id: str) -> None: ...
配置:
VECTOR_STORE_PROVIDER=qdrant
QDRANT_URL=http://qdrant:6333
QDRANT_API_KEY=
QDRANT_COLLECTION=knowledge_chunks_v1
DASHVECTOR_API_KEY=
DASHVECTOR_ENDPOINT=
DASHVECTOR_COLLECTION=knowledge_chunks_v1
EMBEDDING_API_KEY=
EMBEDDING_BASE_URL=
EMBEDDING_MODEL=text-embedding-v4
6.2 向量 metadata¶
向量库 metadata 只放轻量字段:
{
"user_id": "...",
"knowledge_id": "...",
"directory_id": "...",
"file_id": "...",
"chunk_id": "...",
"chunk_index": 12,
"parser_version": "knowledge-parser-v1",
"content_sha256": "..."
}
不建议把完整 chunk_text 放进向量库。检索命中后,应回 PostgreSQL 读取 knowledge_file_chunks.chunk_text。
6.3 推荐选型¶
短期:Qdrant
- 自建部署简单。
- 支持 payload filter。
- 对本项目早期知识库规模足够。
- 可以快速验证 chunk、embedding、召回质量。
生产托管:DashVector
- 国内延迟更稳定。
- 运维成本低。
- 适合后续多租户和更大数据量。
暂不优先:Cloudflare Vectorize
- 国内访问延迟高。
- 当前实现以
task_id为主,不适合知识库长期索引。 - metadata filter 和全文回查模型需要重做。
7. 上传确认与入队¶
confirm-upload 调整为:
- 校验
file_key是否属于当前kb_id。 - 调用
OSSService.head_object校验对象存在。 - 读取
content_length、content_type。 - 创建或复用
KnowledgeFile。 - 创建
KnowledgeFileParseJob。 - 提交事务。
- 调用
arq_queue_service.enqueue_knowledge_file_ingest(job_id)。 - 返回
KnowledgeFileResponseDTO,状态为pending。
幂等规则:
- 同一
file_key已存在未删除文件时,直接返回已有文件。 - 若该文件已有
PENDING/PROCESSING任务,不重复入队。 - 若文件已
success且内容 hash 未变化,不重复解析。 - 重跑应走单独
reparse接口。
8. Worker 设计¶
新增 ARQ job:
handler 职责:
- 根据
job_id加载任务和KnowledgeFile。 - 检查文件是否已软删除。
- 下载 OSS 文件。
- 调用
document_parse_service.extract_full_markdown(file_bytes, filename)。 - 写入
knowledge_file_contents。 - 将 Markdown 切成 chunk,写入
knowledge_file_chunks。 - 批量调用 embedding。
- 写入向量库。
- 回写文件和任务状态。
失败处理:
- 下载失败:任务
FAILED,文件failed。 - 文档解析失败:任务
FAILED,文件failed,写parse_error。 - embedding 失败:任务
FAILED,文件failed,embedding_status=failed。 - 向量库写入失败:任务
FAILED,保留 chunks,允许后续只重建索引。 - 文件已删除:任务
CANCELLED,不继续写内容和向量。
9. 切块策略¶
一期建议基于 Markdown 切块:
- 优先按标题层级切。
- 标题块过长时再按段落、表格边界切。
- 单 chunk 目标长度建议
800-1200中文字。 - chunk overlap 建议
100-200中文字。 - 保留
heading_path,用于引用来源和生成上下文。 - 保留页码信息;如果解析器不能稳定给页码,先允许为空。
切块原则:
- 不把超大表格硬切碎到失去语义。
- 不把目录页、页眉页脚、空白页作为高权重内容。
- 对短文件允许单 chunk。
- 每次重建索引前先按
knowledge_file_id删除旧向量。
10. 检索与生成集成¶
新增知识库检索服务:
检索流程:
query text
-> embedding
-> vector_store.search(filters)
-> 返回 chunk_id + score
-> PostgreSQL 查询 chunk_text 和来源信息
-> 组装引用上下文
过滤条件必须包含:
标书生成集成点:
- 当前
context_assembler只拼知识库文件名。 - 应改为根据章节标题、评分项、写作提示组合 query。
- 只在用户关联的
knowledge_file_ids范围内检索。 - 将 top chunks 作为“关联技术资料原文片段”放入章节生成上下文。
- 生成结果可记录引用 chunk,后续支持溯源。
生成上下文示例:
11. 产品库与图库边界¶
知识库摄入不应默认把所有文件内容写入产品库或图库。
建议边界:
- 通用文件默认只入知识库内容表、chunk 表和向量库。
- 产品清单或产品资料需要用户选择“导入产品库”后,再走产品库解析链路。
- 文档中抽取出的图片先作为知识库资产保存,确认后再进入图库。
- 结构化 facts 可先放
knowledge_file_facts,不要直接污染业务主表。
后续可新增:
12. 配置与命名¶
不再建议依赖全局 LLM_API_KEY 作为所有模型调用的隐式 fallback。
知识库摄入相关建议使用明确配置:
PARSER_EXTRACT_API_KEY=
PARSER_EXTRACT_BASE_URL=
PARSER_EXTRACT_MODEL=
EMBEDDING_API_KEY=
EMBEDDING_BASE_URL=
EMBEDDING_MODEL=
VECTOR_STORE_PROVIDER=qdrant
LLM 清洗链路已删除,知识库摄入直接使用 document_parse_service 的 Markdown 输出。
13. 接口建议¶
保留:
POST /v1/knowledges/{kb_id}/files/presigned-url
POST /v1/knowledges/{kb_id}/files/confirm-upload
GET /v1/knowledges/{kb_id}/files
DELETE /v1/knowledges/{kb_id}/file/{file_id}
新增:
POST /v1/knowledges/{kb_id}/files/{file_id}/reparse
POST /v1/knowledges/{kb_id}/files/{file_id}/reindex
列表返回建议补充:
file_key
content_type
ext
page_count
status
embedding_status
chunk_count
parse_error
parsed_at
parser_version
14. 删除、重试与重建索引¶
软删除文件时:
knowledge_files.is_deleted=true。- 检索时过滤软删除文件。
- 可异步删除向量库中该
file_id的 vectors。 - OSS 原文不立即物理删除,交给清理任务。
重试解析:
- 失败文件可调用
reparse。 reparse会创建新的 parse job。- 成功后替换 contents、chunks、vectors。
重建索引:
- 不重新解析文件。
- 使用已有
knowledge_file_chunks。 - 重新 embedding 并 upsert vectors。
- 适合 embedding 模型或向量库切换。
15. 分阶段落地¶
Phase 1:知识库摄入闭环¶
- 扩展
KnowledgeFile字段。 - 新增
knowledge_file_parse_jobs、knowledge_file_contents、knowledge_file_chunks。 confirm-upload创建任务并入队。- 新增
knowledge_file_ingestworker。 - 解析 Markdown、切块、落库。
Phase 2:Qdrant 向量检索¶
- 新增
VectorStore抽象。 - 实现
QdrantVectorStore。 - worker 完成 embedding 和 upsert。
- 新增知识库 search API。
Phase 3:标书生成接入¶
- 改造
context_assembler。 - 章节生成时检索关联知识库文件。
- 将 chunk 正文和引用信息拼入上下文。
- 保存生成引用关系。
Phase 4:DashVector 生产切换¶
- 实现
DashVectorStore。 - 增加批量 reindex 工具。
- 通过配置切换 provider。
- 压测召回延迟和并发。
16. 风险与注意事项¶
- 文档解析质量会直接影响 RAG 质量,PDF 表格和扫描件需要单独处理。
- embedding 模型更换会导致向量空间不兼容,必须支持全量 reindex。
- 向量库 metadata filter 必须带
user_id,否则会有多租户串数据风险。 - chunk 全文不要只存在向量库,否则换库和溯源会很困难。
- 解析成功但索引失败时,应保留 chunks,并支持单独重建索引。
- 大文件解析和 embedding 应通过 ARQ worker 异步完成,不要阻塞上传确认接口。
17. 最终建议¶
知识库模块应按“文件资产 + 解析任务 + 正文内容 + chunk + 向量索引”的模型建设。
短期直接落地:
- 自建 Qdrant。
- PostgreSQL 保存全文和 chunk。
- ARQ worker 做异步摄入。
- 标书生成时按用户关联的知识库文件做 RAG 检索。
中期演进:
- 切换到 DashVector 降低运维。
- 增加产品库、图库、结构化 facts 的人工确认导入。
- 增加引用溯源和 reindex 管理能力。