返回 RAG 工程实战目录

系统设计:架构与接口设计

设计分层架构、数据模型、接口和失败边界

生命周期 · 阶段三 系统设计 | 上游:需求与选型结论(02)| 下游:开发实现(04~07)

本文站在系统设计者(架构师)角度,给出一套部门级 RAG 的参考架构、数据模型、接口和时序。它是设计起点,不是可以原样套用的生产规范;容量、合规、可用性和权限要求应由项目需求决定。

一、分层架构设计

flowchart TB A["接入层 | Nginx 网关 HTTPS · 鉴权 · 限流 · SSE 透传"] B["应用层 | FastAPI 服务(无状态,可横向扩容) 文档管理 · 问答服务 · 知识库管理 · 反馈统计 · 权限对接"] C["异步层 | Celery Worker ×N + Redis 队列 接收 → 解析 → OCR → 切片 → 向量化 → 索引"] D["编排层 | 查询链路 改写 → 混合检索 → RRF → 重排 → Prompt → 生成"] E["模型层(OpenAI 兼容协议,可插拔) Embedding(TEI) · Rerank · LLM(vLLM/Ollama/API)"] F["存储层 MySQL(元数据) · Qdrant/Milvus(向量) · MinIO(原件) · Redis(队列/缓存) · ES(可选,BM25)"] A --> B B --> C B --> D C --> E D --> E C --> F D --> F style A fill:#f5f5f5,stroke:#999 style E fill:#fff0f5,stroke:#e06fa8 style F fill:#f0fff4,stroke:#4abf60

设计原则

  • 应用层无状态 → 可任意扩容;重活(入库)全部下沉异步层
  • 模型层独立部署、接口标准化(OpenAI 兼容协议),模型可插拔
  • 存储分工明确:关系数据、向量、文件各归其位,不混用

二、模块职责与失败影响

模块职责失败影响可用性要求
网关统一入口、TLS、限流全站不可用主备/多活
应用服务业务 API、编排查询链路问答不可用多副本
入库 Worker执行流水线任务新文档无法入库,存量问答不受影响可降级
模型服务Embedding/Rerank/LLM问答不可用(可降级纯检索)多实例
向量库切片存储检索问答不可用副本/持久卷
MySQL元数据、任务状态管理功能异常主从

提示 设计要点

入库链路与问答链路解耦。入库挂了不影响已有知识的服务,这是架构可用性的关键分割线。

三、数据模型设计(MySQL)

-- 知识库表(多知识库/多租户基础)
CREATE TABLE kb_knowledge_base (
  id            BIGINT PRIMARY KEY AUTO_INCREMENT,
  name          VARCHAR(128) NOT NULL COMMENT '知识库名称',
  owner_dept    VARCHAR(64)  COMMENT '归属部门',
  embed_model   VARCHAR(64)  NOT NULL COMMENT '向量模型(锁定,换模型须重建)',
  chunk_size    INT DEFAULT 500,
  chunk_overlap INT DEFAULT 50,
  created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,
  is_deleted    TINYINT DEFAULT 0,
  INDEX idx_dept (owner_dept)
);

-- 文档表
CREATE TABLE kb_document (
  id            BIGINT PRIMARY KEY AUTO_INCREMENT,
  kb_id         BIGINT NOT NULL,
  doc_name      VARCHAR(256) NOT NULL,
  file_hash     CHAR(64) NOT NULL COMMENT 'SHA256,去重依据',
  file_type     VARCHAR(16),
  file_size     BIGINT,
  storage_path  VARCHAR(512) COMMENT 'MinIO 路径',
  version       INT DEFAULT 1,
  status        VARCHAR(16) DEFAULT 'PENDING' COMMENT '见任务状态机',
  security      VARCHAR(16) DEFAULT '内部' COMMENT '公开/内部/秘密',
  uploader      VARCHAR(64),
  created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at    DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uk_hash_kb (kb_id, file_hash, version),
  INDEX idx_kb_status (kb_id, status)
);

