存量培训系统 AI 升级的接入方案:统一 AI 能力网关与适配层设计

简介: 一家企业培训中心的系统已经跑了 8 年,管理着 3 万员工的在线学习数据。业务部门想引入 AI 出题、AI 阅卷、AI 问答,但 CIO 的第一反应是拒绝——替换系统意味着 8 年的数据迁移、3 万员工的重新培训、无法估量的业务中断风险。AI 升级必须在不替换存量系统、不迁移历史数据的前提下完成。本文拆解一套"统一 AI 能力网关 + 适配层"的接入方案:存量系统通过标准 API 网关快速获得 AI 能力,能力路由、认证鉴权、用量日志、限流熔断在网关层统一解决,旧系统零侵入接入。

一、问题拆解:存量系统 AI 升级的三个现实约束

企业培训平台的存量系统通常有这些特征:技术栈老旧(可能是十年前的单体架构)、核心业务稳定运行(不敢动)、数据资产庞大(迁移成本高)。AI 升级要在这三个约束下完成。

约束一:不能替换系统。 存量系统承载了课程、学员、考试、证书等核心业务数据,替换意味着巨大的迁移成本和业务中断风险。CIO 的决策永远是"优先在现有系统上加能力"。

约束二:不能大改存量代码。 存量系统的代码可能是外包团队写的、核心开发早已离职、文档严重缺失。在这种代码库上做大改动,改坏一个地方就是事故。AI 接入必须做到"低侵入"——最好只加一个依赖、配一段配置。

约束三:部署模式多元。 AI 能力的落地方式取决于客户的环境:合规要求高的客户要求私有化部署(模型跑在客户内网);有自有云账号的客户倾向于对接云服务商模型服务(token 费用走客户自己的云账单);采用 SaaS 模式的客户则由平台统一提供能力。网关方案必须同时适配这三种模式。

这三个约束指向同一个技术方案:把 AI 能力从业务系统里抽出来,做成独立的服务,通过统一的 API 网关暴露给存量系统调用。存量系统不需要知道 AI 能力背后的实现细节(调用了哪个大模型、用了哪家 ASR、算力跑在哪),只需要按标准 API 规范发起请求。

二、整体架构:AI 能力网关 + 三层适配

整体方案的核心是"一网关、两中心、三层适配":

┌─────────────────────────────────────────────────────────────┐
│                    存量培训系统(旧系统)                      │
│  课程管理    学员管理    考试系统    学习平台    ……            │
└──────────────────────────┬──────────────────────────────────┘
                           │ 标准 REST API / 异步任务
┌──────────────────────────▼──────────────────────────────────┐
│                统一 AI 能力网关 (AI Gateway)                  │
│                                                              │
│  ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────┐  │
│  │ 认证鉴权    │ │ 路由分发    │ │ 用量日志    │ │ 限流熔断  │  │
│  │ (API Key)  │ │ (能力路由)  │ │ (全量审计)  │ │ (兜底)   │  │
│  └────────────┘ └────────────┘ └────────────┘ └──────────┘  │
│                                                              │
│  ┌───────────────────────────────────────────────────────┐  │
│  │            AI 能力适配层 (Adapter Layer)               │  │
│  │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────────┐  │  │
│  │  │ ASR适配  │ │ TTS适配  │ │ LLM适配  │ │ 数字人适配  │  │  │
│  │  │ 器      │ │ 器      │ │ 器      │ │ 器         │  │  │
│  │  └────┬────┘ └────┬────┘ └────┬────┘ └─────┬──────┘  │  │
│  └───────┼───────────┼───────────┼────────────┼─────────┘  │
└──────────┼───────────┼───────────┼────────────┼────────────┘
           │           │           │            │
     ┌─────▼───┐  ┌────▼───┐  ┌───▼────┐  ┌────▼─────┐
     │ 云ASR    │  │ 云TTS   │  │ 云LLM   │  │ 私有化    │
     │ 服务商   │  │ 服务商  │  │ 服务商   │  │ 推理节点  │
     └─────────┘  └─────────┘  └─────────┘  └──────────┘

一网关:统一 AI 能力网关,是所有 AI 调用的唯一入口。存量系统只需要对接这一个地址,认证、路由、用量日志、限流全部在网关完成。

两中心:能力注册中心(管理有哪些 AI 能力、每个能力的路由和供应商配置)和用量日志中心(记录每一笔调用的完整信息,作为审计和成本核算的依据)。

三层适配:适配层屏蔽供应商差异。ASR 可能来自供应商 A、TTS 可能来自供应商 B、LLM 可能跑在私有化环境——存量系统不需要知道,网关按能力路由到正确的供应商。

关键设计:网关层不做"计费",只做"记账"

这是本方案与 SaaS 模式计量计费体系的关键区别。在存量系统接入场景中,AI 能力的计费主体由部署模式决定,网关不介入计价决策:

部署模式 能力来源 计费主体 网关职责
客户自有云 云服务商模型服务(阿里云百炼 / 华为云 ModelArts / 腾讯云混元等) 客户自己的云账号(token 按云服务商价格计费) 只路由 + 记用量日志,不介入计费
私有化部署 客户内网的模型推理节点 软件授权费(一次性/年费),不按量计费 只路由 + 记用量日志,供客户本地成本分析
平台 SaaS 平台统一提供 AI 能力 平台计费体系(如企学豆按量计费) 路由 + 用量日志(作为计费系统的事件源)

