Skip to content

fix(model): 统一模型配置解析,修复类型切换数据串扰#271

Open
XiaoBuHaly wants to merge 16 commits intoChevey339:masterfrom
XiaoBuHaly:feat-2-model-type-switch-reset-abilities
Open

fix(model): 统一模型配置解析,修复类型切换数据串扰#271
XiaoBuHaly wants to merge 16 commits intoChevey339:masterfrom
XiaoBuHaly:feat-2-model-type-switch-reset-abilities

Conversation

@XiaoBuHaly
Copy link
Contributor

@XiaoBuHaly XiaoBuHaly commented Jan 20, 2026

fix(model): 统一模型配置解析,修复类型切换数据串扰

统一 overrides 解析 · 修复 Chat/Embedding 切换串数据 · 存量配置迁移

Fixes #223


PR 类型
Bug Fix
破坏性变更
文件数
18
代码增删
███████░░░
+1677 -711


核心目标

本次 PR 解决三个问题:

1 数据污染 — 用户在 ChatEmbedding 模型间切换时,abilitiesoutput 字段会残留,导致 Embedding 模型错误地显示"联网/推理"能力。

2 逻辑散乱 — Provider Overrides 的解析逻辑散落在各个 UI 和 Service 中,维护困难且行为不一致。

3 展示混乱 — 不同页面的模型能力标签样式和显示逻辑不统一。

架构设计总览

flowchart LR
  subgraph Data["数据层"]
    A["Raw Overrides\n(Map)"] --清洗--> B["ModelOverrideResolver"]
    B --解析 & 约束--> C["ModelInfo\n(有效实例)"]
  end

  subgraph State["状态 & 迁移"]
    Switch["ModelEditTypeSwitch\n(编辑器状态切换)"] --缓存/恢复--> C
    Migrate["SettingsProvider\n(启动迁移)"] --清理脏数据--> A
  end

  subgraph UI["表现层"]
    Mobile["移动端\nModelSelectSheet\nModelDetailSheet"] --> Tag["ModelTagWrap\nModelCapsulesRow"]
    Desktop["桌面端\nDesktopEditDialog\nSettingsPage"] --> Tag
  end

  C ==> UI
  C ==> State
Loading
展开:编辑器类型切换时序图
sequenceDiagram
    participant 用户
    participant 编辑器UI
    participant ModelEditTypeSwitch
    participant 状态缓存

    rect rgb(240, 248, 255)
    Note over 用户,状态缓存: Chat → Embedding
    用户->>编辑器UI: 点击切换到 Embedding
    编辑器UI->>ModelEditTypeSwitch: apply(prev=Chat, next=Embedding)
    ModelEditTypeSwitch->>状态缓存: 保存当前 Chat 设置
    ModelEditTypeSwitch->>ModelEditTypeSwitch: 清空 abilities, 锁定 output=text
    ModelEditTypeSwitch-->>编辑器UI: 返回新状态 (frozen sets)
    编辑器UI->>编辑器UI: 刷新 UI (Tools 置灰)
    end

    rect rgb(255, 248, 240)
    Note over 用户,状态缓存: Embedding → Chat
    用户->>编辑器UI: 点击切换回 Chat
    编辑器UI->>ModelEditTypeSwitch: apply(prev=Embedding, next=Chat)
    ModelEditTypeSwitch->>状态缓存: 读取之前保存的 Chat 设置
    ModelEditTypeSwitch-->>编辑器UI: 返回恢复后的状态
    编辑器UI->>编辑器UI: 刷新 UI (Tools 恢复)
    end
Loading

变更详情漫游

核心服务与模型

文件 变更要点 增删
new
model_override_resolver.dart
lib/core/services/
集中化解析服务 / 强制 Embedding 约束
  • 统一归一化:Key/Value 自动 trim 和转小写
  • 强制约束:若类型为 embedding,强制 abilities = []output = [text]
  • 诊断日志:未知 override value 输出调试信息
