基于 Spring Boot 构建生产级 AI 应用平台

摘要

把一个能调用模型的 Spring Boot 服务直接称为 AI 平台,通常只完成了平台能力的很小一部分。生产级平台需要同时管理模型、知识库、Agent、会话、工具、权限、成本和运行状态,并把这些能力稳定地提供给多个业务系统。

本文以 Spring Boot 和 Spring AI 为基础,设计一个企业内部 AI 应用平台,重点介绍:

  • 平台与单个 AI 应用的边界;
  • 模型目录、应用配置和运行时上下文;
  • 会话、知识库、工具和 Agent 的统一接入;
  • 多租户隔离、权限和审计;
  • 限流、重试、降级、预算和可观测性;
  • 如何把平台拆成可独立演进的模块;
  • 从单体平台演进到多服务架构的条件。

一、背景与问题

1. 单个 AI 应用解决不了平台问题

前面的文章已经分别实现了模型接入、流式对话、Prompt、知识库、向量检索、Tool Calling、Agent 和调用治理。单独使用这些能力时,每个业务团队都可能复制一套配置:

text 复制代码
订单助手
  ├─ 自己的模型 Key
  ├─ 自己的 Prompt
  ├─ 自己的知识库
  └─ 自己的限流逻辑

客服助手
  ├─ 另一套模型 Key
  ├─ 另一套 Prompt
  ├─ 另一套知识库
  └─ 另一套限流逻辑

复制能够加快第一个项目,但很快会出现:

  • 模型升级需要修改多个服务;
  • Key、预算和供应商策略无法统一;
  • 相同知识库被重复向量化;
  • 工具权限和审计口径不一致;
  • 每个应用都有自己的会话表和消息格式;
  • 故障、成本和质量无法横向比较。

平台的目标不是把所有业务逻辑集中到一个项目,而是把可复用的 AI 能力沉淀为统一运行时。

2. 平台需要管理的对象

对象 平台职责 业务应用职责
模型 供应商、版本、价格、限流、健康状态 选择适合任务的模型
Prompt 模板版本、变量和发布 定义业务提示词
知识库 文档、切片、向量和权限 提供业务资料
工具 注册、Schema、超时和审计 实现业务接口
Agent 工作流、记忆和工具集 定义任务目标
会话 消息、上下文和状态 定义业务会话语义
策略 权限、预算、内容检查 配置业务约束

二、核心概念

1. AI 应用平台

AI 应用平台是一组共享运行能力,向上提供应用、会话和任务接口,向下连接模型、向量库、工具和观测系统。

它至少包含四层:

text 复制代码
接入层
  REST、SSE、管理 API

应用运行层
  应用、Agent、Prompt、会话、上下文

能力层
  模型网关、知识库、工具注册、权限、预算

基础设施层
  MySQL、Redis、向量库、对象存储、观测系统

2. 应用定义

平台中的"应用"不是一个 Spring Boot 进程,而是一份可版本化的运行配置:

yaml 复制代码
application:
  code: customer-support
  model: qwen-plus
  prompt: support-answer:v4
  knowledgeBases:
    - product-docs
    - refund-policy
  tools:
    - query-order
    - create-ticket
  policies:
    maxInputTokens: 12000
    dailyBudget: 300

业务请求携带 applicationCode,平台根据发布版本组装模型、Prompt、知识和工具。

3. 运行时上下文

每次请求都生成不可变的运行时上下文:

text 复制代码
tenantId + userId + applicationVersion
        ↓
RuntimeContext
  ├─ model policy
  ├─ prompt variables
  ├─ permitted tools
  ├─ knowledge scope
  └─ traceId

后续检索、模型调用和工具执行都使用这份上下文,避免各模块重复解析权限。

三、工作原理

1. 请求流程

text 复制代码
客户端
  ↓
API Gateway
  ↓
AiPlatformController
  ↓
ApplicationRuntimeService
  ├─ 加载应用版本
  ├─ 校验用户权限
  ├─ 检查预算和限流
  ├─ 组装 RuntimeContext
  ↓
ConversationOrchestrator
  ├─ 保存用户消息
  ├─ 检索知识库
  ├─ 调用模型或 Agent
  ├─ 执行已授权工具
  └─ 保存回答和 Trace

