跳转至

知识库文件摄入与 RAG 检索技术方案

  • 版本:v1.0
  • 日期:2026-05-21
  • 适用项目:Tender_Documents

1. 背景

当前知识库模块已经具备文件上传、OSS 存储和 knowledge_files 基础记录能力,但还没有形成完整的知识摄入链路。

目标不是把知识库简单做成向量数据库,而是建立一条从“用户上传文件”到“可被标书生成引用”的完整流水线:

  1. 原始文件进入 OSS。
  2. 文件元数据进入 PostgreSQL。
  3. 解析后的 Markdown 与切块正文进入 PostgreSQL。
  4. chunk embedding 进入向量检索服务。
  5. 标书生成时按知识库、文件、权限过滤后召回 chunk 正文。

这样做可以同时满足:

  1. 文件列表、权限、状态、重试等业务管理。
  2. 章节生成时的语义召回。
  3. 返回引用来源、文件名、页码、标题路径。
  4. 后续从自建向量库切换到云托管向量库。

2. 方案结论

采用“OSS + PostgreSQL + ARQ worker + 向量库”的混合架构。

推荐落地顺序:

  1. 第一阶段先自建 Qdrant,跑通知识摄入与检索闭环。
  2. 向量服务层做 provider 抽象,后续可切到阿里云 DashVector
  3. 不继续使用 Cloudflare Vectorize 作为国内主链路向量库。
  4. 向量库只存向量和轻量 metadata,chunk 全文仍然存 PostgreSQL。
  5. 知识库文件摄入不要复用 ingestion_tasks,应使用知识库专用任务表。

原因:

  1. Cloudflare Vectorize 国内延迟较高,不适合作为章节生成的同步依赖。
  2. Qdrant 部署简单,适合先把功能闭环跑通。
  3. DashVector 更适合后续生产托管,但一开始接入会增加调试成本。
  4. PostgreSQL 管理权限、状态、全文、引用来源更稳。
  5. 向量库主键以 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. 当前代码落点

现有可复用模块:

  1. 知识库文件接口:app/routes/v1/knowledges/file.py
  2. 知识库文件服务:app/services/knowledge/file_service.py
  3. 知识库模型:app/models/knowledge.py
  4. OSS 能力:app/services/oss_service.py
  5. ARQ 入队:app/services/arq_queue_service.py
  6. worker 注册:app/worker/parser_jobs.py
  7. 文档解析:app/services/parse/document/service.py
  8. embedding 配置:app/core/config.py

当前需要调整的点:

  1. KnowledgeFile 只保存基础文件信息,不够支撑解析和索引状态。
  2. confirm-upload 只落库,不会创建解析任务或入队。
  3. 现有 vector_service 绑定 Cloudflare Vectorize,且以 task_id 为主,不适合知识库长期索引。
  4. 标书生成上下文目前只拿到知识库文件名,没有检索知识库正文。

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 继续表示文件整体摄入状态:

pending
processing
success
failed

embedding_status 表示向量索引状态:

pending
indexing
success
failed
skipped

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

任务状态:

PENDING
PROCESSING
COMPLETED
FAILED
CANCELLED

任务阶段:

INIT
DOWNLOADING
EXTRACT_MARKDOWN
PERSIST_CONTENT
CHUNK
EMBED
INDEX
DONE

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

说明:

  1. source_markdown 是后续切块、引用、重建索引的主来源。
  2. plain_text 可选,用于关键词搜索或摘要。
  3. 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

推荐索引:

(user_id, knowledge_id)
(knowledge_file_id, chunk_index)
(vector_id)
(content_sha256)

6. 向量库设计

6.1 Provider 抽象

新增向量存储抽象,不让业务代码绑定具体向量库:

app/services/vector_store/
  base.py
  qdrant_store.py
  dashvector_store.py
  factory.py

统一接口:

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

  1. 自建部署简单。
  2. 支持 payload filter。
  3. 对本项目早期知识库规模足够。
  4. 可以快速验证 chunk、embedding、召回质量。

生产托管:DashVector

  1. 国内延迟更稳定。
  2. 运维成本低。
  3. 适合后续多租户和更大数据量。

暂不优先:Cloudflare Vectorize

  1. 国内访问延迟高。
  2. 当前实现以 task_id 为主,不适合知识库长期索引。
  3. metadata filter 和全文回查模型需要重做。

7. 上传确认与入队

confirm-upload 调整为:

  1. 校验 file_key 是否属于当前 kb_id
  2. 调用 OSSService.head_object 校验对象存在。
  3. 读取 content_lengthcontent_type
  4. 创建或复用 KnowledgeFile
  5. 创建 KnowledgeFileParseJob
  6. 提交事务。
  7. 调用 arq_queue_service.enqueue_knowledge_file_ingest(job_id)
  8. 返回 KnowledgeFileResponseDTO,状态为 pending

幂等规则:

  1. 同一 file_key 已存在未删除文件时,直接返回已有文件。
  2. 若该文件已有 PENDING/PROCESSING 任务,不重复入队。
  3. 若文件已 success 且内容 hash 未变化,不重复解析。
  4. 重跑应走单独 reparse 接口。

8. Worker 设计

新增 ARQ job:

knowledge_file_ingest

handler 职责:

  1. 根据 job_id 加载任务和 KnowledgeFile
  2. 检查文件是否已软删除。
  3. 下载 OSS 文件。
  4. 调用 document_parse_service.extract_full_markdown(file_bytes, filename)
  5. 写入 knowledge_file_contents
  6. 将 Markdown 切成 chunk,写入 knowledge_file_chunks
  7. 批量调用 embedding。
  8. 写入向量库。
  9. 回写文件和任务状态。

