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

0 阅读8分钟

摘要

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

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

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

一、背景与问题

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

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

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

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

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

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

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

2. 平台需要管理的对象

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

二、核心概念

1. AI 应用平台

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

它至少包含四层:

接入层
  REST、SSE、管理 API

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

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

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

2. 应用定义

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

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. 运行时上下文

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

tenantId + userId + applicationVersion
        ↓
RuntimeContext
  ├─ model policy
  ├─ prompt variables
  ├─ permitted tools
  ├─ knowledge scope
  └─ traceId

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

三、工作原理

1. 请求流程

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

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

2. 模块划分

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

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。平台使用草稿、审核和发布三个状态:

DRAFT → REVIEW → PUBLISHED → ROLLED_BACK

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

4. 数据边界

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

四、实战示例

1. 平台接口

@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. 运行时组装

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. 统一编排

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. 模型目录

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. 平台最小表

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 可以先承载这个单体平台,但模块边界、配置发布、租户隔离和可观测性要从第一阶段建立。

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

参考资料