Skip to main content

AI基础设施层宏观设计

前面的 RAG 理论系列和知识库实战系列,我们从 RAG 的概念讲到了评估优化,从文件上传讲到了分块存储。到这里,你应该已经清楚一个 RAG 系统的完整链路:用户提问 → 检索知识库 → 重排序 → 喂给大模型生成回答。

但有一个问题一直被我们当作黑盒处理——模型调用。

前面的文章里,每次需要调大模型的时候,我们都是直接写 OkHttp 请求,往百炼或者硅基流动发一个 HTTP 调用,拿到结果就完事。这在教学环境里没问题,但放到 Ragent 这样一个需要同时支持百炼、硅基流动、Ollama 等多个供应商,并且覆盖 Chat、Embedding、Rerank 三种模型能力的项目里,事情就没这么简单了。

这些模型调用在项目中到底是怎么组织的?直接在 Service 里写 OkHttp 调用?如果百炼挂了怎么办?如果要加一个新的供应商怎么办?

从这篇开始,我们进入大模型调度引擎实战系列。这个系列会逐篇拆解 Ragent 项目的 infra-ai 模块——它是整个项目的 AI 基础设施层,负责屏蔽供应商差异、实现多模型路由和故障转移。这一篇先从宏观视角讲清楚它的整体设计。

为什么需要 AI 基础设施层

1. 没有 infra 层的世界

假设你在做一个电商客服知识库系统。产品需求很明确:用户提问时,先调 Embedding 模型把问题向量化,再去向量数据库检索相关文档,用 Reranker 精排一遍,最后把 chunk 喂给 Chat 模型生成回答。

一开始,你选了百炼作为模型供应商,直接在业务 Service 里写调用代码:

// ChatService.java —— 直接调百炼 Chat API
public String chat(String prompt) {
JsonObject body = new JsonObject();
body.addProperty("model", "qwen-plus");
body.add("messages", buildMessages(prompt));

Request request = new Request.Builder()
.url("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions")
.addHeader("Authorization", "Bearer " + BAILIAN_API_KEY)
.post(RequestBody.create(body.toString(), JSON))
.build();

Response response = httpClient.newCall(request).execute();
// ... 解析响应
}

能跑,没毛病。Embedding 调用也差不多,换个 URL 和请求格式就行。

然后问题来了。

场景一:百炼 API 挂了。 某天下午百炼服务抖了一下,返回 500 错误。你的整个客服系统直接瘫痪——因为所有对话都走百炼,没有备选方案。老板问你为什么系统挂了,你说供应商挂了。老板说那你怎么不做个备份?

场景二:加一个硅基流动做备份。 老板的要求合理,你开始加硅基流动。但硅基流动的 API 地址不一样(https://api.siliconflow.cn/v1/chat/completions),认证方式一样但模型名不同(deepseek-ai/DeepSeek-V3)。你在 ChatService 里加了一堆 if-else:

public String chat(String prompt) {
if ("bailian".equals(currentProvider)) {
// 百炼的 URL、Header、模型名
} else if ("siliconflow".equals(currentProvider)) {
// 硅基流动的 URL、Header、模型名
}
// 失败了还要 try-catch 切换到另一个供应商...
}

场景三:再加一个 Ollama 做本地部署。 测试环境不想烧钱调云端 API,装了一个 Ollama 跑本地模型。但 Ollama 不需要 API Key,端点路径也不一样(http://localhost:11434/v1/chat/completions)。又是一套 if-else。

场景四:Embedding 和 Rerank 也要同样的逻辑。 你发现 EmbeddingService 也需要做供应商切换和容错,RerankService 也要。三个 Service 里面,每个都有一堆重复的 HTTP 调用代码、供应商判断逻辑、错误处理代码。

到这里,代码已经变成了这个样子:三个 Service 里散落着三个供应商的 if-else 判断,每个 Service 都在重复做 HTTP 请求构建、响应解析、错误处理。供应商的 URL、API Key、模型名硬编码在代码里。想加个新供应商?每个 Service 都得改。想调整优先级?改代码重新部署。

这就是没有基础设施层的代价。

2. 我们需要什么

从上面的反面例子里,可以提炼出四个核心需求:

统一接口,屏蔽差异。 业务层不应该关心当前调的是百炼还是硅基流动,更不应该知道各家 API 的请求格式有什么区别。业务层只需要说我要做一次 Chat 调用,传入 Prompt,拿到结果。

多模型路由与故障转移。 同一种能力(比如 Chat)可以配置多个候选模型,按优先级排序。高优先级的模型挂了,自动切换到下一个,业务层完全无感知。

配置驱动,零代码切换。 加一个新供应商、调整模型优先级、切换默认模型——这些操作不应该改代码,改配置文件就够了。

新供应商可插拔扩展。 哪天要接入一个 vLLM 私有部署的推理服务,只需要实现一个客户端类,不需要动任何已有代码。

这就是 AI 基础设施层要解决的问题。

3. infra-ai 模块的定位

用一句话概括:infra-ai 是业务层和模型供应商之间的中间层。

业务层只依赖 infra-ai 暴露的三个接口(LLMServiceEmbeddingServiceRerankService),完全不感知具体供应商。供应商是谁、优先级怎么排、挂了怎么切——这些全部由 infra-ai 内部处理。

整体架构总览

1. 模块包结构

infra-ai 模块共有 9 个包,每个包有明确的职责边界:

包名职责核心类
config配置根AIModelProperties——将 YAML 配置绑定为类型安全的 Java 对象
enums类型词汇表ModelProvider(供应商枚举)、ModelCapability(能力枚举)
model路由核心ModelSelector(选谁)、ModelHealthStore(能不能调)、ModelRoutingExecutor(怎么调)、ModelTarget(调用目标)、ModelCaller(函数式调用接口)
httpHTTP 基础设施ModelUrlResolver(URL 解析)、HttpResponseHelper(响应工具)、ModelClientException(统一异常)、ModelClientErrorType(错误分类)、HttpMediaTypes(常量)
chatLLM 对话子系统LLMService(业务接口)、ChatClient(供应商接口)、AbstractOpenAIStyleChatClient(模板方法基类)、三个供应商实现、RoutingLLMService(路由服务)、流式相关组件
embedding向量化子系统EmbeddingService(业务接口)、EmbeddingClient(供应商接口)、两个供应商实现、RoutingEmbeddingService(路由服务)
rerank重排序子系统RerankService(业务接口)、RerankClient(供应商接口)、BaiLianRerankClient(百炼实现)、NoopRerankClient(空对象实现)、RoutingRerankService(路由服务)
tokenToken 估算TokenCounterService(接口)、HeuristicTokenCounterService(启发式实现)
util工具类LLMResponseCleaner(清理大模型响应中的 Markdown 代码围栏)

整个模块约 40 个类,代码量不大,但设计上很紧凑。9 个包可以分成三层来理解:

  • 底层基础设施configenumshttptokenutil——提供配置、常量、HTTP 工具等基础能力
  • 路由核心model——提供模型选择、健康检查、故障转移执行器,是整个模块的骨架
  • 能力子系统chatembeddingrerank——三条并行的业务线,每条线都建立在路由核心之上

2. 三种能力的并行结构

Chat、Embedding、Rerank 三条线的结构高度一致,都遵循同样的三层模式:

关键点在于:三种能力子系统共享同一套路由核心(ModelSelectorModelHealthStoreModelRoutingExecutor)。这意味着路由逻辑、熔断逻辑、故障转移逻辑只需要写一次,三种能力都能复用。

3. 一次模型调用的完整链路

解锁付费内容,👉 戳