后端AI中台架构:统一模型网关与能力编排的工程实践
2026/7/11 22:11:23 网站建设 项目流程

后端AI中台架构:统一模型网关与能力编排的工程实践

当你的公司接了 GPT-4、Claude、文心一言、通义千问、以及三个开源自部署模型后,每个业务团队都在各自封装 SDK,Token 消耗一片混乱,Prompt 散落在各个微服务里无法集中管控。AI 中台要解决的就是这个问题:用一层统一的网关把多模型供应商的差异屏蔽掉。

一、AI 中台的核心定位与架构总览

AI 中台不是一个"模型调用工具",而是一层位于业务应用和模型供应商之间的能力抽象层。它的核心职责包括:

  1. 统一接入:多供应商、多模型协议的标准化适配
  2. 能力抽象:将文本生成、图像理解、语音识别、代码生成等能力作为独立可编排单元
  3. 请求路由:基于能力需求、成本、延迟、可用性做智能路由
  4. Token 计费与配额:统一计量、部门级配额、预算预警
  5. 插件化扩展:后处理、安全审核、Prompt 增强等能力以插件形式热加载
graph TB subgraph 业务应用层 B1[智能客服] B2[代码助手] B3[内容平台] B4[数据分析] end subgraph AI中台 - 控制面 C1[能力注册中心] C2[配置管理中心<br/>Prompt模板/路由策略] C3[监控与计费] end subgraph AI中台 - 数据面 G[统一模型网关] --> ROUTER[智能路由器] ROUTER --> PLUGIN[插件链] end subgraph 能力编排层 ORCH[能力编排引擎] ORCH --> T1[文本生成能力] ORCH --> T2[图像理解能力] ORCH --> T3[代码生成能力] ORCH --> T4[语音识别能力] end subgraph 模型供应商 T1 --> M1[OpenAI GPT-4] T1 --> M2[Claude 3.5] T2 --> M3[GPT-4o Vision] T3 --> M4[通义千问] T3 --> M5[自部署 DeepSeek] T4 --> M6[Whisper] end B1 --> G B2 --> G B3 --> G B4 --> G G --> ORCH C1 -.-> ORCH C2 -.-> ROUTER C3 -.-> G style 业务应用层 fill:#e3f2fd style AI中台 - 控制面 fill:#fff8e1 style AI中台 - 数据面 fill:#e8f5e9 style 能力编排层 fill:#f3e5f5 style 模型供应商 fill:#ffebee

控制面与数据面的分离是 AI 中台架构的核心设计原则。控制面负责能力注册、配置管理、监控计费,数据面负责实际请求的路由和转发。两者可以独立扩缩容。

二、多模型供应商的统一接入层

2.1 供应商适配器模式

不同模型供应商的 API 协议差异巨大——OpenAI 用 Chat Completions API,Claude 用 Messages API,国内厂商各有自己的参数体系。统一接入层的核心是"适配器模式":

