按业务档位选模型
同一套代码既要跑便宜的快速模型,又要在用户选择"更聪明"时切到更强的模型,是接入大模型时的常见需求。做法不是把模型名写死在调用处,而是让业务只表达档位意图(快 / 思考 / 专家),由一层映射把档位翻译成"平台 + 模型",再交给 UniChatRequest 统一发送。
这样做的收益很直接:
- 业务代码只出现
fast/thinking/expert这类稳定语义,不散落各家模型名 - 换模型、调价、平台不可用时降级,都只改映射这一处
- 档位可以随请求体从客户端一路传到服务端,前端不需要知道任何模型名
一、模型常量
各平台的可选模型都以常量形式集中在对应接口里,业务侧引用常量而不是手写字符串,拼错时编译期就能发现。
Anthropic 系列(经交换 token 接入时用同一套模型标识):
import nexus.io.exchangetoken.ExchangetokenModels;
String sonnet = ExchangetokenModels.CLAUDE_SONNET_5_5; // claude-sonnet-5_5
String opus = ExchangetokenModels.CLAUDE_OPUS_5_5; // claude-opus-5-5
DeepSeek 官方平台的轻量模型:
import nexus.io.deepseek.DeepSeekModels;
String flash = DeepSeekModels.deepseek_flash; // deepseek-flash
平台名同样有常量,避免手写字符串:
import nexus.io.consts.ModelPlatformName;
String deepseek = ModelPlatformName.DEEPSEEK; // deepseek
String exchange = ModelPlatformName.EXCHANGE_TOKEN_ANTHROPIC; // exchange_token_anthropic
二、把档位映射成平台与模型
映射集中放在一个方法里,业务只调用它。要点是先判档位、再留一个显式覆盖的出口:请求里若已经指定了模型,就以它为准,方便测试与内部任务精确控制。
package com.example.model;
import nexus.io.consts.ModelPlatformName;
import nexus.io.deepseek.DeepSeekModels;
import nexus.io.exchangetoken.ExchangetokenModels;
import nexus.io.tio.utils.hutool.StrUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class GenerationModeRouter {
public static final String FAST = "fast";
public static final String THINKING = "thinking";
public static final String EXPERT = "expert";
/**
* 把档位翻译成平台与模型。档位为空或无法识别时按 fast 处理,
* 保证不传这个字段的老客户端行为不变。
*/
public void applyMode(ModelChoice choice) {
String mode = StrUtil.isBlank(choice.getMode()) ? FAST : choice.getMode().trim().toLowerCase();
if (THINKING.equals(mode)) {
choice.setPlatform(ModelPlatformName.EXCHANGE_TOKEN_ANTHROPIC);
if (StrUtil.isBlank(choice.getModel())) {
choice.setModel(ExchangetokenModels.CLAUDE_SONNET_5_5);
}
} else if (EXPERT.equals(mode)) {
choice.setPlatform(ModelPlatformName.EXCHANGE_TOKEN_ANTHROPIC);
if (StrUtil.isBlank(choice.getModel())) {
choice.setModel(ExchangetokenModels.CLAUDE_OPUS_5_5);
}
} else {
choice.setPlatform(ModelPlatformName.DEEPSEEK);
if (StrUtil.isBlank(choice.getModel())) {
choice.setModel(DeepSeekModels.deepseek_flash);
}
}
log.info("mode:{}, platform:{}, model:{}", mode, choice.getPlatform(), choice.getModel());
}
}
ModelChoice 是承载平台、模型与档位的最小对象;实际工程里通常是已有的请求 VO,只要带上这三个字段即可。
三、在 handler 里传入档位
档位随请求体进来,handler 只负责把它交给路由,不自己拼模型名。
import com.litongjava.study11.model.ExplanationVo;
import com.example.model.GenerationModeRouter;
public void bindMode(ExplanationVo input) {
// mode 由请求体解析而来,例如 "thinking"
// 平台与模型由路由决定,不再按域名或入口写死
Aop.get(GenerationModeRouter.class).applyMode(input);
}
Aop 取自 nexus.io.jfinal.aop.Aop。业务服务同样通过它取实例,不要 new。
四、发现请求实际用了哪个模型
每次映射都打一行日志,线上排查"某个档位出片慢/贵"时,直接看这一行就能确认实际命中的平台与模型:
mode:thinking, platform:exchange_token_anthropic, model:claude-sonnet-5_5
若某次请求在映射之后仍被覆盖成别的模型,说明业务侧显式设置了 model;这是预期行为,日志会真实反映最终取值。
五、要点
- 档位是给用户的稳定语义,模型名是易变细节,两者之间必须有一层映射
- 显式指定的
model优先级高于档位,映射只在它为空时兜底 - 档位为空按快速档处理,兼容不传该字段的旧客户端
- 每个平台的模型常量都在对应接口里,引用常量而不是手写字符串
