LLMRix Model Router 面向 Java 的开源多模型路由与编排框架

65 阅读24分钟

当 AI 应用从 Demo 进入生产,麻烦通常不在 Prompt,而在模型调用本身:这次请求交给谁?主模型限流后怎么处理?成本怎么控制?流式响应中断后能不能切换?多实例运行时,配额和健康状态如何保持一致?

LLMRix Model Router 把这些问题收进了一层面向 Java 的开源路由框架。

这两年,模型接入的门槛已经降了很多。

用一个 SDK、写几行代码,应用就能调用 OpenAI、DeepSeek 或其他兼容接口。进入生产环境后,问题会换一批:上游会限流、超时,也可能发生区域性故障;模型的能力、价格、上下文窗口和响应速度各不相同。业务代码如果直接绑定供应商,换一次模型,接口、异常处理、监控和配置往往都要跟着改。

简单轮询或反向代理也解决不了这些判断。它们不知道请求是否需要工具调用,不知道图片输入应排除哪些模型,也不知道某个候选是否已经超出成本预算或处于冷却期。

LLMRix Model Router 就位于这层边界上。它是面向 Java 的多模型路由与编排框架,把供应商差异、模型选择、故障切换、配额、成本和观测从业务代码中移出来。

它位于 AI 应用和模型服务之间,承担运行时决策与请求转发。聊天 SDK 解决“如何调用”,Router 还要判断“调用谁、何时重试以及如何收敛状态”。

1. 架构总览:把决策、执行和基础设施拆开

项目的架构取舍很清楚:路由策略可以变化,请求执行的正确性不能跟着变化。为此,LLMRix 将系统拆成几层,各层有明确边界:

  1. 接入层:提供嵌入式 Java API、Orion 远程客户端,以及 Spring Boot 下的 OpenAI 兼容 HTTP/SSE 接口。
  2. 统一契约层:在 llmrix-model-open 中定义 Chat、Embedding、Rerank、Audio、Image、Video 等模型接口、请求/响应对象和异常类型。
  3. 路由核心层:llmrix-model-router-core 负责目标模型、能力匹配、策略选择、执行预算、超时、重试、健康和生命周期事件。
  4. 集成层:llmrix-model-router-integrations 提供 OpenAI、DeepSeek、OpenRouter、Ollama、Redis、Bucket4j、ONNX、Shadow、评测和 Fugu 适配器。
  5. 状态与观测层:状态可以落在本地内存/Caffeine,也可以落到 Redis;事件通过 Listener 暴露,Spring 再接入 Micrometer、Observation 和 Actuator。
  6. 基础设施层:TLS、WAF、外部负载均衡、密钥管理、Redis 高可用和容器编排交给部署环境负责。

LLMRix Model Router 架构设计图

中间的 Router Core 是决策和执行的交界处。它不绑定某一家供应商,也不把业务请求改写成供应商私有对象,而是用统一的 ModelClient 和 ModelTarget 管理候选。新增供应商时,改动主要集中在 Provider SPI 和 Transport 适配器,不会扩散到路由策略或业务代码。

1.1 生产模块与职责

Maven Artifact 职责 典型使用方式
llmrix-model-open 公共模型契约、通用异常、认证 SPI、OpenAI 兼容传输与适配器 开发客户端或自定义集成时复用统一类型
llmrix-model-router-core Router Builder、模型目标、策略、执行器、状态 SPI、配额、健康与事件 在普通 Java 应用中嵌入 Router
llmrix-model-router-integrations 内置供应商、Redis、Bucket4j、ONNX、Shadow、评测与 Fugu 使用官方集成或高级路由能力
llmrix-model-router-spring-starter 自动配置、YAML 属性、HTTP/SSE、认证、Actuator 与 Micrometer 构建 Spring Boot 路由服务
llmrix-model-orion 无框架依赖的远程 Java 客户端 从 Java 服务调用独立 Router
llmrix-model-orion-spring-starter Orion 自动配置与 Micrometer 适配 在 Spring Boot 业务服务中注入远程模型客户端

这样的模块边界方便按需引入依赖。远程业务服务可以只依赖 Orion 和公共模型契约,不必把 Redis、ONNX 或整套 Router Runtime 一起带进来。

1.2 一次请求的决策时序

