Prhub

#47844 [Rust Frontend] Handle `continue_final_message` with renderer sentinel

原始 PR 作者 BugenZhao 合并时间 2026-07-08 19:57 文件变更 3 提交数 2 评论 0 代码增减 +200 / -17

执行摘要

sentinel 机制实现 continue_final_message,解锁测试

根据 PR body,之前仅将 continue_final_message 参数传递给 chat template,但实践中几乎没有 Hugging Face chat template 观察该值,因此是无操作。本 PR 将 Transformers v5 语义移植到 Rust 前端,通过 sentinel 标记机制实现对 continue_final_message 的正确处理,并解锁相关端到端测试用例。

值得关注其 sentinel 设计模式——不依赖模板感知就能实现参数效果,该思路可复用于其他类似 flag。但团队需了解此行为与 Transformers v5 对齐,未来升级模板时需保持一致。

讨论亮点

本 PR 没有实质性的 review 讨论。claude[bot] 自动提示配置代码审查,njhill 直接批准,无质疑或建议。

实现拆解

  1. 核心逻辑修改:在 rust/src/chat/src/renderer/hf/mod.rsapply_chat_template_inner 方法中,将 messages 改为可变,并在渲染前判断 continue_final_message 标志,若为 true 则调用新函数 append_continue_final_message_tag 在最后一条消息的文本末尾追加 sentinel tag CONTINUE_FINAL_MESSAGE_TAG
  2. 渲染后截断:渲染完成后,调用 truncate_prompt_at_continue_final_message_tag 在 sentinel 最右侧出现位置截断 prompt,丢弃模板添加的后缀(如 <|eot_id|>)。
  3. 新常量与辅助函数:定义常量 CONTINUE_FINAL_MESSAGE_TAG(与 Transformers v5 一致),新增 append_continue_final_message_tagtruncate_prompt_at_continue_final_message_tag 两个函数,分别处理不同消息内容格式(纯文本或 OpenAI 多段内容)和截断逻辑。
  4. CI 配置解锁测试:修改 .buildkite/test-amd.yaml.buildkite/test_areas/rust_frontend.yaml,在 Rust 前端 Serve/Admin Coverage 命令中去掉 not test_tokenize_chat 过滤条件,允许该测试运行。
文件 模块 状态 重要度
rust/src/chat/src/renderer/hf/mod.rs 聊天渲染 modified 8.6
.buildkite/test-amd.yaml CI 配置 modified 2.5
.buildkite/test_areas/rust_frontend.yaml CI 配置 modified 2.5

关键符号

append_continue_final_message_tag truncate_prompt_at_continue_final_message_tag

关键源码片段

rust/src/chat/src/renderer/hf/mod.rs core-logic

核心实现文件,新增 sentinel 追加和截断逻辑,修改 apply_chat_template_inner 以支持 continue_final_message

// Sentinel 标记常量,与 Transformers v5 相同
const CONTINUE_FINAL_MESSAGE_TAG: &str = "CONTINUE_FINAL_MESSAGE_TAG ";/// 在最终消息的尾部文本后追加 [`CONTINUE_FINAL_MESSAGE_TAG`],
/// 返回原始文本用于渲染后校验。
fn append_continue_final_message_tag(
    message: &mut TemplateMessage,
) -> Result<String> {
    // 获取最后一个可变的文本切片
    let text = match &mut message.content {
        TemplateContent::String(text) => Some(text),
        // 对于多段内容,反向查找最后一个文本部分
        TemplateContent::OpenAi(parts) => parts
            .iter_mut()
            .rev()
            .find_map(|part| match part {
                TemplateContentPart::Text { text } => Some(text),
                TemplateContentPart::Image => None,
            }),
    };
    let text = text.ok_or_else(|| {
        Error::ChatTemplate(
            "continue_final_message 已设置但最终消息中没有可延续的文本"
                .to_string(),
        )
    })?;
    let original = text.clone();
    text.push_str(CONTINUE_FINAL_MESSAGE_TAG);
    Ok(original)
}/// 在渲染后的 prompt 中定位最后出现的 [`CONTINUE_FINAL_MESSAGE_TAG`],
/// 截断丢弃其后所有字符,从而去除模板添加的后缀。
fn truncate_prompt_at_continue_final_message_tag(
    prompt: String,
    final_message_text: &str,
) -> Result<String> {
    // 使用 rfind 确保取最右侧的 tag(因为用户 prompt 中也可能有)
    let tag_start = prompt
        .rfind(CONTINUE_FINAL_MESSAGE_TAG)
        .ok_or_else(|| {
            Error::ChatTemplate(
                "渲染后的 prompt 中未找到 sentinel 标记。 \
                 可能是模板丢弃了最终消息内容",
            )
        })?;
    let mut truncated = prompt;
    truncated.truncate(tag_start);
    // 去除模板在消息后可能插入的空白字符,与 Transformers v5 保持一致
    Ok(truncated.trim_end().to_string())
}

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

风险较低。主要风险:1)sentinel 字符串 'CONTINUE_FINAL_MESSAGE_TAG ' 可能出现在用户 prompt 或模板输出中,虽然截断使用 rfind 取最右侧匹配以降低误截断风险,但仍存在边界情况(与 Transformers v5 行为一致)。2)若最终消息无文本内容(仅图片),append_continue_final_message_tag 会返回错误,需确保调用前已校验。3)仅影响 Rust 前端且 continue_final_message 为可选参数,回归影响面窄。

影响范围仅限于使用 Rust 前端且启用 continue_final_message 的场景。此前该参数无效,现在正确生效。同时解锁了 test_tokenize_chat 端到端测试,提升 CI 覆盖率。对未启用该参数的请求无影响。

sentinel 字符串冲突可能 缺少独立单元测试 仅 Rust 前端生效

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论