Skip to content

feat: route auto group by request path - #5586

Closed
dofastted wants to merge 7 commits into
QuantumNous:mainfrom
dofastted:feat/auto-path-group-routing
Closed

feat: route auto group by request path#5586
dofastted wants to merge 7 commits into
QuantumNous:mainfrom
dofastted:feat/auto-path-group-routing

Conversation

@dofastted

@dofastted dofastted commented Jun 18, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

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

📝 变更描述 / Description

auto 分组增加请求路径分流,并补齐重试保护:

  • /v1/chat/completionsauto 在分发前切到 codex-completions,同时更新 UsingGroup 和 retry 使用的 TokenGroup,避免首次命中 completions 分组后,retry 又回到 auto 并打到 responses 渠道。
  • /v1/responses 与子路径:保持 auto,但把本次请求的自动分组限制为 codexcodex-pro,避免未来 codex-completions 出现在 AutoGroups 时 responses 请求乱飞。
  • 客户端取消 / context canceled / client_gone:不再进入 channel retry,也不再记录为普通 channel 500 错误,避免一次客户端断开被放大成多次上游请求和多条错误日志。

本次代码由 AI 辅助生成并由提交者整理提交。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

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

📸 运行证明 / Proof of Work

已在当前 WSL 环境使用 Go 1.25.1 完成目标验证:

gofmt -w constant/context_key.go controller/relay.go controller/relay_retry_test.go middleware/distributor.go middleware/distributor_test.go service/channel_select.go service/group.go service/group_test.go types/error.go

go test ./middleware ./service ./controller -run 'Test(AutoGroupForRequestPath|RouteAutoGroupForRequestPath|GetRequestAutoGroupFiltersRouteScopedGroups|ShouldRetrySkipsClientCanceledErrors)'
# go test: 3 packages ok

PR 分支已合并上游 main 以解决冲突,当前状态为 mergeable。

@coderabbitai

coderabbitai Bot commented Jun 18, 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

The PR introduces four independent functional features: (1) request-path-aware auto group remapping in the distributor middleware with service-layer filtering by route-scoped allowlists, (2) client request cancellation detection to skip retries when clients disconnect, (3) comprehensive input safety implementation and rule specification documentation for local content filtering before relay, and (4) a custom responsive landing page with analytics integration.

Changes

Request-Aware Auto Group Remapping and Filtering

Layer / File(s) Summary
Auto group routing in distributor middleware
middleware/distributor.go, constant/context_key.go
Adds autoGroupForRequestPath(usingGroup, requestPath) that returns "codex-completions" when usingGroup is "auto" and path is exactly /v1/chat/completions or /v1/chat/completions/.... Adds routeAutoGroupForRequestPath(c, usingGroup) to set context keys (ContextKeyUsingGroup, ContextKeyTokenGroup for chat-completions; ContextKeyRouteAutoGroups for responses). Integrates into Distribute() after playground selection. Defines new ContextKeyRouteAutoGroups context key constant.
Request context filtering for auto groups in service layer
service/group.go, service/channel_select.go
Adds GetRequestAutoGroup(c, userGroup) that computes base auto-groups from GetUserAutoGroup, then filters by ContextKeyRouteAutoGroups if present in request context. CacheGetRandomSatisfiedChannel now calls GetRequestAutoGroup instead of GetUserAutoGroup to apply route-scoped filtering.
Tests for auto group routing and filtering
middleware/distributor_test.go, service/group_test.go
Table-driven TestAutoGroupForRequestPath covers: chat-completions remapping, responses pass-through, explicit group preservation, embedded path non-match, and similar-prefix non-match. Context tests validate ContextKeyUsingGroup/ContextKeyTokenGroup updates for chat-completions and ContextKeyRouteAutoGroups for responses. Service-layer test validates GetRequestAutoGroup filters by route-scoped allowlist intersection.

Client Request Cancellation Handling

Layer / File(s) Summary
Client cancellation error classification
types/error.go
Adds IsClientCanceledError(*NewAPIError) bool helper that detects client-side cancellations by checking context.Canceled with errors.Is and fallback substring matching on lowercased error message for known phrases (context canceled, request context done, client_gone).
Retry loop integration and shouldRetry check
controller/relay.go, controller/relay_retry_test.go
Relay request retry loop breaks early when IsClientCanceledError detects client cancellation. shouldRetry returns false immediately for client-canceled errors, preventing retries regardless of other conditions. Test validates that multiple client-cancellation scenarios (context cancellation, client-gone/stream-ended markers) are classified as non-retryable.

