Prhub

#52021 [Bugfix] Preserve Anthropic disable_parallel_tool_use

原始 PR 作者 taneem-ibrahim 合并时间 2026-08-13 20:25 文件变更 2 提交数 1 评论 7 代码增减 +4 / -0

执行摘要

修复 Anthropic disable_parallel_tool_use 被静默丢弃的问题

PR body 明确说明:Anthropic 的 disable_parallel_tool_use 字段被静默丢弃,转换后的 OpenAI 请求总是 parallel_tool_calls=True。reproducer 表明传入 'disable_parallel_tool_use': True 时,转换后 parallel_tool_calls 仍为 True,与用户意图相反,因此需要保留并映射到 OpenAI 的对应字段。

值得快速阅读:改动虽小,但展示了协议兼容层中“保留源字段 + 取反映射”的典型做法。建议关注 _convert_tool_choice 中先设置 parallel_tool_calls 再按 type 分支的顺序,以及 None 缺省值如何在不改动其他路径的前提下维持向后兼容。

讨论亮点

review 过程非常简短:维护者 yewentao256 给出 APPROVED,评论为 'LGTM, thanks for the work!'。Claude bot 说明 fork 的 PR 默认不进行自动 review,可由维护者手动触发。没有其他实质讨论;缺省 Nonenot 取反后为 True 与旧行为一致的兼容性决策未在评论中显式提及,但属于该实现的关键设计点。

实现拆解

  1. 协议模型补字段:在 vllm/entrypoints/anthropic/protocol.pyAnthropicToolChoice 类中新增 disable_parallel_tool_use: bool | None = None,使请求体中的该字段能被 Pydantic 解析并保留。
  2. 转换逻辑映射:在 vllm/entrypoints/anthropic/serving.py_convert_tool_choice 类方法中,在按 tool_choice.type 分支之前设置 req.parallel_tool_calls = not anthropic_request.tool_choice.disable_parallel_tool_use,利用两个协议字段语义相反的关系完成取反映射。
  3. 验证与配套:PR 未新增测试文件,通过 tests/entrypoints/anthropic/test_anthropic_messages_conversion.py 全部 51 个用例验证;CI 在 Buildkite 上经过 2 次 retry 后通过。
文件 模块 状态 重要度
vllm/entrypoints/anthropic/serving.py 请求转换 modified 4.99
vllm/entrypoints/anthropic/protocol.py 协议模型 modified 4.35

关键符号

_convert_tool_choice AnthropicToolChoice

关键源码片段

vllm/entrypoints/anthropic/serving.py core-logic

核心转换逻辑所在:`_convert_tool_choice` 中新增 `parallel_tool_calls` 取反映射,直接修复字段被丢弃的问题。

@classmethod
def _convert_tool_choice(
    cls,
    anthropic_request: AnthropicMessagesRequest | AnthropicCountTokensRequest,
    req: ChatCompletionRequest,
) -> None:
    """Convert Anthropic tool_choice to OpenAI format"""
    if anthropic_request.tool_choice is None:
        req.tool_choice = None
        return
​
    # 关键修复:此前该字段被静默丢弃, parallel_tool_calls 恒为 True
    # Anthropic 的 disable_parallel_tool_use 与 OpenAI 的 parallel_tool_calls 语义相反,取反映射
    # 缺省值 None 取反后为 True,与历史行为保持一致
    req.parallel_tool_calls = (
        not anthropic_request.tool_choice.disable_parallel_tool_use
    )
    tool_choice_type = anthropic_request.tool_choice.type
    if tool_choice_type == "auto":
        req.tool_choice = "auto"
    elif tool_choice_type == "any":
        req.tool_choice = "required"
    elif tool_choice_type == "none":
        req.tool_choice = "none"
    elif tool_choice_type == "tool":
        req.tool_choice = ChatCompletionNamedToolChoiceParam.model_validate(
            {
                "type": "function",
                "function": {"name": anthropic_request.tool_choice.name},
            }
        )
vllm/entrypoints/anthropic/protocol.py core-logic

协议模型定义处:为 `AnthropicToolChoice` 补充 `disable_parallel_tool_use` 字段,使该入参不再被忽略。

class AnthropicToolChoice(BaseModel):
    """Tool Choice definition"""
​
    type: Literal["auto", "any", "tool", "none"]
    name: str | None = None
    # 新增字段:Anthropic 允许在 tool_choice 中显式禁用并行工具调用
    # 若不声明此字段,Pydantic 会忽略请求体中的该键,导致后续转换丢失信息
    disable_parallel_tool_use: bool | None = None
​
    @model_validator(mode="after")
    def validate_name_required_for_tool(self) -> "AnthropicToolChoice":
        if self.type == "tool" and not self.name:
            raise ValueError("tool_choice.name is required when type is 'tool'")
        return self

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

主要风险点:

1) disable_parallel_tool_use 缺省为 Nonenot NoneTrue,保证了向后兼容,但若未来 Anthropic 改变字段默认值或引入三态枚举,此处取反逻辑可能产生语义偏差;
2) tool_choiceNone 时方法提前 return,不设置 parallel_tool_calls,此时依赖 ChatCompletionRequest 自身的默认值(通常为 True),该路径行为未变;
3) 未新增专门测试,若后续重构 _convert_tool_choice 分支顺序可能破坏映射。

影响面限于 Anthropic 兼容层的请求转换(/v1/messages 端点)。用户现在可以通过 disable_parallel_tool_use 显式控制是否并行调用工具,对既有未传该字段的请求行为完全不变。对团队而言,该改动提供了一个简洁的协议字段取反映射示例,后续 Anthropic 其他字段的转换可参考此模式。

缺省值依赖隐式取反 未新增测试文件

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论