Skip to main content

多模型路由与智能选择

上一篇我们从宏观视角看了 infra-ai 模块的整体架构,知道了一次模型调用会经过 ModelSelectorModelRoutingExecutorModelHealthStoreChatClient 这条链路。其中 ModelSelector 是链路的起点——它决定了候选列表的顺序,而顺序直接决定了优先调谁、失败了切换到谁。

上一篇用一段话概述了它的工作流程:读取配置 → 过滤禁用 → 按优先级排序 → 提升首选模型 → 排除熔断模型 → 返回有序列表。但没有展示代码,那些步骤里的细节都是黑盒。

这一篇打开 ModelSelector 这个黑盒,看看它内部到底是怎么从一份 YAML 配置中选出一个有序的、可用的候选列表的。同时也会讲清楚 ModelTarget 的构建过程和 ModelUrlResolver 的 URL 解析逻辑——它们和 ModelSelector 一起,构成了从配置到实际 HTTP 调用之间的完整桥梁。

ModelSelector 的选择算法

ModelSelector 是一个 Spring 组件,依赖注入两个对象:AIModelProperties(配置)和 ModelHealthStore(熔断器)。

@Slf4j
@Component
@RequiredArgsConstructor
public class ModelSelector {

private final AIModelProperties properties;
private final ModelHealthStore healthStore;
// ...
}

它对外暴露的方法不多,但每一个背后都有一套完整的选择算法。

1. 入口方法

ModelSelector 提供三个入口方法,分别对应三种能力:

public List<ModelTarget> selectChatCandidates(boolean deepThinking) {
AIModelProperties.ModelGroup group = properties.getChat();
if (group == null) {
return List.of();
}

String firstChoiceModelId = resolveFirstChoiceModel(group, deepThinking);
return selectCandidates(group, firstChoiceModelId, deepThinking);
}

public List<ModelTarget> selectEmbeddingCandidates() {
return selectCandidates(properties.getEmbedding());
}

public List<ModelTarget> selectRerankCandidates() {
return selectCandidates(properties.getRerank());
}

Chat 和其他两种能力有一个明显的差异:Chat 多了一个 deepThinking 参数。这个参数决定了是走普通模式还是深度思考模式,两种模式选出来的候选列表可能完全不同——不只是顺序不同,连包含哪些候选都不一样。

Embedding 和 Rerank 的选择逻辑是 Chat 的简化版。它们调的是一个重载方法,内部不做深度思考过滤,其他步骤和 Chat 一致:

private List<ModelTarget> selectCandidates(AIModelProperties.ModelGroup group) {
if (group == null) {
return List.of();
}
return selectCandidates(group, group.getDefaultModel(), false);
}

2. 确定首选模型

在正式排序之前,ModelSelector 先确定谁应该排第一位。这个逻辑由 resolveFirstChoiceModel 方法完成:

private String resolveFirstChoiceModel(AIModelProperties.ModelGroup group, boolean deepThinking) {
if (deepThinking) {
String deepModel = group.getDeepThinkingModel();
if (StrUtil.isNotBlank(deepModel)) {
return deepModel;
}
}
return group.getDefaultModel();
}

逻辑很直白:如果是深度思考模式且配了 deep-thinking-model,用它;否则用 default-model

对应到配置:

chat:
default-model: qwen3-max # 普通模式的首选
deep-thinking-model: qwen3-max # 深度思考模式的首选

这两个字段的值可以相同也可以不同。如果你希望深度思考走一个专门的推理模型(比如 OpenAI o1、DeepSeek-R1),就把 deep-thinking-model 配成那个模型的 id。如果没配 deep-thinking-model,深度思考模式会回退到 default-model

注意:resolveFirstChoiceModel 返回的是一个模型 id 字符串,不是模型对象。这个 id 会在后续的首选模型提升步骤中用到。

3. 过滤与排序

确定了首选模型后,进入核心的过滤排序逻辑。整个 selectCandidates 方法分两步:先准备有序候选列表,再构建可用目标。

private List<ModelTarget> selectCandidates(
AIModelProperties.ModelGroup group,
String firstChoiceModelId,
boolean deepThinking) {
if (group == null || group.getCandidates() == null) {
return List.of();
}

List<AIModelProperties.ModelCandidate> orderedCandidates =
filterAndSortCandidates(group.getCandidates(), firstChoiceModelId, deepThinking);

return buildAvailableTargets(orderedCandidates);
}

filterAndSortCandidates 是整个选择算法中信息密度最高的方法:

private List<AIModelProperties.ModelCandidate> filterAndSortCandidates(
List<AIModelProperties.ModelCandidate> candidates,
String firstChoiceModelId,
boolean deepThinking) {

List<AIModelProperties.ModelCandidate> enabled = candidates.stream()
.filter(c -> c != null && !Boolean.FALSE.equals(c.getEnabled()))
.filter(c -> !deepThinking || Boolean.TRUE.equals(c.getSupportsThinking()))
.sorted(Comparator
.comparing((AIModelProperties.ModelCandidate c) ->
!Objects.equals(resolveId(c), firstChoiceModelId))
.thenComparing(AIModelProperties.ModelCandidate::getPriority,
Comparator.nullsLast(Integer::compareTo))
.thenComparing(AIModelProperties.ModelCandidate::getId,
Comparator.nullsLast(String::compareTo)))
.collect(Collectors.toList());

if (deepThinking && enabled.isEmpty()) {
log.warn("深度思考模式没有可用候选模型");
}

return enabled;
}

这段代码在一条 Stream 链里完成了过滤、排序、首选提升三件事,拆开来看:

3.1 过滤禁用候选

.filter(c -> c != null && !Boolean.FALSE.equals(c.getEnabled()))

enabled 字段默认是 true。只有显式配了 enabled: false 的候选才会被过滤掉。这意味着你可以在不删除配置的情况下临时禁用某个模型——比如某个供应商在做维护,你把它的候选标记为 enabled: false,等恢复了再改回来。

3.2 深度思考模式的额外过滤

.filter(c -> !deepThinking || Boolean.TRUE.equals(c.getSupportsThinking()))

这行的逻辑是:如果不是深度思考模式(deepThinkingfalse),所有候选都放行;如果是深度思考模式,只放行 supportsThinking = true 的候选。

用自然语言翻译就是:普通模式不做额外过滤;深度思考模式只保留支持深度思考的模型。

3.3 排序规则(含首选模型提升)

解锁付费内容,👉 戳