-- 入库任务表(流水线状态机载体)
CREATE TABLE kb_ingest_task (
  id            BIGINT PRIMARY KEY AUTO_INCREMENT,
  doc_id        BIGINT NOT NULL,
  stage         VARCHAR(16) COMMENT 'PARSING/OCR/CHUNKING/EMBEDDING/INDEXING',
  status        VARCHAR(16) DEFAULT 'PENDING' COMMENT 'PENDING/RUNNING/SUCCESS/FAILED',
  progress      INT DEFAULT 0 COMMENT '0-100',
  chunk_count   INT DEFAULT 0,
  error_msg     TEXT,
  retry_count   INT DEFAULT 0,
  started_at    DATETIME,
  finished_at   DATETIME,
  INDEX idx_doc (doc_id), INDEX idx_status (status)
);

-- 切片镜像表(可选:原文在向量库 payload,此表用于管理与对账)
CREATE TABLE kb_chunk (
  id            BIGINT PRIMARY KEY AUTO_INCREMENT,
  doc_id        BIGINT NOT NULL,
  chunk_index   INT NOT NULL,
  content       MEDIUMTEXT,
  token_count   INT,
  page          INT,
  section       VARCHAR(256),
  vector_id     VARCHAR(64) COMMENT '向量库中对应 point id',
  created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_doc_idx (doc_id, chunk_index)
);

