Skip to content
LangChain4j 文档版本 1.22.0 源快照 7eea271f 更新于 2026-10-14

模型路由

NOTE

模型路由尚处于实验阶段,未来版本可能变化。

不同的请求需要不同的模型:一句问候或一个简短的事实问题可以由一个小巧、快速、 便宜的模型回答,而写代码或做详尽分析则需要一个更大的模型。借助模型路由,每个请求都会被 发送给适合它的模型,而无需改动应用的其余部分。

RoutingChatModel 是一个 ChatModel,它按 ChatModelRouter 的决定,把每个请求发送到若干聊天模型(路由)之一。由于它就是一个 ChatModel,凡是用到聊天模型的地方都能用它:AI 服务、智能体、 RAG 等。

java
ChatModel chatModel = RoutingChatModel.builder()
        .route("simple", "Greetings, small talk and short factual questions", smallModel)
        .route("complex", "Writing or debugging code, multi-step reasoning, detailed analysis", largeModel)
        .router(new DecisionModelChatModelRouter(decisionModel))
        .defaultRoute("complex")
        .build();

Assistant assistant = AiServices.create(Assistant.class, chatModel);

smallModel 与 largeModel 可以是任意 ChatModel,decisionModel 可以是任意 DecisionModel,例如 TypeSafeDecisionModel(参见决策模型)。无需模型的路由器在下文给出。

对于流式,请使用搭配 StreamingChatModel 的 RoutingStreamingChatModel;其工作方式相同。

路由器

一个 ChatModelRouter 返回一个 ChatModelRoutingResult:用 ChatModelRoutingResult.route(name) 选择某条路由, 或用 ChatModelRoutingResult.defaultRoute() 选择默认路由。 默认路由是必需的,以确保始终有一个模型可接收请求,例如当某个调用模型的路由器拿不准或失败时。

决策模型路由器

DecisionModelChatModelRouter 使用一个决策模型来选择描述最契合该请求的路由。它会发送对话的最后 3 条消息(maxMessages),以便像"是的,继续吧"这样的简短追问能被理解。只有用户消息和 AI 的文本响应会被发送并计入:系统消息、工具调用与工具结果会被排除。文本之外的内容(例如图像)用一个标记(例如 [attached image])来表示,以便为其选中一条描述提及图像的路由。 决策模型通常比聊天模型快得多、便宜得多,因此相较于聊天模型的调用,路由带来的延迟与成本开销很小。请把每条路由描述好;没有描述的路由会由其名称来描述。

当请求没有用户消息时,它会选择默认路由;当被选路由的概率低于 minProbability 时也会如此,这有助于把决策模型拿不准的请求交给更大的模型。minProbability 需要一个能上报概率的决策模型;否则调用会失败。当决策模型失败时,会使用默认路由并记录一条警告;设置 fallbackStrategy(FAIL) 可改为让请求失败。

java
ChatModelRouter router = DecisionModelChatModelRouter.builder()
        .decisionModel(decisionModel)
        .minProbability(0.7)
        .build();

一条用于涉及多个不相关主题请求的路由,可以有多个描述:

java
ChatModel chatModel = RoutingChatModel.builder()
        .route("simple", "Greetings, small talk and short factual questions", smallModel)
        .route("complex", List.of("Writing or debugging code", "Legal contract analysis", "Tax planning"), largeModel)
        .router(new DecisionModelChatModelRouter(decisionModel))
        .defaultRoute("simple")
        .build();

决策模型随后会把每个描述视为一个独立选项,而该路由的概率是其各描述概率之和。与把所有主题罗列在一个描述中相比,这能让决策更自信,尤其是在使用较小的决策模型时。当决策模型拿不准时,它会把概率均摊到所有选项上,因此描述更多的路由会分到更多:请保持各路由描述数量的均衡,除非你希望某条路由在拿不准的情况下胜出。

自定义路由器

路由器也可以是一条简单规则:

java
ChatModel chatModel = RoutingChatModel.builder()
        .route("simple", smallModel)
        .route("complex", largeModel)
        .router(request -> ChatModelRoutingResult.route(
                request.chatRequest().messages().size() > 20 ? "complex" : "simple"))
        .defaultRoute("simple")
        .build();

路由器接收 ChatRequest、本次调用的选项,以及能处理它的路由(ChatModelRoute: 名称与描述)。路由器还可以通过实现 validate(...),在创建路由聊天模型时检查各路由。

异步调用

chat(...) 以及搭配 StreamingChatResponseHandler 的流式会在调用线程上执行 route(...),而选择路由期间会阻塞: 用 DecisionModelChatModelRouter 时,直到决策模型回答为止。不要在事件循环线程上调用它们(例如在 Vert.x 或 Quarkus 响应式端点中)。非阻塞方法 chatAsync(...) 以及向 Publisher 流式(StreamingChatModel.chat(ChatRequest))改为调用 routeAsync(...),从而路由永不阻塞。routeAsync(...) 默认不实现, 因此用 lambda 写的路由器会让这些调用以 AsyncNotSupportedException 失败。 要在非阻塞调用中使用这样的路由器,请实现 routeAsync(...):

