Prhub

#51235 [Rust Frontend] Upgrade MiniJinja to 2.22 & remove method lookup workaround

原始 PR 作者 BugenZhao 合并时间 2026-08-11 08:23 文件变更 5 提交数 2 评论 4 代码增减 +12 / -98

执行摘要

升级 MiniJinja 至 2.22,删除 TemplateMap 绕行方案

升级上游依赖以消除本地 workaround。PR body 指出 MiniJinja 2.22 修复了 mappings 中 items 等同名键对方法查找优先级的遮蔽问题(mitsuhiko/minijinja#903),从而可以移除 #44311 引入的 TemplateMap,改为以普通 serde_json::Value 传递工具定义与工具调用参数;同时新版本采用 Jinja2 风格的直接布尔渲染(True/False),需要同步调整测试期望。

值得精读。它展示了如何在一个 upstream issue 修复后及时回退本地 workaround,并以真实模型 roundtrip + prompt hash 对比来验证行为等价,是处理"上游修复-本地绕行"类变更的标准范式。关注点:布尔渲染的兼容性验证方式,以及删除自定义 Object 后对序列化语义的影响。此类小步升级也可作为依赖升级 PR 的参考模板。

讨论亮点

本 PR 无实质性 code review 讨论。claude[bot] 仅发出自动提示,njhill 给出 APPROVE 并表示 "Thanks @BugenZhao";WoosukKwon 与 njhill 各触发一次 /ci run,Buildkite CI 均通过。

实现拆解

  1. 依赖升级:在 rust/Cargo.tomlminijinjaminijinja-contrib2.0(Cargo.lock 实际锁定 2.18.0)提升到 2.22,并同步更新 rust/Cargo.lock
  2. 删除绕行实现:整个删除 rust/src/chat/src/renderer/hf/value.rs,其中 TemplateValue 递归包装 JSON 值、TemplateMap 基于 IndexMap 保序并在 call_method 中固定返回 UnknownMethod,把 dict 方法路由交给 pycompat 回调。
  3. 接线简化rust/src/chat/src/renderer/hf/mod.rsTemplateToolFunction::argumentsTemplateToolDefinition::parametersTemplateValue 改回 serde_json::Value,去掉 to_template_value 调用、mod value; 声明与相关 userust/src/chat/Cargo.toml 同步移除 indexmap 依赖。
  4. 测试同步mod.rs 内三处单元测试断言从 "true"/"none|true" 更新为 "True"/"none|True",以匹配新的布尔渲染行为。
  5. 回归验证cargo test -p vllm-chat --lib(255 个)、chat 测试(17 个)、clippy 与 fmt 全部通过;真实模型 roundtrip 套件 16/16 通过,所有 prompt hash 在升级前后保持一致,MiniMax-M3 覆盖了原始 items 键冲突场景。
文件 模块 状态 重要度
rust/src/chat/src/renderer/hf/value.rs 模板渲染 removed 8.21
rust/src/chat/src/renderer/hf/mod.rs 模板渲染 modified 5.66
rust/Cargo.toml 依赖配置 modified 3.25
rust/Cargo.lock 依赖锁定 modified 2.73
rust/src/chat/Cargo.toml 依赖配置 modified 2.53

关键符号

to_template_value ( 移除 ) TemplateMap::call_method ( 移除 ) TemplateMap::get_value ( 移除 ) to_template_tool_calls to_template_tools

关键源码片段

rust/src/chat/src/renderer/hf/value.rs deletion

整个文件被删除,包含 TemplateValue 包装类型与 TemplateMap 自定义对象,是本次取消 workaround 的核心。

// 本文件在 PR 中整体删除(rust/src/chat/src/renderer/hf/value.rs)。
// 它当初是为了规避 minijinja#903:旧版 MiniJinja 的 Object::call_method
// 会先解析同名 map key,导致 map 中叫 `items` 的字段遮蔽 Python dict 方法。
// TemplateMap 通过强制返回 UnknownMethod,把 dict 方法交给 pycompat 回调。
use std::sync::Arc;
use indexmap::IndexMap;
use minijinja::value::{Enumerator, Object, ObjectExt, ObjectRepr};
use minijinja::{Error as TemplateError, ErrorKind as TemplateErrorKind, State};
use serde::Serialize;
use serde_json::Value as JsonValue;#[derive(Debug, Serialize)]
#[serde(transparent)]
pub(super) struct TemplateValue(minijinja::Value);pub(super) fn to_template_value(value: JsonValue) -> TemplateValue {
    TemplateValue(match value {
        JsonValue::Array(values) => values
            .into_iter()
            .map(to_template_value)
            .map(|v| v.0)
            .collect::<minijinja::Value>(),
        JsonValue::Object(values) => minijinja::Value::from_object(TemplateMap(
            values
                .into_iter()
                .map(|(k, v)| (k, to_template_value(v).0))
                .collect(),
        )),
        // 原始值直接走 from_serialize,保持数值表示不受 arbitrary_precision 影响
        value => minijinja::Value::from_serialize(value),
    })
}#[derive(Debug)]
struct TemplateMap(IndexMap<String, minijinja::Value>);impl Object for TemplateMap {
    fn repr(self: &Arc<Self>) -> ObjectRepr {
        ObjectRepr::Map
    }    fn get_value(self: &Arc<Self>, key: &minijinja::Value) -> Option<minijinja::Value> {
        self.0.get(key.as_str()?).cloned()
    }    // 所有方法调用一律返回 UnknownMethod,让 pycompat 的
    // unknown_method_callback 统一处理 dict 方法(items/keys/values 等)
    fn call_method(
        self: &Arc<Self>,
        _state: &State<'_, '_>,
        _method: &str,
        _args: &[minijinja::Value],
    ) -> std::result::Result<minijinja::Value, TemplateError> {
        Err(TemplateError::from(TemplateErrorKind::UnknownMethod))
    }
}
rust/src/chat/src/renderer/hf/mod.rs dependency-wiring

工具参数接线从 TemplateValue 改回 JsonValue,删除 to_template_value 调用与 value 模块声明,并更新布尔渲染相关单测期望。

// rust/src/chat/src/renderer/hf/mod.rs 升级后的新接线:
// 工具参数不再经过 to_template_value 包装,而是以原生 JSON 直接进入模板。
use serde_json::Value as JsonValue;#[derive(Debug, Serialize)]
struct TemplateToolFunction {
    name: String,
    // Tool call arguments 是普通 JSON Value;MiniJinja 2.22 起
    // 同名字段不会再遮蔽 dict 方法,故这里直接传递
    arguments: JsonValue,
}#[derive(Debug, Serialize)]
struct TemplateToolDefinition {
    name: String,
    description: Option<String>,
    parameters: JsonValue,
    strict: Option<bool>,
}fn to_template_tools(tools: &[ChatTool]) -> Vec<TemplateTool> {
    tools
        .iter()
        .map(|tool| TemplateTool {
            tool_type: "function",
            function: TemplateToolDefinition {
                name: tool.name.clone(),
                description: tool.description.clone(),
                // 保留原始 JSON,键序由 serde_json 的 preserve_order 特性保证
                parameters: tool.parameters.clone(),
                strict: tool.strict,
            },
        })
        .collect()
}

评论区精华

无实质 review 讨论,CI 与审批记录 other

PR 没有收到 code review 评论。claude[bot] 仅提示仓库配置了手动 review 模式;njhill 给出 APPROVE 并留言 "Thanks @BugenZhao";WoosukKwon 与 njhill 各触发一次 /ci run,Buildkite CI 通过。

结论:直接合并,无未解决疑虑。 · 已解决

风险与影响

  1. 布尔渲染行为变更:MiniJinja 2.22 将模板内直接渲染的布尔值从 true/false 改为 True/False,任何依赖小写输出的自定义模板可能出现 prompt 变化;roundtrip 覆盖 16 个模型但未覆盖全部模板。
  2. 依赖上游修复:删除 TemplateMap 后,items 等字段遮蔽问题完全依赖 MiniJinja 2.22 的修复。本项目未新增针对该冲突的专项单测,仅靠 roundtrip 的 MiniMax-M3 覆盖,后续上游行为变化时可能静默回归。
  3. 序列化语义变化:工具参数从自定义 Object 改走原生 serde_json::Value 路径,虽然 preserve_orderarbitrary_precision 配置保持不变,但对象 repr、枚举方式等细节与之前不同,需要观察模板是否依赖对象类型特征。

影响 Rust 前端(vllm-chat crate)的 HF chat 模板渲染链路,工具定义与工具调用参数的数据流被简化,代码净减约 90 行,维护成本降低。对用户的可见影响主要是布尔值渲染大小写变化(trueTrue),可能影响 prompt 哈希与缓存一致性;对团队而言,后续升级 MiniJinja 时无需再维护该 workaround。

布尔渲染行为变更 依赖上游修复 缺少专项回归测试

关联 Issue

#903 Python dict methods are shadowed by same-named map keys in `Object::call_method`
#44311 [Rust Frontend] Fix several hf chat template rendering issues

完整报告

参与讨论