Prhub

#47735 [Rust Frontend][CI] Unblock more end-to-end test cases

原始 PR 作者 BugenZhao 合并时间 2026-07-07 16:27 文件变更 14 提交数 7 评论 1 代码增减 +105 / -40

执行摘要

调整非流式响应 null 序列化并解锁 Rust 前端 CI 测试

PR body 指出:“Unblock some more end-to-end test cases in CI for Rust frontend as we're filling more gaps. This PR only enables the test cases that require no or minimal code and test changes.”

值得精读,因为它确立了 Rust 前端与 Python 后端在响应序列化上的一致性,并对 CLI 参数处理方式给出了范例。后续涉及响应结构体的 PR 应参考此变更。

讨论亮点

AndreasKaratzas 评论:“Can we reflect these in test-amd.yaml too? We just merged #47478”。作者在后续提交中处理了冲突并同步更新了 test-amd.yaml,已解决问题。

实现拆解

  1. 响应序列化约定变更:移除 ChatCompletionResponseChatCompletionChoiceChatCompletionMessageUsageCompletionResponseGenerateResponseChoice 等结构体上的 #[serde_with::skip_serializing_none],使非流式响应中未设置的字段序列化为显式 null
  2. tool_calls 字段类型调整:将 ChatCompletionMessage 中的 tool_callsOption<Vec<ToolCall>> 改为 Vec<ToolCall>,并使用 #[serde(skip_serializing_if = "Vec::is_empty")],以匹配 Python 端空数组被弹出的行为。
  3. CLI 参数修复:将 enable_tokenizer_info_endpoint 的类型从 Unsupported 改为 Noop 并添加 hide = true,因为 Rust 前端尚未实现 /tokenizer_info 端点。
  4. 测试断言增强:在 tests.rsnon_stream_chat_returns_json_responsenon_stream_completions_return_json_response 等测试中新增对 null 字段存在性和值的断言。
  5. CI 配置扩展:在 .buildkite/test_areas/rust_frontend.yaml.buildkite/test-amd.yaml 中添加更多端到端测试条目。
文件 模块 状态 重要度
rust/src/server/src/routes/tests.rs 测试集 modified 6.73
rust/src/cmd/src/cli/unsupported.rs CLI 参数 modified 5.45
rust/src/server/src/routes/openai/chat_completions/types.rs 响应类型 modified 5.24
rust/src/server/src/routes/openai/utils/types.rs 工具类型 modified 5.17
.buildkite/test_areas/rust_frontend.yaml CI 配置 modified 4.04
.buildkite/test-amd.yaml AMD CI modified 3.77

关键源码片段

rust/src/server/src/routes/tests.rs entrypoint

新增对非流式响应 null 字段的断言,是验证序列化约定变更的核心测试文件。

// 在 non_stream_chat_returns_json_response 测试中,验证未设置字段必须为显式 null
let response_object = json.as_object().expect("response object");
let choice = json["choices"][0].as_object().expect("choice object");
let message = choice["message"].as_object().expect("message object");// 遍历需要检查 null 的字段列表
for (object, key) in [
    (response_object, "system_fingerprint"),
    (response_object, "prompt_token_ids"),
    (response_object, "kv_transfer_params"),
    (choice, "logprobs"),
    (choice, "stop_reason"),
    (choice, "token_ids"),
    (message, "reasoning"),
] {
    // 断言字段存在且值为 null
    assert!(
        object.contains_key(key) && object[key].is_null(),
        "expected explicit null `{key}`: {json}"
    );
}
// 而 tool_calls 在 Python 端为空时会被弹出,所以不应存在于响应中
assert!(!message.contains_key("tool_calls"), "{json}");
rust/src/cmd/src/cli/unsupported.rs core-logic

修改 `enable_tokenizer_info_endpoint` 参数类型和可见性,使其作为 Noop 并隐藏,避免用户误用。

/// Enable the `/tokenizer_info` endpoint. May expose chat
/// templates and other tokenizer configuration.
///
/// Accepted as a no-op: the Rust frontend serves `/tokenize` and
/// `/detokenize`, but does not implement `/tokenizer_info` yet.
#[arg(
    long,
    visible_alias = "no-enable-tokenizer-info-endpoint",
    default_missing_value = "true",
    num_args = 0..=1,
    hide = true // 将参数隐藏,防止用户误以为支持
)]
pub enable_tokenizer_info_endpoint: Option<Noop>, // 类型从 Unsupported 改为 Noop
rust/src/server/src/routes/openai/chat_completions/types.rs entrypoint

移除了响应结构体上的 `skip_serializing_none`,并调整 `tool_calls` 类型,是序列化约定变更的核心。

/// Mirrors the Python vLLM `ChatCompletionResponse` class.
///
/// Do not skip serializing `None` fields here: non-streaming response types
/// should serialize `None` as explicit `null`.
#[derive(Debug, Clone, Serialize)] // 移除了 skip_serializing_none
pub(super) struct ChatCompletionResponse {
    pub id: String,
    pub object: String,
    pub created: u64,
    pub model: String,
    pub choices: Vec<ChatCompletionChoice>,
    pub usage: Option<Usage>,
    pub system_fingerprint: Option<String>,
    pub prompt_logprobs: Option<Vec<Option<HashMap<String, f32>>>>,
    pub prompt_token_ids: Option<Vec<u32>>,
    pub kv_transfer_params: Option<Value>,
}/// Mirrors the Python vLLM response `ChatMessage` class.
#[derive(Debug, Clone, Serialize)]
pub(super) struct ChatCompletionMessage {
    pub role: AssistantRole,
    pub content: Option<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")] // 空数组被跳过,匹配 Python 端行为
    pub tool_calls: Vec<ToolCall>, // 从 Option<Vec<ToolCall>> 改为 Vec<ToolCall>
    pub reasoning: Option<String>,
}

评论区精华

AMD CI 同步 other

AndreasKaratzas 评论:"Can we reflect these in test-amd.yaml too? We just merged #47478"

结论:作者在后续提交中处理了合并冲突并同步更新了 test-amd.yaml,已解决。 · 已解决

风险与影响

序列化兼容性:非流式响应现在包含显式 null 字段,依赖字段省略的客户端可能解析失败,但测试覆盖了主流响应路径。CLI 参数隐藏enable_tokenizer_info_endpoint 被设为 Noop 并隐藏,用户可能误以为参数无效,但文档注释已说明原因。CI 配置同步:AMD CI 配置需要在合并后验证是否完整。

用户影响:非流式响应中将出现更多 null 字段,与 OpenAI API 规范及 Python 后端行为对齐,预期向前兼容。系统影响:CI 覆盖增加,Rust 前端稳定性提升。团队影响:明确了非流式响应序列化约定,后续开发需遵循。

序列化兼容性 CLI 参数隐藏 CI 配置同步

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论