-- 会话与反馈表
CREATE TABLE kb_conversation (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id VARCHAR(64), kb_id BIGINT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE kb_message (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  conv_id BIGINT, role VARCHAR(8), content TEXT,
  retrieved_json JSON COMMENT '检索明细(排障用,见06篇)',
  latency_ms INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_conv (conv_id)
);
CREATE TABLE kb_feedback (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  message_id BIGINT, user_id VARCHAR(64),
  rating TINYINT COMMENT '1赞/-1踩',
  comment VARCHAR(512),
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_msg (message_id)
);

任务状态机(入库流水线的"进度条")

stateDiagram-v2 [*] --> PENDING PENDING --> PARSING PARSING --> OCR_STAGE: 扫描件/图片型 PARSING --> CHUNKING: 文本型直接进 OCR_STAGE --> CHUNKING CHUNKING --> EMBEDDING EMBEDDING --> INDEXING INDEXING --> SUCCESS PENDING --> FAILED PARSING --> FAILED OCR_STAGE --> FAILED CHUNKING --> FAILED EMBEDDING --> FAILED INDEXING --> FAILED FAILED --> PENDING: 重试续跑(从失败 stage 继续) SUCCESS --> [*] note right of FAILED 记录 stage 与 error_msg end note note right of SUCCESS 回写 chunk_count 与 finished_at end note

四、向量库设计

多知识库隔离方案对比

方案说明适用
单 Collection + kb_id 过滤简单,资源省;过滤有轻微性能损耗知识库数 < 20,切片总量 < 500 万
Partition 按 kb 划分Milvus partition,隔离较好中大型,库数适中
每知识库一个 Collection物理隔离最彻底,管理成本高租户强隔离要求(对外 SaaS)

字段与索引设计

沿用 05 篇 Schema,补充设计考量:

  • vector_id 生成规则:{doc_id}:{chunk_index} → 天然幂等,重复入库直接覆盖
  • payload 冗余 doc_name/page/section:检索结果无需回表即可展示引用
  • department/security 必须进 payload:权限过滤是检索层强制项

五、API 接口设计

接口清单

方法路径说明权限
POST/api/v1/kbs创建知识库管理员
POST/api/v1/documents上传文档(multipart)→ 返回任务 ID库成员
GET/api/v1/documents/{id}/status查询入库进度库成员
GET/api/v1/documents?page=1&size=20文档列表(分页)库成员
DELETE/api/v1/documents/{id}删除文档(联动删切片)管理员
POST/api/v1/chat问答(SSE 流式)登录用户
GET/api/v1/chat/history/{conv_id}会话历史本人
POST/api/v1/feedback点赞/点踩登录用户
GET/api/v1/health健康检查(网关探活)匿名

核心接口示例

上传文档

POST /api/v1/documents?kb_id=1
Headers: Authorization: Bearer <jwt>
Content-Type: multipart/form-data

file=<二进制>
// 200
{
  "code": 0,
  "data": {
    "doc_id": 1001,
    "task_id": 88,
    "status": "PENDING",
    "deduplicated": false
  }
}

问答(SSE)

POST /api/v1/chat
{ "kb_id": 1, "conv_id": 3, "question": "抵押物处置流程是什么?" }
data: {"type":"meta","retrieved":[{"doc":"处置手册","page":12,"score":0.87}]}
data: {"type":"delta","content":"根据"}
data: {"type":"delta","content":"《处置操作手册》第12页"}
data: {"type":"done","message_id":5001,"latency_ms":2310}
data: {"type":"refuse","reason":"知识库中未找到相关内容"}

错误码规范

错误码含义
0成功
1001参数校验失败
1002不支持的文件格式
1003文件超过大小限制
2001无该知识库权限
2002文档已存在(重复上传)
3001入库任务失败(附 stage)
5001模型服务不可用/降级中

设计规范

  • URL 版本化(/api/v1),破坏性变更升 v2
  • 上传接口支持 Idempotency-Key 头 + 文件哈希双重防重
  • 分页统一 page/size,响应统一 {code, message, data} 包装
  • SSE 响应禁止缓冲(网关配置见 10 篇)

六、核心时序图

入库时序

sequenceDiagram autonumber actor U as 用户 participant A as 应用服务 participant MY as MySQL participant MO as MinIO participant Q as Redis队列 participant W as Worker participant E as 模型服务 participant V as 向量库 U->>A: 上传文档 A->>MO: 存原件 A->>MY: 建 doc + task(PENDING) A->>Q: 发任务 A-->>U: 返回 task_id Q->>W: 取任务 W->>MY: 解析/OCR/切片(状态逐段回写) W->>E: 批量向量化 W->>V: upsert 向量 + payload W->>MY: task = SUCCESS U->>A: 轮询进度 A-->>U: 返回进度/最终状态

问答时序

sequenceDiagram autonumber actor U as 用户 participant A as 应用服务 participant RW as 改写(LLM/规则) participant V as 向量库 participant ES as ES(BM25) participant RR as Rerank participant L as LLM U->>A: 提问 A->>RW: 清洗/改写/补全 RW-->>A: 返回改写 query par 双路并发 A->>V: 向量检索 top20 and A->>ES: 关键词检索 top20 end A->>A: RRF 融合 → 候选 30 A->>RR: 精排 RR-->>A: top5 A->>L: 组装 Prompt(指令+切片+问题) L-->>A: SSE 流式 delta A-->>U: 流式回答 + 引用 A->>A: 落库 message(检索明细/耗时)

七、关键设计决策记录

#决策点结论理由
1入库同步 or 异步超过项目实测阈值后转异步任务用解析耗时和网关超时实测确定阈值,示例可从 1MB 或 10 页开始验证
2切片镜像表要不要保留 kb_chunk对账、切片级管理、重建向量库时免重新解析
3查询改写放哪规则清洗默认开,LLM 改写可配置省延迟,效果不足再开 LLM
4缓存策略归一化 query 哈希 → 缓存答案 30min高频重复问题省 LLM 开销
5模型接入OpenAI 兼容协议 + 适配器模式一套代码切换 vLLM/Ollama/商用 API
6权限过滤位置检索层 filter 强制执行Prompt 层约束可被注入绕过(红线)
7删除策略软删文档 + 物理删切片审计可回溯,检索即刻干净

八、非功能设计要求

  • 高可用:应用 ≥2 副本无状态部署;模型服务多实例;向量库持久卷 + 副本;任一 Worker 宕机任务可被其他节点接管(任务表抢占机制)
  • 性能指标写入设计:按真实业务定义问答 P95、入库时延和并发目标;例如先以 P95 8s、50 页入库 3min、50 并发作为待验证基线
  • 可观测性内建:trace_id 从网关贯穿到 LLM 调用;检索明细全量落 kb_message
  • 可测试性内建:模型层全部 Mock 注入点;评测集回归脚本随代码库维护(见 09 篇)
  • 安全设计:JWT 鉴权、上传文件类型白名单 + 魔数校验、审计日志三件套(谁/何时/干了什么)

本篇交付与下一步

本阶段应交付架构图、数据模型、接口契约、权限过滤位置、失败降级和容量假设,并为每个数字注明来源或“待压测”。下一步进入 04-环境搭建与开发准备,用最小环境验证这些设计假设。