// 核心逻辑
if (effectiveType == ModelType.embedding) {
  return base.copyWith(
    type: ModelType.embedding,
    output: const [Modality.text],
    abilities: const <ModelAbility>[],
  );
}
+155
new
model_types.dart
lib/core/models/
类型定义抽取 / 数据归一化
  • 抽取枚举 ModelType, Modality, ModelAbility
  • 新增 ModelInfo 不可变类
  • 构造时自动排序和去重 modalities
+83
refactor
chat_api_service.dart
lib/core/services/api/
运行时接入 Resolver
  • 移除硬编码解析,调用 ModelOverrideResolver
  • 增加 try-catch 兜底
+8
-20

状态管理与迁移

文件 变更要点 增删
feature
settings_provider.dart
lib/core/providers/
存量数据清洗 / 自动备份
  • 启动检查:版本号落后时触发
  • 自动备份:写入 provider_configs_backup_v1
  • 清洗:移除 embedding 的 tools, abilities, output
if (_embeddingChatOnlyFields.any(ov.containsKey)) {
  for (final k in _embeddingChatOnlyFields) {
    m.remove(k);
  }
}
+139
new
model_edit_state_helper.dart
lib/features/model/widgets/
编辑器状态机 / 防串数据
  • Chat → Embedding:缓存后清空
  • Embedding → Chat:从缓存恢复
  • 返回 frozen sets 防污染
+130
refactor
model_provider.dart
lib/core/providers/
适配新的 ModelInfo 类型定义,简化 infer 逻辑 +12
-37

UI 组件与交互

文件 变更要点 增删
new
model_tag_wrap.dart
lib/shared/widgets/
统一能力标签组件
  • ModelTagWrap:移动端流式标签
  • ModelCapsulesRow:桌面端胶囊
  • 深色模式 + 无障碍支持
+345
refactor
model_select_sheet.dart
lib/features/model/widgets/
接入 Resolver / Isolate 清洗
  • 后台 Isolate 使用 Resolver
  • 修复 Map 类型不兼容问题
  • 使用 ModelTagWrap 替换散落逻辑
+153
-209
refactor
model_edit_dialog.dart
model_detail_sheet.dart
接入 ModelEditTypeSwitch,保存时过滤无效字段,修复 Controller 释放遗漏 +366
-164
cleanup
provider_detail_page.dart
model_fetch_dialog.dart
desktop_settings_page.dart
移除冗余 tag 构建逻辑,统一使用新组件 +107
-281

测试与国际化

