Files
AIRegulation-DocAnalysis/docs/architecture/backend-project-architecture.md

24 KiB
Raw Blame History

Backend Project Architecture

1. Purpose

本文档定义当前 backend 的目标态架构,用于在保持单服务部署的前提下,将系统整理为职责清晰、边界稳定、可替换实现的模块化结构。本文档的重点不是描述理想化分层,而是基于当前真实代码形态,明确后续重构时必须遵守的模块职责、依赖方向、内部稳定接口和替换边界。

本文档与 docs/rfc/backend-api-parsing-embedding-migration-requirements.md 的关系如下:

  • RFC 负责冻结本轮迁移需求、范围、风险和约束。
  • 本文档负责冻结目标模块边界、依赖规则和实现组织方式。
  • 后续任何代码重构、能力替换或底座升级,都应同时满足 RFC 与本文档。

2. Current-State Problems

基于当前代码,后端已经具备以下能力:

  • 文档上传、下载、列表
  • 文档解析与切片
  • 向量化与 Milvus 入库
  • 检索
  • 基于 RAG 的 Agent 问答 workflow

但这些能力当前主要是“可运行”,还不是“结构清晰、便于替换、便于演进”的状态。核心问题如下。

2.1 DocumentProcessor 责任过载

现状判断:

  • backend/app/services/document_processor.py 已经不再是当前主要编排点
  • 上传、查询和问答的核心组织逻辑已经明显下沉到 backend/app/application/* 以及 backend/app/shared/bootstrap.py
  • DocumentProcessor 当前更接近一个兼容旧调用路径的 legacy façade

其历史上曾集中承载过以下职责:

  • 文档解析
  • 摘要生成
  • 分块
  • 向量化
  • Milvus 入库
  • 检索入口

当前真正还需要收口的问题不是“继续拆这个大类”,而是把文档元数据、解析、分块、向量化和索引写入的稳定边界进一步收束到 application service 与端口接口中,避免后续重构继续围绕旧实现形态做判断。

2.2 检索逻辑缺少稳定边界

现状判断:

  • backend/app/services/rag/retriever.py 已经明显退化为兼容层
  • 真正承担检索编排的是 KnowledgeRetrievalServiceshared/bootstrap 中的 wiring
  • Retriever 应被视为业务侧端口,而不是当前主编排单元

其历史上曾集中承载过以下职责:

  • embedder 初始化
  • Milvus 连接与 collection lifecycle
  • 检索执行
  • 结果映射

所以这里的重点不是把旧 retriever 当成未来架构核心,而是确认检索能力的稳定边界已经迁移到 application 层,并继续清理仍然残留的 provider-specific 细节。

2.3 QAAgent 责任过载

现状判断:

  • backend/app/services/agent/qa_agent.py 已经不是当前主要问答编排点
  • 当前实际的问答编排已经主要落到 AgentConversationService
  • QAAgent 当前更接近向后兼容的 legacy façade

其历史上曾集中承载过以下职责:

  • 检索调用
  • 上下文构建
  • Prompt 选择
  • LLM 调用
  • SSE 流式问答流程
  • 会话 workflow 编排

剩余风险不在于“它还很大”,而在于仍有少量 API 入口和 session 读写没有完全收口到 application service导致目标边界和现状之间还存在断点。

2.4 API 层直接编排具体服务

当前 API 路由主要在:

  • backend/app/api/routes/documents.py
  • backend/app/api/routes/knowledge.py
  • backend/app/api/routes/agent.py

这些路由直接实例化具体服务类,例如 DocumentProcessorQAAgentMinIOClient。这意味着:

  • API 层不仅处理 transport concerns也在做业务编排
  • 路由层知道过多内部实现细节
  • 后续如果内部模块调整,路由层也要跟着改

现状里这条判断需要修正:大部分主流程已经通过 backend/app/shared/bootstrap.py 统一装配,并由 application service 承担编排;真正还没有完全收口的是 backend/app/api/routes/agent.py 中的 session/history/feedback 这类接口,它们仍直接访问 ConversationStore

因此,当前问题不是“路由层仍然是主编排中心”,而是“少数会话管理接口还没有通过 application service 统一封装”,这会持续打破 API 只做 transport concerns 的边界。

2.5 文档元数据与对象存储组织方式耦合

当前文档列表与下载逻辑高度依赖 MinIO 对象命名方式和对象遍历结果。对象存储目前承担了部分“业务真相”的角色,但对象存储只适合作为文件二进制载体,不适合作为完整文档元数据和状态管理的唯一来源。

2.6 knowledgeagent 共享检索底座的边界不清晰

当前 /knowledge/*/agent/* 都依赖检索能力,但共享方式不够清晰:

  • knowledge 通过 DocumentProcessor.search() 访问检索
  • agent 通过 Retriever 访问检索

这条问题描述需要更新。现在检索主链路已经被 KnowledgeRetrievalService 统一收束,knowledgeagent 的共享底座比过去清晰得多。

当前仍未完全收口的是 Agent 的 session 相关能力:公开 API 里还存在 /agent/session/{id}/agent/session/{id}/history/agent/sessions/agent/feedback 等接口直接读写 ConversationStore,这意味着“问答编排统一了,但会话管理还没有统一入口”。

3. Architecture Goals

本项目后端的目标态架构必须满足以下目标。

3.1 单服务部署

系统继续保持单服务部署,不拆分为多个微服务。架构治理发生在单服务内部,通过清晰模块边界实现高内聚低耦合,而不是通过进程级拆分回避设计问题。

3.2 高内聚、低耦合优先级最高

后续模块设计以“一个模块只承载一类稳定职责”为原则。跨能力流程统一在编排层组织,不允许继续把 parser、embedding、storage、retrieval、LLM workflow 堆进同一个服务类。

3.3 外部 API 尽量保持兼容

现有前端与外部调用方依赖的主接口保持不变优先,包括但不限于:

  • /api/v1/documents/*
  • /api/v1/knowledge/*
  • /api/v1/agent/*

内部可以重组,但外部接口不应因为内部重构而被迫大改。

3.4 关键能力必须可替换

以下能力必须通过稳定端口隔离实现细节:

  • 文档解析
  • 分块构建
  • 向量化
  • 向量索引
  • 检索
  • LLM 回答生成
  • 会话存储
  • 原始文件存储

后续替换方案时,只允许替换实现,不允许穿透影响其他模块。

3.5 knowledgeagent 共用同一检索底座

检索必须被视为独立的业务能力,由统一的 retrieval application service 对外暴露。knowledgeagent 必须复用同一个 retrieval 底座,避免两套召回策略、两套元数据模型、两套 adapter。

3.6 依赖必须单向流动

系统必须形成稳定的单向依赖关系:

  • api -> application -> domain
  • application -> domain ports
  • composition root -> application + infrastructure bindings
  • infrastructure -> external systems

不允许出现基础设施实现反向驱动业务编排,也不允许 domain 依赖 Web 或第三方 SDK。

4. Target Module Layout

目标目录结构如下:

backend/app/
  api/
  application/
    documents/
    knowledge/
    agent/
  domain/
    documents/
    retrieval/
    conversation/
  infrastructure/
    storage/
    vectorstore/
    parser/
    embedding/
    llm/
    session/
  shared/

该结构是本项目 backend 的目标态模块布局。后续实现可以渐进迁移,但职责边界不能偏离。

4.1 api

职责:

  • HTTP 路由注册
  • 请求参数校验
  • 响应模型映射
  • 异常转换
  • SSE 事件格式输出

非职责:

  • 不直接组织完整业务流程
  • 不直接访问 MinIO、Milvus、Parser SDK、LLM SDK
  • 不直接 new 具体基础设施客户端

4.2 application

职责:

  • 用例编排
  • 跨领域能力协作
  • 业务流程统一入口
  • workflow 级别的状态推进

非职责:

  • 不直接依赖第三方 SDK
  • 不承担具体存储、向量库、解析器实现细节

4.3 domain

职责:

  • 核心业务对象
  • 领域术语
  • 稳定端口接口
  • 统一元数据模型
  • 检索结果模型
  • 会话消息模型

非职责:

  • 不依赖 FastAPI
  • 不依赖 MinIO、Milvus、LLM SDK
  • 不依赖路由请求响应模型

4.4 infrastructure

职责:

  • 外部系统适配器实现
  • 第三方 SDK 封装
  • provider-specific 配置适配
  • 数据格式转换

包含但不限于:

  • MinIO binary store
  • Milvus vector index
  • Aliyun / local parser adapter
  • OpenAI-compatible embedding adapter
  • DeepSeek / Qwen LLM adapter
  • in-memory / Redis session store

4.5 shared

职责:

  • 配置
  • 日志
  • 通用异常
  • 通用工具
  • 公共基础设施无关组件
  • composition root 与依赖装配
  • 运行时共享的 wiring 辅助入口

非职责:

  • 不承载业务编排
  • 不变成新的 services 大杂烩目录

说明:

  • shared 不是业务层,但它是本项目当前明确的装配层。
  • backend/app/shared/bootstrap.py 是现阶段的 composition root负责把端口实现、基础设施适配器和 application service 连接起来。
  • 后续如果新增 wiring 入口,应继续保持在同一类装配边界内,不要把依赖装配拆回各个路由或 service 构造函数中。

5. Module Responsibilities

5.1 api

api 是 transport 层,只关心请求进来和响应出去的表达方式。它应该把请求转换为 application service 的输入,把 application service 的结果转换为 HTTP 响应。

api 不应该知道:

  • MinIO bucket 怎么组织
  • Milvus collection 怎么建
  • parser 是本地还是阿里云
  • embedding 是本地模型还是 API
  • session 是内存还是 Redis

5.2 application

application 是业务编排层,是系统内唯一允许跨模块组织完整流程的层。它应该定义稳定的用例服务,而不是把流程散落在路由或基础设施实现中。

本项目至少固定以下 4 类 application service

  • DocumentCommandService
  • DocumentQueryService
  • KnowledgeRetrievalService
  • AgentConversationService

说明:

  • 这 4 类是当前已经明确成型的核心用例服务,但不是 application 的全部上限。
  • 现有 Agent session 管理接口所需的会话查询、历史读取、删除和反馈归档能力,后续应补入 application 层的明确用例服务或子服务,而不是继续让 API 直连 ConversationStore

5.3 domain

domain 层定义系统内部真正稳定的概念,例如:

  • Document
  • DocumentStatus
  • ParsedDocument
  • Chunk
  • RetrievalQuery
  • RetrievedChunk
  • ConversationSession
  • ConversationMessage
  • AnswerSource

这些对象必须脱离具体技术实现,成为 parser、embedding、vector index、agent workflow 之间的公共契约。

5.4 infrastructure

infrastructure 只负责“怎么接某个外部系统”,不负责“业务上应该先做什么后做什么”。例如:

  • MinIO adapter 负责上传和下载文件
  • Milvus adapter 负责 upsert/search/delete
  • Qwen / DeepSeek adapter 负责生成回答
  • Aliyun parser adapter 负责把解析结果映射成统一 ParsedDocument

5.5 shared

shared 只放横切能力。任何和文档 ingest、检索、问答编排直接相关的业务逻辑都不应该放进 shared

6. Stable Internal Ports

以下端口是系统内部稳定契约。后续方案替换时,只能替换实现,不允许改动上层 application service 的调用方式,也不允许影响 sibling 模块。

6.1 DocumentRepository

职责:

  • 管理文档元数据
  • 管理文档状态
  • 管理统计字段,例如 chunk 数、索引状态、摘要状态

说明:

  • 列表和状态查询应以 DocumentRepository 为主,而不是直接遍历对象存储。

6.2 DocumentBinaryStore

职责:

  • 保存原始文件
  • 下载原始文件
  • 删除原始文件
  • 处理对象存储相关细节

说明:

  • 替换 MinIO 或对象存储方案时,只替换该实现。

6.3 DocumentParser

职责:

  • 输入原始文件
  • 输出统一结构化解析结果

说明:

  • 本地 PDF/MinerU 或阿里云解析只能作为实现差异,不能外溢到业务流程层。

6.4 ChunkBuilder

职责:

  • 输入统一解析结果
  • 输出统一 chunk 模型

说明:

  • chunk 规则变化只能影响该端口实现,不应影响 retrieval、agent 或 API。

6.5 EmbeddingProvider

职责:

  • 输入文本列表
  • 输出 embedding 向量结果

说明:

  • 从本地模型切到 OpenAI-compatible embedding只替换该实现。

6.6 VectorIndex

职责:

  • upsert chunks
  • delete by document
  • search by query vector
  • 管理索引内部 schema

说明:

  • Milvus schema 或向量库替换,只能影响该层。

6.7 Retriever

职责:

  • 基于 query、filter、top_k 返回统一检索结果

说明:

  • Retriever 是业务侧的检索端口,不应再直接持有 embedder、Milvus lifecycle 和 provider-specific 逻辑。

6.8 AnswerGenerator

职责:

  • 基于 query 与 context 生成最终回答
  • 屏蔽具体 LLM provider 差异

说明:

  • DeepSeek、Qwen 或其他模型切换时,只替换该实现。

6.9 ConversationStore

职责:

  • 创建和读取 session
  • 持久化消息历史
  • 管理会话生命周期

说明:

  • 从内存实现切到 Redis 或数据库实现时,只替换该实现。

7. Application Services

7.1 DocumentCommandService

职责:

  • 接收文档上传命令
  • 生成 doc_id
  • 保存原始文件
  • 触发解析、分块、向量化、入库
  • 更新文档状态和统计信息
  • 返回最终处理结果

说明:

  • 当前 DocumentProcessor 的“流程编排”职责在目标态应迁移到这里。
  • parser、chunker、embedder、vector index 的具体实现不应继续塞进一个大类里统一管理。

7.2 DocumentQueryService

职责:

  • 文档列表
  • 文档下载
  • 文档状态查询
  • 文档管理视图查询

说明:

  • 列表和状态查询应基于 DocumentRepository
  • 下载应通过 DocumentBinaryStore
  • 不再依赖 MinIO 对象结构作为业务视图主来源

7.3 KnowledgeRetrievalService

职责:

  • 对外提供统一检索能力
  • 管理 retrieval query 到 retrieval result 的业务转换
  • /knowledge/* 和 Agent workflow 共用

说明:

  • 当前 knowledgeagent 必须统一依赖这一层,不允许各自再维护一套检索流程。

7.4 AgentConversationService

职责:

  • 统一管理问答 workflow
  • 读取或创建会话
  • 调用 KnowledgeRetrievalService
  • 构建问答上下文
  • 调用 AnswerGenerator
  • 保存回答和引用来源

说明:

  • 当前 QAAgent 的 workflow 编排职责在目标态应迁移到这里,或被其吸收后只保留 façade 角色。
  • SSE 与普通问答必须共用这一层,不允许复制业务编排逻辑。

7.5 AgentSessionService

职责:

  • 读取会话详情
  • 读取会话历史
  • 列出会话
  • 删除会话
  • 记录会话反馈

说明:

  • 这类能力目前仍散落在 backend/app/api/routes/agent.py,并直接依赖 ConversationStore
  • 目标态应把它们收口到 application 层,保证 API 只处理 transport concerns而不是继续承担 session 级业务访问。

8. Core Workflows

8.1 文档上传入库链路

目标流程如下:

  1. api/documents 接收上传请求并完成输入校验。
  2. DocumentCommandService 生成 doc_id,初始化文档记录和状态。
  3. DocumentBinaryStore 保存原始文件。
  4. DocumentParser 对原始文件执行解析,输出统一结构化结果。
  5. ChunkBuilder 将解析结果转换为统一 chunk 集合。
  6. EmbeddingProvider 为 chunks 生成向量。
  7. VectorIndex 将 chunks 与 vectors 写入索引。
  8. DocumentRepository 更新文档状态、chunk 数量、索引状态、元数据。
  9. API 返回处理结果。

约束:

  • 上传处理链路的主编排必须只存在于 DocumentCommandService
  • 不允许再由 route 或基础设施类直接组织全流程

8.2 文档查询链路

目标流程如下:

  1. api/documents 调用 DocumentQueryService
  2. 文档列表与状态查询通过 DocumentRepository
  3. 文档下载通过 DocumentBinaryStore
  4. 对象存储命名规则只作为实现细节,不作为最终业务真相

约束:

  • 文档“存在、状态、统计信息”必须有稳定元数据模型
  • 不允许继续通过对象存储遍历结果拼出全部业务语义

8.3 Agent 问答链路

目标流程如下:

  1. api/agent 接收问答请求
  2. AgentConversationService 读取或创建 session
  3. KnowledgeRetrievalService 统一执行检索
  4. AnswerGenerator 基于 query 和 retrieval context 生成回答
  5. ConversationStore 保存消息历史和引用来源
  6. API 将结果以普通 JSON 或 SSE 格式输出

约束:

  • 普通问答和 SSE 问答只允许输出形式不同
  • 业务编排链必须完全复用
  • 检索能力必须来自同一 KnowledgeRetrievalService

8.4 Agent Session 管理链路

目标流程如下:

  1. api/agent/session/*api/agent/sessions 接收会话管理请求
  2. AgentSessionService 读取、列出、删除或归档反馈
  3. ConversationStore 负责持久化与会话生命周期细节
  4. API 输出 session 详情、历史、列表或反馈结果

约束:

  • 会话详情、历史、列表和反馈接口不属于问答编排本身,应由独立的 session application service 处理。
  • 这部分能力不应继续直接暴露 ConversationStore 给路由层。

9. Dependency Rules

系统内部依赖方向固定如下:

api -> application -> domain
application -> domain ports
composition root -> application + infrastructure bindings
infrastructure -> external systems

具体规则如下:

  • api 可以依赖 application 和 API 自己的 request/response models
  • application 只能依赖 domain、端口接口,以及通过 composition root 注入进来的实现实例
  • domain 不能依赖 apiinfrastructure
  • infrastructure 可以依赖 domain 定义的端口和数据模型,但不能反向驱动 application 逻辑

说明:

  • 端口绑定发生在 shared/bootstrap.py 这类 composition root而不是 application 层内部。
  • application 只能接收已装配好的依赖实例,不能反向“依赖某个 infrastructure 实现类”作为其设计前提。

10. Migration Mapping From Current Code

当前关键代码到目标模块的映射如下。

10.0 旧 facade 存续策略

当前仓库里仍保留若干旧 facade / 兼容入口,包括但不限于:

  • backend/app/services/document_processor.py
  • backend/app/services/rag/retriever.py
  • backend/app/services/agent/qa_agent.py

这些类的定位是“兼容现有调用方的过渡入口”,不是目标态的主要编排层。它们在迁移完成前应继续保留,直到对应的调用方、测试和 API 入口都迁移到 application service 或新的适配层为止。

约束:

  • 不允许因为目标架构已经定义,就在没有替换调用方的情况下直接删除这些 facade。
  • 任何清理旧 services 的动作,都必须先确认是否还存在非 HTTP 的内部调用、测试依赖或临时适配依赖。
  • 这些 facade 可以逐步瘦身,但在迁移窗口内应优先保持行为兼容,而不是追求“文件级清零”。

10.1 文档处理

当前:

  • backend/app/services/document_processor.py

目标:

  • 其流程编排职责迁移到 application/documents/DocumentCommandService
  • 解析、分块、向量、入库分别通过端口接入
  • 检索入口从该类中剥离,不再由 ingest orchestration 承担 search 职责
  • 迁移期间该类作为兼容 facade 保留,直到所有直接调用点收口完成

10.2 检索

当前:

  • backend/app/services/rag/retriever.py

目标:

  • domain/retrieval 中定义 Retriever 端口和统一检索结果模型
  • infrastructure/vectorstore 中承载具体检索实现
  • application/knowledge/KnowledgeRetrievalService 作为统一检索用例入口
  • 迁移期间该类作为兼容 facade 保留,避免影响现有检索调用方

10.3 Agent Workflow

当前:

  • backend/app/services/agent/qa_agent.py

目标:

  • workflow 编排职责迁移到 application/agent/AgentConversationService
  • 具体 LLM 调用走 AnswerGenerator
  • 具体 session 读写走 ConversationStore
  • 检索统一走 KnowledgeRetrievalService
  • 迁移期间该类作为兼容 facade 保留,避免影响现有问答和测试调用点

10.4 存储

当前:

  • backend/app/services/storage/minio_client.py
  • backend/app/services/storage/milvus_client.py

目标:

  • MinIO 迁移到 infrastructure/storage
  • Milvus 迁移到 infrastructure/vectorstore

10.5 解析

当前:

  • backend/app/services/parser/*
  • backend/app/services/parser/mineru_parser.py

目标:

  • 全部迁移到 infrastructure/parser
  • 对外只暴露统一 DocumentParser 端口实现

10.6 向量化

当前:

  • backend/app/services/embedding/*

目标:

  • 迁移到 infrastructure/embedding
  • 对外只暴露统一 EmbeddingProvider

10.7 LLM

当前:

  • backend/app/services/llm/*

目标:

  • 迁移到 infrastructure/llm
  • AnswerGenerator 屏蔽 provider 差异

10.8 会话

当前:

  • backend/app/services/agent/session_manager.py

目标:

  • 迁移到 infrastructure/session
  • 对外通过 ConversationStore 暴露

10.9 API 模型与内部模型

当前:

  • backend/app/api/models/*
  • backend/app/schemas/*

目标:

  • 对外 request/response model 保留在 api
  • 内部 DTO / VO / domain object 收敛到 applicationdomain
  • 不允许 API model 直接渗透到 domain

11. Technology Replacement Boundaries

11.1 本地解析 / MinerU -> 阿里云文档解析

替换原则:

  • 只替换 DocumentParser adapter
  • DocumentCommandService 不应感知解析提供商差异
  • ChunkBuilder 只接收统一解析结果模型

11.2 BGE-M3 -> OpenAI-compatible embedding

替换原则:

  • 只替换 EmbeddingProvider
  • KnowledgeRetrievalServiceDocumentCommandService 不应感知 embedding 来源变化

11.3 Milvus 1024 + sparse -> 1536 dense-only

替换原则:

  • 只替换 VectorIndex 实现
  • collection schema、index 参数、dense-only search 属于 index 内部实现细节
  • 上层 retrieval 和 agent workflow 不应因为 schema 变化而改业务接口

11.4 DeepSeek / Qwen 切换

替换原则:

  • 只替换 AnswerGenerator 背后的 provider adapter
  • 上层 conversation workflow 不应直接依赖具体模型 SDK

11.5 内存 session -> Redis / DB session

替换原则:

  • 只替换 ConversationStore
  • API 和 application service 不应感知 session 持久化细节

12. Guardrails

后续所有 backend 重构和新增功能必须遵守以下规则:

  • 禁止 api/routes 直接实例化 parser、embedder、Milvus、MinIO、LLM client
  • 禁止 application 层直接 import 第三方 SDK
  • 禁止 domain 层依赖 FastAPI、Pydantic route model、MinIO SDK、Milvus SDK、LLM SDK
  • 禁止 SSE 和普通问答各自维护独立 workflow
  • 禁止把对象存储命名规则作为唯一业务元数据来源
  • 禁止新建第二个“大一统流程类”替代 DocumentProcessor
  • 禁止 knowledgeagent 各自维护独立检索实现
  • 禁止 parser、embedding、vector index、llm provider 的替换穿透到 API 层

13. Architecture Review Checklist

后续评审和重构验收时,至少核对以下问题:

  1. 上传、下载、列表、解析、切片、向量、入库、检索、Agent Workflow 是否都映射到了明确模块。
  2. 系统是否仍保持单服务,而不是被动演化成伪微服务结构。
  3. 是否存在唯一、清晰的目标目录结构。
  4. 是否定义了稳定端口列表。
  5. 是否定义了文档上传入库、文档查询、Agent 问答三条核心 workflow。
  6. 是否定义了单向依赖方向。
  7. 是否明确列出了架构禁令。
  8. 是否定义了当前关键代码到目标模块的映射。
  9. 是否明确定义了 parser、embedding、vector index、LLM、session store 的替换边界。
  10. 是否明确 knowledgeagent 共用同一 retrieval 底座。
  11. 是否明确 API 层只负责 transport concerns不再直接承担业务编排。
  12. 是否保证后续替换方案时,上层 application service 与外部 API 契约不被迫变化。