java
ChatModelRouter router = new ChatModelRouter() {

    @Override
    public ChatModelRoutingResult route(ChatModelRoutingRequest request) {
        return ChatModelRoutingResult.route(
                request.chatRequest().messages().size() > 20 ? "complex" : "simple");
    }

    @Override
    public CompletableFuture<ChatModelRoutingResult> routeAsync(ChatModelRoutingRequest request) {
        return CompletableFuture.completedFuture(route(request)); // route(...) does not block
    }
};

一个会阻塞的路由器(例如会在数据库中查找用户的路由器)可以改为用 CompletableFuture.supplyAsync(() -> route(request), executor) 在其自选的执行器上运行 route(...),或者更好的是,使用一个非阻塞客户端。DecisionModelChatModelRouter 用 DecisionModel.decideAsync(...) 实现了 routeAsync(...)。

请求是如何被路由的

  • 被选中的模型会像被直接调用那样处理请求:它的默认参数与监听器生效。
  • 请求可以发给不同提供者的模型,因此只设置通用的请求参数 (ChatRequestParameters),而非提供者专有的参数。
  • 被选路由的名称会被加到该调用的监听器属性中(在 RoutingChatModel.ROUTE_ATTRIBUTE 下),以便被选中模型的监听器能上报它,并存入返回的 AiMessage 的属性中。
  • 工具调用循环的各轮会停留在同一个模型上:一个以工具结果收尾的请求会被发送给请求这些工具的那个 AiMessage 中所存的路由,而不再询问路由器。这在持久化聊天记忆下也能工作,只要它保留消息的属性(默认序列化会保留)。
  • supportedCapabilities() 返回至少一条路由所支持的能力。需要某种能力的请求(例如 JSON schema 响应格式)只会被路由到声明了它的那些路由:路由器只能看到这些路由,若默认路由未声明它,则使用第一个声明它的路由。若没有路由声明该能力,则所有路由都仍是候选,被选中的模型会像被直接调用时那样自行接受或拒绝该请求。
  • 若路由器返回了一个未知路由,请求会以 IllegalStateException 失败。
  • 被选中的模型会在完成路由的那个线程上被调用:即调用线程,或在非阻塞调用中完成 routeAsync(...) 的线程(例如决策模型响应的线程)。
  • 当路由聊天模型相互嵌套时,存入 AiMessage 的名称属于外层的 路由聊天模型。在工具调用循环中,内层的路由聊天模型因此找不到自己的路由,于是再次询问它的路由器,这可能选中另一个模型。若某条内层路由与某条外层路由同名,内层的路由聊天模型会使用它那条同名的路由。请为嵌套路由聊天模型的路由取不同的名称。

需要牢记的事项

  • 所有路由共享同一段对话:在一条新的用户消息之后,某条路由可能会收到 另一条路由此前产生的那些消息。来自不同提供者的路由必须能读懂彼此的消息。这在遇到提供者专有的内容时可能失败,例如带有签名的返回思考,或格式不被另一提供者接受的工具调用 id。在同一提供者的模型之间路由可避免这一点。
  • DecisionModelChatModelRouter 看到的是消息的文本,而非图像或其他附件的内容:它只知道它们被附带了。
  • 在带 RAG 的 AI 服务中,DecisionModelChatModelRouter 看到的是把检索到的内容追加进去后的用户消息,正如聊天模型那样:检索到的文档会参与决策,而较长的内容可能超出决策模型的输入上限,此时会使用默认路由并记录一条警告。
  • 提示词缓存是按模型的:一条在路由之间切换的对话无法受益于上一个模型的缓存,这在长对话中可能盖过路由带来的节省。
  • 路由聊天模型自身没有默认请求参数,且 provider() 返回 OTHER:从聊天模型读取默认请求参数的代码(例如为了调整 toolChoice)会看到空参数,而非各路由的参数。被选中模型的默认参数仍会应用于每个请求。
  • 当路由聊天模型及其路由都是同一类型的 bean 时,请确保 AI 服务使用的是路由聊天模型,例如在 Spring Boot 中用 @AiService(wiringMode = EXPLICIT, chatModel = "routingChatModel")。
  • 监听器是配置在路由模型上,而非路由聊天模型上:被选中模型的监听器会观察每次调用,且路由名称可在其属性中获取(RoutingChatModel.ROUTE_ATTRIBUTE),但 StreamingChatModel.chat(ChatRequest) 返回的 Publisher 除外(它不接收选项):此时路由名称只存在于响应的属性中。
  • 路由是在请求发送前选定模型;它不会在另一模型上重试失败的请求。

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。