下面的时序图按源码的执行顺序绘制,包含能力匹配、配额控制和故障切换。图中分开标出了成功返回、首个可见输出前重试,以及流式输出开始后不再重放三条路径:

LLMRix Router 一次请求的决策顺序

“适不适合这次请求”和“这次调用能否完成”由两部分处理:候选快照与策略负责前者,执行器、状态存储和异常分类负责后者。

2. 功能矩阵:模型选择与运行治理

能力域 具体能力 能解决的问题
模型抽象 Provider-neutral ModelClient、多模态请求/响应、统一异常 业务代码不再绑定供应商 SDK
能力匹配 operations、features、input-modalities、traits 避免把工具、图片、音频请求发给不支持的模型
路由策略 priority、round-robin、weighted random、least-busy、latency-aware、cost-aware、balanced、cache-aware 按业务目标在稳定性、成本、延迟和缓存命中之间取舍
动态决策 semantic scoring、contextual bandit、可自定义 RoutingStrategy 用请求语义或反馈数据持续改进选型
可靠性 每次尝试超时、总预算、重试谓词、失败阈值、目标冷却、候选池继续执行 应对限流、超时、临时故障和局部不可用
流式安全 首 token 超时、流空闲超时、首块前切换、取消传播、工具请求避免盲目重放 防止重复输出和有副作用的工具调用被重放
成本治理 输入/输出/缓存/推理 token 定价、请求 maxCostUsd、路由 RPM/TPM 让成本约束进入请求决策,而不是事后统计
配额与并发 目标级限制、路由级限制、认证配额分区、本地 Caffeine、Redis 原子租约 控制单模型、单路由和单租户的资源使用
多模态 Chat、Responses 核心子集、Embeddings、Rerank、Audio、Image、Video 统一处理文本、图像、音频、文件和视频工作流
评测与编排 Online Shadow、离线 Evaluation、Fugu Worker/Thinker/Verifier、ONNX 策略 在不影响主流量的情况下比较模型并组织多轮协作
可观测性 Router/Fugu Listener、Micrometer、Observation、Actuator、request ID 解释“选了谁、耗时多少、为何重试、花了多少”
扩展性 ModelProvider、ProviderAuthenticator、ModelPricingResolver、RouterStateStore 接入企业代理、签名认证、内部价格目录和自有状态系统

2.1 模型声明为什么要分成四类

LLMRix 没有把“模型能力”简化成一个布尔值,而是要求配置分别声明:

  • operations:它能执行什么操作,例如 chat、embeddings、rerank、video-generation;
  • features:它支持什么协议特性,例如 streaming、tools、structured-output、prompt-cache;
  • input-modalities:它能接收什么输入,例如 vision、video、audio、file;
  • traits:它更擅长什么类型的任务,例如 code、reasoning、long-context。

四类声明彼此独立。应用启动时先校验,收到请求后再按声明筛选。相比维护一份“万能模型列表”,这种方式更容易审计,也能减少生产环境中的能力错配。

3. 从“调用模型”改为“调用路由”

LLMRix 让应用依赖稳定的路由名,而不是上游模型名。

例如,业务只请求 general、code 或 reasoning。至于这次请求最终交给 OpenAI、DeepSeek、OpenRouter 还是本地 Ollama,由路由器根据能力、健康状态、延迟、成本和策略决定。

模型调整因此落在路由配置中,而不是业务代码里。新增供应商、替换模型或改变选型策略时,上层应用通常不需要重写。

LLMRix 提供三种接入方式:

  • 作为嵌入式 Java SDK,直接放进现有应用进程;
  • 作为 Spring Boot Starter,通过配置完成自动装配;
  • 作为独立的 OpenAI 兼容服务,对其他语言和已有工具提供统一 HTTP 接口。

Java 服务需要远程调用 Router 时,可以使用项目自带的轻量客户端 Orion。供应商密钥只保留在 Router 端,客户端只接触路由名和统一协议。

4. 一次请求如何被处理

LLMRix 处理的不是一张模型清单,而是一条有明确边界的执行流水线。

收到请求后,Router 先检查操作类型、工具调用、结构化输出、输入模态、上下文长度和调用方传入的路由约束。无法满足条件的模型会在执行前排除。带图片的对话不会发送给只支持文本的模型;请求要求 reasoning 或 code 特征时,未声明这些特征的候选也不会进入选择阶段。