网关层统一做"用量日志"(每次调用记录:谁调用了什么能力、消耗了多少时长/token/次数),但"单价多少、向谁收钱"由上层计费系统决定。这样网关的逻辑在三种模式下完全一致,不需要为每种部署模式定制。

三、网关核心:统一 API 规范与认证鉴权

3.1 统一 API 规范

存量系统接入 AI 能力,统一通过 /ai/v1/{capability} 路径调用。规范的核心是"一个能力一个接口、请求响应格式统一"。

POST /ai/v1/asr/transcribe       语音识别(录音文件)
POST /ai/v1/tts/synthesize       语音合成
POST /ai/v1/llm/chat             大模型对话
POST /ai/v1/llm/extract          知识点萃取
POST /ai/v1/llm/qa               AI 问答
POST /ai/v1/question/generate    AI 出题
POST /ai/v1/grading/objective    客观题批阅
POST /ai/v1/grading/subjective   主观题批阅
POST /ai/v1/avatar/generate      数字人课件生成
POST /ai/v1/translate/document   文档翻译
POST /ai/v1/subtitle/generate    字幕生成

统一请求头:

X-Api-Key: <存量系统分配的 API Key>
X-Tenant-Id: <租户 ID>
X-Request-Id: <请求追踪 ID,幂等用>
Content-Type: application/json

统一响应结构:

{
   
  "code": 0,
  "message": "success",
  "requestId": "req_8f3a2b9c",
  "data": {
    ... }
}

错误码规范:

code 含义 处理建议
0 成功 -
40001 认证失败(API Key 无效) 检查 API Key 配置
40002 权限不足(能力未开通) 联系平台开通能力
40003 参数错误 检查请求参数
42901 触发限流 降低调用频率
50001 AI 服务内部错误 重试或联系平台
50002 供应商超时 重试或降级

3.2 网关认证与路由

网关基于 Spring Cloud Gateway 实现,核心是认证过滤器 + 路由断言。新增一个存量系统接入时,只需在配置中心注册 API Key 和开通的能力列表。

/**
 * AI 能力网关入口
 * 基于 Spring Cloud Gateway
 */
@Configuration
public class AiGatewayConfig {
   

    /**
     * 认证过滤器:校验 API Key 和租户权限
     */
    @Bean
    public GlobalFilter apiKeyAuthFilter() {
   
        return (exchange, chain) -> {
   
            // 1. 提取 API Key
            String apiKey = exchange.getRequest()
                .getHeaders().getFirst("X-Api-Key");
            if (apiKey == null || apiKey.isEmpty()) {
   
                return unauthorized(exchange, "缺少 X-Api-Key 请求头");
            }

            // 2. 校验 API Key 并获取租户信息
            TenantInfo tenant = tenantService.getByApiKey(apiKey);
            if (tenant == null) {
   
                return unauthorized(exchange, "API Key 无效");
            }

            // 3. 校验该租户是否开通了此能力
            String capability = extractCapability(exchange.getRequest().getPath().value());
            if (!tenant.hasCapability(capability)) {
   
                return forbidden(exchange, "未开通能力: " + capability);
            }

            // 4. 把租户信息放入请求上下文(后续路由和用量日志使用)
            exchange.getAttributes().put("tenant", tenant);

            return chain.filter(exchange);
        };
    }

    /**
     * 能力路由:把请求转发到对应的 AI 能力服务
     * 路由规则在配置中心维护,支持动态调整供应商
     */
    @Bean
    public RouteLocator aiRouteLocator(RouteLocatorBuilder builder) {
   
        return builder.routes()
            // 语音识别能力 → ASR 服务
            .route("asr-route", r -> r
                .path("/ai/v1/asr/**")
                .uri("lb://ai-asr-service"))  // 服务发现,按能力路由

            // 大模型能力 → LLM 服务
            .route("llm-route", r -> r
                .path("/ai/v1/llm/**")
                .uri("lb://ai-llm-service"))

            // 文档翻译 → 翻译服务
            .route("translate-route", r -> r
                .path("/ai/v1/translate/**")
                .uri("lb://ai-translate-service"))

            // 其他能力 → 默认服务
            .route("default-route", r -> r
                .path("/ai/v1/**")
                .uri("lb://ai-generic-service"))
            .build();
    }
}

3.3 存量系统的接入:三行代码

存量系统的接入成本被压到极低——只需要一个 HTTP 客户端封装。以 Java 存量系统为例:

/**
 * 存量系统侧的 AI 网关客户端
 * 
 * 接入成本:引入一个依赖 + 配置 3 个参数
 * 存量系统不需要引入任何 AI SDK,只需要 HTTP 调用
 */
@Component
public class AiGatewayClient {
   

    @Value("${ai.gateway.url:https://ai-gateway.example.com}")
    private String gatewayUrl;

    @Value("${ai.gateway.apiKey}")
    private String apiKey;