Input Safety Implementation and Rules Documentation

Layer / File(s) Summary
Input safety implementation handoff guide
docs/input_safety_handoff.md
Documents local input safety interception for Codex: reviewed request fields by API type (Chat Completions, Responses, Images, Claude, Gemini), OpenAI-compatible blocking responses (HTTP 400, type=invalid_request_error, code=input_safety_blocked), recommended integration point before Distribute(), request body reuse constraints (no direct reading), extraction/normalization strategies, error construction interface, logging restrictions (no complete prompts/secrets, hash allowed), test coverage requirements, and deployment checklist with environment variables and execution prompts.
Safety rule definitions and deployment strategy
docs/input_safety_rules.md
Specifies rule categories with rule ids, scores, and actions: Cyber Abuse (credential theft, malware generation, evasion, real-target attacks, phishing, DDoS, platform abuse, payment fraud), NSFW (child safety, explicit generation, non-consensual, image generation), Privacy & doxing (sensitive field disclosure), Self-harm & violence (self-injury and violence guidance). Each rule includes keyword/regex trigger logic and allowlist exceptions. Documents combination rules, JSON config structure, logging field recommendations, sensitive data exclusion policies, and rollout sequence (log-first observation, gradual block enablement, optional secondary model review).

Custom Responsive Landing Page with Analytics Integration

Layer / File(s) Summary
Custom landing page HTML structure and styling
web/custom/home.html
Introduces a responsive zh-CN landing page introducing Codex API with sticky navigation (pricing/models/playground/sign-in/dashboard links), hero section with API request preview, three service feature cards, testimonial block, CTA section with pricing and dashboard links, and footer. Includes CSS variables for theming, gradient overlays, entrance animations, reduced-motion accessibility, and semantic HTML structure.
Router setup and analytics injection for custom page
main.go, router/web-router.go
Embeds web/custom/home.html as customHomePage in main.go and wires into router.ThemeAssets.CustomHomePage. Adds CustomHomePage []byte field to ThemeAssets struct. Creates explicit GET "/" route in SetWebRouter that serves custom page with Cache-Control: no-cache header as text/html. Extends InjectUmamiAnalytics and InjectGoogleAnalytics to apply placeholder replacements to customHomePage alongside existing index pages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

  • QuantumNous/new-api#1239: Both PRs modify middleware/distributor.go's Distribute() and adjust how "auto"/token-group behavior is handled around channel selection with CacheGetRandomSatisfiedChannel.

Suggested Reviewers

  • seefs001

Poem

🐇 Four features hop across this PR with care:
Auto-routes remap groups with flair,
Client cancellations skip their retries,
Safety rules guide what to deny,
A landing page greets from the top—
This PR's a feature-bunny's hop! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% 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
Title check ✅ Passed The title 'feat: route auto group by request path' directly summarizes the main feature: request path-based routing for auto grouping, which is the central objective of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@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 `@middleware/distributor_test.go`:
- Around line 3-36: Refactor the three individual test functions
(TestAutoGroupForRequestPathRoutesChatCompletions,
TestAutoGroupForRequestPathKeepsResponsesAuto,
TestAutoGroupForRequestPathKeepsExplicitGroup) into a single table-driven test
by consolidating them into one test function with a slice of test cases, each
containing inputs (group, path) and expected outputs (expectedGroup,
expectedChanged). Replace all t.Fatal and t.Fatalf calls with require.Equal from
the testify package (import github.com/stretchr/testify/require), and iterate
through the test cases table using a for loop with subtests via t.Run to
maintain deterministic execution and better error reporting.

In `@middleware/distributor.go`:
- Around line 179-180: Replace the unsafe substring matching using
strings.Contains(requestPath, "/v1/chat/completions") with a safer
endpoint-matching approach. Use strings.HasPrefix to match only paths that start
with the exact endpoint "/v1/chat/completions", or implement exact endpoint
matching if you only want to match the exact path. This prevents unintended
paths containing that substring fragment from being incorrectly routed to the
"codex-completions" group.
🪄 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: b2fda319-a496-47a0-acd5-250089aba71e

