Prhub

#51089 [Feature] Parse request priority from HTTP header

原始 PR 作者 chaunceyjiang 合并时间 2026-08-06 12:10 文件变更 8 提交数 4 评论 7 代码增减 +81 / -8

执行摘要

新增 X-Vllm-Priority 头覆盖 body 优先级

issue #51023 提出:运营侧希望在中间代理/负载均衡器上通过修改 HTTP header 调整请求优先级,比解析并改写 JSON 请求体简单一个数量级;优先级信息往往对客户端不可见,且可能因运营或业务决策随时变化。issue 还明确要求 header 优先级必须覆盖 body 中的优先级。本 PR 按此语义实现。

值得快速浏览,适合作为“在双前端同步新增 header 能力”的参考样例。设计上值得借鉴的是将 header 解析收敛到单一静态方法(_get_priority)并由三条 API 复用,以及 Rust 侧用 ctx.priority.or(request.priority) 表达覆盖优先级。若团队关心健壮性,可在此 PR 基础上补 Python 端单测并考虑对非法 header 值返回 400。

讨论亮点

评审过程没有出现实质技术争论:DarkLight1337 与 BugenZhao 均批准,BugenZhao 评论 LGTM;Claude bot 因 PR 来自 fork 而自动评审被禁用,提示维护者可评论 @claude review 手动触发。值得注意的是,PR body 中 Test Plan 部分为空,最终由维护者直接评审放行。

实现拆解

  1. Python 入口层新增 header 解析vllm/entrypoints/generate/base/serving.py 新增 PRIORITY_HEADER = "X-Vllm-Priority" 常量与 _get_priority 静态方法,该方法优先读取 header 并尝试 int() 转换,解析失败则回退到 request.priority;这与已有 X-Session-ID 的 header 处理模式保持一致,避免新增依赖。
  2. 三类 API 统一接入vllm/entrypoints/openai/chat_completion/serving.pyvllm/entrypoints/openai/completion/serving.pyvllm/entrypoints/openai/responses/serving.py 中调用 engine_client.generate(...) 时的 priority=request.priority 均替换为 priority=self._get_priority(request, raw_request),使 header 覆盖规则一次性地作用于 Chat Completions、Completions 与 Responses 三条路径。
  3. Rust 前端解析并合并优先级rust/src/server/src/utils.rsResolvedRequestContext 新增 priority: Option<i32> 字段,resolve_request_contextX-Vllm-Priority 头读取并 trim().parse()rust/src/server/src/routes/openai/completions/convert.rsrust/src/server/src/routes/openai/chat_completions/convert.rspriority 计算改为 ctx.priority.or(request.priority).unwrap_or(0),实现与 Python 端一致的“header 优先、body 次之、缺省 0”的语义。
  4. 测试与文档配套:两个 Rust 文件各新增单元测试 prepare_completion_request_header_priority_overrides_bodyprepare_chat_request_header_priority_overrides_body,验证 body priority=10 被 header -5 覆盖;docs/serving/online_serving/openai_compatible_server.md 补充 X-Vllm-Priority 的取值约束(整数)、覆盖语义、非零值要求启用优先级调度以及 Python 客户端示例。本 PR 未新增 Python 端测试。
文件 模块 状态 重要度
vllm/entrypoints/generate/base/serving.py 入口服务 modified 6.65
rust/src/server/src/utils.rs 请求解析 modified 5.24
rust/src/server/src/routes/openai/completions/convert.rs 请求转换 modified 6.6
rust/src/server/src/routes/openai/chat_completions/convert.rs 请求转换 modified 6.52
vllm/entrypoints/openai/chat_completion/serving.py 聊天服务 modified 4.82
vllm/entrypoints/openai/completion/serving.py 完成服务 modified 4.82
vllm/entrypoints/openai/responses/serving.py 响应服务 modified 4.82
docs/serving/online_serving/openai_compatible_server.md 在线文档 modified 2.86

关键符号

_get_priority resolve_request_context prepare_completion_request prepare_chat_request

关键源码片段

vllm/entrypoints/generate/base/serving.py core-logic

定义 PRIORITY_HEADER 常量和 _get_priority 静态方法,是 Python 入口优先级覆盖的核心实现,后续所有 API 复用。

# vllm/entrypoints/generate/base/serving.py
SESSION_ID_HEADER = "X-Session-ID"
# 新增常量,统一 header 键名,避免散落在各 serving 模块中
PRIORITY_HEADER = "X-Vllm-Priority"
​
​
class GenerateBaseServing(...):
    # 省略其他内容 ...
​
    @staticmethod
    def _get_priority(
        request: ChatCompletionRequest | CompletionRequest | ResponsesRequest,
        raw_request: Request | None,
    ) -> int:
        """优先从 HTTP header 解析优先级,解析失败时回退到请求体中的 priority。        这样中间代理或负载均衡器只需设置 X-Vllm-Priority 头即可覆盖
        客户端 JSON 里携带的优先级,无需解析整个请求体。
        """
        if raw_request is not None:
            priority = raw_request.headers.get(PRIORITY_HEADER)
            if priority is not None:
                try:
                    # header 值必须是整数,否则抛 ValueError
                    return int(priority)
                except ValueError:
                    # header 值不是合法整数时静默回退到 body 字段
                    pass
        # 最终回退到 body 里的 priority(可能为 None,由调用方决定默认值)
        return request.priority