平台负责编排,具体订单、工单和知识处理仍由领域服务完成。

2. 模块划分

在一个 Spring Boot 单体中先按模块隔离:

text 复制代码
ai-platform
├─ platform-api
├─ platform-application
├─ platform-conversation
├─ platform-model
├─ platform-knowledge
├─ platform-tool
├─ platform-policy
└─ platform-observability

模块之间通过接口通信。platform-api 不能直接访问模型 SDK,模型供应商细节停留在 platform-model。

3. 配置发布

应用配置不能只存在于 application.yml。平台使用草稿、审核和发布三个状态:

text 复制代码
DRAFT → REVIEW → PUBLISHED → ROLLED_BACK

已发布版本不可原地修改。Prompt、工具集或模型调整时创建新版本,便于比较效果和回滚。

4. 数据边界

数据 建议存储 原因
应用、版本、权限 MySQL 需要事务和审计
会话和消息 MySQL 需要分页、检索和归档
限流、缓存 Redis 需要高并发和过期控制
文档原文 对象存储 文件体积大
向量 向量库 支持向量检索
Trace 和指标 观测系统 需要聚合和告警

四、实战示例

1. 平台接口

java 复制代码
@RestController
@RequestMapping("/api/ai/applications")
public class AiApplicationController {

    private final ApplicationRuntimeService runtimeService;

    @PostMapping("/{code}/chat")
    public ChatAcceptedResponse chat(@PathVariable String code,
                                     @RequestBody ChatCommand command,
                                     Authentication authentication) {
        ChatRequest request = ChatRequest.builder()
                .applicationCode(code)
                .tenantId(command.tenantId())
                .userId(authentication.getName())
                .conversationId(command.conversationId())
                .message(command.message())
                .build();
        return runtimeService.submit(request);
    }
}

接口立即返回 traceId,长任务由异步执行器处理;短对话也可以在同一接口中选择同步或 SSE 响应。

2. 运行时组装

java 复制代码
public RuntimeContext load(ChatRequest request) {
    PublishedApplication app = applicationRepository
            .findPublished(request.tenantId(), request.applicationCode())
            .orElseThrow(() -> new ApplicationNotFoundException(request.applicationCode()));

    policyService.checkAccess(request, app);
    budgetService.reserve(request.tenantId(), app.dailyBudget());
    rateLimiter.acquire(request.tenantId(), app.code());

    return RuntimeContext.builder()
            .traceId(tracer.nextId())
            .tenantId(request.tenantId())
            .userId(request.userId())
            .applicationVersion(app.version())
            .modelPolicy(modelCatalog.get(app.modelCode()))
            .prompt(promptRepository.get(app.promptRef()))
            .knowledgeScopes(app.knowledgeBases())
            .tools(toolCatalog.findPermitted(request, app.tools()))
            .build();
}

预算预留发生在模型调用之前。请求失败、取消或实际 Token 少于预估值时,再释放或修正预留额度。

3. 统一编排

java 复制代码
public ChatResult orchestrate(RuntimeContext context, ChatRequest request) {
    conversationStore.appendUserMessage(context, request.message());

    List<KnowledgeChunk> chunks = knowledgeService.retrieve(
            context.knowledgeScopes(), request.message(), 5);

    ChatResponse response = modelGateway.chat(ModelCommand.builder()
            .context(context)
            .history(conversationStore.recent(context, 20))
            .knowledge(chunks)
            .tools(context.tools())
            .build());

    conversationStore.appendAssistantMessage(context, response);
    traceStore.save(context.traceId(), response.usage(), response.toolCalls());
    return ChatResult.from(response);
}

Agent 模式可以替换 modelGateway.chat(),但权限、预算和 Trace 仍由平台统一处理。

4. 模型目录

yaml 复制代码
models:
  - code: qwen-plus
    provider: dashscope
    upstream: qwen-plus
    maxContext: 128000
    inputPrice: 0.0008
    outputPrice: 0.002
    timeout: 30s
    fallback: qwen-turbo
  - code: local-coder
    provider: openai-compatible
    baseUrl: ${LOCAL_MODEL_BASE_URL}
    upstream: qwen2.5-coder
    maxContext: 32768
    timeout: 60s

