Prhub

#53219 Add Cohere ChatV2 render endpoint

原始 PR 作者 andrewbcohere 合并时间 2026-08-24 20:27 文件变更 5 提交数 3 评论 4 代码增减 +380 / -24

执行摘要

为 Cohere ChatV2 新增 render 端点,仅分词不生成

PR body 明确说明目的是 "Add /cohere/v2/chat/render endpoint to match other endpoint prompt render capability",即让 Cohere Chat v2 与其他端点(如 OpenAI chat render)保持一致的 prompt 渲染能力,使调用方可以在不触发生成的情况下预检模板化与分词结果。

值得精读,尤其是 vllm/entrypoints/cohere/serving.py 中把请求转换逻辑提取为公开方法的设计:它让 render 端点在零 tokenizer/engine 依赖下复用完整转换链路,是「协议转换与生成执行解耦」的干净示例。关注 Cohere 兼容层或 render 端点扩展的同学可重点看 api_router.py 的路由错误处理与 test_chat_v2.py 的跨层契约测试。

讨论亮点

本 PR 没有实质性的代码 review 讨论:Claude[bot] 因 PR 来自 fork 自动跳过审查;DarkLight1337 在解决冲突并触发 Buildkite CI 后直接 APPROVED。唯一的外部交互是 mergify[bot] 两次提示分支与 main 存在合并冲突、要求 rebase,作者通过两次 merge origin/main 解决。

实现拆解

实现分四步完成:

  1. 提取公开转换方法(serving.py):把 CohereServingChatV2 中原本内联在 create_chat_v2 里的 _convert_v2_to_chat_completion 调用及 debug 日志封装为公开方法 to_chat_completion_request,create_chat_v2 改为调用该方法。这样 render 端点无需实例化完整 chat serving 即可复用同一套 v2→ChatCompletionRequest 转换,避免两条路径逻辑漂移。

  2. 新增 render 路由(api_router.py):在 Cohere 路由中新增 _serving_render(request) 辅助函数读取 app.state.serving_render;随后注册 @router.post("/cohere/v2/chat/render"),响应模型为 GenerateRequest。路由先通过 handler.to_chat_completion_request 把 CohereChatV2Request 转成 ChatCompletionRequest,再交给 ServingRender.render_chat_request,最终原样返回 GenerateRequest;对 ErrorResponse、handler 缺失(501)和异常(500)的处理与既有 /cohere/v2/chat 保持一致的 CohereError 错误格式。

  3. 环境变量注释同步(envs.py):更新 VLLM_ENABLE_COHERE_API 的注释,说明开启后同时暴露 chat 与 render 两个端点。行为不变,仍默认关闭。

  4. 测试配套:

    • tests/entrypoints/cohere/test_api_router.py 新增 _RenderChatHandler、_RenderHandler、_generate_request、_build_render_app 等测试脚手架,以及 TestRenderEndpoint 覆盖转换链路、ErrorResponse 透传、异常 500、校验 400/422 等分支;
    • tests/entrypoints/cohere/test_chat_v2.py 新增 3 个端到端用例,验证采样参数(temperature、stop_sequences)在 v2→ChatCompletionRequest→SamplingParams 链路中保留,以及通过 /detokenize 反查确认 token_ids 是真实模板化后的 prompt。
文件 模块 状态 重要度
vllm/entrypoints/cohere/api_router.py 路由层 modified 7.78
vllm/entrypoints/cohere/serving.py 服务层 modified 6.64
tests/entrypoints/cohere/test_api_router.py 单测 modified 7.35
tests/entrypoints/cohere/test_chat_v2.py 集成测试 modified 6.24
vllm/envs.py 环境变量 modified 4.02

关键符号

chat_v2_render _serving_render to_chat_completion_request

关键源码片段

vllm/entrypoints/cohere/api_router.py entrypoint

新增 `POST /cohere/v2/chat/render` 路由及 `_serving_render` 辅助函数,是整个 PR 的入口层核心变更,负责把 Cohere v2 请求经转换后送入通用 `ServingRender`,并统一错误翻译。

