系统架构设计:模型服务、业务服务与知识库如何拆分

摘要

完成需求分析后,AI 后端项目需要进入架构设计阶段。架构设计要回答的核心问题不是"用哪个模型",而是系统如何拆分、各模块职责如何划分、数据如何流动、哪些能力可以独立扩展,以及出现故障时如何隔离影响。

以"企业智能客服与知识库助手"为例,一个生产级 AI 后端通常不能把用户、会话、模型调用、文件解析、向量检索、Agent 工具和监控统计全部塞进一个服务里。短期看这样开发最快,长期看会导致模型切换困难、知识库任务拖慢在线对话、业务权限和 AI 能力耦合、成本统计混乱、故障难以定位。

本文围绕模型服务、业务服务和知识库三个核心部分,系统拆解生产级 AI 后端的架构边界。文章会给出推荐模块划分、数据流、接口边界、数据库职责、异步任务、可观测性和演进路线,为后续的数据表设计、模型服务抽象、流式接口、知识库和 Agent 工具实现打基础。

一、背景与问题

1. 为什么不能从 Controller 直接调用模型

很多 AI 项目的第一个版本会写成这样:

text 复制代码
Controller
  ├─ 接收用户问题
  ├─ 查询会话历史
  ├─ 读取知识库文件
  ├─ 调用向量数据库
  ├─ 拼 Prompt
  ├─ 调用模型 API
  ├─ 保存回答
  └─ 返回结果

这在 Demo 阶段可以快速验证,但进入真实业务后会出现一堆问题:

  • Controller 过重,业务逻辑难以测试;
  • 模型供应商切换会影响大量代码;
  • 知识库文件解析和在线对话互相影响;
  • Token、费用、延迟统计分散在各处;
  • 对话权限和知识库权限混在一起;
  • RAG、Agent、工具调用难以扩展;
  • 模型调用失败时缺少统一降级策略;
  • 难以区分业务错误、检索错误和模型错误。

生产级系统必须先做边界拆分。

2. AI 后端比传统后端多了哪些架构对象

传统业务后端常见模块包括:

text 复制代码
用户服务
订单服务
商品服务
支付服务
消息服务
权限服务

AI 后端还需要引入新的核心对象:

对象 作用
模型服务 统一封装大模型调用、流式输出、重试、降级和成本统计
会话服务 管理用户会话、消息、上下文和状态
知识库服务 管理文档、切片、向量化、召回和重排序
Prompt 服务 管理系统提示词、模板、变量和版本
Agent 服务 负责任务规划、工具选择和多步执行
工具服务 封装业务查询、工单创建、订单查询等可调用能力
评估服务 评估回答质量、召回质量和幻觉风险
观测服务 记录 Token、延迟、错误率、费用和调用链

如果这些能力没有清晰边界,系统会很快变成"AI 调用大杂烩"。

3. 本项目的架构目标

以企业智能客服与知识库助手为主线,架构目标可以定义为:

text 复制代码
支持多用户、多会话
支持流式 AI 对话
支持企业知识库问答
支持模型供应商切换
支持文档异步解析和向量化
支持权限隔离和数据安全
支持调用监控和成本统计
支持后续扩展 Agent 工具

这些目标决定了架构拆分方式。

二、核心概念

1. 业务服务

业务服务负责面向用户和业务场景,处理确定性的业务逻辑。

在本项目中,它主要包括:

  • 用户和租户;
  • 会话和消息;
  • 客服问答入口;
  • 权限校验;
  • 用户操作记录;
  • 对话状态管理;
  • 前端 API。

业务服务不应该直接依赖某个模型供应商的 SDK。它应该调用模型服务提供的统一接口。

2. 模型服务

模型服务负责封装所有模型调用细节:

  • OpenAI、通义千问、本地模型等供应商适配;
  • 同步对话和流式对话;
  • Embedding 调用;
  • 模型路由;
  • 超时、重试、降级;
  • Token 统计;
  • 成本估算;
  • 错误标准化;
  • 请求日志和链路追踪。

业务服务只关心:

text 复制代码
给定消息和参数,返回模型结果