剩余候选再交给路由策略排序。项目内置优先级、轮询、加权随机、最空闲、延迟优先、成本优先、平衡评分和 Prompt Cache 亲和等策略,也提供语义路由与上下文多臂老虎机实现。团队还可以实现 RoutingStrategy,把数据地域、租户等级、合规标签或内部模型评分纳入选择规则。

开始调用前,Router 会申请配额和并发租约,并检查单次请求的成本预算。调用成功后,它按实际 token 用量结算成本、释放租约并发布生命周期事件。调用失败时,Router 根据异常类型判断是否重试、是否让目标进入冷却,以及是否继续尝试候选池中的下一个目标。

因此,“选谁”和“怎么调用”是两层职责:策略负责排序,执行器负责超时、重试、配额、健康和资源释放。选型算法可以替换,执行规则仍然保持一致。

5. 流式输出:首块之后不再重放

普通请求的故障切换相对直接,流式响应则不同。

假设模型 A 已经向用户输出了半句话,连接此时中断。若系统切到模型 B 并从头重放,用户会看到重复内容,工具调用也可能被执行两次。

LLMRix 的规则是:第一个数据块对调用方可见之前,可以切换候选;输出开始后,不再重放请求。Router 分别管理首 token 超时和流空闲超时,并把取消信号传给仍在运行的上游请求。

这项规则直接关系到调用方看到的内容是否一致。它把流式响应的边界写进了执行器,而不是留给业务代码自行处理。

6. 从 Chat 到多模态模型接口

截至 1.0.2,LLMRix 支持 Chat、Responses API 核心子集、Embeddings、Rerank、音频、图片和视频操作,Spring Boot Starter 也提供对应的 OpenAI 风格端点。Chat 和 Responses 端点都支持 JSON 与 SSE 流式响应。

内置集成包括 OpenAI、DeepSeek、OpenRouter 和 Ollama。各供应商支持的操作并不完全相同,Router 会读取每个目标的 operations、features、input-modalities 和 traits,不会把“OpenAI 兼容接口”当成能力完全相同的服务。

显式声明能力有两个实际作用。配置错误可以在启动阶段暴露,路由策略也能在请求发往上游之前完成筛选。

6.1 内置供应商能力范围

集成 已实现的主要操作 适合的场景
OpenAI Chat、Embeddings、Audio、Images、Videos 使用 OpenAI 原生多模态接口
DeepSeek Chat 通用对话、代码与推理路由
OpenRouter Chat、Embeddings、Rerank 通过统一上游访问多家模型及免费模型
Ollama Chat、Embeddings 本地开发、离线环境和私有模型
自定义 Provider 由 ModelProvider 实现决定 企业模型平台、内部代理、专有协议

“集成支持”不代表该供应商下的所有模型都支持同一操作。最终能力取决于具体模型、供应商账号和配置声明。例如,某个 OpenRouter 模型可能支持文本对话,却不接受图片输入。Router 只按目标声明筛选,不会替模型补出未声明的能力。

6.2 OpenAI 兼容端点

Endpoint 用途
POST /v1/chat/completions 同步或 SSE 流式对话
POST /v1/responses Responses API 核心子集,支持 JSON 和 SSE
POST /v1/embeddings 文本或 token 数组向量化
POST /v1/rerank Query 与文档重排序
POST /v1/audio/transcriptions 音频转写
POST /v1/audio/translations 音频翻译
POST /v1/audio/speech 文本转语音
POST /v1/images/generations 图片生成
POST /v1/images/edits Multipart 图片编辑
POST /v1/videos 创建视频生成任务
GET /v1/videos/{video_id} 查询视频任务状态
GET /v1/videos/{video_id}/content 下载已完成的视频
DELETE /v1/videos/{video_id} 删除视频任务
POST /v1/videos/{video_id}/remix 基于已有视频创建 Remix 任务
GET /v1/models 获取可调用的路由标识

7. 单机使用本地状态,多实例共享运行状态

开发和小规模部署可以直接使用内存状态,不需要额外基础设施。本地配额分区由 Caffeine 管理,并设置容量和空闲期限,避免动态租户键无限增长。

Router 扩展为多个实例时,可以切换到 Redis。健康状态、并发租约、RPM、TPM 和多臂老虎机状态都能跨实例共享,更新通过 Redis 原子操作完成。Redis 模式采用 fail-closed:配置错误或存储不可用时,系统不会静默退回 JVM 本地状态,避免出现“看起来有限流,实际每台机器各算一份”的情况。

