Skip to content

Add developer-level model association rules#1672

Merged
looplj merged 3 commits into
looplj:unstablefrom
zhaozhaozz:feat/model-developer-rules
May 21, 2026
Merged

Add developer-level model association rules#1672
looplj merged 3 commits into
looplj:unstablefrom
zhaozhaozz:feat/model-developer-rules

Conversation

@zhaozhaozz

Copy link
Copy Markdown
Contributor

Closes #1671

Summary

  • add developer-level model association rules inherited by models from the same developer
  • make developer rules select only channels or channel tags, with each model using its own model ID during routing
  • add model-level opt-out for developer rule inheritance and show developer rule counts in the model list
  • apply effective associations consistently in routing, model list counts, configured model listing, and unassociated-channel detection
  • update GraphQL, frontend queries, i18n, docs, logs, and upgrade compatibility tests

Validation

  • PATH=/usr/local/go/bin:$PATH make generate
  • PATH=/usr/local/go/bin:$PATH go test ./...
  • cd frontend && ./node_modules/.bin/tsc --noEmit --pretty false
  • git diff --check
  • runtime UI check with isolated SQLite DB and temporary backend/frontend ports

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces 'Developer Rules', allowing administrators to configure reusable channel association rules at the model developer level. Models now inherit these rules by default, merging them with model-specific associations based on priority, with an option to explicitly disable inheritance. The implementation includes backend logic for inheritance and merging, frontend UI updates for managing these rules, and comprehensive tests. Feedback is provided regarding the potential for side effects due to shallow cloning of association conditions and performance concerns related to using JSON marshalling for cache signatures on the request hot path.

Comment thread internal/server/biz/model_settings_inheritance.go
Comment thread internal/server/orchestrator/candidates.go
@greptile-apps

greptile-apps Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces developer-level model association rules: a new layer of routing configuration that is configured once per model developer and automatically inherited by all models from that developer, with per-model opt-out support.

  • Adds DeveloperModelSettings storage in SystemModelSettings, a new EffectiveModelAssociations function that merges developer-inherited rules with model-level rules, and replaces the JSON-based association cache signature with a faster FNV-64 hash.
  • Extends the GraphQL schema and frontend dialogs (shared ModelsAssociationDialog now handles both model and developer modes), adds a disableDeveloperSettingsInheritance toggle per model, and surfaces developer rule counts in the model list.
  • Adds backward-compat handling in the resolver so older clients omitting developerSettings do not clear the new field.

Confidence Score: 3/5

The developer-association save path drops modelBlacklistRegex from the mutation payload, clearing it on every save for any user who has set that field.

The backend-inheritance logic, deep-copy, and FNV signature are well-written and covered by tests. However, the developer-mode save in models-association-dialog.tsx forwards all scalar settings from the loaded data but omits modelBlacklistRegex. Because the GraphQL field is nullable and the Go struct field is a plain string, the absent field arrives as "", overwriting any previously configured blacklist regex every time a developer rule is saved.

frontend/src/features/models/components/models-association-dialog.tsx — specifically the updateModelSettings.mutateAsync call in the developer-mode submit path

Important Files Changed