    @Value("${ai.gateway.tenantId}")
    private String tenantId;

    private RestTemplate restTemplate = new RestTemplate();

    /**
     * 通用调用方法:所有 AI 能力都走这里
     */
    public <T> T call(String capability, Object requestBody, Class<T> responseType) {
   
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("X-Api-Key", apiKey);
        headers.set("X-Tenant-Id", tenantId);
        headers.set("X-Request-Id", UUID.randomUUID().toString());

        HttpEntity<String> entity = new HttpEntity<>(
            JSON.toJSONString(requestBody), headers
        );

        String url = gatewayUrl + "/ai/v1/" + capability;
        ResponseEntity<AiResponse> response = restTemplate.postForEntity(
            url, entity, AiResponse.class
        );

        if (response.getBody().getCode() != 0) {
   
            throw new AiGatewayException(
                response.getBody().getCode(),
                response.getBody().getMessage()
            );
        }

        return JSON.parseObject(
            JSON.toJSONString(response.getBody().getData()), responseType
        );
    }

    /**
     * 业务示例:存量考试系统调用 AI 出题
     */
    public List<Question> aiGenerateQuestions(String courseId, String courseText, int count) {
   
        GenerateQuestionRequest req = new GenerateQuestionRequest();
        req.setCourseId(courseId);
        req.setCourseText(courseText);
        req.setQuestionCount(count);
        req.setQuestionTypes(Arrays.asList("single", "multiple", "judge"));

        return call("question/generate", req, QuestionListResponse.class).getQuestions();
    }
}

存量系统接入的完整步骤只有三步:引入网关客户端依赖 → 配置 gateway.url / apiKey / tenantId → 业务代码中调用 aiGatewayClient.call(...)。不需要改数据库、不需要迁移数据、不需要改造已有接口。

四、用量日志:三种部署模式通用的"记账"层

网关不做计费,但必须做"记账"——每一笔 AI 调用的用量记录是后续所有成本核算的基础。这个设计对三种部署模式都成立。

4.1 用量日志过滤器

/**
 * 用量日志过滤器:记录每一笔 AI 调用的完整信息
 * 
 * 记录的字段:
 * - 谁调的(租户、调用方)
 * - 调了什么(能力、供应商)
 * - 用了多少(次数、时长、token 数)
 * - 结果如何(成功/失败、耗时)
 * 
 * 这些原始记录不参与计费决策,只作为事实数据沉淀,
 * 供上层按需加工(成本核算、审计、优化)。
 */
@Component
public class UsageLogFilter implements GlobalFilter {
   

    @Autowired
    private UsageLogRepository logRepository;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   
        long startTime = System.currentTimeMillis();
        TenantInfo tenant = exchange.getAttribute("tenant");
        String path = exchange.getRequest().getPath().value();
        String capability = extractCapability(path);
        String requestId = exchange.getRequest().getHeaders().getFirst("X-Request-Id");

        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
   
            // 从响应中提取用量信息(不同能力提取方式不同)
            UsageRecord usage = extractUsage(exchange, capability);

            // 组装完整的用量记录
            UsageLog log = new UsageLog();
            log.setRequestId(requestId);
            log.setTenantId(tenant.getTenantId());
            log.setCapability(capability);
            log.setVendor(resolveVendor(path));      // 实际路由到的供应商
            log.setAmount(usage.getAmount());         // 用量(次/分钟/token)
            log.setUnit(usage.getUnit());
            log.setDurationMs(System.currentTimeMillis() - startTime);
            log.setSuccess(exchange.getResponse().getStatusCode().is2xxSuccessful());
            log.setRequestTime(new Date(startTime));

            // 异步落库(不阻塞业务响应)
            logRepository.saveAsync(log);
        }));
    }

    /**
     * 从请求/响应中提取用量
     * 不同能力的提取逻辑不同:
     * - 按次:amount = 1
     * - 按时长:从响应中的时长字段提取
     * - 按 token:从 LLM 响应的 usage 字段提取
     */
    private UsageRecord extractUsage(ServerWebExchange exchange, String capability) {
   
        switch (capability) {
   
            case "llm/chat":
            case "llm/extract":
            case "llm/qa":
                // 从响应体解析 token 用量
                return extractTokenUsage(exchange);   // 如 prompt_tokens + completion_tokens
            case "asr/transcribe":
            case "subtitle/generate":
                // 从请求体提取音频时长
                return extractDurationUsage(exchange);
            default:
                // 按次计量
                return new UsageRecord(BigDecimal.ONE, "count");
        }
    }
}

为什么用量日志不能替代计费? 用量日志是"事实记录",计费是"商业决策"。同一个用量,在 SaaS 模式下乘以平台豆值单价就是客户账单;在客户自有云模式下,客户直接在云控制台看自己的 token 账单,网关日志只用来做内部成本分析;在私有化模式下,客户按授权付费,日志只用来评估资源使用率。网关层不掺和"单价",让用量日志保持纯粹的客观性,这是它能在三种模式下通用的原因。

4.2 用量日志的存储与查询

/**
 * 用量日志存储
 * 
 * 存储选型:
 * - 热数据(近 7 天):Elasticsearch,支持多维聚合查询
 * - 冷数据(7 天前):归档到对象存储,按天分桶
 */