本地模式适合试用和小规模服务,Redis 模式用于水平扩展时的共享约束。

7.1 推荐的生产部署拓扑

LLMRix Router 推荐生产部署架构

这张生产部署图沿用前面的模块边界,并补上运行环境中的外部组件:

  1. 调用方层:Java 服务可以使用 Orion,其他语言和 Agent 使用 OpenAI 兼容 HTTP/SSE。调用方只传路由名和业务约束,不持有供应商密钥。
  2. 企业入口层:API Gateway 负责 TLS、WAF、外部身份认证、公网限流、请求体限制和实例负载均衡。这一层不属于 LLMRix。
  3. Router 集群层:每个副本包含 Spring Starter/HTTP 接入、Router Core 决策执行和 Integrations 供应商适配,对应架构纵览中的接入层、核心层和集成层。副本使用相同路由配置,但运行在独立进程中。
  4. 共享状态层:Redis HA 保存跨副本的 RPM/TPM、并发租约、健康冷却和 Bandit 状态。Redis 模式 fail-closed,不会在共享状态不可用时静默退回本地限流。
  5. 模型访问层:Router 通过受控公网出口访问 OpenAI、DeepSeek、OpenRouter,通过私网访问 Ollama 或自定义 Provider。上游超时、配额和账号限制仍然有效。
  6. 平台服务层:Secret Manager 向 Router 注入 API Key 和供应商凭据;Router 的指标、Trace 和日志流向企业观测平台。LLMRix 产生事件和指标,不负责部署这些平台。

主链路是“调用方 → 企业网关 → Router 副本 → Provider Adapter → 模型服务”,响应沿同一连接返回。绿色链路表示 Router 与 Redis 的状态读写,橙色链路表示密钥注入,紫色虚线表示异步遥测。这三类链路都不进入模型响应正文。

小规模系统可以省略网关和 Redis,把 Router 嵌入业务服务或运行单个实例。Router 水平扩展后,应使用共享 Redis 状态,并把入口安全和公网流量治理交给网关。Router 的 API Key 认证负责 Client-to-Router 边界,供应商密钥只保留在 Router 部署环境或密钥管理系统中。

8. 可观测性从请求开始记录

模型路由缺少解释信息,线上排障会很慢。至少要能查到请求选中了谁、为什么重试、哪个候选进入冷却、首 token 等了多久,以及成本花在哪里。

LLMRix 在核心层定义请求开始、路由选择、尝试开始与结束、首 token、用量记录、目标冷却和请求完成等生命周期事件。Spring 集成接入 Micrometer、Observation 和 Actuator 后,可以输出请求量、耗时、尝试次数、首 token 延迟、候选可用性、在途请求和 token 用量等指标。

项目不负责部署 Prometheus、Grafana 或 OpenTelemetry Collector,但提供了稳定的观测边界。已有监控体系的 Java 团队可以直接接入,不必再维护一套专用控制台。

9. 让模型选择利用反馈

在常规路由之外,LLMRix 还提供几项用于评测和反馈的能力。

语义路由根据请求内容给候选评分。上下文多臂老虎机结合选择次数与奖励反馈,在使用当前优选模型和探索其他模型之间调整权重。

Online Shadow 按采样率把无副作用请求发送给影子模型,不影响主请求结果。离线评测组件则汇总多个模型在样本集上的质量、延迟和失败情况。

Fugu 编排支持 Worker、Thinker、Verifier 等角色在限定轮次、token 预算和成本预算内迭代。生成、反思和验证都有停止条件、重试与回退机制。运行时可以加载 ONNX 策略,但训练和策略发布仍由离线系统完成。

这些能力不是所有团队的起点。它们表明 LLMRix 除了静态负载均衡,也为可评估、可学习的模型决策预留了接口。

10. 快速接入

项目要求 Java 17 或更高版本,推荐 Java 21。核心制品已经发布到 Maven Central,并采用 MIT License。

不使用 Spring 时,引入 Core 和 Integrations 即可:

<dependency>
  <groupId>com.llmrix.model</groupId>
  <artifactId>llmrix-model-router-core</artifactId>
  <version>1.0.2</version>