rust/src/server/src/utils.rs core-logic

为 ResolvedRequestContext 新增 priority 字段并在 resolve_request_context 中解析 X-Vllm-Priority,是 Rust 前端优先级来源的统一入口。

// rust/src/server/src/utils.rs/// 从 HTTP header 解析出的公共请求上下文,供各路由复用。
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ResolvedRequestContext {
    pub request_id: String,
    pub data_parallel_rank: Option<u32>,
    pub priority: Option<i32>, // 新增字段:X-Vllm-Priority 解析结果
    pub session_id: Option<String>,
}/// 提取公共请求元数据:外部 request ID、session ID、优先级,
/// 以及用于引擎路由的 data-parallel-rank。
pub fn resolve_request_context(
    headers: &HeaderMap,
    request_id: Option<&str>,
) -> ResolvedRequestContext {
    // 仅当 header 缺失或无法解析为 u32 时为 None
    let data_parallel_rank = headers
        .get("X-data-parallel-rank")
        .and_then(|v| v.to_str().ok())
        .and_then(|s| s.trim().parse().ok());    // 新增:X-Vllm-Priority 解析为 i32,trim 后 parse,失败则为 None
    // 语义与 Python 端“解析失败回退”一致
    let priority = headers
        .get("X-Vllm-Priority")
        .and_then(|value| value.to_str().ok())
        .and_then(|value| value.trim().parse().ok());    // 请求 ID、session ID 的解析逻辑保持不变 ...
    ResolvedRequestContext {
        request_id,
        data_parallel_rank,
        priority,
        session_id,
    }
}
rust/src/server/src/routes/openai/completions/convert.rs entrypoint

构造 CompletionRequest 时改用 ctx.priority.or(request.priority) 实现 header 覆盖 body,并新增对应单元测试。

// rust/src/server/src/routes/openai/completions/convert.rs
// 构造内部请求时,header 优先级(ctx.priority)优先于 body 字段(request.priority),
// 两者都缺失时默认 0。chat_completions/convert.rs 中的 prepare_chat_request 同样处理。
priority: ctx.priority.or(request.priority).unwrap_or(0),// 对应单元测试:body priority=10 被 header -5 覆盖
#[test]
fn prepare_completion_request_header_priority_overrides_body() {
    let request: CompletionRequest = serde_json::from_value(json!({
        "model": "Qwen/Qwen1.5-0.5B-Chat",
        "prompt": "hello",
        "priority": 10,
    }))
    .expect("parse request");    let mut headers = HeaderMap::new();
    headers.insert("X-Vllm-Priority", "-5".parse().unwrap());
    let prepared = prepare_completion_request(
        request,
        &served(&["Qwen/Qwen1.5-0.5B-Chat"]),
        request_context(&headers, None),
        &test_tokenizer(),
    )
    .expect("prepare");
    // 验证 header 覆盖 body 中的 priority
    assert_eq!(prepared.text_request.priority, -5);
}

评论区精华

fork 自动化审核被禁用 other

Claude bot 指出 PR 来自 fork,自动评审被禁用;维护者可以评论 @claude review 手动触发。

结论:无后续动作,改由维护者人工评审。 · 已解决

维护者批准 other

DarkLight1337 无评论批准,BugenZhao 留下 LGTM 并批准。

结论:PR 被合并。 · 已解决

风险与影响

  1. 双栈语义一致性风险:Python 端 _get_priority 在 header 值无法转 int 时回退 body,Rust 端 resolve_request_context 解析失败得到 None 后同样回退 body,两者语义已对齐;但若后续单边修改(例如对非法值报错),三条 API 与两条前端的行为可能分叉。
  2. 非法值静默回退:header 值拼写错误或非整数时会被静默忽略,运维配置错误难以第一时间暴露;可考虑未来对非法值返回 400。
  3. 优先级调度前提:文档明确非零优先级需要服务器启用优先级调度;若未启用,header 设置的非零值可能被忽略或产生与预期不符的排队行为,用户需自行保证配置。
  4. Python 端缺少测试_get_priority 及三个 serving 文件的替换没有 Python 测试覆盖,回归只能依赖 Rust 端测试和人工验证。
  5. Responses API 联动:Responses 路径同样被修改,但本次没有针对 Responses 的 Rust/Python 定向测试。
  1. 用户侧:代理/负载均衡器无需解析和改写整个 JSON body 即可动态调整单请求优先级,对含大附件、图片的请求尤其降低代理开销;header 覆盖语义让客户端无法绕过运营设定的优先级。
  2. 系统侧:优先级最终仍通过既有 engine_client.generate(priority=...) 与调度器交互,调度行为不变;入口层多了一次 header 读取与 int 转换,开销可忽略。
  3. 团队侧:新增一种前端 header 约定,需要同时维护 Python/Rust 两条实现路径的一致性;文档已同步,降低使用门槛。
Python 端缺少测试覆盖 非法 header 值静默回退 双栈语义需保持一致 非零优先级依赖优先级调度配置

关联 Issue

#51023 [Feature]: Parse Request Priority from HTTP Header

完整报告

参与讨论