@Service
public class UsageLogService {
   

    @Autowired
    private ElasticsearchTemplate esTemplate;

    /**
     * 按租户 × 能力聚合用量(近 7 天)
     * 三种模式通用的查询:客户/内部看"每个能力用了多少"
     */
    public List<UsageSummary> aggregateByCapability(Long tenantId, String from, String to) {
   
        // ES 聚合查询:按 capability 分组,求和 amount
        return esTemplate.aggregate(
            """
            {
   
              "size": 0,
              "query": {
   
                "bool": {
   
                  "filter": [
                    {
    "term": {
    "tenantId": %d } },
                    {
    "range": {
    "requestTime": {
    "gte": "%s", "lte": "%s" } } }
                  ]
                }
              },
              "aggs": {
   
                "by_capability": {
   
                  "terms": {
    "field": "capability.keyword" },
                  "aggs": {
   
                    "total_amount": {
    "sum": {
    "field": "amount" } }
                  }
                }
              }
            }
            """.formatted(tenantId, from, to)
        );
    }

    /**
     * 按供应商聚合用量
     * 客户自有云模式下,这个数据可以直接和云账单对账
     */
    public List<UsageSummary> aggregateByVendor(Long tenantId, String from, String to) {
   
        // 类似聚合,按 vendor 分组
    }
}

五、能力适配层:异构供应商的统一封装

适配层是网关背后的"翻译官"。同一个能力背后可能对接多家供应商——云服务商(阿里云百炼、华为云 ModelArts、腾讯云混元)、私有化推理节点、开源模型——适配层把差异全部隔离在内部,网关和存量系统看到的是统一的接口。

5.1 适配器接口设计

以"语音识别(ASR)"为例——供应商 A 按音频时长计费、支持 URL 上传;供应商 B 按文件大小计费、需要 Base64 上传。适配层把它们统一成同一个接口:

/**
 * ASR 能力适配器接口
 * 
 * 设计原则:适配器内部处理供应商差异,对外暴露统一语义
 */
public interface AsrAdapter {
   

    /**
     * 提交录音文件识别任务
     * 
     * @param audioUrl 音频文件的公网/私有 URL
     * @param options  识别选项(语言、是否需要说话人分离等)
     * @return 任务 ID(异步任务)
     */
    String submitTranscribe(String audioUrl, AsrOptions options);

    /**
     * 查询任务结果
     */
    AsrResult queryResult(String taskId);

    /**
     * 供应商名称(用于路由和日志)
     */
    String vendorName();

    /**
     * 供应商成本模型描述(内部使用,用于成本分析)
     * 例如:按时长计费 / 按 token 计费 / 本地算力
     */
    CostModel costModel();
}

/**
 * 适配器统一响应
 * 无论供应商返回什么格式,都转换为这个统一结构
 */
@Data
public class AsrResult {
   
    private String status;                 // SUCCESS / FAILED / PROCESSING
    private List<Segment> segments;        // 分段识别结果
    private String errorMessage;

    @Data
    public static class Segment {
   
        private String text;
        private Double startTime;           // 秒
        private Double endTime;             // 秒
        private String speakerId;           // 说话人 ID(开启分离时)
    }
}

/**
 * 供应商成本模型
 * 区分三种计费来源,供内部成本分析使用
 */
@Data
public class CostModel {
   
    /** 计费模式:per_use / per_duration / per_token / local_compute */
    private String billingMode;

    /** 计费来源:cloud_vendor(客户云账号) / local(私有化) / platform(SaaS 平台) */
    private String billingSource;

    /** 说明(如"客户云账号按 token 计费") */
    private String description;
}

5.2 云服务商适配实现(客户自有云模式)

客户自有云模式下,AI 能力直接对接客户云账号下的模型服务。适配器使用客户提供的云凭证调用,token 费用直接记在客户云账单上:

/**
 * 阿里云百炼适配器(客户自有云模式)
 * 
 * 关键点:
 * - 使用客户自己的 DashScope API Key(从租户配置读取)
 * - token 消耗直接计入客户云账号账单
 * - 适配器只负责调用和格式转换,不做任何计费
 */
@Component
@ConditionalOnProperty(name = "ai.deploy.mode", havingValue = "customer-cloud")
public class AliyunBailianAdapter implements LlmAdapter {
   

    @Autowired
    private TenantConfigService configService;

    @Override
    public LlmResult chat(LlmRequest request) {
   
        // 1. 从租户配置读取客户自己的 DashScope API Key
        String apiKey = configService.getTenantSecret(
            request.getTenantId(), "aliyun.dashscope.api_key"
        );

        // 2. 构造 OpenAI 兼容请求(百炼支持)
        OpenAiClient client = OpenAiClient.builder()
            .apiKey(apiKey)
            .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
            .build();

        ChatCompletion completion = client.chat().completions().create(
            ChatCompletionCreateParams.builder()
                .model(request.getModel() != null ? request.getModel() : "qwen-plus")
                .addUserMessage(request.getPrompt())
                .build()
        );

        // 3. 转换为统一格式(token 用量从响应中提取,记入用量日志)
        LlmResult result = new LlmResult();
        result.setContent(completion.choices().get(0).message().content());
        result.setPromptTokens(completion.usage().promptTokens());
        result.setCompletionTokens(completion.usage().completionTokens());
        return result;
    }