业务配置只引用 qwen-plus 或 local-coder。供应商地址、价格和 Fallback 变化时,不需要修改业务服务。

5. 平台最小表

sql 复制代码
create table ai_application_version (
    id bigint primary key,
    tenant_id varchar(64) not null,
    app_code varchar(64) not null,
    version int not null,
    model_code varchar(64) not null,
    prompt_ref varchar(128) not null,
    config_json json not null,
    status varchar(32) not null,
    published_at datetime null,
    unique key uk_app_version (tenant_id, app_code, version)
);

工具、知识库和策略可以先放入 config_json。等查询和权限需求稳定后,再拆成独立关联表。

五、常见问题与实践建议

1. 不要把业务系统整体搬进平台

平台保存 AI 应用配置和运行记录,不替代订单、CRM 或工单系统。工具调用应访问既有业务服务,而不是在平台内复制业务表。

2. 先统一模型入口

平台建设可以从一个模型网关开始,再逐步加入知识库、工具和 Agent。一开始就拆成十几个微服务,会让配置、事务和排障成本过高。

3. 发布版本必须可回滚

Prompt 或模型升级可能降低回答质量。每次发布保留旧版本,并允许按租户或流量比例灰度。

4. 预算要按租户和应用细分

只限制平台总预算无法阻止一个应用耗尽全部额度。至少记录租户、应用、模型、用户和 Trace 五个维度。

5. 平台自身也要有降级路径

模型供应商故障时,平台可以切换到备用模型;知识库故障时,可以退化为无检索对话并明确标记;工具故障时,应返回可理解的失败,而不是让模型编造执行结果。

六、进阶思考

1. 单体何时拆分

出现以下情况再拆分服务:

  • 模型调用、文档处理和 Agent 执行的资源曲线明显不同;
  • 多个团队需要独立发布;
  • 知识库索引任务影响在线对话;
  • 安全边界要求工具执行进入独立沙箱。

优先拆出文档索引、模型网关和 Agent 执行器,在线会话服务保持相对稳定。

2. 多租户隔离级别

级别 实现 适用情况
逻辑隔离 tenant_id 条件 一般企业内部应用
索引隔离 每租户独立向量索引 数据敏感性较高
运行隔离 独立 Key、队列和配额 需要限制相互影响
部署隔离 独立服务或集群 高合规要求

隔离级别应写入应用策略,不能只依赖开发约定。

3. 质量评估进入发布流程

平台可以维护一组固定评测样本。Prompt、模型或检索参数发布前运行样本,比较准确率、拒答率、工具成功率和平均成本。未达到阈值的版本不能进入生产。

4. 平台管理面与数据面分离

管理面负责应用配置、模型目录和权限;数据面负责对话和任务执行。管理 API 使用更严格的身份认证,不与普通聊天接口共用同一暴露面。

结论

生产级 AI 应用平台的关键,是把模型、Prompt、知识库、工具、Agent、权限和成本组织成可版本化的运行时,而不是简单堆叠多个 AI 功能。Spring Boot 可以先承载这个单体平台,但模块边界、配置发布、租户隔离和可观测性要从第一阶段建立。

后续可以继续实现平台的数据安全与权限模型,并把模型目录接入本地推理服务,形成云端模型与私有模型统一调度的运行环境。

参考资料

相关推荐
小小张说故事1 小时前
LightGBM 入门指南:更快的梯度提升树,Python 实战
后端·python·机器学习
站大爷IP1 小时前
Python的默认参数把我坑惨了,原来写[]和写None的区别这么大
后端
Ticnix1 小时前
RAG 检索不准,九成的锅不在向量——不同文件,就该有不同的入库方案
后端·python·agent
福兮说1 小时前
Go 优雅退出:Shutdown 之后,后台 goroutine 还在跑(四个实测的坑)
后端·go
用户298698530142 小时前
Python 文档格式转换:Word 转 EPUB 实践
后端·python·api
德先生2 小时前
1.1-main.cpp入口解剖
后端
zed_232 小时前
省钱之道:token 成本日志 + FAQ 缓存
后端
量化分析码农2 小时前
【Python量化因子实战 #07】因子加权怎么选?等权 / IC 加权 / 最大化 IR 三种策略 PK
后端
Wx-bishekaifayuan3 小时前
springboot甘肃特产服务平台50301-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游