Skip to content

带适配器前缀的模型 ID 会丢失 system prompt 与历史消息 #99

@Stwinklein

Description

@Stwinklein

问题描述

当客户端请求使用带有 适配器/模型 命名空间前缀的模型 ID(如 gemini_text/gemini-3.1-flash)时,服务端会错误地把该模型判定为图像生成模型,从而走 parseImageRequest 逻辑,只取最后一条 user 消息发送给目标网站,导致:

  • System Prompt 完全丢失
  • 历史对话完全丢失
  • (对 SillyTavern 等客户端)角色卡、世界书、作者注等上下文全部不会被发送

复现环境

  • 项目版本:3.0.0
  • Node: v24.13.1
  • OS: Windows 11
  • 配置:
    backend.pool.instances:
      - name: browser_default
        workers:
          - name: gemini_text_worker
            type: gemini_text

复现步骤

  1. 配置一个 type: gemini_text 的 worker 并正常登录
  2. 使用 OpenAI 兼容客户端(如 SillyTavern)发起请求,模型名填 gemini_text/gemini-3.1-flash
  3. 请求包含完整的 system 消息与多轮 user/assistant 历史
  4. 查询 data/history/history.dbrequests 表,可以看到该请求的 prompt 字段内容只有最后一条 user 消息,没有三段式结构

实测对比数据(来自 data/history/history.db

model_id 是否带前缀 prompt 长度 prompt 开头
gemini-3-flash(lmarena_text 注册) 1189 ~ 3632 === 系统指令 (永远置顶) ===\nWrite ...
gemini_text/gemini-3.1-flash 2 ~ 46 只有 "Hi" / "你在干嘛" 等原始用户消息 ❌

同一条酒馆请求,仅改模型名,结果截然不同。

根本原因

位于 src/backend/pool/Worker.js:605-622

getModelType(modelKey) {
    if (this.type === 'merge') {
        // merge 分支有前缀处理...
    } else {
        return registry.getModelType(this.type, modelKey);  // ❌ 未剥离 type/ 前缀
    }
}

同文件 318-343 行的 supports() 方法对 type/model 前缀做了剥离处理:

if (modelId.includes('/')) {
    const [specifiedType, actualModel] = modelId.split('/', 2);
    if (specifiedType === this.type) {
        return registry.supportsModel(this.type, actualModel);  // ✅ 用剥离后的 key
    }
    return false;
}
return registry.supportsModel(this.type, modelId);

getModelType() 的非 merge 分支漏掉了相同的前缀剥离,把 "gemini_text/gemini-3.1-flash" 整个字符串传给了 registry.getModelType

src/backend/registry.js:304-312 里用的是精确匹配:

const model = adapter.models.find(m => m.id === modelKey);
return model?.type || 'image';

注册表里的 key 是 "gemini-3.1-flash"(见 src/backend/adapter/gemini_text.js:228),带前缀的 key 当然匹配不到,于是默认返回 'image'

这导致 PoolManager.getModelTypesrc/backend/pool/PoolManager.js:275-282)虽然通过 supports 找到了对应 worker,但 worker.getModelType 返回错误结果。

下游的 src/server/api/openai/parse.js:98-105 根据此错误类型判断:

const type = getModelType ? getModelType(data.model) : 'image';
isTextMode = type === 'text';
if (isTextMode) {
    // parseTextRequest - 构建三段式虚拟上下文 ✅
} else {
    // parseImageRequest - 只取最后一条 user 消息 ❌
}

判定为 image,走了 parseImageRequest,system 和历史被丢弃。

影响范围

任何带 适配器/ 前缀的文本模型 ID:

  • gemini_text/*
  • chatgpt_text/*
  • deepseek_text/*
  • zai_is_text/*
  • doubao_text/*
  • 等等

这些在 WebUI 中出现冲突时会自动带前缀,用户几乎一定会遇到此问题。

建议修复

参照 supports() 的写法,在 Worker.getModelType() 非 merge 分支加入前缀剥离:

getModelType(modelKey) {
    if (this.type === 'merge') {
        // ... 保持不变
    } else {
        if (modelKey.includes('/')) {
            const [specifiedType, actualModel] = modelKey.split('/', 2);
            if (specifiedType === this.type) {
                return registry.getModelType(this.type, actualModel);
            }
        }
        return registry.getModelType(this.type, modelKey);
    }
}

同样,建议检查 getImagePolicy 等其他会接受 modelKey 的方法是否存在相同问题。

临时绕过方法

客户端模型名不使用前缀,直接填 gemini-3.1-flash(仅当该 key 在各适配器中不冲突时可用)。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions