sgl-project/sglang · error · ValueError

tool_choice={tc_type!r} requires at least one custom tool; a

Error message

tool_choice={tc_type!r} requires at least one custom tool; all supplied tools were server-side Anthropic built-ins which the OpenAI-compatible backend cannot invoke

What it means

Raised when tool_choice is 'any' or 'tool' but the request contains no custom (client-defined) tools — only server-side Anthropic built-in tools, which SGLang's OpenAI-compatible backend cannot invoke. Forcing tool use therefore has nothing to force, so the request is rejected with a 400.

Source

Thrown at python/sglang/srt/entrypoints/anthropic/serving.py:724

                    tool_name = anthropic_request.tool_choice.name
                    # ``Tool.function`` is a ``Function`` Pydantic model, not
                    # a dict — access by attribute. A dict ``.get`` would
                    # AttributeError and surface as a 500 instead of the
                    # intended 400 / happy path.
                    if not any(
                        t.function.name == tool_name for t in chat_request.tools
                    ):
                        raise ValueError(
                            f"tool_choice references tool {tool_name!r} but it "
                            f"is not in the forwarded tools list "
                            f"(server-side Anthropic tools cannot be selected)"
                        )
                    chat_request.tool_choice = ToolChoice(
                        type="function",
                        function=ToolChoiceFuncName(name=tool_name),
                    )
            elif tc_type in ("any", "tool"):
                raise ValueError(
                    f"tool_choice={tc_type!r} requires at least one custom "
                    f"tool; all supplied tools were server-side Anthropic "
                    f"built-ins which the OpenAI-compatible backend cannot "
                    f"invoke"
                )
        elif chat_request.tools:
            chat_request.tool_choice = "auto"

        return chat_request

    async def _handle_non_streaming(
        self,
        chat_request: ChatCompletionRequest,
        anthropic_request: AnthropicMessagesRequest,
        raw_request: Request,
    ) -> JSONResponse:
        """Handle non-streaming Anthropic request by delegating to OpenAI handler."""
        # ``monotonic_time`` is ``time.perf_counter`` under the hood; the

View on GitHub (pinned to 0132848349)

Solutions

  1. Add at least one custom tool definition ({"type": "custom", "name": ..., "input_schema": ...}) if you need tool_choice 'any'/'tool'
  2. Or change tool_choice to 'auto' when only built-in tools are supplied
  3. Note built-in tools are forwarded but not executed by the backend; implement tools client-side

Example fix

// before
{"tools": [{"type": "web_search_20250305", "name": "web_search"}], "tool_choice": {"type": "any"}}
// after
{"tools": [{"type": "web_search_20250305", "name": "web_search"}], "tool_choice": {"type": "auto"}}
Defensive patterns

Strategy: validation

Validate before calling

has_custom = any(t.get("type") in ("custom", "function") for t in tools)
if tool_choice.get("type") in ("any", "tool") and not has_custom:
    tool_choice = {"type": "auto"}

Type guard

def can_force_tool(tool_choice, tools) -> bool:
    return tool_choice.get("type") not in ("any", "tool") or any(
        t.get("type") in ("custom", "function") for t in tools)

Prevention

When it happens

Trigger: POST /v1/messages with tool_choice={"type": "any"} and tools containing only Anthropic built-ins like {"type": "web_search_20250305", "name": "web_search"}.

Common situations: Copy-pasting Anthropic cookbook examples that combine built-in tools with forced tool_choice; assuming SGLang proxies Anthropic server-side tool execution (it doesn't — those tools aren't executed locally).

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/deb368deb19e0bd1. Report an issue: GitHub.