Files
st-react/docs/预设模块优化设计.md
2026-03-13 21:51:42 +08:00

11 KiB
Raw Blame History

预设模块Preset导入与启用逻辑优化设计

一、背景与问题

当前系统支持从 SillyTavern / TavernAI 风格的 JSON 文件导入预设并将其中的采样参数和提示词配置prompts / prompt_order存入后端 AIPreset.Extensions 字段中,前端在 PresetManagePage / EditPresetModal 中提供基本的编辑能力。

在实际使用中暴露出一个明显问题:

  • 问题导入预设后提示词块prompts的启用 / 禁用状态与原始 JSON 文件不一致。

具体表现:

  • SillyTavern 预设中的「默认启用 / 禁用」信息主要体现在:
    • prompts[*].enabled(整体默认开关,可能存在)
    • prompt_order[*].order[*].enabled(更精细的 per-character 启用信息,是真正生效的一层)
  • 我们当前前端 UI 在编辑时:
    • 仅从 extensions.prompts 中取出 prompts
    • marker 字段反向推导「启用」状态(checked={!prompt.marker}
    • 完全没有解析和尊重 prompt_order 里的 enabled 状态

因此,对于像 Sudachi.Next1.21.json 这种复杂预设:

  • 文件中已经通过 prompt_order 精确控制了某些块默认 enabled: false
  • 导入后数据虽然保存在 extensions.prompt_order 中,但前端不读取,导致 UI 展示的勾选状态与原始预设不一致

二、设计目标

  1. 忠实还原 ST 预设的启用 / 禁用行为
    • 导入后,在不改动 ST 原始结构的前提下,让 UI 所见的启用状态尽可能与 ST 一致。
  2. 保持数据的完整与可扩展性
    • 保留 promptsprompt_order 的原始结构,未来可以继续扩展更高级行为(多角色、多上下文)。
  3. 简化前端编辑模型
    • 前端在编辑时只需要关注:
      • 哪些 prompt 当前“启用”
      • 顺序如何
    • 不强制实现 ST 全量复杂逻辑,但要保证基础行为正确。
  4. 向后兼容现有数据
    • 对于已存在的 AIPreset,在没有 prompt_order / enabled 信息时,仍然能正常工作。

三、数据结构与语义梳理

3.1 ST 预设结构(简化视图)

  • 顶层采样 / 行为字段(已正确映射到 AIPreset
    • temperature, top_p, top_k, min_p, top_a
    • frequency_penalty, presence_penalty, repetition_penalty
    • openai_max_tokens, openai_max_context, stream_openai, 等
  • prompts: Prompt[]
    • name: string
    • identifier: string(如 "main", "nsfw", "jailbreak" 或 UUID
    • role: "system" | "user" | "assistant"
    • content: string
    • system_prompt: boolean
    • marker: boolean(占位块;本身不一定等于“禁用”)
    • enabled?: boolean(整体缺省启用的开关)
    • injection_position: number
    • injection_depth: number
    • injection_order: number
    • injection_trigger: any[]
    • forbid_overrides: boolean
  • prompt_order: Array<{ character_id: number; order: Array<{ identifier: string; enabled: boolean }> }>
    • 按「角色 / 场景」维度,指定各 identifier 的启用状态与顺序

关键点:
真正决定「是否启用」的是 prompt_order[*].order[*].enabledprompts[*].enabled 更多是全局默认值。

3.2 当前系统存储结构

后端 PresetService.ImportPresetFromJSON 当前的行为:

  • prompts / prompt_order 原样塞入:
extensions := map[string]interface{}{
  "prompts":      stPreset.Prompts,
  "prompt_order": stPreset.PromptOrder,
}

前端 EditPresetModal 当前的行为:

  • 通过 preset.extensions.prompts 取得 prompts 列表
  • 「启用」复选框用的是:
    • checked={!prompt.marker}
    • 也就是:只要不是 marker就视为启用
  • 没有考虑:
    • prompt.enabled
    • prompt_order[*].order[*].enabled

四、优化方案总览

整体策略:

  • 后端保持“原样存储”原则,继续把 ST 的 promptsprompt_order 保存在 extensions 里,方便以后做高级兼容。
  • 新增一层“系统视角配置”(推荐写在 extensions.stMapping 内),用于:
    • 提取出我们当前真正要用到的“默认启用 / 禁用信息”
    • 为前端提供简单直观的数据结构
  • 前端 UI 只依赖这层映射来决定默认勾选状态、顺序等,避免直接耦合 ST 的复杂行为。

五、后端导入逻辑优化设计

5.1 导入时的启用状态归一化

PresetService.ImportPresetFromJSON 中,对 stPreset.PromptsstPreset.PromptOrder 做一次归一化处理,得到:

  • map[string]boolidentifierenabled(系统视角下的默认启用状态)

处理规则建议如下:

  1. prompt_order 中选出一个「主视角 character_id」
    • 优先策略:取 prompt_order第一个条目character_id(例如 Sudachi 文件中的 100000)。
    • 这样可理解为“该预设的主角 / 默认角色对应的一组配置”。
  2. 针对该 character_idorder 数组:
    • 遍历每一项 { identifier, enabled }
    • 记入 enabledMap[identifier] = enabled
  3. 对于 prompts 中存在但在 order 中未出现的 identifier
    • prompt.enabled 明确存在,则用该字段
    • 否则默认 true(保持向后兼容,除非 ST 未来规范有特别说明)

归一化后的结果示意:

{
  "stMapping": {
    "mainCharacterId": 100000,
    "enabledByIdentifier": {
      "main": true,
      "worldInfoBefore": true,
      "enhanceDefinitions": false,
      "nsfw": true,
      ...
    }
  }
}

注意:

  • 原始的 prompts / prompt_order 仍然原封不动地存放在 extensions.prompts / extensions.prompt_order 中。
  • stMapping 只是我们系统内部使用的“视图”,不影响原始数据。

5.2 可选:为 prompts 注入统一的 enabled 字段

为了前端更好使用,也可以在导入时(或者在后端读取时)对 prompts 做一次增强:

  • 对每个 prompt
    • 查询 enabled := stMapping.enabledByIdentifier[prompt.identifier]
    • 如果找到了,就在该 prompt 上写入 / 覆盖 prompt["enabled"] = enabled

这样,前端只要拿到 extensions.prompts,就能直接看到每条的 enabled 字段,不必额外关联 prompt_order

兼容建议:

  • 不删除原有的 enabled,只是统一成一个规范值;
  • 如果未来 ST 升级行为,我们仍保留原始 JSON可以再次调整映射逻辑。

六、前端编辑行为优化设计

6.1 EditPresetModal 中的启用逻辑修改

当前逻辑:

  • 通过 getPrompts()extensions.prompts 取出数组
  • 复选框逻辑:
<input
  type="checkbox"
  checked={!prompt.marker}
  onChange={(e) => handlePromptChange(index, 'marker', !e.target.checked)}
/>

问题:

  • 把 “是否 marker” 当成了 “是否启用”,与 ST 语义不符。

优化方案:

  1. 新增对 enabled 字段的支持
    • 解析时,优先使用:
      • prompt.enabled(如果存在)
      • 否则回退为 !prompt.marker(为了兼容老数据)
  2. UI 含义调整:
    • 勾选框表示「此 prompt 是否启用」,而非「是否 marker」
    • marker 仍然保留,用于区分“占位块”(如 chatHistorydialogueExamples)和“实际有内容的块”,但不再被直接当作启用条件。

示例(伪代码):

const isEnabled = prompt.enabled !== undefined
  ? prompt.enabled
  : !prompt.marker

<input
  type="checkbox"
  checked={isEnabled}
  onChange={(e) => handlePromptChange(index, 'enabled', e.target.checked)}
/>
  1. 保存时,将 enabled 写回 extensions.prompts 中对应项。

提醒:

  • 短期内可以不在前端去写 prompt_order,只要我们的对话生成逻辑是“按 prompts + enabled 过滤”就足够;
  • 如果未来要做到 ST 完整语义(如多角色不同顺序),再在前端提供更高级的顺序管理 UI。

6.2 默认显示顺序

当前 UI 在 EditPresetModal 里对 prompts 没有太多排序逻辑,直接用数组顺序:

  • 初期优化阶段,可以继续沿用 ST 原始 prompts 顺序
  • 如果希望更贴近 ST 行为,可以:
    • 根据 injection_position / injection_depth / injection_order 做一个简易排序
    • 或者读取 stMapping.mainCharacterId 对应 prompt_order 的顺序,按其中 identifier 顺序排列

建议分阶段:

  1. 第一阶段(快速修复启用逻辑)
    • 先只修复 enabled 使用,仍按原数组顺序展示
  2. 第二阶段(顺序优化)
    • 再根据 prompt_order / injection 字段优化展示顺序

七、对话生成逻辑的使用建议(可选)

目前后端对话生成Convesation Service是如何使用预设参数需要在代码中再具体确认。但从设计角度建议

  1. 生成最终 prompt 时:
    • extensions.prompts 中筛选:enabled != false 的 prompt
    • 按既定顺序(可先用原始数组顺序,或更细致地用 injection_* / prompt_order)拼接成系统提示。
  2. 如需高度还原 ST 行为:
    • 在后端增加一个专门的「ST Prompt 编译器」:
      • 输入:prompts + prompt_order + 当前角色 / 会话上下文
      • 输出:一串系统消息数组(包含角色、内容、插入位置信息)
    • 这一步可以在后续迭代中实现,本次优化先以「启用状态正确」为目标。

八、兼容性与迁移策略

  1. 已有预设数据
    • 已有的 AIPreset.Extensions 中可能只有 prompts,没有 prompt_order / stMapping / enabled
      • EditPresetModal 在解析时:
        • 若找不到 prompt.enabled,则默认 enabled = !marker
      • 新导入的预设,将会有完整的 enabled / stMapping 信息。
  2. 导出为 ST JSON
    • 目前导出的逻辑是从 AIPreset 再组装为 ST 样式的 JSON
      • 建议导出时仍以 extensions.prompts / extensions.prompt_order 为主
      • enabled 字段如果已更新,也可以同步写回到导出 JSON 中对应位置,保持一致性
  3. 前后端升级顺序
    • 先在后端实现 stMapping 和 prompts 的 enabled 归一化逻辑
    • 再在前端切换到从 prompt.enabled 读取启用状态
    • 测试导入 Sudachi 预设后,界面上默认启用 / 禁用是否与原文件一致

九、后续扩展方向(可选)

  1. 多角色 / 多场景支持
    • 完整解析 prompt_order 中所有 character_id 条目,根据当前对话绑定的角色 ID 切换不同启用方案。
  2. 可视化 prompt 顺序编辑
    • 在 Preset 管理页面增加「提示词顺序拖拽调整」、分组展示(角色定义 / 文风 / COT / 变量初始化等)。
  3. 预设版本管理与对比
    • 由于 ST 预设经常迭代,可考虑为 AIPreset 增加版本号 / 变更日志,以便回滚与对比。

以上设计以「最小代价修复启用/禁用逻辑不一致」为优先目标,同时为后续更深入兼容 ST 行为(如多角色、多 prompt_order 视图)预留空间。后续具体实现时,可以按本文件拆分为:

  • 后端改动:PresetService.ImportPresetFromJSON + 可选的 Prompt 编译辅助函数
  • 前端改动:PresetManagePage / EditPresetModal 中的 prompts 解析与复选框逻辑