不关心底层是哪个模型。

3. 知识库服务

知识库服务负责把企业文档变成可检索、可引用的知识。

主要职责包括:

  • 文件上传记录;
  • 文档解析;
  • 文本切片;
  • Embedding;
  • 向量入库;
  • 关键词索引;
  • 召回;
  • 重排序;
  • 引用来源返回;
  • 文档权限控制。

知识库服务既有在线接口,也有异步任务。文件解析和向量化不能阻塞用户对话主链路。

4. Agent 和工具服务

第一阶段可以先不做复杂 Agent,但架构上要预留位置。

Agent 服务负责:

  • 判断用户意图;
  • 拆解任务步骤;
  • 选择工具;
  • 调用工具;
  • 汇总结果;
  • 控制权限和执行边界。

工具服务负责封装业务能力,例如:

  • 查询订单;
  • 查询物流;
  • 创建工单;
  • 查询会员等级;
  • 查询售后政策;
  • 转人工。

Agent 不能直接访问数据库,而应该通过受控工具调用业务能力。

三、工作原理

1. 推荐的总体架构

第一阶段可以采用"模块化单体 + 清晰边界"的架构,而不是一开始就拆成很多微服务。

text 复制代码
前端 Web / 管理后台
        │
        ▼
API 网关 / 统一鉴权
        │
        ▼
AI 后端应用
 ├─ 用户与权限模块
 ├─ 会话与消息模块
 ├─ AI 编排模块
 ├─ 模型服务模块
 ├─ 知识库模块
 ├─ Agent 工具模块
 ├─ 监控与费用模块
 └─ 管理后台模块
        │
        ├──────────────► 模型供应商 / 本地模型
        ├──────────────► 向量数据库
        ├──────────────► 关系型数据库
        ├──────────────► 对象存储
        └──────────────► 消息队列

模块化单体的优点:

  • 开发效率高;
  • 部署简单;
  • 事务和调试方便;
  • 适合早期快速演进;
  • 后续可以按边界拆服务。

关键是代码层面必须先拆清楚边界,不要因为是单体就把所有逻辑混在一起。

2. 在线对话链路

企业知识库问答的在线链路如下:

text 复制代码
用户发送问题
  ↓
业务服务鉴权
  ↓
保存用户消息
  ↓
AI 编排模块加载会话上下文
  ↓
知识库服务召回相关片段
  ↓
Prompt 服务组装提示词
  ↓
模型服务发起流式调用
  ↓
后端持续推送模型片段
  ↓
保存助手回答
  ↓
记录 Token、延迟和引用来源

在这个链路里,职责要分清:

步骤 所属模块
鉴权 用户与权限模块
保存消息 会话与消息模块
加载历史 会话与消息模块
知识召回 知识库模块
Prompt 组装 AI 编排 / Prompt 模块
模型调用 模型服务模块
流式推送 API 层 / AI 编排模块
统计费用 监控与费用模块

3. 文件入库链路

知识库文件上传后,不应该同步完成所有处理。

推荐链路:

text 复制代码
管理员上传文件
  ↓
保存文件元数据
  ↓
文件进入对象存储
  ↓
投递解析任务
  ↓
文档解析
  ↓
文本清洗
  ↓
切片
  ↓
Embedding
  ↓
向量入库
  ↓
更新知识库状态

如果把解析、切片和向量化放在上传接口里,用户会长时间等待,接口也容易超时。

4. 模型服务的内部结构

模型服务建议再拆成几层:

text 复制代码
ModelService
  ├─ ChatModelClient
  ├─ EmbeddingModelClient
  ├─ ModelRouter
  ├─ ModelProviderAdapter
  ├─ RetryAndFallbackPolicy
  ├─ TokenUsageRecorder
  └─ ModelCallLogRepository

每一层的职责:

组件 职责
ModelService 对业务提供统一入口
ModelRouter 根据场景选择模型
ProviderAdapter 适配不同供应商协议
RetryAndFallbackPolicy 控制重试和降级
TokenUsageRecorder 记录 Token 和费用
ModelCallLogRepository 保存调用日志

