Skip to content

feat: smart channel routing based on request API type - #5017

Open
xemxx wants to merge 9 commits into
QuantumNous:mainfrom
xemxx:feat/smart-channel-routing
Open

feat: smart channel routing based on request API type#5017
xemxx wants to merge 9 commits into
QuantumNous:mainfrom
xemxx:feat/smart-channel-routing

Conversation

@xemxx

@xemxx xemxx commented May 21, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

当前 new-api 支持同一模型配置多个渠道(如 Claude、OpenAI、Gemini 等),但渠道选择仅按优先级 + 权重随机,未考虑客户端请求的 API 格式。这导致:

  • 用户用原生 Claude API 请求 claude-sonnet-4.5 时,可能被路由到 OpenAI 格式的渠道
  • 触发不必要的请求/响应格式转换,增加延迟和出错概率

当请求模型例如 mimo-v2.5-pro 的时候,小米官方提供两种接口,但是 new-api 这块默认渠道只能配置 openai 或者 claude,如果我同时需要两种接口访问,当前只能配置一种然后做转换,但是转换有时候在 claude code 或者 hermes agent 会失败

这时需要一个策略来自动选择,比如配置两个小米官方的渠道,分别是 openai 和 claude,当用户访问的时候,自动选择对应的官方接口去直接访问,这样避免转换带来的误差以及问题出现,但是也要考虑现有逻辑,所以需要当两个渠道同优先级的时候再进入这种策略,否则依旧保证高优先级的先访问,走默认的转换逻辑

兼容性:

  • 单渠道或优先级不相同的场景,行为完全不变
  • 无匹配渠道时自动回退,不破坏现有路由逻辑
  • RelayFormat 时跳过智能路由,保持原有行为

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix)
  • ✨ 新功能 (New feature)
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

image image image image

Summary by CodeRabbit

  • New Features
    • Improved channel routing by inferring the request “relay format” from the request path and matching it to compatible provider API types.
    • Enhanced random weighted channel selection for same-priority options, prioritizing channels that best fit the inferred request format.
  • Bug Fixes
    • Reduced instances of requests being routed to less compatible channels when a better-matched option exists.
    • Kept existing selection behavior when the request format can’t be inferred or mapped, preventing unexpected routing changes.

xemxx added 3 commits May 21, 2026 15:29
When multiple channels share the same priority for a model, prefer channels
whose native API type matches the client's request format (OpenAI/Claude/Gemini).
This avoids unnecessary request/response conversion and improves relay performance.

Changes:
- Add RelayFormatToAPIType() mapping in types/relay_format.go
- Pass RelayFormat through RetryParam to channel selection
- Smart routing in model/ability.go (DB query path, memory cache disabled)
- Smart routing in model/channel_cache.go (memory cache enabled)
- Default to OpenAI format in middleware/distributor.go (pre-detection phase)

Fixes QuantumNous#4982
When the smart channel routing query joins abilities with channels,
both tables expose 'group' and 'weight' columns, causing SQLite to
fail with 'ambiguous column name' errors.

- Qualify the group filter with abilities. prefix in JOINed queries
- Wrap the priority subquery in parentheses so the generated SQL is
  valid (priority = (SELECT MAX(priority) ...) instead of priority =
  SELECT ...)
- Order by abilities.weight DESC explicitly, and select abilities.*
  on the filtered query to avoid column collision when scanning rows
The Distribute middleware runs before the per-route handler that
sets the actual RelayFormat, so smart channel routing was always
seeing RelayFormatOpenAI and could never prefer a Claude- or
Gemini-native channel for /v1/messages or Gemini paths.

Add InferRelayFormatFromPath, which mirrors the routing table in
router/relay-router.go, and use it in the distributor so requests
hitting /v1/messages get RelayFormatClaude (and so on) before
channel selection runs.
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds relay-format inference and API-type mapping helpers, then uses them to route abilities and same-priority channels toward matches for the request path’s inferred API type.

Changes

Smart Channel Routing by Relay Format