📥 Commits

Reviewing files that changed from the base of the PR and between 4e8b5e9 and e09542d.

📒 Files selected for processing (2)
  • middleware/distributor.go
  • middleware/distributor_test.go

Comment thread middleware/distributor_test.go Outdated
Comment thread middleware/distributor.go Outdated

@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 `@middleware/distributor_test.go`:
- Around line 48-49: In the test function in middleware/distributor_test.go,
replace the two require.Equal calls that check tt.expectedGroup and
tt.expectedChanged with assert.Equal calls instead, since these are non-fatal
value assertions rather than setup or fatal preconditions that would prevent the
test from continuing.
🪄 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: 309f1e79-7aeb-4a55-b81a-16a382c0a6a9

📥 Commits

Reviewing files that changed from the base of the PR and between e09542d and 003d7c0.

📒 Files selected for processing (2)
  • middleware/distributor.go
  • middleware/distributor_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • middleware/distributor.go

Comment thread middleware/distributor_test.go Outdated

@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: 4

🧹 Nitpick comments (3)
docs/input_safety_rules.md (1)

982-1005: ⚡ Quick win

Tighten the global allowlist terms.

Standalone tokens like 授权, 防御, 修复, 检测, and 监控 are too coarse for a score reducer; they will show up in ordinary prompts and dilute the signal of the safety rules. Based on learnings: the allowlist is intended to reduce false positives, not act as a broad exception list.

🤖 Prompt for 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.

In `@docs/input_safety_rules.md` around lines 982 - 1005, The allowlist terms in
section 9.2 降低误杀白名单 are too generic and will trigger false negatives in ordinary
prompts, diluting the safety signal. Review both the English terms (defense,
defensive, protect, mitigate, patch, fix, detect, monitor, incident response,
ctf, lab, localhost, training, education, medical, legal compliance) and the
corresponding Chinese terms to identify and remove overly broad standalone
tokens that commonly appear in benign content. Replace generic terms with more
specific, contextual phrases that clearly indicate legitimate security research,
educational, or compliance activities, ensuring the allowlist narrows its scope
to reduce false positives rather than acting as a blanket exception list.
web/custom/home.html (1)

1-7: 💤 Low value

Consider adding a favicon link.

The default landing page (web/default/index.html) includes a favicon reference (<link rel="icon" type="image/png" href="/logo.png" />), but this custom page does not. Adding a favicon improves the user experience by displaying a recognizable icon in the browser tab.

📎 Suggested addition
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1" />
     <meta name="description" content="fkcodex 提供 Codex API 服务,多种方案可选,价格清楚,接入简单。" />
+    <link rel="icon" type="image/png" href="/logo.png" />
     <title>fkcodex - Codex API Service</title>
🤖 Prompt for 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.

In `@web/custom/home.html` around lines 1 - 7, The custom home.html file is
missing a favicon link reference in the head section that is present in the
default landing page. Add a link element for the favicon (referencing the
/logo.png file with type image/png) in the head section of the custom home.html
file, placing it after the existing meta tags and before or near the title
element to match the pattern used in the default page.
router/web-router.go (1)

33-37: 💤 Low value

Consider removing the redundant Cache-Control header.

The Cache middleware (line 32) already sets Cache-Control: no-cache for the "/" path (see middleware/cache.go:9). Setting it again at line 35 is redundant.

♻️ Proposed simplification
 	router.GET("/", func(c *gin.Context) {
 		c.Set(middleware.RouteTagKey, "web")
-		c.Header("Cache-Control", "no-cache")
 		c.Data(http.StatusOK, "text/html; charset=utf-8", assets.CustomHomePage)
 	})
🤖 Prompt for 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.

In `@router/web-router.go` around lines 33 - 37, Remove the redundant c.Header
call that sets Cache-Control in the GET handler for the "/" route. The Cache
middleware already sets the same Cache-Control header for this path, so the
duplicate c.Header("Cache-Control", "no-cache") line in the anonymous function
should be deleted to avoid redundancy and maintain consistency with the
middleware configuration.
🤖 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 `@docs/input_safety_handoff.md`:
- Around line 39-41: The documentation lists Gemini route endpoints as
`/v1beta/models/*` and `/v1/models/*`, but these do not match the actual router
configuration where geminiCompatibleRouter is mounted at `/v1beta/openai/models`
in relay-router.go. Update the route paths in the documentation to reflect the
correct actual mount point of `/v1beta/openai/models/*` so implementers can find
the correct entrypoint.

