{"record":{"id":"deb368deb19e0bd1","repo":"sgl-project/sglang","slug":"tool-choice-tc-type-r-requires-at-least-one-cust","errorCode":null,"errorMessage":"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","messagePattern":"tool_choice=(.+?) requires at least one custom tool; all supplied tools were server-side Anthropic built-ins which the OpenAI-compatible backend cannot invoke","errorType":"http","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"python/sglang/srt/entrypoints/anthropic/serving.py","lineNumber":724,"sourceCode":"                    tool_name = anthropic_request.tool_choice.name\n                    # ``Tool.function`` is a ``Function`` Pydantic model, not\n                    # a dict — access by attribute. A dict ``.get`` would\n                    # AttributeError and surface as a 500 instead of the\n                    # intended 400 / happy path.\n                    if not any(\n                        t.function.name == tool_name for t in chat_request.tools\n                    ):\n                        raise ValueError(\n                            f\"tool_choice references tool {tool_name!r} but it \"\n                            f\"is not in the forwarded tools list \"\n                            f\"(server-side Anthropic tools cannot be selected)\"\n                        )\n                    chat_request.tool_choice = ToolChoice(\n                        type=\"function\",\n                        function=ToolChoiceFuncName(name=tool_name),\n                    )\n            elif tc_type in (\"any\", \"tool\"):\n                raise ValueError(\n                    f\"tool_choice={tc_type!r} requires at least one custom \"\n                    f\"tool; all supplied tools were server-side Anthropic \"\n                    f\"built-ins which the OpenAI-compatible backend cannot \"\n                    f\"invoke\"\n                )\n        elif chat_request.tools:\n            chat_request.tool_choice = \"auto\"\n\n        return chat_request\n\n    async def _handle_non_streaming(\n        self,\n        chat_request: ChatCompletionRequest,\n        anthropic_request: AnthropicMessagesRequest,\n        raw_request: Request,\n    ) -> JSONResponse:\n        \"\"\"Handle non-streaming Anthropic request by delegating to OpenAI handler.\"\"\"\n        # ``monotonic_time`` is ``time.perf_counter`` under the hood; the","sourceCodeStart":706,"sourceCodeEnd":742,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/entrypoints/anthropic/serving.py#L706-L742","documentation":"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.","triggerScenarios":"POST /v1/messages with tool_choice={\"type\": \"any\"} and tools containing only Anthropic built-ins like {\"type\": \"web_search_20250305\", \"name\": \"web_search\"}.","commonSituations":"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).","solutions":["Add at least one custom tool definition ({\"type\": \"custom\", \"name\": ..., \"input_schema\": ...}) if you need tool_choice 'any'/'tool'","Or change tool_choice to 'auto' when only built-in tools are supplied","Note built-in tools are forwarded but not executed by the backend; implement tools client-side"],"exampleFix":"// before\n{\"tools\": [{\"type\": \"web_search_20250305\", \"name\": \"web_search\"}], \"tool_choice\": {\"type\": \"any\"}}\n// after\n{\"tools\": [{\"type\": \"web_search_20250305\", \"name\": \"web_search\"}], \"tool_choice\": {\"type\": \"auto\"}}","handlingStrategy":"validation","validationCode":"has_custom = any(t.get(\"type\") in (\"custom\", \"function\") for t in tools)\nif tool_choice.get(\"type\") in (\"any\", \"tool\") and not has_custom:\n    tool_choice = {\"type\": \"auto\"}","typeGuard":"def can_force_tool(tool_choice, tools) -> bool:\n    return tool_choice.get(\"type\") not in (\"any\", \"tool\") or any(\n        t.get(\"type\") in (\"custom\", \"function\") for t in tools)","tryCatchPattern":null,"preventionTips":["Only force tool use when you define custom tools","Fall back to 'auto' when tools are all built-ins"],"tags":["anthropic","tool-choice","built-in-tools","request-conversion"],"backgroundTag":"tool-choice-mismatch","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}