Filename Overview
frontend/src/features/models/components/models-association-dialog.tsx Large refactor adding developer-mode UI; developer save omits modelBlacklistRegex, silently clearing it on every save
internal/server/biz/model_settings_inheritance.go New file implementing developer-rule inheritance with proper deep-copy (including When/Condition), merge, validate, and normalize logic
internal/server/orchestrator/candidates.go Replaces JSON-based association signature with FNV-64 hash; adds developer-inheritance lookup to effective-association resolution
internal/server/gql/system.resolvers.go Adds backward-compat guard that preserves DeveloperSettings when an older client omits the field
frontend/src/features/models/components/models-settings-dialog.tsx Passes `settings?.developerSettings
internal/server/biz/model_settings_inheritance_test.go Tests for developer-rule inheritance, merge ordering, and opt-out flag
internal/server/orchestrator/candidates_developer_settings_test.go End-to-end routing tests covering developer-rule inheritance through the candidate-selection pipeline

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming Request: model ID] --> B[EffectiveModelAssociations]
    B --> C{DisableDeveloperInheritance?}
    C -- Yes --> D[Model-level rules only]
    C -- No --> E[developerAssociationsForDeveloper]
    E --> F[inheritDeveloperAssociationsForModel\nfill ModelID from request model]
    F --> G[mergeInheritedModelAssociations\npriority sort: model rules first at equal priority]
    D --> G
    G --> H[resolveAssociations\nFNV cache lookup / fill]
    H --> I{Cache hit?}
    I -- Yes --> J[Return cached candidates]
    I -- No --> K[Match channels / tags]
    K --> L[Store in associationCache]
    L --> J
    J --> M[filterResolvedCandidatesForRequest\napply When conditions]
    M --> N[ChannelModelsCandidate list]
Loading

Reviews (4): Last reviewed commit: "fix: satisfy errcheck for association si..." | Re-trigger Greptile

Comment thread internal/server/orchestrator/candidates.go Outdated
Comment thread internal/server/biz/model_settings_inheritance_test.go Outdated
@zhaozhaozz

Copy link
Copy Markdown
Contributor Author

已处理本轮检视意见:

  • cloneModelAssociation 现在会深拷贝 When 和嵌套 Condition,并补了回归测试。
  • modelAssociationSignature 已从每请求 json.Marshal 改为字段级 FNV 签名,并补了嵌套条件变化的签名测试。
  • 修正了继承测试中 developer 与 model ID 不匹配的测试数据。
  • UpdateSystemModelSettings 的兼容保护保留现有 last-writer-wins 语义,并在注释中明确并发边界。这里暂不引入 CAS/version,因为这会改变系统设置整对象写入协议,超出本 PR 的开发者规则机制范围。

验证:

  • PATH=/usr/local/go/bin:$PATH go test ./internal/server/biz ./internal/server/orchestrator ./internal/server/gql
  • PATH=/usr/local/go/bin:$PATH go test ./...
  • git diff --check

@looplj

looplj commented May 20, 2026

Copy link
Copy Markdown
Owner

感谢 pr ,代码有点冲突了。

是否可以看下截图,大概是怎么个交互。

@zhaozhaozz zhaozhaozz force-pushed the feat/model-developer-rules branch from 6e302e4 to f4ac948 Compare May 21, 2026 01:53
@zhaozhaozz

Copy link
Copy Markdown
Contributor Author

@looplj 已经解决冲突并补充了交互截图,整体交互如下。

开发者级模型渠道规则说明

本次功能的目标是减少同一开发者下多个模型的重复渠道配置。原来每个模型都要单独配置自己的渠道规则;现在可以先在开发者层配置一组通用规则,下面的模型默认继承这些规则,同时仍然保留模型自己的单独配置能力。

主界面入口

进入“模型”页面后,模型会按开发者分组展示。每个开发者分组的操作区新增了“开发者规则”入口,旁边的数字表示这个开发者当前配置了几条规则。

模型列表中的开发者规则入口

在截图中,Anthropic 分组下有 2 个模型,右侧显示 开发者规则 1,表示该开发者已经配置了 1 条开发者级规则。点击这个入口会打开开发者规则配置窗口。

开发者规则配置

开发者规则窗口用于配置同一开发者下所有模型共享的渠道选择规则。

开发者规则配置窗口

这里的关键点是:开发者规则只选择“渠道”或“渠道标签”,不选择具体模型 ID。

例如配置一条“渠道标签”规则后,系统会在实际路由时为每个继承该规则的模型自动填入自己的模型 ID。也就是说,同一条开发者规则可以同时服务该开发者下的多个模型,而不会把某一个具体模型固定写死。

右侧“预览”区域会展示当前规则能匹配到的渠道,便于确认规则是否能命中预期渠道。

默认继承关系

同一个 developer 下的模型默认继承开发者规则。继承后,请求路由会基于“有效规则”工作,有效规则由两部分组成:

  • 开发者规则:开发者层配置的通用规则。
  • 模型规则:模型自己单独配置的规则。

合并规则时遵循两个原则:

  • 优先级数字越小,越先执行。
  • 同优先级时,模型自己的规则优先于开发者规则。

因此,开发者规则适合放通用渠道策略,模型规则适合放少数模型的特殊覆盖策略。

模型级配置窗口

从某个模型行的操作菜单进入“管理关联”,会打开模型自己的关联配置窗口。

模型继承开发者规则时的预览

截图中的模型自己没有单独配置规则,但因为存在对应开发者规则,右侧预览仍然能看到继承来的渠道。这个预览展示的是模型实际会使用的有效规则,而不是只展示模型自身保存的规则。

不继承开发者配置

模型关联窗口新增了“不继承开发者配置”开关。打开后,这个模型只使用自己单独配置的规则,开发者规则不会再应用到它。

模型关闭开发者规则继承

截图中打开“不继承开发者配置”后,因为该模型本身没有配置独立规则,右侧预览变为空。

实际验证结果:

  • 关闭继承前,两个同开发者模型都继承开发者规则,关联渠道数都是 1
  • 其中一个模型打开“不继承开发者配置”后,它的关联渠道数变为 0
  • 另一个同开发者模型没有打开该开关,仍然继承开发者规则,关联渠道数保持 1

生效范围

开发者规则继承后的有效规则已经统一应用到主要模型关联路径:

  • 请求路由时选择候选渠道。
  • 模型列表中的关联渠道数量统计。
  • 模型关联窗口中的渠道预览。
  • 已配置模型和未关联渠道相关判断。
  • 开发者分组上的规则数量展示。

这保证了界面展示、预览结果和实际请求路由使用的是同一套规则语义。

兼容性

该功能对老版本配置保持兼容:

  • 旧系统配置中没有 developerSettings 时,会按空列表处理。
  • 旧模型配置中没有 disableDeveloperSettingsInheritance 时,默认值为 false,也就是继续继承开发者规则。
  • 老客户端更新系统模型设置但不传 developerSettings 时,会保留已有开发者规则,避免升级过程中被意外清空。

推荐用法

建议把稳定、通用的渠道策略放到开发者规则里,例如某个开发者统一走一组带标签的渠道。

当单个模型需要特殊处理时,再进入模型自己的“管理关联”窗口追加模型规则。如果这个模型完全不应该继承开发者规则,则打开“不继承开发者配置”。

Comment on lines +679 to +685
await updateModelSettings.mutateAsync({
fallbackToChannelsOnModelNotFound: settings!.fallbackToChannelsOnModelNotFound,
queryAllChannelModels: settings!.queryAllChannelModels,
defaultModelAPIIncludeAll: settings!.defaultModelAPIIncludeAll,
autoReasoningEffort: settings!.autoReasoningEffort,
developerSettings: nextDeveloperSettings.sort((a, b) => a.developer.localeCompare(b.developer)),
});

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.

P1 modelBlacklistRegex is missing from the developer-association save call. Because the GraphQL input field is nullable (String, not String!) and the backing Go struct uses a plain string (not *string), an absent field is unmarshalled as "", which overwrites any blacklist regex the user has set. Every time a developer rule is saved from this dialog, the blacklist is silently cleared.

Suggested change
await updateModelSettings.mutateAsync({
fallbackToChannelsOnModelNotFound: settings!.fallbackToChannelsOnModelNotFound,
queryAllChannelModels: settings!.queryAllChannelModels,
defaultModelAPIIncludeAll: settings!.defaultModelAPIIncludeAll,
autoReasoningEffort: settings!.autoReasoningEffort,
developerSettings: nextDeveloperSettings.sort((a, b) => a.developer.localeCompare(b.developer)),
});
await updateModelSettings.mutateAsync({
fallbackToChannelsOnModelNotFound: settings!.fallbackToChannelsOnModelNotFound,
queryAllChannelModels: settings!.queryAllChannelModels,
defaultModelAPIIncludeAll: settings!.defaultModelAPIIncludeAll,
autoReasoningEffort: settings!.autoReasoningEffort,
modelBlacklistRegex: settings!.modelBlacklistRegex,
developerSettings: nextDeveloperSettings.sort((a, b) => a.developer.localeCompare(b.developer)),
});

@looplj looplj merged commit 085ee06 into looplj:unstable May 21, 2026
4 checks passed
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.

[Feature/功能]: 开发者级模型关联规则

2 participants