这样后续从 OpenAI 切到通义千问、本地模型或企业模型网关时,不需要改业务模块。

5. 知识库服务的内部结构

知识库服务可以拆成:

text 复制代码
KnowledgeBaseService
  ├─ DocumentService
  ├─ FileStorageService
  ├─ DocumentParser
  ├─ ChunkService
  ├─ EmbeddingTaskService
  ├─ RetrievalService
  ├─ RerankService
  └─ CitationService

关键数据对象包括:

  • 知识库;
  • 文档;
  • 文档版本;
  • 文档切片;
  • 向量记录;
  • 解析任务;
  • 召回记录;
  • 引用来源。

知识库不是简单的"文件表 + 向量表",还要支持状态、版本、权限和任务失败重试。

四、实战示例

1. 包结构设计

在模块化单体中,可以先使用清晰的包结构:

text 复制代码
com.example.ai
 ├─ user
 │   ├─ api
 │   ├─ domain
 │   ├─ service
 │   └─ repository
 ├─ conversation
 │   ├─ api
 │   ├─ domain
 │   ├─ service
 │   └─ repository
 ├─ model
 │   ├─ api
 │   ├─ application
 │   ├─ domain
 │   ├─ provider
 │   └─ repository
 ├─ knowledge
 │   ├─ api
 │   ├─ application
 │   ├─ domain
 │   ├─ parser
 │   ├─ retrieval
 │   └─ repository
 ├─ agent
 │   ├─ application
 │   ├─ tool
 │   └─ domain
 ├─ observability
 │   ├─ metrics
 │   ├─ log
 │   └─ cost
 └─ common
     ├─ error
     ├─ security
     └─ config

注意不要按技术分成:

text 复制代码
controller
service
repository
entity

这种分法在项目变大后会让业务边界变得模糊。

2. 定义模型服务接口

业务层不要直接依赖供应商 SDK,可以先定义模型服务接口:

java 复制代码
public interface ModelService {

    ChatResult chat(ChatRequest request);

    Flux<ChatStreamChunk> streamChat(ChatRequest request);

    EmbeddingResult embed(EmbeddingRequest request);
}

请求对象:

java 复制代码
public record ChatRequest(
        String tenantId,
        String userId,
        String conversationId,
        String scene,
        List<ChatMessage> messages,
        ModelOptions options
) {
}

流式片段:

java 复制代码
public record ChatStreamChunk(
        String requestId,
        String type,
        String content,
        TokenUsage usage
) {
}

业务服务只调用这个接口,不关心底层模型。

3. 定义知识库检索接口

java 复制代码
public interface KnowledgeRetrievalService {

    RetrievalResult retrieve(RetrievalRequest request);
}

请求对象:

java 复制代码
public record RetrievalRequest(
        String tenantId,
        String userId,
        List<String> knowledgeBaseIds,
        String query,
        int topK
) {
}

返回对象:

java 复制代码
public record RetrievalResult(
        List<RetrievedChunk> chunks
) {
}

public record RetrievedChunk(
        String documentId,
        String chunkId,
        String title,
        String content,
        double score
) {
}

这里要把租户、用户和知识库 ID 传入检索服务,避免越权召回。

4. AI 编排服务

AI 编排模块负责把会话、知识库和模型服务串起来:

java 复制代码
@Service
public class AiConversationOrchestrator {

    private final ConversationService conversationService;
    private final KnowledgeRetrievalService retrievalService;
    private final PromptBuilder promptBuilder;
    private final ModelService modelService;