</dependency>
<dependency>
  <groupId>com.llmrix.model</groupId>
  <artifactId>llmrix-model-router-integrations</artifactId>
  <version>1.0.2</version>
</dependency>
<button class="v-md-copy-code-btn" type="button">
  <i>
    <svg viewBox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true">
      <path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path>
    </svg>
  </i>
</button><!--beforeend--></div><!--afterend--></div><p>接着把模型注册到路由池:</p>
try (LlmRouter router = LlmRouter.builder()
        .integration("openai", provider -> provider
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .model("gpt-4.1-mini", model -> model
                .operations(ModelOperation.CHAT)
                .features(ModelFeature.TOOLS)))
        .integration("deepseek", provider -> provider
            .apiKey(System.getenv("DEEPSEEK_API_KEY"))
            .model("deepseek-chat", model -> model
                .operations(ModelOperation.CHAT)
                .features(ModelFeature.TOOLS)
                .traits(ModelTrait.CODE)))
        .route("general", route -> route
            .strategy("balanced")
            .quota(600L, 100_000L)
            .models("openai/gpt-4.1-mini", "deepseek/deepseek-chat"))
        .build()) {
    ChatResponse response = router.chat("Review this Java code");
}
<button class="v-md-copy-code-btn" type="button">
  <i>
    <svg viewBox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true">
      <path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path>
    </svg>
  </i>
</button><!--beforeend--></div><!--afterend--></div><p>使用 Spring Boot 的团队可以改用 Starter 和 YAML 配置。非 Java 调用方可以启用 OpenAI 兼容 HTTP API,将现有 SDK 的 <code>base_url</code> 指向 Router。请求中的 <code>model</code> 填路由名,不填供应商的真实模型 ID。</p>

11. 配置与调用示例

11.1 示例一:Spring Boot 配置多条业务路由

在 Spring Boot 服务中,路由可以完全由 YAML 管理。下面把普通对话、代码分析、向量检索和重排序拆成四条路由,并为普通对话设置共享 RPM/TPM 配额:

llmrix:
  model:
    router:
      enabled: true
      default-route: general
      routes:
        general:
          strategy: balanced
          quota:
            requests-per-minute: 600
            tokens-per-minute: 100000
          models:
            - integration: openai
              model: gpt-4.1-mini
            - integration: deepseek
              model: deepseek-chat
        code:
          strategy: cost-aware
          models:
            - integration: deepseek
              model: deepseek-chat
            - integration: openrouter
              model: cohere/north-mini-code:free
        embeddings:
          strategy: latency-aware
          models:
            - integration: openai
              model: text-embedding-3-small
            - integration: openrouter
              model: nvidia/nemotron-3-embed-1b:free
        rerank:
          strategy: priority
          models:
            - integration: openrouter
              model: nvidia/llama-nemotron-rerank-vl-1b-v2:free
      execution:
        timeout: 30s
        max-retries: 1
      http:
        enabled: true
        auth:
          mode: api-key
          bootstrap-key: ${LLMRIX_MODEL_ROUTER_API_KEY}
      integrations:
        openai:
          provider: openai
          api-key: ${OPENAI_API_KEY}
          models:
            - name: gpt-4.1-mini
              operations: [chat]
              features: [streaming, tools]
            - name: text-embedding-3-small
              operations: [embeddings]
        deepseek:
          provider: deepseek
          api-key: ${DEEPSEEK_API_KEY}
          models:
            - name: deepseek-chat
              operations: [chat]
              features: [streaming, tools]
              traits: [code]
        openrouter:
          provider: openrouter
          api-key: ${OPENROUTER_API_KEY}
          models:
            - name: cohere/north-mini-code:free
              operations: [chat]
              traits: [code]
            - name: nvidia/nemotron-3-embed-1b:free
              operations: [embeddings]
            - name: nvidia/llama-nemotron-rerank-vl-1b-v2:free
              operations: [rerank]
<button class="v-md-copy-code-btn" type="button">
  <i>
    <svg viewBox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true">
      <path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path>
    </svg>
  </i>
</button><!--beforeend--></div><!--afterend--></div><p>路由中的 <code>models</code> 是完整候选池。系统在池内选择目标,失败后继续尝试,不需要另外维护容易失真的 <code>fallbacks</code> 列表。供应商的 <code>base-url</code> 可以指向官方端点、企业代理或私有网关。</p>

11.2 示例二:用路由提示表达业务约束

