diff --git a/studio/backend/models/inference.py b/studio/backend/models/inference.py index 75f0599a09..0dbaddfef1 100644 --- a/studio/backend/models/inference.py +++ b/studio/backend/models/inference.py @@ -621,7 +621,7 @@ class AnthropicTool(BaseModel): class AnthropicMessagesRequest(BaseModel): model: str = "default" - max_tokens: int + max_tokens: Optional[int] = None messages: list[AnthropicMessage] system: Optional[Union[str, list]] = None tools: Optional[list[AnthropicTool]] = None @@ -632,6 +632,10 @@ class AnthropicMessagesRequest(BaseModel): top_k: Optional[int] = None stop_sequences: Optional[list[str]] = None metadata: Optional[dict] = None + # [x-unsloth] extensions — mirror the OpenAI endpoint convenience fields + enable_tools: Optional[bool] = None + enabled_tools: Optional[list[str]] = None + session_id: Optional[str] = None model_config = {"extra": "allow"} diff --git a/studio/backend/routes/inference.py b/studio/backend/routes/inference.py index db1e3b8317..8786e35cb3 100644 --- a/studio/backend/routes/inference.py +++ b/studio/backend/routes/inference.py @@ -2255,16 +2255,26 @@ async def anthropic_messages( cancel_event = threading.Event() # ── Tool-calling path ───────────────────────────────────── - use_tools = ( - payload.tools - and len(payload.tools) > 0 - and llama_backend.supports_tools + # Two ways to enable tools: + # 1. Anthropic-style: send full tool definitions in payload.tools + # 2. Unsloth shorthand: enable_tools=true + optional enabled_tools list + use_tools = llama_backend.supports_tools and ( + (payload.tools and len(payload.tools) > 0) + or payload.enable_tools ) if use_tools: from core.inference.tools import ALL_TOOLS - openai_tools = anthropic_tools_to_openai(payload.tools) + if payload.tools and len(payload.tools) > 0: + openai_tools = anthropic_tools_to_openai(payload.tools) + elif payload.enabled_tools is not None: + openai_tools = [ + t for t in ALL_TOOLS + if t["function"]["name"] in payload.enabled_tools + ] + else: + openai_tools = ALL_TOOLS # Build tool-use system prompt nudge (same logic as /chat/completions) _tool_names = {t["function"]["name"] for t in openai_tools} @@ -2339,6 +2349,7 @@ async def anthropic_messages( max_tool_iterations = 25, auto_heal_tool_calls = True, tool_call_timeout = 300, + session_id = payload.session_id, ) if payload.stream: diff --git a/studio/backend/tests/test_anthropic_messages.py b/studio/backend/tests/test_anthropic_messages.py index d0c8bfcb34..51af665c1c 100644 --- a/studio/backend/tests/test_anthropic_messages.py +++ b/studio/backend/tests/test_anthropic_messages.py @@ -41,13 +41,19 @@ from core.inference.anthropic_compat import ( class TestAnthropicModels: def test_minimal_request(self): + req = AnthropicMessagesRequest( + messages = [{"role": "user", "content": "Hi"}], + ) + assert req.max_tokens is None + assert req.model == "default" + assert req.stream is False + + def test_max_tokens_optional(self): req = AnthropicMessagesRequest( max_tokens = 100, messages = [{"role": "user", "content": "Hi"}], ) assert req.max_tokens == 100 - assert req.model == "default" - assert req.stream is False def test_system_as_string(self): req = AnthropicMessagesRequest( @@ -81,6 +87,25 @@ class TestAnthropicModels: ) assert req.stream is False + def test_enable_tools_shorthand(self): + req = AnthropicMessagesRequest( + messages = [{"role": "user", "content": "Hi"}], + enable_tools = True, + enabled_tools = ["web_search", "python"], + session_id = "my-session", + ) + assert req.enable_tools is True + assert req.enabled_tools == ["web_search", "python"] + assert req.session_id == "my-session" + + def test_extension_fields_default_none(self): + req = AnthropicMessagesRequest( + messages = [{"role": "user", "content": "Hi"}], + ) + assert req.enable_tools is None + assert req.enabled_tools is None + assert req.session_id is None + def test_response_model_defaults(self): resp = AnthropicMessagesResponse() assert resp.type == "message"