    public Flux<ChatStreamChunk> streamAnswer(AskQuestionCommand command) {
        conversationService.checkPermission(command.userId(), command.conversationId());

        conversationService.saveUserMessage(
                command.conversationId(),
                command.userId(),
                command.question()
        );

        List<ChatMessage> history = conversationService.loadRecentMessages(
                command.conversationId(),
                10
        );

        RetrievalResult retrieval = retrievalService.retrieve(
                new RetrievalRequest(
                        command.tenantId(),
                        command.userId(),
                        command.knowledgeBaseIds(),
                        command.question(),
                        5
                )
        );

        List<ChatMessage> promptMessages = promptBuilder.buildForKnowledgeQa(
                command.question(),
                history,
                retrieval.chunks()
        );

        ChatRequest request = new ChatRequest(
                command.tenantId(),
                command.userId(),
                command.conversationId(),
                "knowledge_qa",
                promptMessages,
                ModelOptions.defaultOptions()
        );

        return modelService.streamChat(request)
                .doOnComplete(() -> {
                    // TODO 保存最终 assistant 消息、引用来源和费用统计
                });
    }
}

这段代码体现了一个重要原则:

编排服务负责串联流程,但具体能力由各模块提供。

5. 数据库职责划分

关系型数据库适合保存:

  • 用户;
  • 租户;
  • 会话;
  • 消息;
  • 文件元数据;
  • 文档状态;
  • 切片元数据;
  • 模型调用日志;
  • Token 和费用统计。

对象存储适合保存:

  • 原始文件;
  • 解析后的中间文件;
  • 导出的报告。

向量数据库适合保存:

  • 文档切片向量;
  • 语义检索索引。

不要把所有数据都塞进向量数据库。向量库负责检索,不负责完整业务状态。

6. 异步任务设计

知识库处理适合使用任务表或消息队列:

text 复制代码
document_parse_task
  ├─ id
  ├─ document_id
  ├─ status
  ├─ retry_count
  ├─ error_message
  ├─ created_at
  └─ updated_at

任务状态:

text 复制代码
PENDING
PROCESSING
SUCCESS
FAILED
RETRYING

这样可以支持:

  • 失败重试;
  • 后台进度查询;
  • 管理员重新处理;
  • 大文件限流;
  • 多 worker 并发消费。

五、常见问题与实践建议

1. 一开始要不要拆微服务

不建议一开始就拆很多微服务。

更推荐:

text 复制代码
第一阶段:模块化单体
第二阶段:拆出知识库处理 worker
第三阶段:拆出模型服务或模型网关
第四阶段:根据流量和团队边界拆更多服务

架构演进要按痛点驱动,而不是为了"看起来先进"。

2. 模型服务和业务服务为什么要分开

模型服务变化很频繁:

  • 模型供应商会换;
  • 模型版本会升级;
  • 价格会变化;
  • 限流策略会变化;
  • 参数会调整;
  • 需要新增降级模型。

业务服务应该稳定表达业务流程,不应该被模型供应商牵着走。

3. 知识库服务为什么要独立边界

知识库处理有明显的离线任务特征:

  • 文件可能很大;
  • 解析耗时;
  • 向量化耗费模型资源;
  • 失败率比普通业务接口高;
  • 需要重试和进度管理;
  • 不同格式文件处理方式不同。

如果知识库逻辑和在线对话混在一起,线上问答接口会被离线任务拖慢。

4. Prompt 放在哪里

Prompt 不应该散落在代码各处。建议至少做到:

  • 按场景管理;
  • 有版本号;
  • 支持灰度;
  • 记录每次调用使用的 Prompt 版本;
  • 重要 Prompt 变更需要评审;
  • 支持回滚。

Prompt 是 AI 系统的业务逻辑之一,不能当成普通字符串随意修改。

5. 如何避免知识库越权

知识库检索前必须做权限过滤。

常见方式:

text 复制代码
用户问题
  ↓
根据用户和租户计算可访问知识库
  ↓
只在可访问知识库内检索
  ↓
返回引用来源

不要先召回全部内容,再在回答阶段要求模型"不要泄露"。权限必须由程序控制。

6. 架构图不能只画组件,还要画数据流

很多架构图只画:

text 复制代码
前端 -> 后端 -> 模型

这不够。AI 后端架构图必须画清楚:

  • 用户问题如何进入系统;
  • 会话历史从哪里来;
  • 知识片段如何召回;
  • Prompt 如何构造;
  • 模型调用如何记录;
  • 生成结果如何保存;
  • 引用来源如何返回;
  • 异步任务如何更新状态。