失败处理:

  1. 下载失败:任务 FAILED,文件 failed
  2. 文档解析失败:任务 FAILED,文件 failed,写 parse_error
  3. embedding 失败:任务 FAILED,文件 failedembedding_status=failed
  4. 向量库写入失败:任务 FAILED,保留 chunks,允许后续只重建索引。
  5. 文件已删除:任务 CANCELLED,不继续写内容和向量。

9. 切块策略

一期建议基于 Markdown 切块:

  1. 优先按标题层级切。
  2. 标题块过长时再按段落、表格边界切。
  3. 单 chunk 目标长度建议 800-1200 中文字。
  4. chunk overlap 建议 100-200 中文字。
  5. 保留 heading_path,用于引用来源和生成上下文。
  6. 保留页码信息;如果解析器不能稳定给页码,先允许为空。

切块原则:

  1. 不把超大表格硬切碎到失去语义。
  2. 不把目录页、页眉页脚、空白页作为高权重内容。
  3. 对短文件允许单 chunk。
  4. 每次重建索引前先按 knowledge_file_id 删除旧向量。

10. 检索与生成集成

新增知识库检索服务:

app/services/knowledge/search_service.py

检索流程:

query text
  -> embedding
  -> vector_store.search(filters)
  -> 返回 chunk_id + score
  -> PostgreSQL 查询 chunk_text 和来源信息
  -> 组装引用上下文

过滤条件必须包含:

user_id
knowledge_id 或 knowledge_file_ids
is_deleted=false
status=success

标书生成集成点:

  1. 当前 context_assembler 只拼知识库文件名。
  2. 应改为根据章节标题、评分项、写作提示组合 query。
  3. 只在用户关联的 knowledge_file_ids 范围内检索。
  4. 将 top chunks 作为“关联技术资料原文片段”放入章节生成上下文。
  5. 生成结果可记录引用 chunk,后续支持溯源。

生成上下文示例:

以下为关联知识库资料片段,请优先参考其事实、参数和表述:

[资料1] 文件:xxx.pdf;章节:产品参数/核心能力;页码:12;相关度:0.82
...

11. 产品库与图库边界

知识库摄入不应默认把所有文件内容写入产品库或图库。

建议边界:

  1. 通用文件默认只入知识库内容表、chunk 表和向量库。
  2. 产品清单或产品资料需要用户选择“导入产品库”后,再走产品库解析链路。
  3. 文档中抽取出的图片先作为知识库资产保存,确认后再进入图库。
  4. 结构化 facts 可先放 knowledge_file_facts,不要直接污染业务主表。

后续可新增:

knowledge_file_assets
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. 删除、重试与重建索引

软删除文件时:

  1. knowledge_files.is_deleted=true
  2. 检索时过滤软删除文件。
  3. 可异步删除向量库中该 file_id 的 vectors。
  4. OSS 原文不立即物理删除,交给清理任务。

重试解析:

  1. 失败文件可调用 reparse
  2. reparse 会创建新的 parse job。
  3. 成功后替换 contents、chunks、vectors。

重建索引:

  1. 不重新解析文件。
  2. 使用已有 knowledge_file_chunks
  3. 重新 embedding 并 upsert vectors。
  4. 适合 embedding 模型或向量库切换。

15. 分阶段落地

Phase 1:知识库摄入闭环

  1. 扩展 KnowledgeFile 字段。
  2. 新增 knowledge_file_parse_jobsknowledge_file_contentsknowledge_file_chunks
  3. confirm-upload 创建任务并入队。
  4. 新增 knowledge_file_ingest worker。
  5. 解析 Markdown、切块、落库。

Phase 2:Qdrant 向量检索

  1. 新增 VectorStore 抽象。
  2. 实现 QdrantVectorStore
  3. worker 完成 embedding 和 upsert。
  4. 新增知识库 search API。

Phase 3:标书生成接入

  1. 改造 context_assembler
  2. 章节生成时检索关联知识库文件。
  3. 将 chunk 正文和引用信息拼入上下文。
  4. 保存生成引用关系。

Phase 4:DashVector 生产切换

  1. 实现 DashVectorStore
  2. 增加批量 reindex 工具。
  3. 通过配置切换 provider。
  4. 压测召回延迟和并发。

16. 风险与注意事项

  1. 文档解析质量会直接影响 RAG 质量,PDF 表格和扫描件需要单独处理。
  2. embedding 模型更换会导致向量空间不兼容,必须支持全量 reindex。
  3. 向量库 metadata filter 必须带 user_id,否则会有多租户串数据风险。
  4. chunk 全文不要只存在向量库,否则换库和溯源会很困难。
  5. 解析成功但索引失败时,应保留 chunks,并支持单独重建索引。
  6. 大文件解析和 embedding 应通过 ARQ worker 异步完成,不要阻塞上传确认接口。

17. 最终建议

知识库模块应按“文件资产 + 解析任务 + 正文内容 + chunk + 向量索引”的模型建设。

短期直接落地:

  1. 自建 Qdrant。
  2. PostgreSQL 保存全文和 chunk。
  3. ARQ worker 做异步摄入。
  4. 标书生成时按用户关联的知识库文件做 RAG 检索。

中期演进:

  1. 切换到 DashVector 降低运维。
  2. 增加产品库、图库、结构化 facts 的人工确认导入。
  3. 增加引用溯源和 reindex 管理能力。