
摘要把一个能调用模型的 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-supportmodel:qwen-plusprompt:support-answer:v4knowledgeBases:-product-docs-refund-policytools:-query-order-create-ticketpolicies:maxInputTokens:12000dailyBudget: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. 平台接口RestControllerRequestMapping(/api/ai/applications)publicclassAiApplicationController{privatefinalApplicationRuntimeServiceruntimeService;PostMapping(/{code}/chat)publicChatAcceptedResponsechat(PathVariableStringcode,RequestBodyChatCommandcommand,Authenticationauthentication){ChatRequestrequestChatRequest.builder().applicationCode(code).tenantId(command.tenantId()).userId(authentication.getName()).conversationId(command.conversationId()).message(command.message()).build();returnruntimeService.submit(request);}}接口立即返回traceId长任务由异步执行器处理短对话也可以在同一接口中选择同步或 SSE 响应。2. 运行时组装publicRuntimeContextload(ChatRequestrequest){PublishedApplicationappapplicationRepository.findPublished(request.tenantId(),request.applicationCode()).orElseThrow(()-newApplicationNotFoundException(request.applicationCode()));policyService.checkAccess(request,app);budgetService.reserve(request.tenantId(),app.dailyBudget());rateLimiter.acquire(request.tenantId(),app.code());returnRuntimeContext.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. 统一编排publicChatResultorchestrate(RuntimeContextcontext,ChatRequestrequest){conversationStore.appendUserMessage(context,request.message());ListKnowledgeChunkchunksknowledgeService.retrieve(context.knowledgeScopes(),request.message(),5);ChatResponseresponsemodelGateway.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());returnChatResult.from(response);}Agent 模式可以替换modelGateway.chat()但权限、预算和 Trace 仍由平台统一处理。4. 模型目录models:-code:qwen-plusprovider:dashscopeupstream:qwen-plusmaxContext:128000inputPrice:0.0008outputPrice:0.002timeout:30sfallback:qwen-turbo-code:local-coderprovider:openai-compatiblebaseUrl:${LOCAL_MODEL_BASE_URL}upstream:qwen2.5-codermaxContext:32768timeout:60s业务配置只引用qwen-plus或local-coder。供应商地址、价格和 Fallback 变化时不需要修改业务服务。5. 平台最小表createtableai_application_version(idbigintprimarykey,tenant_idvarchar(64)notnull,app_codevarchar(64)notnull,versionintnotnull,model_codevarchar(64)notnull,prompt_refvarchar(128)notnull,config_json jsonnotnull,statusvarchar(32)notnull,published_atdatetimenull,uniquekeyuk_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 可以先承载这个单体平台但模块边界、配置发布、租户隔离和可观测性要从第一阶段建立。后续可以继续实现平台的数据安全与权限模型并把模型目录接入本地推理服务形成云端模型与私有模型统一调度的运行环境。参考资料Spring AI ReferenceSpring Boot ReferenceOpenTelemetry DocumentationResilience4j Documentation