    @Override
    public String vendorName() {
   
        return "aliyun-bailian";
    }

    @Override
    public CostModel costModel() {
   
        // 客户云账号按 token 计费
        return new CostModel("per_token", "cloud_vendor", "客户云账号账单按 token 计费");
    }
}

5.3 私有化适配器实现(私有化部署模式)

私有化模式下,模型跑在客户内网的推理节点上(如 vLLM 部署的开源模型)。适配器指向本地推理服务:

/**
 * 私有化推理节点适配器(客户内网部署)
 * 
 * 关键点:
 * - 指向客户内网部署的 vLLM / Triton 等推理服务
 * - 无按量计费,成本已包含在软件授权费中
 * - 仍记录 token 用量(供客户评估资源使用率和容量规划)
 */
@Component
@ConditionalOnProperty(name = "ai.deploy.mode", havingValue = "private")
public class LocalVllmAdapter implements LlmAdapter {
   

    @Value("${ai.model.local.vllm-url:http://127.0.0.1:8000/v1}")
    private String vllmUrl;

    @Override
    public LlmResult chat(LlmRequest request) {
   
        // 本地 vLLM 提供 OpenAI 兼容接口
        OpenAiClient client = OpenAiClient.builder()
            .apiKey("local-dummy-key")   // 本地推理无需鉴权
            .baseUrl(vllmUrl)
            .build();

        ChatCompletion completion = client.chat().completions().create(
            ChatCompletionCreateParams.builder()
                .model(request.getModel() != null ? request.getModel() : "qwen2.5-14b")
                .addUserMessage(request.getPrompt())
                .build()
        );

        // 转换统一格式(token 用量记入日志,供容量分析)
        LlmResult result = new LlmResult();
        result.setContent(completion.choices().get(0).message().content());
        result.setPromptTokens(completion.usage().promptTokens());
        result.setCompletionTokens(completion.usage().completionTokens());
        return result;
    }

    @Override
    public String vendorName() {
   
        return "local-vllm";
    }

    @Override
    public CostModel costModel() {
   
        // 本地算力,无按量计费
        return new CostModel("local_compute", "local", "本地算力,包含在授权费中");
    }
}

5.4 供应商路由:按部署模式选择

适配层通过"供应商路由策略"决定每次调用走哪家。路由规则的核心维度是部署模式:

/**
 * 供应商路由服务
 * 
 * 路由策略(按优先级):
 * 1. 部署模式:客户自有云 → 云服务商适配器;私有化 → 本地节点;SaaS → 平台服务
 * 2. 租户指定:部分客户合同约定使用指定供应商
 * 3. 故障切换:主供应商失败后自动切换到备用
 */
@Service
public class VendorRouter {
   

    @Autowired
    private ApplicationContext context;

    /**
     * 获取当前部署模式下应该使用的 LLM 适配器
     */
    public LlmAdapter routeLlm(TenantInfo tenant) {
   
        // 1. 按部署模式筛选
        String deployMode = tenant.getDeployMode();  // saas / customer-cloud / private

        Map<String, LlmAdapter> allAdapters = context.getBeansOfType(LlmAdapter.class);

        // 2. 租户指定优先
        String preferred = tenant.getPreferredVendor("llm");
        if (preferred != null && allAdapters.containsKey(preferred)) {
   
            return allAdapters.get(preferred);
        }

        // 3. 按部署模式 + 健康状态选择
        return allAdapters.values().stream()
            .filter(a -> matchesDeployMode(a, deployMode))
            .filter(a -> !circuitBreaker.isOpen(a.vendorName()))
            .findFirst()
            .orElseThrow(() -> new NoAvailableVendorException("llm"));
    }

    private boolean matchesDeployMode(LlmAdapter adapter, String deployMode) {
   
        CostModel costModel = adapter.costModel();
        switch (deployMode) {
   
            case "private":
                return "local".equals(costModel.getBillingSource());
            case "customer-cloud":
                return "cloud_vendor".equals(costModel.getBillingSource());
            default:  // saas
                return "platform".equals(costModel.getBillingSource());
        }
    }
}

六、接入模式:同步调用与异步任务

不同的 AI 能力耗时差异巨大。AI 问答(LLM)几秒内返回,可以同步调用;AI 字幕(ASR + 翻译)可能几分钟,必须异步任务。网关需要同时支持两种模式。

6.1 同步调用(低延迟能力)

存量系统 ──POST /ai/v1/llm/qa──▶ 网关 ──▶ LLM 适配器 ──▶ 大模型
存量系统 ◀──────── 2-5s 返回 ──── 网关 ◀── 适配器响应

同步调用适合:AI 问答、客观题批阅、知识点萃取(单段)。

/**
 * 同步调用示例:AI 问答
 */
public String aiQa(Long tenantId, String question) {
   
    QaRequest req = new QaRequest();
    req.setQuestion(question);
    req.setKnowledgeBaseId("kb_product_manual");

    return gatewayClient.call("llm/qa", req, QaResponse.class).getAnswer();
}

