执行摘要
本 PR 修复了 Rust 前端 Kimi K3 renderer 对标准兼容值 reasoning_effort="none" 的误拒绝。核心改动在 rust/src/chat/src/renderer/kimi_k3/encoding.rs:none 不再参与 K3 原生 effort 校验,而是归一化为“禁用思考”;当显式 thinking=true 或 enable_thinking=true 覆盖时,回退到默认 effort(max)。配套测试同步更新并新增两个覆盖场景,使 Rust 渲染器与 Python renderer 的语义对齐。
功能与动机
PR body 说明:Rust renderer 会拒绝包含 {"chat_template_kwargs": {"reasoning_effort": "none"}} 的请求,报错 unsupported thinking_effort="none"。Kimi K3 原生只接受 low、high、max 作为 thinking-effort 级别,但 none 是标准兼容值,用于禁用思考,Python renderer 已按此处理。本 PR 的目标是让合法兼容请求不再失败,同时保留 Kimi K3 原生思考控制的优先级。
实现拆解
- 变更入口:
rust/src/chat/src/renderer/kimi_k3/encoding.rs 中的 thinking_enabled 与 thinking_effort 两个函数,二者共同决定最终渲染的思考开关与 effort 值。
- thinking_enabled 改造:将 typed
chat_options.reasoning_effort 的判断从原先的 map(...).unwrap_or(true) 链中拆出并提前返回;随后新增对 template_kwargs["reasoning_effort"] == "none" 的检查,使 kwarg 来源的兼容值也能单独禁用思考。最终优先级链为:thinking > enable_thinking > typed reasoning_effort > kwarg reasoning_effort="none"。
- thinking_effort 归一化:无论 effort 来自 typed 字段还是 kwarg,只要值为
none 就映射为 DEFAULT_THINKING_EFFORT(测试断言渲染结果为 thinking_effort=max),避免 none 被 VALID_THINKING_EFFORTS 校验拒绝,也保证覆盖场景下输出合法的原生 effort。
- 测试配套:
tests.rs 中 standard_none_disables_thinking 改为仅凭 reasoning_effort="none" 断言禁用思考;新增 native_thinking_true_overrides_standard_none 与 enable_thinking_true_overrides_standard_none 验证原生开关覆盖兼容值;typed_none_disables_thinking、rejects_removed_medium_thinking_effort 等既有行为不变,形成完整的回归网。
- 演进过程:第一个 commit 完成基础修复;第二个 commit 根据 sagearc 的 review 反馈补上 fallback 到
thinking_effort="max" 的逻辑;第三个 commit 为合并 main。
rust/src/chat/src/renderer/kimi_k3/encoding.rs
核心逻辑所在:thinking_enabled 与 thinking_effort 两个函数共同定义了 reasoning_effort="none" 的归一化语义与参数优先级。
// 判断 Kimi K3 请求是否启用思考。
// 优先级从高到低:thinking > enable_thinking > typed reasoning_effort > kwarg reasoning_effort。
// 兼容值 reasoning_effort="none" 用于禁用思考,与 Python renderer 语义一致。
fn thinking_enabled(request: &ChatRequest) -> Result<bool> {
// 原生控制参数优先:thinking 显式给出时直接决定开关
if let Some(thinking) = request.parse_template_bool("thinking")? {
return Ok(thinking);
}
if let Some(enable_thinking) = request.parse_template_bool("enable_thinking")? {
return Ok(enable_thinking);
}
// OpenAI 兼容 typed 字段:None 表示禁用思考,其余级别保持启用
if let Some(reasoning_effort) = request.chat_options.reasoning_effort {
return Ok(reasoning_effort != crate::request::ReasoningEffort::None);
}
// chat_template_kwargs 中的字符串 "none" 同样视为禁用;非字符串按未提供处理
Ok(request
.chat_options
.template_kwargs
.get("reasoning_effort")
.and_then(Value::as_str)
!= Some("none"))
}
// 解析最终输出的 thinking effort。有效级别为 low / high / max。
// "none" 是标准兼容值,不是 K3 原生级别,因此在这里归一化为默认级别。
fn thinking_effort(request: &ChatRequest) -> Result<String> {
let effort = if let Some(value) = request.chat_options.template_kwargs.get("thinking_effort") {
value.as_str().ok_or_else(|| {
Error::ChatTemplate(format!(
"template kwarg `thinking_effort` must be a string, got {value}"
))
})?
} else if let Some(effort) = request.chat_options.reasoning_effort {
// typed reasoning_effort 为 None 时回退到默认级别,避免 "none" 被误判为非法值
if effort == crate::request::ReasoningEffort::None {
DEFAULT_THINKING_EFFORT
} else {
effort.as_str()
}
} else if let Some(value) = request.chat_options.template_kwargs.get("reasoning_effort") {
let effort = value.as_str().ok_or_else(|| {
Error::ChatTemplate(format!(
"template kwarg `reasoning_effort` must be a string, got {value}"
))
})?;
// kwarg 来源的 "none" 同样归一化:当 thinking=true 覆盖它时仍输出合法 effort
if effort == "none" {
DEFAULT_THINKING_EFFORT
} else {
effort
}
} else {
DEFAULT_THINKING_EFFORT
};
// 最终只校验 K3 原生级别;默认级别通常为 "max"(测试断言 thinking_effort=max)
if !VALID_THINKING_EFFORTS.contains(&effort) {
return Err(Error::ChatTemplate(format!(
"unsupported thinking_effort={effort:?}; supported values are `low`, `high`, and `max`"
)));
}
Ok(effort.to_string())
}
rust/src/chat/src/renderer/kimi_k3/tests.rs
保存了此次兼容性语义的行为契约:修改 standard_none_disables_thinking,并新增两个原生开关覆盖 none 的测试。
// 单独的 reasoning_effort="none" 即可禁用思考,无需依赖 enable_thinking=false
#[test]
fn standard_none_disables_thinking() {
let mut request = crate::request::ChatRequest::for_test();
request
.chat_options
.template_kwargs
.insert("reasoning_effort".to_string(), json!("none"));
let rendered = render_request(&request);
// 不输出 thinking-effort 标签,且以 response 标签结尾,表示思考已关闭
assert!(!rendered.contains("type=\"thinking-effort\""));
assert!(rendered.ends_with("<|open|>response<|sep|>"));
}
// 原生 thinking=true 覆盖兼容值 none:保持思考,并回退到默认 effort=max
#[test]
fn native_thinking_true_overrides_standard_none() {
let mut request = crate::request::ChatRequest::for_test();
request.chat_options.template_kwargs.extend([
("thinking".to_string(), json!(true)),
("reasoning_effort".to_string(), json!("none")),
]);
let rendered = render_request(&request);
assert!(rendered.contains("thinking_effort=max"));
assert!(rendered.ends_with("<|open|>think<|sep|>"));
}
// enable_thinking=true 同样覆盖兼容值 none
#[test]
fn enable_thinking_true_overrides_standard_none() {
let mut request = crate::request::ChatRequest::for_test();
request.chat_options.template_kwargs.extend([
("enable_thinking".to_string(), json!(true)),
("reasoning_effort".to_string(), json!("none")),
]);
let rendered = render_request(&request);
assert!(rendered.contains("thinking_effort=max"));
assert!(rendered.ends_with("<|open|>think<|sep|>"));
}
评论区精华
sagearc(tests.rs:363):“shouldn't we fall back to thinking_effort=max?”,并引用官方 tokenization_kimi.py L378-L379 作为行为依据。
这条评论直接推动了第二个 commit:当 thinking=true 或 enable_thinking=true 覆盖 reasoning_effort="none" 时,渲染结果回退为 thinking_effort=max,并保持 think 标签。
Codex(encoding.rs:247,P2):“当 chat_template_kwargs 同时包含 reasoning_effort="none" 和不受支持的原生值(如 thinking_effort="medium")时……请求被接受而不是触发现有校验器……Python Kimi K3 renderer 仍然校验 thinking_effort。”
该评论指出校验绕过差异,建议即使标准兼容设置禁用了思考,也应校验显式提供的原生 effort。此问题在本次 PR 中未解决,属于遗留的 Rust/Python 行为不一致点。
风险与影响
- Rust/Python 行为不一致:Codex 指出的校验绕过问题未被修复,
reasoning_effort="none" 与 thinking_effort="medium" 组合在 Rust renderer 下被静默接受,Python renderer 会报错。
- 组合语义含糊:
thinking_effort kwarg 优先级高于 reasoning_effort,但当 reasoning_effort="none" 关闭思考时,显式提供的 thinking_effort 既不输出也不校验,可能出现“给了 effort 但思考关闭”的语义。
- 回归面较小:low/high/max 与非法值主路径均有测试覆盖,
typed_none_disables_thinking 与 rejects_removed_medium_thinking_effort 保持原断言。
- 影响范围:Rust 前端调用 Kimi K3 且带
reasoning_effort="none" 的请求从报错变为正常禁用思考,属于兼容性修复;对 Python 前端无影响。
关联脉络
本 PR 是 Rust 前端与 Python 前端行为对齐工程线的一部分,PR body 明确以“Python renderer already handles it that way”为基准。关联的历史 PR 中,#54482 同为 Kimi K3 支持与测试线,反映该模型在 Rust 前端与 CI 测试基础设施上的持续投入;#54492 等前端重构则体现了 Rust/Python 双前端并行演进的整体方向。
参与讨论