diff --git a/studio/backend/core/inference/llama_cpp.py b/studio/backend/core/inference/llama_cpp.py index 757467a00..b4f8fe1ca 100644 --- a/studio/backend/core/inference/llama_cpp.py +++ b/studio/backend/core/inference/llama_cpp.py @@ -67,7 +67,6 @@ from core.tool_healing import ( _strip_bracket_tag_calls, apply_tool_strip_patterns, strip_outside_think, - strip_tool_call_markup, ) from utils.native_path_leases import child_env_without_native_path_secret from utils.hf_xet_fallback import hf_hub_download_with_xet_fallback @@ -1781,6 +1780,13 @@ class LlamaCppBackend: return False return self._supports_tools + @property + def supports_tool_passthrough(self) -> bool: + # supports_tools is forced off for DiffusionGemma (its agentic loop drops the + # per-step canvas frames), but client passthrough skips that loop, so it uses + # the real _supports_tools. + return self._supports_tools + @property def cache_type_kv(self) -> Optional[str]: return self._cache_type_kv diff --git a/studio/backend/routes/inference.py b/studio/backend/routes/inference.py index 5332037e0..ce755ae1f 100644 --- a/studio/backend/routes/inference.py +++ b/studio/backend/routes/inference.py @@ -6053,14 +6053,13 @@ async def openai_chat_completions( # free-form sampling. Guided decoding does not require ``supports_tools`` -- # the grammar machinery is independent of tool-call parsing. _has_response_format = _extract_response_format(payload) is not None - _tools_passthrough = llama_backend.supports_tools and ( - (payload.tools and len(payload.tools) > 0) or _has_tool_messages - ) - if ( - using_gguf - and not _effective_enable_tools(payload) - and (_tools_passthrough or _has_response_format) - ): + _tools_passthrough = getattr( + llama_backend, "supports_tool_passthrough", llama_backend.supports_tools + ) and ((payload.tools and len(payload.tools) > 0) or _has_tool_messages) + # DiffusionGemma keeps supports_tools off, so the server-side tool loop can't + # claim the request; fall through to client passthrough, matching /v1/messages. + _server_tool_loop = _effective_enable_tools(payload) and llama_backend.supports_tools + if using_gguf and not _server_tool_loop and (_tools_passthrough or _has_response_format): if _wants_multiple_choices(payload): raise _reject_unsupported_n("GGUF tool or response_format passthrough") if payload.audio_base64: @@ -10222,7 +10221,9 @@ async def anthropic_messages( and not _has_image ) client_tools = ( - not server_tools and len(openai_client_tools) > 0 and llama_backend.supports_tools + not server_tools + and len(openai_client_tools) > 0 + and getattr(llama_backend, "supports_tool_passthrough", llama_backend.supports_tools) ) # Anthropic tool_choice.disable_parallel_tool_use caps the response to a