Layer / File(s) Summary
Relay Format Utilities
types/relay_format.go
Adds RelayFormatToAPIType and InferRelayFormatFromPath for mapping relay formats to API type IDs and inferring relay format from request paths.
Ability Layer API-Type Filtering
model/ability.go
Switches GetChannel to filterAbilitiesByAPIType; the helper infers relay format, maps expected API type, loads candidate channels, compares channel API types, and falls back when inference, lookup, or matching does not succeed.
Channel Cache Smart Routing
model/channel_cache.go
Updates same-priority weighted selection in GetRandomSatisfiedChannel to infer relay format from requestPath, keep preferred channels whose native API type matches, and recompute weight totals for the preferred subset.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I bounced through paths with sniffing nose,
And found the API type that glows.
OpenAI or Claude, the trail is bright,
Matching channels steer just right.
With weights adjusted, hop hooray,
The rabbit’s routing found its way.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: smarter channel routing based on the request API type.
Linked Issues check ✅ Passed The PR implements API-type-aware routing with fallback behavior, matching the issue's goal of automatic multi-backend routing by caller protocol.
Out of Scope Changes check ✅ Passed The added relay-format helpers and routing filters are directly related to the stated smart-routing objective and do not appear unrelated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@types/relay_format.go`:
- Around line 70-71: The case in types/relay_format.go that matches routes with
strings.HasPrefix(path, "/v1/engines/") && strings.HasSuffix(path,
"/embeddings") currently returns RelayFormatGemini which misclassifies legacy
OpenAI-style embeddings; update that branch to return the OpenAI embeddings
relay format constant (e.g., RelayFormatOpenAIEmbeddings or the project’s
embeddings-specific RelayFormat) instead of RelayFormatGemini so
/v1/engines/*/embeddings is routed to the embeddings backend.
- Around line 50-76: The InferRelayFormatFromPath function currently returns
RelayFormatOpenAI in the default case which biases routing for unknown paths;
change the default return to RelayFormatUnknown to preserve neutral behavior,
and add a new case branch so paths starting with "/v1beta/models/" return
RelayFormatGemini (similar to the existing "/v1/models/" handling); update the
switch in InferRelayFormatFromPath accordingly and ensure RelayFormatUnknown is
used downstream where absence of an API-type hint is intended.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a466504f-b7f0-43c6-98eb-6743b74a7024

📥 Commits

Reviewing files that changed from the base of the PR and between 006e801 and e0fe3e4.

📒 Files selected for processing (6)
  • controller/relay.go
  • middleware/distributor.go
  • model/ability.go
  • model/channel_cache.go
  • service/channel_select.go
  • types/relay_format.go

Comment thread types/relay_format.go Outdated
Comment thread types/relay_format.go Outdated
…d unknown paths

Address review feedback on smart channel routing:

- /v1/engines/*/embeddings is OpenAI's legacy embeddings endpoint, not
  Gemini. Return RelayFormatEmbedding so it routes to the correct backend.
- /v1beta/models/ is the Gemini API path used by upstream clients (see
  middleware/distributor.go handling); add it alongside /v1/models/ so
  Gemini routes are inferred consistently.
- Default branch now returns an empty RelayFormat instead of
  RelayFormatOpenAI. Downstream GetChannel treats empty as 'no API-type
  hint' and falls back to the original priority/weight-based selection,
  which is the truly neutral behaviour for unknown paths.
@xemxx

xemxx commented May 22, 2026

Copy link
Copy Markdown
Author

关于 #4759

@seefs001#4759 提了同主题的修复(早本 PR 10 天),思路一致:同模型多渠道时优先选原生格式渠道。两者实现路径不同,简单对比:

维度 #4759 #5017
过滤层 Go 内存 lo.Filter SQL WHERE api_type = ?
抽象 EndpointType(细粒度) api_type(int)
改动 +95 / 3 文件 +209 / 6 文件
路径覆盖 7 类 13 类(含 responses / realtime / audio / embeddings / rerank)
DB 查询 候选集 + 二次查 channel 单次 SQL

另:#4759 引用了 common.GetEndpointTypesByChannelType,我在当前 main 没找到实现,可能依赖未合入的分支,请维护者确认。

我都可以配合 — 关闭本 PR、或在 #4759 基础上补齐路径覆盖、或在本 PR 引入 EndpointType 替换 api_type。听维护者安排。

xem added 3 commits May 26, 2026 18:24
…am-advanced-custom

# Conflicts:
#	controller/relay.go
#	middleware/distributor.go
#	model/ability.go
#	model/channel_cache.go
#	service/channel_select.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@model/ability.go`:
- Around line 182-191: The DB filtering in ability matching should preserve the
cache path’s explicit map lookup check, because a missing apiTypeById entry
currently defaults to 0 and can incorrectly match APITypeOpenAI. Update the
logic in the DB path within the ability filtering routine (the loop over
abilities in the method handling channels and abilities) to use an ok-style
presence check before comparing the mapped API type to expectedAPIType, matching
the behavior used in the cache path and preventing unmapped channels from
slipping through.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9cb91808-a2c5-495f-913c-886862f9f876

📥 Commits

Reviewing files that changed from the base of the PR and between 132e926 and a9ec051.

📒 Files selected for processing (2)
  • model/ability.go
  • model/channel_cache.go

Comment thread model/ability.go Outdated
xem added 2 commits June 30, 2026 16:42
When a channel id is absent from the lookup map (channel deleted or
ChannelType2APIType returned !ok), apiTypeById[id] yields the int zero
value, which equals APITypeOpenAI. With expectedAPIType=0 that produced
a false match. Use the two-value map lookup so only channels with a
known api type can be preferred.

Reported by CodeRabbit on PR QuantumNous#5017.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

建议:支持统一接口多后端自动路由能力(如 Claude/OpenAI 同模型自动分流)

1 participant