6.2 异步任务(高耗时能力)

对于耗时超过 30 秒的能力,网关提供标准的"提交任务 + 查询状态 + 结果回调"三接口模式:

存量系统 ──POST /ai/v1/asr/transcribe──▶ 网关 ──提交任务──▶ 适配器
存量系统 ◀──── {taskId: "task_123"} ───── 网关
存量系统 ──GET /ai/v1/asr/tasks/task_123──▶ 网关
存量系统 ◀──── {status: "PROCESSING"} ──── 网关
     ... 轮询或等回调 ...
存量系统 ◀──── {status: "SUCCESS", data: {...}} ──── 网关
/**
 * 异步任务管理(网关侧)
 */
@RestController
@RequestMapping("/ai/v1")
public class AsyncTaskController {
   

    @Autowired
    private AsyncTaskService taskService;

    /**
     * 提交异步任务
     */
    @PostMapping("/{capability}/tasks")
    public Result<SubmitResponse> submitTask(
            @PathVariable String capability,
            @RequestBody SubmitRequest request,
            ServerWebExchange exchange) {
   

        TenantInfo tenant = (TenantInfo) exchange.getAttributes().get("tenant");

        // 创建任务记录
        AsyncTask task = taskService.createTask(
            tenant.getTenantId(), capability, request
        );

        // 异步执行(投递到任务队列)
        taskService.executeAsync(task.getTaskId());

        return Result.ok(new SubmitResponse(task.getTaskId()));
    }

    /**
     * 查询任务状态
     */
    @GetMapping("/{capability}/tasks/{taskId}")
    public Result<AsyncTaskStatus> queryTask(
            @PathVariable String taskId) {
   
        return Result.ok(taskService.getStatus(taskId));
    }

    /**
     * 结果回调(可选,替代轮询)
     * 存量系统注册回调地址,任务完成时网关主动通知
     */
    @PostMapping("/{capability}/tasks/{taskId}/callback")
    public Result<Void> registerCallback(
            @PathVariable String taskId,
            @RequestBody CallbackConfig config) {
   
        taskService.registerCallback(taskId, config.getCallbackUrl());
        return Result.ok();
    }
}

七、私有化部署架构:数据不出域的合规方案

金融、国企客户的合规要求是"数据不出域"——员工的培训数据、考试数据不能离开企业网络。私有化模式下,网关和模型推理全部部署在客户内网:

┌────────────────────────────────────────────────┐
│              企业内网(客户私有环境)              │
│                                                │
│  ┌──────────┐   ┌──────────┐   ┌────────────┐  │
│  │ 存量系统   │   │ AI 能力   │   │ 私有化模型   │  │
│  │          │   │ 网关      │   │ 推理服务     │  │
│  │ (不动)    │──▶│ (私有化部署)│──▶│ (vLLM等)   │  │
│  └──────────┘   └──────────┘   └────────────┘  │
│                      │                         │
│                 ┌────▼────┐   ┌────────────┐   │
│                 │ 用量日志  │   │ ASR/TTS    │   │
│                 │ (本地存储) │   │ 私有化节点  │   │
│                 └─────────┘   └────────────┘   │
└────────────────────────────────────────────────┘
        │(仅同步用量汇总/审计摘要,不传业务数据)
┌───────▼────────────────────────────────────────┐
│       平台管理面(可选,用于运维支持)             │
│  能力配置下发 · 模型更新包 · 用量汇总上报          │
└────────────────────────────────────────────────┘

私有化部署的关键设计:

能力配置可下发。 网关的能力路由规则、供应商配置支持从平台管理面下发,客户侧部署的网关定期同步配置。新增一个能力时,客户不需要重新部署网关。

模型更新离线化。 大模型、ASR、TTS 模型的更新以"更新包"形式提供,客户下载后在私有环境离线安装,模型推理全程不离开企业网络。

用量日志本地存储。 私有化模式下用量日志只存在客户本地(用于客户自己的容量规划和成本分析),只向平台上报"用量汇总摘要"(按功能聚合的统计值),不上报具体业务内容。

# 私有化部署的网关配置示例
ai:
  gateway:
    mode: private            # 私有化模式
    sync-config-interval: 3600  # 每小时从平台管理面同步配置

  model:
    llm: local-vllm          # 本地大模型推理服务
    asr: local-asr-node      # 本地 ASR 节点
    tts: local-tts-node      # 本地 TTS 节点

  usage-log:
    storage: local           # 用量日志本地存储
    report-summary: "0 0 2 * * ?"  # 每天上报聚合摘要

  compliance:
    data-residency: on-prem   # 数据驻留本地
    audit-log: enabled        # 全量审计日志

八、工程细节:限流、熔断与降级

8.1 按能力限流

存量系统接入后,如果调用方代码有 bug(比如死循环调用 AI 问答),可能把 AI 服务打爆。网关按"租户 × 能力"维度限流:

/**
 * 限流过滤器:按租户 × 能力维度限流
 * 实现:Redis 滑动窗口计数
 */
@Component
public class RateLimitFilter implements GlobalFilter {
   

    @Autowired
    private StringRedisTemplate redisTemplate;