In `@docs/input_safety_rules.md`:
- Around line 808-819: The keyword `topless` in the list of English
nudity/pornography words has an unintended leading space before it, which will
cause the matcher to look for " topless" (with space) instead of the actual word
"topless". Remove the leading whitespace before `topless` to align it with the
formatting of the other keywords in the list so that exact keyword matching will
work correctly.
- Around line 11-33: The error response examples in the input safety rules
documentation hardcode the param field as "input", but this is incorrect because
param must contain the actual blocked field path which varies by API. Replace
the literal "input" value in the param field with a field-path placeholder in
both the Chinese and English JSON examples to indicate that this is a variable
representing the actual blocked field path rather than a fixed literal value.

In `@middleware/distributor_test.go`:
- Around line 65-91: The two test functions
TestRouteAutoGroupForRequestPathUpdatesRetryTokenGroup and
TestRouteAutoGroupForRequestPathKeepsResponsesRetryAuto have nearly identical
structure with only different inputs and expected outputs. Consolidate them into
a single table-driven test by creating a test table with test cases that include
the request path, initial context values, and expected outputs for routedGroup,
ContextKeyUsingGroup, and ContextKeyTokenGroup. Then loop through each test case
to execute the same test logic with different inputs, eliminating code
duplication and improving maintainability.

---

Nitpick comments:
In `@docs/input_safety_rules.md`:
- Around line 982-1005: The allowlist terms in section 9.2 降低误杀白名单 are too
generic and will trigger false negatives in ordinary prompts, diluting the
safety signal. Review both the English terms (defense, defensive, protect,
mitigate, patch, fix, detect, monitor, incident response, ctf, lab, localhost,
training, education, medical, legal compliance) and the corresponding Chinese
terms to identify and remove overly broad standalone tokens that commonly appear
in benign content. Replace generic terms with more specific, contextual phrases
that clearly indicate legitimate security research, educational, or compliance
activities, ensuring the allowlist narrows its scope to reduce false positives
rather than acting as a blanket exception list.

In `@router/web-router.go`:
- Around line 33-37: Remove the redundant c.Header call that sets Cache-Control
in the GET handler for the "/" route. The Cache middleware already sets the same
Cache-Control header for this path, so the duplicate c.Header("Cache-Control",
"no-cache") line in the anonymous function should be deleted to avoid redundancy
and maintain consistency with the middleware configuration.

In `@web/custom/home.html`:
- Around line 1-7: The custom home.html file is missing a favicon link reference
in the head section that is present in the default landing page. Add a link
element for the favicon (referencing the /logo.png file with type image/png) in
the head section of the custom home.html file, placing it after the existing
meta tags and before or near the title element to match the pattern used in the
default page.
🪄 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: 64eac0d3-67fb-4664-9d29-6e40127a5950

📥 Commits

Reviewing files that changed from the base of the PR and between 87e52e8 and a4a78df.

📒 Files selected for processing (7)
  • docs/input_safety_handoff.md
  • docs/input_safety_rules.md
  • main.go
  • middleware/distributor.go
  • middleware/distributor_test.go
  • router/web-router.go
  • web/custom/home.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • middleware/distributor.go