路由名表达稳定的业务意图,RoutingHints 表达单次请求的临时约束。例如,下面的代码审查请求要求模型具备代码特征,同时把成本限制在 5 美分以内:

ChatRequest request = ChatRequest.builder()
    .userMessage("Find the race condition in this Java code")
    .routingHints(RoutingHints.builder()
        .require(ModelTrait.CODE)
        .maxCostUsd(0.05)
        .maxLatency(Duration.ofSeconds(8))
        .attribute("tenant", "acme")
        .build())
    .build();


ChatResponse response = router.chatRoute("code").chat(request); 

ChatResponse response = router.chatRoute("code").chat(request);

<button class="v-md-copy-code-btn" type="button">
  <i>
    <svg viewBox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true">
      <path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path>
    </svg>
  </i>
</button><!--beforeend--></div><!--afterend--></div><p>提示可以要求特定操作、工具或输入模态,可以允许或拒绝指定目标,也能设置最大成本、最大延迟并传递租户或业务标签。认证请求还可通过 <code>RoutingHints.AUTH_QUOTA_KEY</code> 为不同调用方分配独立配额分区。</p>

11.3 示例三:通过 OpenAI 兼容 HTTP API 接入现有应用

启用 Spring Starter 的 HTTP 端点后,Python、Node.js、Go 或已有 OpenAI SDK 不需要了解 Router 的内部实现。把 base_url 指向 Router,再把模型字段改成路由名即可:

export BASE_URL=http://127.0.0.1:8080
export API_KEY=your-llmrix-http-key


curl --no-buffer "BASE_URL</span>/v1/chat/completions"</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>   <span class="token parameter variable">-H</span> <span class="token string">"Authorization: Bearer <span class="token variable">API_KEY" </span></span> -H "Content-Type: application/json" </span></span> -d '{
"model": "code",
"messages": [{"role": "user", "content": "Review this pull request"}],
"stream": true,
"temperature": 0
}' 

curl --no-buffer "BASEURL</span>/v1/chat/completions"</span><spanclass="tokenpunctuation">\</span><spanclass="tokenpunctuation">\</span><spanclass="tokenparametervariable">−H</span><spanclass="tokenstring">"Authorization:Bearer<spanclass="tokenvariable">BASE_URL</span>/v1/chat/completions"</span> <span class="token punctuation">\</span><span class="token punctuation">\</span> <span class="token parameter variable">-H</span> <span class="token string">"Authorization: Bearer <span class="token variable">API_KEY" </span></span> -H "Content-Type: application/json" </span></span> -d '{ "model": "code", "messages": [{"role": "user", "content": "Review this pull request"}], "stream": true, "temperature": 0 }'

<button class="v-md-copy-code-btn" type="button">
  <i>
    <svg viewBox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true">
      <path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path>
    </svg>
  </i>
</button><!--beforeend--></div><!--afterend--></div><p>HTTP 层除了 Chat Completions,还提供 Responses 核心子集、Embeddings、Rerank、音频转写与翻译、语音合成、图片生成与编辑、视频任务和 Models 目录。控制器级错误使用 OpenAI 风格结构,包含 <code>error.message</code>、<code>error.type</code>、<code>error.code</code> 和 <code>error.param</code>。在进入控制器前被拒绝的认证请求可能返回更精简的错误体。调用方应按 HTTP 状态和错误类型处理,不要匹配完整错误文本。</p>

11.4 示例四:Orion 客户端调用远程 Router

模型密钥需要集中保管,或业务服务需要与 Router 独立扩缩容时,可以使用 Orion:

OrionModelClient client = OrionModelClient.builder()
    .baseUrl("https://router.example.com/v1")
    .apiKey(System.getenv("LLMRIX_API_KEY"))
    .defaultModel("general")
    .defaultEmbeddingModel("embeddings")
    .defaultRerankModel("rerank")
    .build();


ChatResponse answer = client.chat(ChatRequest.user("Summarize this incident"));
EmbeddingResponse vector = client.embed(EmbeddingRequest.text("Text to index"));
RerankResponse ranked = client.rerank(new RerankRequest(
"refund policy", List.of("Support contact", "Refunds within 30 days"))); 

ChatResponse answer = client.chat(ChatRequest.user("Summarize this incident")); EmbeddingResponse vector = client.embed(EmbeddingRequest.text("Text to index")); RerankResponse ranked = client.rerank(new RerankRequest( "refund policy", List.of("Support contact", "Refunds within 30 days")));