文件 变更要点 增删
test
model_override_resolver_test.dart
单元测试:类型切换清理、未知值容错、DisplayName 覆盖、非模型字段透传 +143
i18n
lib/l10n/*.arb
新增中/英文错误提示文案 +36

行为变化对比

场景 修复前 修复后
Chat → Embedding 切换 abilities 残留,UI 仍显示"工具调用"标签 自动清空 abilities,UI 正确隐藏能力标签
Embedding → Chat 切换 之前的 Chat 设置丢失 从会话缓存恢复,设置不丢失
存量脏数据 Embedding 模型可能含 abilities/tools 字段 启动时自动迁移清理
Override 解析 散落在 5+ 个文件,行为不一致 统一入口 ModelOverrideResolver
能力标签展示 各页面样式/逻辑不统一 复用 ModelTagWrap/ModelCapsulesRow

风险控制与回滚

风险点 应对措施 回滚方案
配置迁移失败 try-catch 包裹,失败仅打印日志,不阻塞启动 删除 migrations_version_v1,从 provider_configs_backup_v1 恢复
Override 解析异常 Resolver 内置容错,未知结构降级返回 Base Model 无需回滚,修正配置即可

审阅指引

建议重点审阅以下文件:

优先级 文件 原因
P0 model_override_resolver.dart 核心解析逻辑,影响全局行为
P0 model_edit_state_helper.dart 类型切换状态机,防串数据核心
P1 settings_provider.dart 迁移逻辑,涉及用户数据
P2 model_tag_wrap.dart UI 组件,影响视觉一致性

验证清单

自动化测试
flutter test test/core/services/model_override_resolver_test.dart

预期:全部通过

手动验证 - 迁移
步骤 操作 预期结果
1 构造含脏数据的 provider_configs_v1 -
2 启动 App 控制台输出 [Migration] 日志
3 检查配置 脏字段已被移除
手动验证 - 编辑交互
步骤 操作 预期结果
1 打开模型编辑,选 Chat,勾选 Tools Tools 选中
2 切换到 Embedding Tools 选项消失/置灰
3 切换回 Chat Tools 恢复选中状态
UI 走查
页面 检查点
移动端模型列表 Tag 显示正常
桌面端模型列表 胶囊显示正常
Provider 详情页 能力展示与列表一致

本 PR 由 feat-2-model-type-switch-reset-abilities 分支发起

…models

- Clear chat-only state when switching to embedding; keep embedding input modalities configurable
- Persist embedding input only; omit chat-only output/abilities from overrides
- Migrate stored embedding overrides to drop abilities (versioned, one-time)
- Use shared ModelTagWrap/ModelCapsulesRow for consistent tags/capsules (show eye for image input)
- Ensure effective embedding model info never carries chat abilities while preserving modalities
…ution

- Treat embedding output as text-only and clear chat-only abilities everywhere (service, lists, provider details)
- Make override type parsing robust with trim().toLowerCase()
- Harden edit flows: init-time embedding normalization, cache-first restore with inference fallback, omit builtInTools for embeddings
- Prevent potential desktop Row overflow by wrapping ModelCapsulesRow with Flexible
- Dedupe modalities/abilities in ModelTagWrap and refactor embedding override migration for clarity + debug stats
…akage

- Normalize override type parsing to avoid unsafe casts
- Enforce embedding invariants (text-only output, no abilities/tools) in effective resolution and migrations
- Use explicit chat defaults when switching embedding -> chat without cache
- Disable built-in tools toggles for embedding and improve model capsule semantics/dedup
- Add shared ModelOverrideResolver for consistent override parsing (type/modality/abilities) across UI and API resolution
- Ignore unknown override values instead of coercing to valid enums to avoid mislabeling capabilities
- Enforce non-empty chat input/output defaults and guard cache restores to prevent invalid empty modality sets
- Extract migration constants and add release-visible logging for embedding override cleanup failures
- Remove unused provider helper to reduce dead code
- Move model type enums and ModelInfo into core/models/model_types.dart
- Dedupe and strictly parse override modalities/abilities while preserving order
- Normalize override map keys to String on desktop to avoid cast issues
- Add tooltip/semantics labels to model capability capsules
- Reduce provider modelOverrides migration log verbosity
- Enforce embedding invariants on type overrides (clear abilities, force text-only output)
- Add value equality/hashCode to ModelInfo for stable comparisons
- Refactor model ability chips rendering and remove unused UI helpers
- Add coverage for ModelOverrideResolver.applyModelOverride
- Remove `Flexible` wrapping around `ModelCapsulesRow` in desktop model selector tiles so capsules + actions stay right-aligned
- Minor formatting cleanup in `_desktopModelTile` for readability (no behavior change besides alignment)
- Treat explicit empty modalities/abilities overrides as “clear”
- Prefer apiModelId override when inferring base model info
- Extract and reuse model type-switch cache helper across UI
- Dispose dynamic header/body controllers to avoid leaks
- Make ModelInfo list fields unmodifiable and normalize alpha usage
- Add test to ignore unknown type override
…sing

- Backup provider configs before migrating embedding modelOverrides and add migration logs.
- Treat non-empty but invalid modalities/abilities lists as "no override" (avoid unintended clears).
- Reset type-switch state safely (embedding defaults to text-only; restore cached chat state consistently).
- Normalize override maps to `Map<String, dynamic>` across UI to prevent cast/runtime issues.
- Guard model fetch actions against double taps and surface failures via snackbar.
- Use built-in tool name constants and add coverage for `applyDisplayName` behavior.
- Treat override `name` as a string (trimmed) in model edit/detail and provider model display.
- Dispose header/body field controllers when removing rows in model edit/detail UI.
- Make model tags/capsules resilient to missing l10n and wrap chips on narrow layouts.
- Add regression test to ensure non-model override keys are a no-op.
- Normalize `ModelInfo` modalities/abilities (dedupe + stable ordering).
- Apply model overrides consistently in desktop editors/sheets; treat invalid list overrides as explicit clears.
- Guard override application to avoid crashes on malformed configs, and harden model fetch dialog error handling.
- Enforce mutual exclusion for URL Context vs Code Execution tool toggles.
- Persist provider configs only on successful writes and only run the migration when configs are loaded
- Guard override parsing for headers/body and coerce values to strings to avoid type crashes
- Add a shared modality toggle helper to keep a valid default selection
- Deep-copy cached edit state to prevent cross-type mutation leaks
- Improve resilience when applying overrides with debug-only error logging
- Use override display name and effective model tags in provider model cards
- Normalize embedding invariants in the model registry (force text output; clear abilities)
- Make embedding overrides migration accept legacy type keys/values and avoid marking migration on write failure
- Add JSON-safe sanitization for provider modelOverrides before passing to UI
- Improve diagnostics for unknown modality/ability values in overrides (debug-only)
- Guard desktop fetch dialog actions when widget is unmounted
- Show user feedback when saving a duplicate apiModelId key or when persistence fails
- Default empty modality lists to `text` and enforce embedding output invariants
- Sanitize persisted override maps by normalizing keys to strings
- Replace debug-only override failures with `FlutterLogger` error reporting (include stack traces)
- Make provider model cards resilient when a provider config is missing
- Localize duplicate `apiModelId` warning and save-failure message in model detail sheet
- Add `SvgPicture` error fallbacks for model tags and guard modality toggles by index
- Always include text modality when normalizing model modalities
- Expand embedding override cleanup to cover tools/built_in_tools aliases
- Persist migrated provider configs safely (encode/write guards, abort on backup failure) and add debug-only decode/migration diagnostics
- Make model type switch state immutable to prevent cross-type mutation leakage and honor cached embedding input
- Add configurable ModelOverrideResolver logging and disable platform logging inside background isolates
- Improve override apply failure logging with stack traces and reduce UI error detail leakage
- Log missing deepthink icon in debug to aid asset troubleshooting
@luosc
Copy link
Contributor

luosc commented Jan 26, 2026

model_types.dart 这里是不是也可以顺带规范化reasoning啊,对chat模型支持的reasoning种类(比如说是否支持自动,是低/中/高还是需要输入token之类的),你这个如果merge之后我可以来加相关逻辑。

@luosc
Copy link
Contributor

luosc commented Jan 30, 2026

@XiaoBuHaly
Copy link
Contributor Author

@luosc 我这次本来只想修切换模型种类时的模型能力异常,结果已经顺带改了一些偏离目标的安全修复性质的代码。为了保持 PR 聚焦,剩下的那个建议单独提一个 PR,这样回滚或追溯也更清晰。

@luosc
Copy link
Contributor

luosc commented Jan 31, 2026

@luosc 我这次本来只想修切换模型种类时的模型能力异常,结果已经顺带改了一些偏离目标的安全修复性质的代码。为了保持 PR 聚焦,剩下的那个建议单独提一个 PR,这样回滚或追溯也更清晰。

嗯嗯谢谢啦,我打算新PR,就提一嘴,希望owner能先并你这个我好在此基础上修改

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.

模型类型从 Chat 切换为 Embedding 后,能力标签仍异常显示/被保存

2 participants