Comment on lines +39 to +41
/v1/messages Claude messages 中 role=user 的 content
/v1beta/models/* Gemini contents 中 role=user 或空 role 的 parts[].text
/v1/models/* Gemini contents 中 role=user 或空 role 的 parts[].text

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align the Gemini route names with the actual router mount.

This points implementers at /v1beta/models/* and /v1/models/*, but the current router mounts Gemini under /v1beta/openai/models, so the real entrypoint would be missed. Based on learnings: router/relay-router.go mounts geminiCompatibleRouter at /v1beta/openai/models.

🤖 Prompt for 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.

In `@docs/input_safety_handoff.md` around lines 39 - 41, The documentation lists
Gemini route endpoints as `/v1beta/models/*` and `/v1/models/*`, but these do
not match the actual router configuration where geminiCompatibleRouter is
mounted at `/v1beta/openai/models` in relay-router.go. Update the route paths in
the documentation to reflect the correct actual mount point of
`/v1beta/openai/models/*` so implementers can find the correct entrypoint.

Comment on lines +11 to +33
```json
{
"error": {
"message": "请求内容不符合输入安全规则,请修改 prompt 后重试。",
"type": "invalid_request_error",
"param": "input",
"code": "input_safety_blocked"
}
}
```

英文部署可使用:

```json
{
"error": {
"message": "Your request was blocked by the input safety policy. Please revise your prompt and try again.",
"type": "invalid_request_error",
"param": "input",
"code": "input_safety_blocked"
}
}
```

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a field-path placeholder for param.

"input" only fits one API shape; the handoff doc says param must carry the blocked user-input path, so copying this example will be wrong for Chat/Claude/Gemini. Based on learnings: param is the blocked field path, not a fixed literal.

🤖 Prompt for 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.

In `@docs/input_safety_rules.md` around lines 11 - 33, The error response examples
in the input safety rules documentation hardcode the param field as "input", but
this is incorrect because param must contain the actual blocked field path which
varies by API. Replace the literal "input" value in the param field with a
field-path placeholder in both the Chinese and English JSON examples to indicate
that this is a variable representing the actual blocked field path rather than a
fixed literal value.

Comment on lines +808 to +819
英文裸露/色情词:

```text
nude
naked
topless
see-through
lingerie
porn
explicit
spread legs
```

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the leading space before topless.

As written, that token will never match an exact keyword scan. Based on learnings: the rule lists here are meant to be consumed directly by the matcher.

🤖 Prompt for 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.

In `@docs/input_safety_rules.md` around lines 808 - 819, The keyword `topless` in
the list of English nudity/pornography words has an unintended leading space
before it, which will cause the matcher to look for " topless" (with space)
instead of the actual word "topless". Remove the leading whitespace before
`topless` to align it with the formatting of the other keywords in the list so
that exact keyword matching will work correctly.

Comment on lines +65 to +91
func TestRouteAutoGroupForRequestPathUpdatesRetryTokenGroup(t *testing.T) {
gin.SetMode(gin.TestMode)
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
common.SetContextKey(c, constant.ContextKeyUsingGroup, "auto")
common.SetContextKey(c, constant.ContextKeyTokenGroup, "auto")

routedGroup := routeAutoGroupForRequestPath(c, "auto")

assert.Equal(t, "codex-completions", routedGroup)
assert.Equal(t, "codex-completions", common.GetContextKeyString(c, constant.ContextKeyUsingGroup))
assert.Equal(t, "codex-completions", common.GetContextKeyString(c, constant.ContextKeyTokenGroup))
}

func TestRouteAutoGroupForRequestPathKeepsResponsesRetryAuto(t *testing.T) {
gin.SetMode(gin.TestMode)
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", nil)
common.SetContextKey(c, constant.ContextKeyUsingGroup, "auto")
common.SetContextKey(c, constant.ContextKeyTokenGroup, "auto")

routedGroup := routeAutoGroupForRequestPath(c, "auto")

assert.Equal(t, "auto", routedGroup)
assert.Equal(t, "auto", common.GetContextKeyString(c, constant.ContextKeyUsingGroup))
assert.Equal(t, "auto", common.GetContextKeyString(c, constant.ContextKeyTokenGroup))
}

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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Consolidate into a table-driven test to follow project guidelines.

These two test functions have nearly identical structure with different inputs and expected outputs. Per coding guidelines, backend tests should prefer deterministic table tests with explicit inputs and expected outputs.

Consolidating them into a single table-driven test would improve maintainability and align with the existing TestAutoGroupForRequestPath pattern (which was refactored per earlier review feedback).

♻️ Proposed table-driven refactor
-func TestRouteAutoGroupForRequestPathUpdatesRetryTokenGroup(t *testing.T) {
-	gin.SetMode(gin.TestMode)
-	c, _ := gin.CreateTestContext(httptest.NewRecorder())
-	c.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
-	common.SetContextKey(c, constant.ContextKeyUsingGroup, "auto")
-	common.SetContextKey(c, constant.ContextKeyTokenGroup, "auto")
-
-	routedGroup := routeAutoGroupForRequestPath(c, "auto")
-
-	assert.Equal(t, "codex-completions", routedGroup)
-	assert.Equal(t, "codex-completions", common.GetContextKeyString(c, constant.ContextKeyUsingGroup))
-	assert.Equal(t, "codex-completions", common.GetContextKeyString(c, constant.ContextKeyTokenGroup))
-}
-
-func TestRouteAutoGroupForRequestPathKeepsResponsesRetryAuto(t *testing.T) {
-	gin.SetMode(gin.TestMode)
-	c, _ := gin.CreateTestContext(httptest.NewRecorder())
-	c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", nil)
-	common.SetContextKey(c, constant.ContextKeyUsingGroup, "auto")
-	common.SetContextKey(c, constant.ContextKeyTokenGroup, "auto")
-
-	routedGroup := routeAutoGroupForRequestPath(c, "auto")
-
-	assert.Equal(t, "auto", routedGroup)
-	assert.Equal(t, "auto", common.GetContextKeyString(c, constant.ContextKeyUsingGroup))
-	assert.Equal(t, "auto", common.GetContextKeyString(c, constant.ContextKeyTokenGroup))
-}
+func TestRouteAutoGroupForRequestPath(t *testing.T) {
+	tests := []struct {
+		name                 string
+		path                 string
+		initialGroup         string
+		expectedRoutedGroup  string
+		expectedUsingGroup   string
+		expectedTokenGroup   string
+	}{
+		{
+			name:                "chat completions updates retry token group",
+			path:                "/v1/chat/completions",
+			initialGroup:        "auto",
+			expectedRoutedGroup: "codex-completions",
+			expectedUsingGroup:  "codex-completions",
+			expectedTokenGroup:  "codex-completions",
+		},
+		{
+			name:                "responses keeps retry auto",
+			path:                "/v1/responses",
+			initialGroup:        "auto",
+			expectedRoutedGroup: "auto",
+			expectedUsingGroup:  "auto",
+			expectedTokenGroup:  "auto",
+		},
+	}
+
+	gin.SetMode(gin.TestMode)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			c, _ := gin.CreateTestContext(httptest.NewRecorder())
+			c.Request = httptest.NewRequest(http.MethodPost, tt.path, nil)
+			common.SetContextKey(c, constant.ContextKeyUsingGroup, tt.initialGroup)
+			common.SetContextKey(c, constant.ContextKeyTokenGroup, tt.initialGroup)
+
+			routedGroup := routeAutoGroupForRequestPath(c, tt.initialGroup)
+
+			assert.Equal(t, tt.expectedRoutedGroup, routedGroup)
+			assert.Equal(t, tt.expectedUsingGroup, common.GetContextKeyString(c, constant.ContextKeyUsingGroup))
+			assert.Equal(t, tt.expectedTokenGroup, common.GetContextKeyString(c, constant.ContextKeyTokenGroup))
+		})
+	}
+}

As per coding guidelines: "Prefer deterministic table tests with explicit inputs and expected outputs."

🤖 Prompt for 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.

In `@middleware/distributor_test.go` around lines 65 - 91, The two test functions
TestRouteAutoGroupForRequestPathUpdatesRetryTokenGroup and
TestRouteAutoGroupForRequestPathKeepsResponsesRetryAuto have nearly identical
structure with only different inputs and expected outputs. Consolidate them into
a single table-driven test by creating a test table with test cases that include
the request path, initial context values, and expected outputs for routedGroup,
ContextKeyUsingGroup, and ContextKeyTokenGroup. Then loop through each test case
to execute the same test logic with different inputs, eliminating code
duplication and improving maintainability.

Source: Coding guidelines

@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

🧹 Nitpick comments (1)
controller/relay_retry_test.go (1)

12-13: ⚡ Quick win

Use require for setup assertions in this new backend test.

This test currently uses only assert; please add require for setup/fatal checks (e.g., validating created test context) and keep assert for value assertions.

Proposed patch
 import (
 	"context"
 	"fmt"
 	"net/http"
 	"net/http/httptest"
 	"testing"
 
 	"github.com/QuantumNous/new-api/types"
 	"github.com/gin-gonic/gin"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
@@
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
+			require.NotNil(t, ctx)
 
 			assert.False(t, shouldRetry(ctx, tt.err, 3))
 		})
 	}
 }

As per coding guidelines, "New or substantially rewritten Go backend tests MUST use github.com/stretchr/testify/require for setup and fatal assertions, and github.com/stretchr/testify/assert for non-fatal value checks."

Also applies to: 49-51

🤖 Prompt for 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.

In `@controller/relay_retry_test.go` around lines 12 - 13, Add the `require`
import from github.com/stretchr/testify alongside the existing `assert` import.
Then, locate setup and initialization assertions in the test (like validating
created test context or other setup validations referenced in the flagged
sections around lines 49-51) and replace those `assert` calls with `require`
calls to ensure fatal failures during setup. Keep `assert` for non-fatal value
verification assertions that check expected behavior outcomes.

Source: Coding guidelines

🤖 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 `@service/group_test.go`:
- Around line 11-12: Add the missing testify assert import alongside the
existing require import at the top of the file. Then locate the final behavior
assertion (the value check comparing the function result against expected
output, currently using require.Equal) and change it from require.Equal to
assert.Equal, since this is a non-fatal behavior check rather than a setup or
fatal condition check. Keep the require.NoError calls in the setup section as
they are correct for fatal checks.

---

Nitpick comments:
In `@controller/relay_retry_test.go`:
- Around line 12-13: Add the `require` import from github.com/stretchr/testify
alongside the existing `assert` import. Then, locate setup and initialization
assertions in the test (like validating created test context or other setup
validations referenced in the flagged sections around lines 49-51) and replace
those `assert` calls with `require` calls to ensure fatal failures during setup.
Keep `assert` for non-fatal value verification assertions that check expected
behavior outcomes.
🪄 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: 3ba60996-0f02-4b6b-ad23-a5456894cf62

📥 Commits

Reviewing files that changed from the base of the PR and between a4a78df and 17a08e5.

📒 Files selected for processing (9)
  • constant/context_key.go
  • controller/relay.go
  • controller/relay_retry_test.go
  • middleware/distributor.go
  • middleware/distributor_test.go
  • service/channel_select.go
  • service/group.go
  • service/group_test.go
  • types/error.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • middleware/distributor_test.go
  • middleware/distributor.go

Comment thread service/group_test.go
Comment on lines +11 to +12
"github.com/stretchr/testify/require"
)

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify assertion style usage in this updated test file.
rg -n --type=go 'require\.(Equal|NotEqual|Len|Contains)\(|assert\.(Equal|NotEqual|Len|Contains)\(' service/group_test.go

Repository: QuantumNous/new-api

Length of output: 124


🏁 Script executed:

cat -n service/group_test.go

Repository: QuantumNous/new-api

Length of output: 1429


Use assert.Equal for the final behavior assertion in this test.

Line 30 performs a value check (comparing the function result against the expected output), which is a non-fatal assertion and should use assert.Equal instead of require.Equal. The require functions should be reserved for setup/fatal checks (lines 19–23 correctly use require.NoError).

Add the missing import and update the assertion:

Required changes
 import (
 	"net/http/httptest"
 	"testing"
 
 	"github.com/QuantumNous/new-api/common"
 	"github.com/QuantumNous/new-api/constant"
 	"github.com/QuantumNous/new-api/setting"
 	"github.com/gin-gonic/gin"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
...
-	require.Equal(t, []string{"codex", "codex-pro"}, groups)
+	assert.Equal(t, []string{"codex", "codex-pro"}, groups)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"github.com/stretchr/testify/require"
)
import (
"net/http/httptest"
"testing"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/setting"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Suggested change
"github.com/stretchr/testify/require"
)
assert.Equal(t, []string{"codex", "codex-pro"}, groups)
🤖 Prompt for 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.

In `@service/group_test.go` around lines 11 - 12, Add the missing testify assert
import alongside the existing require import at the top of the file. Then locate
the final behavior assertion (the value check comparing the function result
against expected output, currently using require.Equal) and change it from
require.Equal to assert.Equal, since this is a non-fatal behavior check rather
than a setup or fatal condition check. Keep the require.NoError calls in the
setup section as they are correct for fatal checks.

Source: Coding guidelines

@dofastted dofastted closed this Jul 15, 2026
@dofastted
dofastted deleted the feat/auto-path-group-routing branch July 15, 2026 12:22
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.

1 participant