# POST /cohere/v2/chat/render 路由与 serving 读取辅助函数
# (基于 api_router.py 实际结构整理,省略请求校验中间件等外围细节)
​
def _serving_render(request: Request) -> ServingRender | None:
    """从 app.state 读取渲染服务实例,未注册时返回 None。"""
    return getattr(request.app.state, "serving_render", None)
​
​
@router.post(
    "/cohere/v2/chat/render",
    dependencies=[Depends(validate_json_request)],
    response_model=GenerateRequest,
)
async def chat_v2_render(
    request: CohereChatV2Request, raw_request: Request
) -> GenerateRequest | JSONResponse:
    """镜像 /v1/chat/completions/render:只做分词与模板化,不运行生成。"""
    handler = _serving(raw_request)
    render = _serving_render(raw_request)
    if handler is None or render is None:
        # chat_v2 用 501 表示模型不支持,render 保持同样的语义
        return JSONResponse(
            status_code=HTTPStatus.NOT_IMPLEMENTED.value,
            content=CohereError(
                message="The model does not support the Cohere v2 chat API.",
                id=_request_id(raw_request),
            ).model_dump(exclude_none=True),
        )
​
    try:
        # 先复用 Cohere v2 的公开转换方法得到 OpenAI 请求,
        # 再交给通用渲染服务,保证与 OpenAI render 端点路径一致
        chat_req = handler.to_chat_completion_request(request)
        result = await render.render_chat_request(chat_req)
    except Exception as e: # noqa: BLE001 - 统一按 500 输出,与 chat_v2 对齐
        logger.exception("Error in /cohere/v2/chat/render: %s", e)
        return JSONResponse(
            status_code=HTTPStatus.INTERNAL_SERVER_ERROR.value,
            content=CohereError(
                message=sanitize_message(str(e)),
                id=_request_id(raw_request),
            ).model_dump(exclude_none=True),
        )
​
    if isinstance(result, ErrorResponse):
        # 转换或校验阶段产生的 vLLM 错误信封,统一翻译为 Cohere 错误格式
        return _error_response(result, raw_request)
    return result

评论区精华

合并冲突与 CI 触发 other

mergify[bot] 两次提示该 fork 分支与 main 存在合并冲突并要求 rebase;Claude[bot] 因 PR 来自 fork 自动跳过代码审查;合并者 DarkLight1337 在冲突解决后触发 Buildkite CI,并给出 APPROVED。

结论:作者两次 merge origin/main 解决冲突,CI 通过后由 DarkLight1337 合并。 · 已解决

风险与影响

  • ServingRender 注册依赖:/cohere/v2/chat/render 依赖 app.state.serving_render 存在,若部署未启用 render 模块,_serving_render 返回 None。当前代码对 handler 或 render 缺失统一返回 501,但仍需确认 render 服务是否在所有启用 VLLM_ENABLE_COHERE_API 的部署中都被正确挂载,否则可能误报 501。
  • create_chat_v2 重构回归:create_chat_v2 从直接调用 _convert_v2_to_chat_completion 改为调用 to_chat_completion_request,行为等价(转换 + debug 日志),但作为既有聊天主路径,仍存在潜在的回归窗口,需依赖既有测试覆盖。
  • 错误语义一致性:新端点复用了 _error_response 与 CohereError 信封,但路由中 handler is None 时返回的 message 沿用 chat 端点文案,未单独说明 render 不可用,可能对排障造成轻微混淆。
  • 门控默认关闭:端点仍受 VLLM_ENABLE_COHERE_API 控制且默认关闭,对默认部署无影响,但用户若未显式开启会看不到该端点。

影响范围集中在 Cohere 兼容 API 层:调用方现在可以在生成前通过 /cohere/v2/chat/render 获取模板化后的 token_ids 与 sampling_params,方便做 prompt 预检、缓存或前后端分离;对服务端核心路径无性能影响。团队层面看,这是 Cohere v2 协议对齐 OpenAI render 能力的重要一步,后续其他协议也可能复用同一 ServingRender 模式。由于是新增端点且门控默认关闭,对现有用户是严格向后兼容的增强。

新端点依赖 serving_render 注册 create_chat_v2 调用路径重构 门控默认关闭需显式开启

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论