    /** Lua 脚本:滑动窗口限流 */
    private static final String RATE_LIMIT_LUA =
        "local key = KEYS[1] " +
        "local window = tonumber(ARGV[1]) " +   // 窗口大小(秒)
        "local limit = tonumber(ARGV[2]) " +    // 窗口内最大次数
        "local now = redis.call('TIME')[1] " +
        "redis.call('ZREMRANGEBYSCORE', key, 0, now - window) " +
        "local count = redis.call('ZCARD', key) " +
        "if count < limit then " +
        "  redis.call('ZADD', key, now, now .. '_' .. ARGV[3]) " +
        "  redis.call('EXPIRE', key, window) " +
        "  return 1 " +
        "end " +
        "return 0 ";

    public boolean tryAcquire(TenantInfo tenant, String capability) {
   
        String key = "ai:ratelimit:" + tenant.getTenantId() + ":" + capability;
        String reqId = UUID.randomUUID().toString().substring(0, 8);

        // 默认:每分钟 60 次(可在配置中心调整)
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(RATE_LIMIT_LUA, Long.class),
            List.of(key),
            "60", "60", reqId
        );

        return result != null && result == 1L;
    }
}

8.2 供应商故障熔断

某个供应商(如客户云账号下的模型服务)故障时,不能把存量系统的调用全部拖死。熔断机制配合"供应商路由"实现自动切换:

/**
 * 供应商熔断器
 * 
 * 策略(滑动窗口):
 * - 10 秒内错误率超过 50%,熔断 30 秒
 * - 熔断期间自动切换到备用供应商
 * - 30 秒后进入半开状态,放少量请求探测
 */
@Component
public class CircuitBreaker {
   

    private final Map<String, CircuitState> states = new ConcurrentHashMap<>();

    public void recordFailure(String vendorName) {
   
        CircuitState state = states.computeIfAbsent(vendorName, k -> new CircuitState());
        state.recordFailure();
    }

    public boolean isOpen(String vendorName) {
   
        CircuitState state = states.get(vendorName);
        return state != null && state.isOpen();
    }

    @Data
    public static class CircuitState {
   
        private final Deque<Long> failureTimes = new ArrayDeque<>();
        private long openedAt = 0;

        public void recordFailure() {
   
            long now = System.currentTimeMillis();
            // 清理 10 秒前的记录
            while (!failureTimes.isEmpty() && now - failureTimes.peekFirst() > 10000) {
   
                failureTimes.pollFirst();
            }
            failureTimes.addLast(now);

            // 10 秒内错误超过 5 次 → 熔断 30 秒
            if (failureTimes.size() >= 5) {
   
                openedAt = now;
                log.warn("Circuit opened for vendor, failures: {}", failureTimes.size());
            }
        }

        public boolean isOpen() {
   
            if (openedAt == 0) return false;
            // 熔断 30 秒后自动半开
            if (System.currentTimeMillis() - openedAt > 30000) {
   
                openedAt = 0;
                failureTimes.clear();
                return false;
            }
            return true;
        }
    }
}

8.3 存量系统的降级策略

存量系统接入 AI 后,必须考虑"AI 不可用时业务怎么办"。降级策略在存量系统侧配置:

/**
 * AI 能力降级策略(存量系统侧)
 * 
 * 原则:AI 是增强能力,不是核心依赖。
 * AI 故障时,业务应该回到"人工模式",而不是让整个业务挂掉。
 */
@Component
public class AiFallbackManager {
   

    /**
     * 统一降级处理
     * 
     * @param capability AI 能力标识
     * @param fallback   人工/传统模式的处理逻辑
     */
    public <T> T withFallback(String capability, Supplier<T> aiCall,
                              Supplier<T> fallback) {
   
        try {
   
            return aiCall.get();
        } catch (AiGatewayException e) {
   
            log.warn("AI capability {} failed (code={}), falling back",
                capability, e.getCode());

            // 记录降级事件(供运营统计 AI 可用率)
            metricCollector.recordFallback(capability, e.getCode());

            // 执行降级逻辑
            return fallback.get();
        }
    }

    /**
     * 示例:AI 出题失败 → 人工出题
     */
    public List<Question> generateQuestionsWithFallback(String courseText) {
   
        return withFallback(
            "question/generate",
            () -> aiGatewayClient.call("question/generate", ...),
            () -> manualQuestionService.generateByTemplate(courseText)  // 传统模板出题
        );
    }

    /**
     * 示例:AI 批阅失败 → 人工批阅
     */
    public GradingResult gradeWithFallback(String answerText, String standardAnswer) {
   
        return withFallback(
            "grading/subjective",
            () -> aiGatewayClient.call("grading/subjective", ...),
            () -> {
   
                // 传统模式:标记为待人工批阅,进入人工队列
                manualGradingQueue.enqueue(answerText, standardAnswer);
                return GradingResult.pendingManual();
            }
        );
    }
}

九、效果总结

这套接入方案在多个存量培训系统升级项目中落地,关键数据:

指标 效果
存量系统接入成本 引入 1 个 HTTP 客户端依赖 + 配置 3 个参数
接入周期 单个存量系统从评估到联调上线约 2-3 天
存量代码改动量 0 行(业务数据、表结构、已有接口全部不动)
部署模式适配 SaaS / 客户自有云 / 私有化三模式切换只改配置
AI 能力上线速度 新能力只需网关加一条路由 + 配一个适配器
供应商切换耗时 分钟级(改配置中心路由即可,业务无感)
私有化合规 业务数据零出域,仅上报用量聚合摘要
AI 可用率 网关层限流 + 熔断 + 降级,存量业务零中断

三个核心经验:

网关层不做计费,只做记账。 这是本方案与 SaaS 计量体系的关键区别。AI 能力的计费主体由部署模式决定——客户自有云走云服务商 token 账单、私有化走软件授权费、SaaS 才走平台计量。网关统一做"用量日志"(事实记录),计费决策留给上层,这样一套网关逻辑就能适配三种部署模式。

适配层决定可扩展性。 云服务商适配器用客户的云凭证调用(token 费用走客户账单),私有化适配器指向本地推理节点。切换部署模式或供应商只需要改配置,存量系统无感知。适配层的 CostModel 明确标注"计费来源",避免网关误判计费主体。

降级策略必须在接入前设计好。 AI 是增强能力,不是核心依赖。存量系统的业务连续性不能押在 AI 供应商的稳定性上,每项 AI 能力都要设计"AI 不可用时回到人工模式"的兜底路径。

如果你的培训系统也有 AI 升级需求但担心改动风险,这个方案的核心思路是:不动存量系统,把 AI 能力做成标准化的外部服务,通过网关接入。 业务系统还是那个业务系统,只是多了一个"AI 能力供应商"。

目录
相关文章
|
18天前
|
人工智能 缓存 前端开发
DeepSeek Harness 首发实测 + 入门教程,夯爆了!梁神我错了
DeepSeek Harness + DeepSeek V4 Pro 项目实战保姆级教程!手把手带你从零安装开源 AI 编程工具,开发架构图、知识讲解网站、3D 网页游戏、全栈 AI 应用 4 个项目,覆盖运行模式选择、插件安装与开发,看看能不能对标 Claude。
13029 82
DeepSeek Harness 首发实测 + 入门教程,夯爆了!梁神我错了
|
6天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
|
12天前
|
Web App开发 人工智能 API
16 个超火的 DeepSeek Harness 插件,大肥鱼已经落后 N 个版本了。。。
DeepSeek Harness 精选插件推荐合集,从图片识别、浏览器操控、多 Agent 协作到手机远程控制,一口气带你看完 DSH 社区热门的十几个插件,覆盖技能扩展、UI 界面增强、整活玩法三大类,让你的鲸鱼变得更强。
1693 4
|
人工智能 JavaScript 开发工具
DeepSeek Harness 本地安装与使用指南
DeepSeek Harness(DSH)是DeepSeek AI开源的Agent运行框架,支持本地文件操作、命令执行与工具调用。基于Cordis插件架构,具备高扩展性与强可控性,适合开发者搭建可控Agent环境或开展模型基准测试。当前为开发者预览版,需Node.js环境,推荐先用`npx @deepseek-ai/dsh web`快速体验。
5093 0
|
13天前
|
人工智能 Java BI
【AI】DeepSeek Harness 安装、运行、管理插件
本文介绍了如何运行DeepSeek开源的Agent框架DeepSeek Harness(dsh)。主要内容包括:使用nvm安装适配的Node版本;通过代理加速克隆GitHub源码;使用pnpm安装依赖并启动项目;配置DeepSeek API Token;安装扩展功能的插件。该框架自带Web界面,支持模型适配、文件编辑等插件化功能
1857 1
|
15天前
|
人工智能 JavaScript 测试技术
保姆级教程:DeepSeek Harness从安装到跑通测试,30分钟上手
DeepSeek Harness是DeepSeek开源的AI Agent运行时,主打“一行命令安装、5分钟跑通”。它让模型真正动手干活——读代码、跑测试、分析失败、生成修复方案。本文手把手教你30分钟从零上手,覆盖安装、配置、实测及避坑指南,助你快速掌握下一代AI编程范式。
|
16天前
|
开发工具 Swift git
DeepSeek Harness 插件推荐:4 款开源神器让写代码直接起飞
DeepSeek Harness 插件推荐:ModLens 视觉、Web UI 全家桶、Mac 原生与 GenUI 渲染,4 款开源插件给纯文本模型补齐短板。
2050 6
DeepSeek Harness 插件推荐:4 款开源神器让写代码直接起飞
|
14天前
|
人工智能 JavaScript 测试技术
从 0 到 1,DeepSeek Harness 保姆级安装与使用教程!
DeepSeek Harness是DeepSeek推出的开源Agent运行框架,秉持“一切皆插件”理念,支持模型、工具、技能、工作流等全模块自由替换与扩展。其核心Cordis内核实现动态插件管理,赋能Agent自进化。已成GitHub史上增速最快开源项目(15w+ Star),标志着国内大模型从拼价格转向重架构与生态的新拐点。
1328 6
从 0 到 1,DeepSeek Harness 保姆级安装与使用教程!

热门文章

最新文章