<button class="v-md-copy-code-btn" type="button">
  <i>
    <svg viewBox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true">
      <path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path>
    </svg>
  </i>
</button><!--beforeend--></div><!--afterend--></div><p>Orion 支持按请求添加 request ID 和受控的自定义 Header,也支持异步 <code>CompletionStage</code>、<code>Flow.Publisher&lt;ChatChunk&gt;</code> 流式调用,以及音频、图片和视频等类型化模型。远程错误和上游 HTTP 状态会映射为客户端异常,业务侧可以统一处理。</p>

11.5 示例五:多模态请求与能力筛选

多模态请求不需要调用方手工挑选供应商。Router 根据目标声明的输入模态排除不支持的候选:

ChatRequest multimodal = ChatRequest.builder()
    .message(Message.user(
        new TextPart("Compare this diagram with the attached report."),
        new ImagePart("https://example.com/diagram.png", "high"),
        new FilePart("https://example.com/report.pdf", "report.pdf")))
    .build();


ChatResponse analysis = client.chatModel("multimodal").chat(multimodal); 

ChatResponse analysis = client.chatModel("multimodal").chat(multimodal);

<button class="v-md-copy-code-btn" type="button">
  <i>
    <svg viewBox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true">
      <path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path>
    </svg>
  </i>
</button><!--beforeend--></div><!--afterend--></div><p>对应的模型配置需要声明 <code>input-modalities: [vision, file]</code>,音频和视频输入同理。这样,“模型支持情况”会成为可校验配置,不再依赖隐含约定,也能减少请求到达上游后才暴露的能力错误。</p>

11.6 示例六:把路由解释、指标和成本接入运维系统

排查请求时,可以先查看路由候选和排除原因,再通过 Listener 或 Micrometer 获取运行指标:

RouteExplanation explanation = router.chatRoute("code")
    .explain(ChatRequest.user("Find the race condition"));


System.out.println("selected=" + explanation.selectedTarget());
System.out.println("eligible=" + explanation.eligibleTargets());
System.out.println("excluded=" + explanation.excludedTargets()); 

System.out.println("selected=" + explanation.selectedTarget()); System.out.println("eligible=" + explanation.eligibleTargets()); System.out.println("excluded=" + explanation.excludedTargets());

<button class="v-md-copy-code-btn" type="button">
  <i>
    <svg viewBox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true">
      <path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path>
    </svg>
  </i>
</button><!--beforeend--></div><!--afterend--></div><p>Spring Boot 中的常用指标包括 <code>llm.router.requests</code>、<code>llm.router.attempts</code>、<code>llm.router.request.duration</code>、<code>llm.router.first.token</code>、<code>llm.router.cooldowns</code>、候选可用性和在途请求数量。用量事件分别记录输入、输出、缓存输入、缓存写入和推理 token,可用于按路由、供应商和租户汇总成本。</p>

11.7 示例七:用 Shadow 和 Fugu 做渐进式质量升级

替换模型时不必一次切换全部流量。Online Shadow 按采样率异步调用影子模型,主请求仍由当前生产路由返回。评测组件记录响应质量、延迟、失败和成本,团队可以根据在线对比数据决定是否调整策略。

需要多轮生成、思考和验证时,可以把多个 ChatModel 组合成 Fugu 编排:

  • Worker 负责生成初始答案;
  • Thinker 提供改进建议;
  • Verifier 检查是否满足接受条件;
  • maxTurns、token budget、成本预算和超时共同限制最坏情况;
  • 候选失败时依据策略重试、冷却并回退到备用模型。

这些限制为多轮 AI 工作流划定资源边界,也避免把整套编排逻辑写成一段难以观测的业务代码。

12. 典型落地场景

场景 推荐路由设计 重点能力
智能客服 general、reasoning、translation 分路由 稳定优先、租户配额、故障切换、用量统计
代码助手 code-fast 与 code-deep 分层 代码特征、成本预算、工具调用、长上下文
企业 RAG embeddings、rerank、answer 串联 向量化、重排序、结构化输出、链路观测
多模态审核 vision、audio、video 按操作拆分 输入模态筛选、文件安全、二进制响应
模型迁移评估 主路由外挂 Online Shadow 采样、隔离超时、质量评分、A/B 报告
混合云与本地模型 公有模型与 Ollama/私有 Provider 组成候选池 数据边界、自定义认证、健康与延迟策略
多 Agent 工作流 Fugu 或自定义 Router 组织角色 多轮预算、停止条件、验证、回退与事件流