/** * 统一模型调用请求 - 中台内部标准协议 * 与任何供应商的 API 格式解耦 */ @Data @Builder public class UnifiedModelRequest { /** 能力类型(text_generation / image_understanding / code_generation) */ private String capability; /** 消息列表(统一格式) */ private List<UnifiedMessage> messages; /** 请求参数 */ private UnifiedParameters parameters; /** 上下文信息(用户ID、场景、traceId) */ private RequestContext context; } /** * 模型供应商适配器接口 - 所有供应商实现此接口 */ public interface ModelProviderAdapter { /** 供应商标识 */ String getProviderName(); /** 支持的模型列表 */ List<ModelProfile> getSupportedModels(); /** 将统一请求转换为供应商特定格式 */ Object adaptRequest(UnifiedModelRequest request); /** 将供应商响应转换为统一格式 */ UnifiedModelResponse adaptResponse(Object rawResponse); /** 健康检查 */ HealthStatus checkHealth(); /** 获取当前配额信息 */ QuotaInfo getQuotaInfo(); } /** * OpenAI 适配器实现 */ @Component @Slf4j public class OpenAiAdapter implements ModelProviderAdapter { private final OpenAiClient openAiClient; private final ObjectMapper objectMapper; public OpenAiAdapter(@Value("${ai.openai.api-key}") String apiKey, @Value("${ai.openai.base-url}") String baseUrl, ObjectMapper objectMapper) { this.objectMapper = objectMapper; this.openAiClient = OpenAiClient.builder() .apiKey(apiKey) .baseUrl(baseUrl) .connectTimeout(Duration.ofSeconds(30)) .readTimeout(Duration.ofSeconds(120)) .build(); } @Override public String getProviderName() { return "openai"; } @Override public List<ModelProfile> getSupportedModels() { return List.of( ModelProfile.builder() .modelId("gpt-4") .capabilities(List.of("text_generation", "code_generation")) .maxTokens(8192) .pricing(Pricing.of(0.03, 0.06)) // input/output per 1K tokens .build(), ModelProfile.builder() .modelId("gpt-4o") .capabilities(List.of("text_generation", "image_understanding")) .maxTokens(128000) .pricing(Pricing.of(0.005, 0.015)) .build() ); } @Override public Object adaptRequest(UnifiedModelRequest request) { ChatCompletionRequest chatRequest = ChatCompletionRequest.builder() .model(request.getParameters().getModelId()) .messages(request.getMessages().stream() .map(this::convertMessage) .collect(Collectors.toList())) .temperature(request.getParameters().getTemperature()) .maxTokens(request.getParameters().getMaxTokens()) .topP(request.getParameters().getTopP()) .build(); log.debug("OpenAI 请求适配: model={}, messages={}", chatRequest.model(), chatRequest.messages().size()); return chatRequest; } private ChatMessage convertMessage(UnifiedMessage msg) { return switch (msg.getRole()) { case "system" -> ChatMessage.system(msg.getContent()); case "user" -> ChatMessage.user(msg.getContent()); case "assistant" -> ChatMessage.assistant(msg.getContent()); default -> throw new IllegalArgumentException("不支持的消息角色: " + msg.getRole()); }; } @Override public UnifiedModelResponse adaptResponse(Object rawResponse) { ChatCompletionResponse response = (ChatCompletionResponse) rawResponse; ChatCompletionResponse.Choice choice = response.choices().get(0); return UnifiedModelResponse.builder() .content(choice.message().content()) .model(response.model()) .finishReason(choice.finishReason()) .usage(UsageInfo.builder() .promptTokens(response.usage().promptTokens()) .completionTokens(response.usage().completionTokens()) .totalTokens(response.usage().totalTokens()) .build()) .provider("openai") .latencyMs(response.latencyMs()) .build(); } @Override public HealthStatus checkHealth() { try { // 轻量健康检查:调用 models list API openAiClient.listModels(); return HealthStatus.healthy(); } catch (Exception e) { return HealthStatus.unhealthy(e.getMessage()); } } @Override public QuotaInfo getQuotaInfo() { return openAiClient.getQuotaInfo(); } }

2.2 请求路由与负载均衡

/** * 智能模型路由器 * 基于能力、成本、延迟、可用性做多维度路由 */ @Service @Slf4j public class ModelRouter { private final ModelRegistry registry; // 模型注册中心 private final CostManager costManager; // 成本管理 private final CircuitBreakerManager cbManager; // 熔断管理 public ModelRouter(ModelRegistry registry, CostManager costManager, CircuitBreakerManager cbManager) { this.registry = registry; this.costManager = costManager; this.cbManager = cbManager; } /** * 选择最优模型 * * @param capability 所需能力(如 "text_generation") * @param context 请求上下文(含用户级别、预算约束等) * @return 选中的模型信息 */ public ModelSelectionResult selectModel(String capability, RequestContext context) { // 1. 根据能力和上下文过滤候选模型 List<ModelProfile> candidates = registry.findByCapability(capability).stream() .filter(m -> matchesContext(m, context)) .filter(m -> cbManager.isAvailable(m.getModelId())) .collect(Collectors.toList()); if (candidates.isEmpty()) { throw new NoAvailableModelException( "没有可用的模型: capability=" + capability); } // 2. 多维度加权评分 List<ModelSelectionResult> scored = candidates.stream() .map(m -> scoreModel(m, context)) .sorted(Comparator.comparingDouble(ModelSelectionResult::getScore).reversed()) .collect(Collectors.toList()); ModelSelectionResult selected = scored.get(0); log.info("模型路由: capability={}, selected={}, score={}, candidates={}", capability, selected.getModelId(), selected.getScore(), scored.stream().map(ModelSelectionResult::getModelId) .collect(Collectors.toList())); return selected; } private ModelSelectionResult scoreModel(ModelProfile model, RequestContext context) { double score = 0.0; // 成本维度(权重 0.3):优先使用低成本模型 double costScore = 1.0 - normalizeCost(model.getPricing(), context); score += costScore * 0.3; // 延迟维度(权重 0.25):优先使用低延迟模型 double latencyScore = model.getAverageLatencyMs() < 500 ? 1.0 : model.getAverageLatencyMs() < 2000 ? 0.5 : 0.2; score += latencyScore * 0.25; // 质量维度(权重 0.25):根据任务复杂度选择合适质量级别 double qualityScore = matchQuality(model.getQuality(), context.getTaskComplexity()); score += qualityScore * 0.25; // 可用性维度(权重 0.2):当前可用且历史成功率高的模型加分 double availabilityScore = cbManager.getSuccessRate(model.getModelId()); score += availabilityScore * 0.2; return ModelSelectionResult.builder() .modelId(model.getModelId()) .provider(model.getProvider()) .score(score) .breakdown(Map.of( "cost", costScore, "latency", latencyScore, "quality", qualityScore, "availability", availabilityScore )) .build(); } private double normalizeCost(Pricing pricing, RequestContext context) { double estimatedCost = pricing.getInputPrice() * estimateInputTokens(context) + pricing.getOutputPrice() * context.getExpectedOutputTokens(); double budget = context.getBudgetLimit(); return Math.min(estimatedCost / budget, 1.0); } private double matchQuality(ModelQuality quality, TaskComplexity complexity) { return switch (complexity) { case HIGH -> quality == ModelQuality.PREMIUM ? 1.0 : 0.3; case MEDIUM -> quality == ModelQuality.STANDARD ? 1.0 : 0.6; case LOW -> quality == ModelQuality.BASIC ? 1.0 : 0.7; }; } }

三、Token 计费与配额管理

3.1 统一计量模型

/** * Token 消费追踪器 * 实时计费、配额检查、预算预警 */ @Service @Slf4j public class TokenBillingService { private final RedisTemplate<String, String> redisTemplate; private final MeterRegistry meterRegistry; private final NotificationService notificationService; // 配额 Redis Key 前缀 private static final String QUOTA_KEY_PREFIX = "ai:quota:"; private static final String USAGE_KEY_PREFIX = "ai:usage:"; public TokenBillingService(RedisTemplate<String, String> redisTemplate, MeterRegistry meterRegistry, NotificationService notificationService) { this.redisTemplate = redisTemplate; this.meterRegistry = meterRegistry; this.notificationService = notificationService; } /** * 消费 Token 并检查配额 * * @param departmentId 部门ID * @param userId 用户ID * @param modelId 模型ID * @param tokenUsage Token 使用量 * @return 计费结果 */ public BillingResult bill(String departmentId, String userId, String modelId, TokenUsage tokenUsage) { // 获取模型定价 Pricing pricing = modelRegistry.getPricing(modelId); BigDecimal cost = calculateCost(pricing, tokenUsage); // 获取已用配额 String monthlyKey = USAGE_KEY_PREFIX + departmentId + ":" + YearMonth.now().toString(); Long currentUsage = redisTemplate.opsForValue() .increment(monthlyKey, cost.longValue()); // 获取配额上限 String quotaKey = QUOTA_KEY_PREFIX + departmentId; String quotaStr = redisTemplate.opsForValue().get(quotaKey); long quotaLimit = quotaStr != null ? Long.parseLong(quotaStr) : Long.MAX_VALUE; // 计算使用率 double usageRate = (double) currentUsage / quotaLimit; BillingResult result = BillingResult.builder() .departmentId(departmentId) .userId(userId) .modelId(modelId) .cost(cost) .currentMonthlyUsage(currentUsage) .quotaLimit(quotaLimit) .usageRate(usageRate) .build(); // 配额预警 if (usageRate > 0.90) { String message = String.format( "AI Token 配额告警: 部门 %s 本月已使用 %.1f%% (%.2f / %.2f)", departmentId, usageRate * 100, currentUsage / 100.0, quotaLimit / 100.0); notificationService.sendAlert(departmentId, message); if (usageRate >= 1.0) { result.setRejected(true); result.setRejectReason("部门配额已耗尽"); } } // 记录指标 meterRegistry.counter("ai.billing.tokens", "department", departmentId, "model", modelId ).increment(tokenUsage.getTotalTokens()); meterRegistry.counter("ai.billing.cost", "department", departmentId, "model", modelId ).increment(cost.doubleValue()); log.info("Token 计费: dept={}, user={}, model={}, " + "tokens={}, cost=${}, monthlyUsage=${}", departmentId, userId, modelId, tokenUsage.getTotalTokens(), cost, currentUsage / 100.0); return result; } }

四、插件化能力扩展机制

能力扩展的核心需求是:新能力(如"情感分析后处理""敏感词过滤""Prompt 增强")应该在不修改网关核心代码的情况下热加载。插件机制通过 SPI 实现:

/** * 能力插件接口 - 所有扩展能力实现此接口 */ public interface CapabilityPlugin { /** 插件名称 */ String getName(); /** 插件类型(PRE_PROCESS / POST_PROCESS / OBSERVER) */ PluginType getType(); /** 优先级(数字越小越先执行) */ int getOrder(); /** 是否启用 */ boolean isEnabled(); /** * 处理请求/响应 * * @param input 输入(请求或响应) * @param context 处理上下文 * @return 处理后的结果 */ PluginResult process(Object input, PluginContext context); } /** * 插件链引擎 * 按优先级串行执行所有注册的插件 */ @Component public class PluginChainEngine { private final List<CapabilityPlugin> preProcessors; private final List<CapabilityPlugin> postProcessors; private final List<CapabilityPlugin> observers; public PluginChainEngine(List<CapabilityPlugin> allPlugins) { // 按类型和优先级分组 Comparator<CapabilityPlugin> byOrder = Comparator.comparingInt(CapabilityPlugin::getOrder); this.preProcessors = allPlugins.stream() .filter(p -> p.getType() == PluginType.PRE_PROCESS && p.isEnabled()) .sorted(byOrder) .collect(Collectors.toList()); this.postProcessors = allPlugins.stream() .filter(p -> p.getType() == PluginType.POST_PROCESS && p.isEnabled()) .sorted(byOrder) .collect(Collectors.toList()); this.observers = allPlugins.stream() .filter(p -> p.getType() == PluginType.OBSERVER && p.isEnabled()) .sorted(byOrder) .collect(Collectors.toList()); log.info("插件链初始化完成: pre={}, post={}, observer={}", preProcessors.size(), postProcessors.size(), observers.size()); } /** * 执行前置处理链 */ public UnifiedModelRequest executePreProcess(UnifiedModelRequest request, PluginContext context) { UnifiedModelRequest current = request; for (CapabilityPlugin plugin : preProcessors) { try { PluginResult result = plugin.process(current, context); if (result.isRejected()) { throw new PluginRejectedException( "请求被插件拦截: " + plugin.getName() + ", reason=" + result.getRejectReason()); } if (result.getOutput() instanceof UnifiedModelRequest) { current = (UnifiedModelRequest) result.getOutput(); } } catch (PluginRejectedException e) { throw e; } catch (Exception e) { log.error("前置插件异常: plugin={}", plugin.getName(), e); // 插件异常不应阻断请求(降级处理) } } return current; } /** * 执行后置处理链(如内容安全审核、格式转换) */ public UnifiedModelResponse executePostProcess(UnifiedModelResponse response, PluginContext context) { UnifiedModelResponse current = response; for (CapabilityPlugin plugin : postProcessors) { try { PluginResult result = plugin.process(current, context); if (result.getOutput() instanceof UnifiedModelResponse) { current = (UnifiedModelResponse) result.getOutput(); } } catch (Exception e) { log.error("后置插件异常: plugin={}", plugin.getName(), e); } } return current; } /** * 执行观察者插件(异步、不影响响应) */ public void executeObservers(Object data, PluginContext context) { for (CapabilityPlugin plugin : observers) { CompletableFuture.runAsync(() -> { try { plugin.process(data, context); } catch (Exception e) { log.error("观察者插件异常: plugin={}", plugin.getName(), e); } }); } } }

五、总结

AI 中台架构的核心价值在于解耦业务与模型——让业务团队面向"能力"编程而非面向"GPT-4"编程。这种解耦带来的直接好处是:模型供应商的切换不需要改动业务代码,Token 消耗和配额管理从"一团黑"变成按部门可量化的成本中心。

三个工程实践要点:

第一,控制面与数据面严格分离。控制面管理能力注册、配置变更、监控面板,数据面只负责请求路由和转发——两者不能耦合在同一进程中,否则配置变更可能阻塞请求处理。

第二,适配器模式的粒度在"供应商"级别,而非"模型"级别。因为同一供应商的不同模型通常共享相同的认证机制和错误码体系,在一个适配器中处理所有模型可以减少重复代码。

第三,插件链的降级策略是"fail-open"。前置插件(如 Prompt 增强)失败不应阻断请求,后置插件(如内容审核)失败应记录并告警但不影响响应返回。在插件稳定性和业务可用性之间,永远选择后者。

随着 Agent、RAG、MCP 协议等新范式的成熟,AI 中台的能力抽象层需要从"模型调用"演进到"AI 任务编排"。连接多个模型、工具和数据源的编排能力,将是下一代 AI 中台的核心竞争力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询