feat(model): auto create vendor and bind model metadata on channel insert/update - #6742
feat(model): auto create vendor and bind model metadata on channel insert/update#6742ggdayup wants to merge 1 commit into
Conversation
WalkthroughThe change automatically synchronizes channel vendors and models during channel operations. It also adds four Gemini models to supported listings and configures their cache, model, and completion ratios with tests. ChangesChannel vendor synchronization
Gemini model support
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant ChannelFlow
participant EnsureChannelVendorAndModels
participant VendorModelRecords
ChannelFlow->>EnsureChannelVendorAndModels: synchronize channel metadata
EnsureChannelVendorAndModels->>VendorModelRecords: ensure vendor
EnsureChannelVendorAndModels->>VendorModelRecords: bind channel models
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment Warning |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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/vendor_channel_auto_test.go`:
- Around line 13-14: Update TestEnsureVendorAndAutoBindModelsForNewChannel to
create an isolated database fixture before calling DB.AutoMigrate, assign that
fixture to the global DB, and restore the previous DB value via t.Cleanup.
Ensure the fixture’s database resources and migrated state are also cleaned up
through t.Cleanup so the test is independent of suite order and shared data.
In `@model/vendor_meta.go`:
- Around line 180-196: Update model/vendor_meta.go:180-196 in
AutoBindChannelModelsToVendor to return unexpected lookup errors and model
create or update errors instead of only logging them. Update
model/channel.go:450-450 to roll back and return the synchronization error
before committing the batch transaction; at model/channel.go:530-532 and
582-584, return the error or persist a durable retry state when atomicity is
unavailable. At controller/channel_upstream_update.go:519-519 and 966-966,
propagate or durably retry synchronization failures after automatic and manual
model application.
- Around line 136-153: Update EnsureVendorForChannel to enforce active
vendor-name uniqueness with a portable non-null key column populated only for
active vendors and a unique index on that key, compatible with SQLite, MySQL
5.7.8+, and PostgreSQL 9.6+. Populate or clear the key consistently when vendors
are created or soft-deleted, and when Create conflicts on the unique constraint,
reload and return the existing active Vendor instead of returning the error.
In `@relay/channel/gemini/constant.go`:
- Line 8: Remove the unsupported bare gemini-3.6 identifier while retaining
gemini-3.6-flash: update relay/channel/gemini/constant.go:8-8,
setting/ratio_setting/cache_ratio.go:13-13,
setting/ratio_setting/model_ratio.go:195-195, 339-339, and 592-593 to eliminate
its configuration entries, and update
setting/ratio_setting/model_ratio_test.go:30-34 to remove its test coverage.
Ensure no configuration or test path continues to reference gemini-3.6.
In `@setting/ratio_setting/model_ratio_test.go`:
- Around line 15-18: Update the model ratio test table and assertions to cover
the cache-ratio billing invariant: add an expected cache ratio for each
supported model, call GetCacheRatio for every case, and assert the returned
value alongside the existing model and completion ratio checks.
🪄 Autofix
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 Plus
Run ID: be469855-47df-4ff9-b685-a18b17b2333e
📒 Files selected for processing (8)
controller/channel_upstream_update.gomodel/channel.gomodel/vendor_channel_auto_test.gomodel/vendor_meta.gorelay/channel/gemini/constant.gosetting/ratio_setting/cache_ratio.gosetting/ratio_setting/model_ratio.gosetting/ratio_setting/model_ratio_test.go
| func TestEnsureVendorAndAutoBindModelsForNewChannel(t *testing.T) { | ||
| require.NoError(t, DB.AutoMigrate(&Channel{}, &Ability{}, &Vendor{}, &Model{})) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Create an isolated database fixture.
This test uses the global DB without initializing or restoring it. It can depend on suite order and leave migrated tables and rows in a shared test database.
Initialize an isolated test database, assign and restore DB, and clean up through t.Cleanup. As per coding guidelines, backend tests must initialize database state explicitly in test fixtures.
🤖 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 `@model/vendor_channel_auto_test.go` around lines 13 - 14, Update
TestEnsureVendorAndAutoBindModelsForNewChannel to create an isolated database
fixture before calling DB.AutoMigrate, assign that fixture to the global DB, and
restore the previous DB value via t.Cleanup. Ensure the fixture’s database
resources and migrated state are also cleaned up through t.Cleanup so the test
is independent of suite order and shared data.
Source: Coding guidelines
| err := useDB.Where("name = ? AND deleted_at IS NULL", name).First(&vendor).Error | ||
| if err == nil { | ||
| return vendor.Id, nil | ||
| } | ||
|
|
||
| if errors.Is(err, gorm.ErrRecordNotFound) { | ||
| newVendor := Vendor{ | ||
| Name: name, | ||
| Description: fmt.Sprintf("%s 渠道自动创建供应商", name), | ||
| Icon: icon, | ||
| Status: 1, | ||
| CreatedTime: common.GetTimestamp(), | ||
| UpdatedTime: common.GetTimestamp(), | ||
| } | ||
| if err := useDB.Create(&newVendor).Error; err != nil { | ||
| return 0, err | ||
| } | ||
| return newVendor.Id, nil |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Prevent duplicate active vendor rows.
EnsureVendorForChannel uses a read-then-create sequence. The (name, deleted_at) unique index does not enforce one active row because MySQL, PostgreSQL, and SQLite allow multiple NULL values in a unique key. Concurrent calls can both find no row and create vendors with deleted_at IS NULL.
Use a portable active-name uniqueness strategy. Then handle a conflicting create by loading the existing vendor. As per coding guidelines, database code must support SQLite, MySQL >= 5.7.8, and PostgreSQL >= 9.6.
🤖 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 `@model/vendor_meta.go` around lines 136 - 153, Update EnsureVendorForChannel
to enforce active vendor-name uniqueness with a portable non-null key column
populated only for active vendors and a unique index on that key, compatible
with SQLite, MySQL 5.7.8+, and PostgreSQL 9.6+. Populate or clear the key
consistently when vendors are created or soft-deleted, and when Create conflicts
on the unique constraint, reload and return the existing active Vendor instead
of returning the error.
Source: Coding guidelines
| err := useDB.Where("model_name = ? AND deleted_at IS NULL", modelName).First(&existing).Error | ||
| if errors.Is(err, gorm.ErrRecordNotFound) { | ||
| newModel := Model{ | ||
| ModelName: modelName, | ||
| VendorID: vendorID, | ||
| Status: 1, | ||
| SyncOfficial: 0, | ||
| Endpoints: `["openai"]`, | ||
| CreatedTime: now, | ||
| UpdatedTime: now, | ||
| } | ||
| if err := useDB.Create(&newModel).Error; err != nil { | ||
| common.SysError(fmt.Sprintf("auto bind model failed: %s, err: %v", modelName, err)) | ||
| } | ||
| } else if err == nil && existing.VendorID == 0 { | ||
| useDB.Model(&existing).Update("vendor_id", vendorID) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Do not report successful synchronization after a metadata write fails.
AutoBindChannelModelsToVendor drops database errors, and every caller discards the returned synchronization error. A channel operation can therefore succeed while vendor or model metadata is missing.
model/vendor_meta.go#L180-L196: return unexpected lookup errors and model create or update errors instead of only logging them.model/channel.go#L450-L450: roll back and return the synchronization error before committing the batch transaction.model/channel.go#L530-L532: return the synchronization error, or persist a durable retry state if channel insertion cannot be atomic.model/channel.go#L582-L584: return the synchronization error, or persist a durable retry state if channel updates cannot be atomic.controller/channel_upstream_update.go#L519-L519: propagate or durably retry the synchronization failure after automatic model application.controller/channel_upstream_update.go#L966-L966: propagate or durably retry the synchronization failure after manual model application.
📍 Affects 3 files
model/vendor_meta.go#L180-L196(this comment)model/channel.go#L450-L450model/channel.go#L530-L532model/channel.go#L582-L584controller/channel_upstream_update.go#L519-L519controller/channel_upstream_update.go#L966-L966
🤖 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 `@model/vendor_meta.go` around lines 180 - 196, Update
model/vendor_meta.go:180-196 in AutoBindChannelModelsToVendor to return
unexpected lookup errors and model create or update errors instead of only
logging them. Update model/channel.go:450-450 to roll back and return the
synchronization error before committing the batch transaction; at
model/channel.go:530-532 and 582-584, return the error or persist a durable
retry state when atomicity is unavailable. At
controller/channel_upstream_update.go:519-519 and 966-966, propagate or durably
retry synchronization failures after automatic and manual model application.
| "gemini-2.0-flash-001", "gemini-2.0-flash-lite-001", "gemini-2.0-flash-lite", | ||
| "gemini-2.5-flash-lite", | ||
| "gemini-2.5-flash-lite", "gemini-3.1-flash-lite", "gemini-3.5-flash-lite", | ||
| "gemini-3.6", "gemini-3.6-flash", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify whether an explicit Gemini upstream-name translation exists.
ast-grep outline relay/channel/gemini --items all
rg -n --type=go -C 5 'gemini-3\.6|UpstreamModelName|OriginModelName|ModelList' .Repository: QuantumNous/new-api
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf 'Gemini files:\n'
git ls-files 'relay/channel/gemini/*'
printf '\nGemini constants/adaptor outline/selected content:\n'
sed -n '1,80p' relay/channel/gemini/constant.go
printf '\n'
sed -n '1,260p' relay/channel/gemini/adaptor.go
printf '\nRelevant ratio settings:\n'
sed -n '1,45p' setting/ratio_setting/cache_ratio.go
sed -n '180,210p' setting/ratio_setting/model_ratio.go
sed -n '330,348p' setting/ratio_setting/model_ratio.go
sed -n '580,602p' setting/ratio_setting/model_ratio.go
sed -n '24,38p' setting/ratio_setting/model_ratio_test.go
printf '\nFocused upstream-name translation/search for Gemini 3 identifiers:\n'
rg -n --type=go -C 3 'gemini-3\.6|gemini-3\.6-flash|UpstreamModelName|OriginModelName|ConvertOpenAIRequest|RequestURL.*gemini|Info\.UpstreamModelName|body\.Model' relay setting/dto relay/channel/gemini | sed -n '1,240p'Repository: QuantumNous/new-api
Length of output: 32367
🌐 Web query:
Google Gemini API pricing supported model versions gemini-3.6 gemini-3.6-flash
💡 Result:
As of August 10, 2026, Gemini 3.6 Flash is a generally available (GA) model in the Gemini API and the Gemini Enterprise Agent Platform [1][2][3]. Gemini API Pricing for Gemini 3.6 Flash (Paid Tier) The following pricing applies per 1 million tokens for the gemini-3.6-flash model [4]: Input Price: $1.50 per 1 million tokens [4]. Output Price: $7.50 per 1 million tokens (includes thinking tokens) [4]. Context Caching Storage Price: $1.00 per 1 million tokens per hour [4]. Grounding with Google Search/Maps: The first 5,000 requests per month are free (shared across the Gemini 3 model family); subsequent requests are $14.00 per 1,000 search queries [4]. Key Model Details Model ID: gemini-3.6-flash [1][2] Release Date: July 21, 2026 [2][3] Context Window: 1,048,576 tokens [1][2] Max Output Tokens: 65,536 tokens [1][2] Capabilities: Includes support for function calling, structured outputs, search grounding, context caching, and code execution [1][2]. Note: There is no Free Tier for gemini-3.6-flash in the Gemini API [4]. While a free tier exists for some models within the Gemini API, pricing and availability depend on the specific model selected [5]. You can verify the most current rates and specific model availability directly through the official Gemini API pricing documentation [4].
Citations:
- 1: https://ai.google.dev/gemini-api/docs/models/gemini-3.6-flash
- 2: https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/gemini/3-6-flash
- 3: https://releasebot.io/updates/google/gemini-api
- 4: https://ai.google.dev/gemini-api/docs/pricing
- 5: https://ai.google.dev/gemini-api/docs/billing
Remove the unsupported upstream identifier gemini-3.6.
gemini-3.6-flash is the supported Gemini model ID; the bare gemini-3.6 entry can be sent upstream because the Gemini adaptor builds request URLs from info.UpstreamModelName without translating it. Remove all configuration and test paths for the bare identifier, and keep gemini-3.6-flash in the Gemini model list.
📍 Affects 4 files
relay/channel/gemini/constant.go#L8-L8(this comment)setting/ratio_setting/cache_ratio.go#L13-L13setting/ratio_setting/model_ratio.go#L195-L195setting/ratio_setting/model_ratio.go#L339-L339setting/ratio_setting/model_ratio.go#L592-L593setting/ratio_setting/model_ratio_test.go#L30-L34
🤖 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 `@relay/channel/gemini/constant.go` at line 8, Remove the unsupported bare
gemini-3.6 identifier while retaining gemini-3.6-flash: update
relay/channel/gemini/constant.go:8-8,
setting/ratio_setting/cache_ratio.go:13-13,
setting/ratio_setting/model_ratio.go:195-195, 339-339, and 592-593 to eliminate
its configuration entries, and update
setting/ratio_setting/model_ratio_test.go:30-34 to remove its test coverage.
Ensure no configuration or test path continues to reference gemini-3.6.
| tests := []struct { | ||
| model string | ||
| expectedModelRatio float64 | ||
| expectedCompRatio float64 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Test the cache-ratio billing invariant.
These cases do not call GetCacheRatio. A missing or incorrect cache ratio can change cache-token billing without failing this test. Add the expected cache ratio and assert it for every supported model.
Proposed test update
tests := []struct {
model string
expectedModelRatio float64
expectedCompRatio float64
+ expectedCacheRatio float64
}{
{
model: "gemini-3.1-flash-lite",
expectedModelRatio: 0.125,
expectedCompRatio: 6.0,
+ expectedCacheRatio: 0.1,
}, compRatio := ratio_setting.GetCompletionRatio(tt.model)
assert.InDelta(t, tt.expectedCompRatio, compRatio, 0.0001, "completion ratio mismatch for %s", tt.model)
+
+ cacheRatio, ok := ratio_setting.GetCacheRatio(tt.model)
+ require.True(t, ok, "cache ratio for %s should exist", tt.model)
+ assert.InDelta(t, tt.expectedCacheRatio, cacheRatio, 0.0001, "cache ratio mismatch for %s", tt.model)As per coding guidelines, “Backend tests must protect … billing/accounting invariants.”
Also applies to: 42-49
🤖 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 `@setting/ratio_setting/model_ratio_test.go` around lines 15 - 18, Update the
model ratio test table and assertions to cover the cache-ratio billing
invariant: add an expected cache ratio for each supported model, call
GetCacheRatio for every case, and assert the returned value alongside the
existing model and completion ratio checks.
Source: Coding guidelines
Important
📝 变更描述 / Description
当添加或更新新渠道(尤其是高级自定义渠道 OmniRoute、SiliconFlow 等)时,系统原先仅更新渠道表和路由能力表,导致
/models/metadata模型广场中缺少对应供应商实体,且模型元数据脱节。本 PR 实现了渠道到供应商与模型元数据的全自动联动:
EnsureVendorForChannel):渠道保存或更新时,自动识别并推导供应商名称,不存在时自动创建。AutoBindChannelModelsToVendor):自动遍历渠道拥有的模型,缺失元数据时自动生成并绑定该供应商 ID。Channel.Insert()、Channel.Update()、BatchInsertChannels()以及上游巡检自动发现模型 Hook 中。注:本 PR 包含 AI 辅助开发/生成 (AI-assisted)。
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
go test ./model -v -run TestEnsureVendorAndAutoBindModelsForNewChannel(PASS)relaykit模块独立构建验证:cd relaykit && GOWORK=off go build ./...(OK)Summary by CodeRabbit
New Features
Bug Fixes
Tests