13. 建议的上线顺序

  1. 先选一条非核心 Chat 路由,接入两个上游模型,统一请求、响应和异常类型。
  2. 再补齐每个目标的 operations、features、modalities、traits、上下文窗口、定价和并发限制。
  3. 接入 request ID、Micrometer/Observation,以及首 token 延迟、重试、冷却、用量和成本看板。
  4. 用 Shadow 收集对比数据后,再逐步启用 cost-aware、semantic 或 contextual bandit。没有数据之前,先用简单策略。

13.1 上线检查清单

  • 供应商密钥来自环境变量或专用 Secret Manager,不进入 Git、YAML 模板和日志;
  • 对外服务部署在 TLS、WAF、网关认证和外部流量限制之后;
  • 多实例使用 Redis 共享配额、并发租约和健康状态,并为 Redis 配置高可用;
  • 为每个模型显式填写实际能力、上下文窗口、定价、RPM、TPM 和最大并发;
  • 路由按业务意图命名,不把供应商或具体版本暴露给业务调用方;
  • 请求总超时、首 token 超时和流空闲超时均按真实链路延迟设置;
  • 重试次数保持有界,区分可重试异常与参数、权限、内容安全等不可重试错误;
  • Prompt 观测默认关闭;确需采集时限定路由、长度并配置脱敏器;
  • Shadow 仅用于无副作用请求,工具调用请求默认不进入影子流量;
  • 为 429、503、首 token 延迟、冷却次数、候选可用性和成本异常设置告警;
  • 升级前阅读 CHANGELOG.md,并在预发环境执行 HTTP/SSE 与多模态回归测试。

14. 适用团队与产品边界

出现以下情况时,LLMRix 比直接维护多套供应商 SDK 更合适:

  • Java 或 Spring Boot 是主力技术栈;
  • 已经接入两个以上模型供应商;
  • 需要按能力、成本、延迟或租户策略选择模型;
  • 对限流、故障切换、流式正确性和可观测性有生产要求;
  • 希望在进程内 SDK 与独立路由服务之间自由选择部署形态。

LLMRix 也有明确边界。它不是模型托管平台、训练平台、密钥管理系统或完整的 API Gateway。TLS、WAF、外部负载均衡、Redis 高可用、密钥托管和容器编排仍由相应基础设施负责。需要完整网关能力时,可以把它部署在 Nginx、APISIX、Higress 或云负载均衡之后。

项目目前还很年轻。仓库创建于 2026 年 7 月,社区规模有限,暂时没有管理控制台、官方容器镜像或 Kubernetes Helm Chart。内置供应商也不算多。复杂企业环境通常需要通过 SPI 接入内部代理、认证和定价系统。

不过,仓库不只包含概念说明。当前代码有 6 个可发布制品和 43 个测试类。笔者在 2026 年 9 月 3 日基于主分支,使用 Java 21 执行完整的 mvn test:共运行 199 项测试,0 失败、0 错误、0 跳过。测试覆盖核心路由、配额与健康状态、协议映射、SSE、多模态控制器、Spring 自动配置、服务启动和 Orion 客户端等路径。这些结果能说明代码具备基本的工程完整性,但不能代替生产案例或性能基准。

15. 结语

AI 应用刚起步时,直接调用一个模型通常最快。模型数量增加、流量上升或业务变得重要之后,模型选择才需要成为独立的基础设施能力。

LLMRix Router 提供的是一条稳定的模型边界。上层应用表达需要什么;Router 判断哪些模型能处理、该选哪个、失败后如何继续,并记录调用成本。

如果代码里已经散落着多套供应商 SDK、重试循环和模型名称,可以先拿一条非核心路由验证。统一接口后,再接入健康、成本和配额策略;等 Shadow 与评测积累了数据,再调整选型方式。

项目地址:github.com/llmrix-inc/…

延伸阅读:服务端部署 · Orion 客户端 · HTTP API · Maven Central

开源协议:MIT
当前版本:1.0.2
运行要求:Java 17+,推荐 Java 21;Spring Boot 3.x 可选