AI 应用的多服务架构设计
AI 应用把系统拆成前端、业务后端、AI 服务三个进程的核心原因、职责边界划分原则、通信协议选型,以及从模块化单体到微服务的演进路径。
#type / concept
#status / growing
#tech / architecture
#tech / ai
[!info] related notes
- 通信模式: SSE, Function Calling
- 实践参考: BodySense 项目 MOC
- 相关概念: Agent Runtime
AI 应用的多服务架构设计
这篇解决什么问题
一个 AI 应用同时涉及 UI 交互、业务逻辑、模型调用三种差异巨大的能力。用一种语言/框架覆盖所有场景,要么性能不够,要么生态不匹配,要么团队能力不匹配。怎么拆?拆成几层?边界在哪?
核心决策:按技术生态的自然边界拆
不要按”微服务”的思路拆(用户服务、订单服务……),而是按技术栈的最佳适用域拆:
| 层 | 核心诉求 | 最佳生态 | 典型语言 |
|---|---|---|---|
| 用户界面 | 交互流畅、组件丰富、状态管理 | React/Vue 生态 | TypeScript |
| 业务逻辑 | 高并发、强类型、稳定可靠、部署轻量 | Go/Java/Rust | Go |
| AI 能力 | LLM 调用、Agent 编排、RAG、OCR | Python AI 生态 | Python |
这三层不是随意拆的,是语言生态的自然边界:
- Python 做不了高并发 API 网关(不是不行,是不擅长)
- Go 缺少 LangChain/LangGraph 等 Agent 框架
- TypeScript 在 CPU 密集型任务和部署体积上不占优
三层职责边界
前端层
只做:UI 渲染、路由、状态管理、SSE 消费、用户交互
不做:直接调 LLM API、直接访问数据库、业务规则判断
业务后端层
只做:API 网关、认证鉴权、业务逻辑、数据持久化、SSE 代理转发
不做:LLM 调用、向量检索、OCR 处理
AI 服务层
只做:LLM 调用、Agent 工作流、RAG 检索、Function Calling、安全检测
不做:用户认证、业务数据 CRUD
关键原则:AI 服务不暴露到外网,只通过业务后端访问。这样 AI 服务不需要处理认证,业务后端控制所有访问权限。
通信协议选型
| 场景 | 推荐协议 | 原因 |
|---|---|---|
| 普通 CRUD | REST (JSON) | 简单、通用、工具链成熟 |
| AI 对话流式输出 | SSE | 单向推送、基于 HTTP、比 WebSocket 简单 |
| 高频内部调用 | gRPC | 强类型、高性能、有代码生成 |
| 异步任务 | 消息队列 | 解耦、削峰、重试 |
AI 对话场景选 SSE 而不是 WebSocket 的理由:
- 服务器→客户端的单向推送就够了(用户发消息用普通 POST)
- SSE 基于 HTTP,不需要额外的连接管理
- 浏览器原生支持 EventSource(虽然实际用 fetch 更灵活)
数据流设计:一次 AI 对话
用户消息 → 前端 POST → 业务后端(鉴权+注入上下文)
→ 业务后端 POST → AI 服务(SSE)
→ AI 服务流式生成事件
→ 业务后端逐行透传
→ 前端逐事件分发到 UI
设计要点:
- 业务后端是透传,不缓冲 SSE 数据(否则延迟增加)
- AI 服务产出的事件类型要提前定义好(text、extracted_info、phase_changed、red_flag、citation、done)
- 前端按事件类型分发到不同 UI 面板,不是所有事件都进聊天框
信任边界
前端 ──(不信任)──► 业务后端 ──(不信任)──► AI 服务
JWT 验证 输入校验
- 前端→后端:所有请求必须带 JWT,后端验证
- 后端→AI 服务:AI 服务返回的数据后端要校验再存库
- AI 服务→后端:AI 服务检测 red flag 后通知后端
- 服务间通信走内网,不暴露 AI 服务端口
与微服务的区别
这不是微服务,是模块化单体(Modular Monolith):
| 维度 | 微服务 | 模块化单体 |
|---|---|---|
| 拆分依据 | 业务领域 | 技术栈 |
| 部署 | 每个服务独立版本 | 统一版本、统一部署 |
| 数据库 | 每个服务独立 | 共享 |
| 服务发现 | 需要 | 不需要(固定地址) |
为什么不搞微服务:MVP 阶段团队小、功能还在快速迭代,微服务的运维成本(服务发现、链路追踪、分布式事务)远超收益。等规模上来再拆也不迟。
什么时候该拆得更细
- AI 服务需要 GPU 资源,和业务后端的部署环境不同
- 某个 AI 能力(如 OCR)需要独立扩缩
- 团队规模增长,需要独立部署和发布
- 引入消息队列解耦同步调用
常见错误
前端直接调 AI 服务
// ❌ 前端直连 AI 服务
const res = await fetch('http://ai-service:8100/chat');
// ✅ 通过业务后端代理
const res = await fetch('/api/v1/consultation/message');
绕过业务后端意味着绕过认证和业务规则。
业务后端缓冲 SSE 数据
// ❌ 读完所有数据再返回
body, _ := io.ReadAll(resp.Body)
c.Data(200, "text/event-stream", body)
// ✅ 逐行透传
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
fmt.Fprintf(c.Writer, "%s\n", scanner.Text())
c.Writer.Flush()
}
缓冲会导致用户等待整个 LLM 响应完成才能看到第一个字。
AI 服务直接操作业务数据库
# ❌ AI 服务直接写用户表
db.execute("UPDATE users SET ...")
# ✅ AI 服务只返回结果,业务后端负责持久化
return {"extracted_info": [...], "phase": "collecting"}
AI 服务不应该知道业务数据的 schema。