没有数据流的架构图很难指导开发。

六、进阶思考

1. 多模型路由

生产系统通常不会只用一个模型。

可以按场景路由:

场景 模型策略
普通客服问答 成本较低、速度较快的模型
复杂投诉分析 能力更强的模型
文档摘要 长上下文模型
敏感内容判断 专门的安全模型
Embedding 独立 Embedding 模型

模型路由应由模型服务负责,业务模块只传入场景和需求。

2. 观测指标设计

AI 后端要重点记录:

  • 请求量;
  • 成功率;
  • 首 token 延迟;
  • 总耗时;
  • 输入 Token;
  • 输出 Token;
  • 总费用;
  • 召回数量;
  • 召回命中率;
  • 模型错误;
  • 用户反馈。

没有这些指标,就无法判断系统是否真的可用。

3. 安全边界

AI 后端至少有四层安全边界:

text 复制代码
用户权限
  ↓
知识库权限
  ↓
工具调用权限
  ↓
模型输出安全

越靠前的安全控制越可靠。不能把所有安全责任都交给模型。

4. 从模块化单体到服务拆分

当系统增长后,可以按以下顺序拆分:

text 复制代码
知识库处理 Worker
  ↓
模型服务 / 模型网关
  ↓
向量检索服务
  ↓
Agent 执行服务
  ↓
评估与监控服务

拆分条件包括:

  • 资源需求明显不同;
  • 部署频率不同;
  • 故障需要隔离;
  • 团队职责独立;
  • 数据边界清晰;
  • 性能瓶颈明确。

不要因为模块名字不同就拆服务。服务拆分要解决真实问题。

结论

生产级 AI 后端的架构设计,关键在于边界清晰。业务服务负责用户、会话、权限和业务流程;模型服务负责模型调用、供应商适配、重试降级和成本统计;知识库服务负责文档解析、切片、向量化、召回和引用来源;Agent 和工具服务则为后续复杂任务执行预留扩展空间。

第一阶段推荐采用模块化单体,通过包结构、接口和数据边界先把职责拆清楚。等知识库任务、模型调用流量或团队协作出现明确瓶颈后,再逐步拆出独立服务。

架构设计不是画一张漂亮的图,而是把系统的变化点隔离开:模型会变、知识库会变、业务流程会变、成本和安全要求也会变。只有边界清楚,后续的数据表设计、模型抽象、流式接口、知识库处理和 Agent 工具接入才能稳定推进。

下一篇可以继续进入"用户、会话与消息表应该如何设计",把本篇中的会话和消息边界落到具体的数据模型上。

相关推荐
jianpeng的工程笔记1 小时前
Qwen-Image-2.1 图像编辑拆解:从透明改字到多图合成
人工智能·qwen·图像生成·comfyui
武子康1 小时前
GPU Pod 已经 Running,为什么扩容还没变成推理容量?
人工智能·llm·agent
sdzhyt1 小时前
济南第六届高层次人才招引大会举行|智慧云通开放AI全栈、售前、销售、产品经理等岗位
人工智能·求职招聘·招聘
shionhana1 小时前
AI PPT 生成的两个关键环节:结构生成和视觉生成,以 PPTMaker 为例
人工智能·chatgpt·agent·ppt
Am-Chestnuts1 小时前
DeepSeek表格复制到Word变竖线错列?用DS随心转对比几种整理方法
人工智能·word·ds随心转
55873 生态系统1 小时前
013+【架构实战】55873 智慧生活:如何让生命更有价值、更有意义?—— 四大生命价值维度设计全解
人工智能·55873全域文明生态体系·55873操作系统·全域文明生态系统·55873智慧生活
智行合一科技1 小时前
字节跳动将飞书并入豆包:一场“工具让位AI”的战略重构
ai·字节跳动·豆包
小小程序猴11 小时前
中小企业AI转型课程模块的工程化设计:从能力矩阵到课程编排
大数据·人工智能
别动我齐刘海1 小时前
ROS2 Jazzy + C++ 实战路线——ros2_control
c++·人工智能·python·opencv·机器学习·机器人·github