From 5a212bea4e6bda16a856aa99d61b8f56ad92eb35 Mon Sep 17 00:00:00 2001 From: Rafael Rincon Date: Mon, 14 Sep 2026 15:12:59 -0400 Subject: [PATCH] fix(tokenizer): respect explicit enable_thinking=false even when tools present resolve_thinking_mode() forced mode='thinking' when tools were present, ignoring an explicit enable_thinking=False from the caller. This wasted generation budget on reasoning tokens for clients that explicitly requested no thinking while using tool calling. An explicit disable now takes precedence over the tools-presumed-thinking heuristic. Closes #474 --- python/freetoken/tokenizer/tokenize.py | 6 ++++++ tests/tokenizer/test_resolve_thinking_mode.py | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/python/freetoken/tokenizer/tokenize.py b/python/freetoken/tokenizer/tokenize.py index 758ffd4dc..f5deea33c 100644 --- a/python/freetoken/tokenizer/tokenize.py +++ b/python/freetoken/tokenizer/tokenize.py @@ -36,8 +36,14 @@ def resolve_thinking_mode(chat_template_kwargs: dict[str, Any] | None, tools: An one implementation prevents the two sides from disagreeing. Thinking is on when tools are offered (dsv4 only emits well-formed tool calls in thinking mode) or when the caller requests it via ``chat_template_kwargs``. + An explicit ``enable_thinking=False`` (or ``thinking=False``) from the + caller always wins, even when tools are present — the caller knows their + intent better than the heuristic. """ ctk = chat_template_kwargs or {} + # Explicit disable takes precedence over the tools-presumed-thinking heuristic. + if ctk.get("enable_thinking") is False or ctk.get("thinking") is False: + return "chat" mode = str(ctk.get("thinking_mode") or "chat") if tools or ctk.get("enable_thinking") or ctk.get("thinking"): mode = "thinking" diff --git a/tests/tokenizer/test_resolve_thinking_mode.py b/tests/tokenizer/test_resolve_thinking_mode.py index 266a74562..23d69cfee 100644 --- a/tests/tokenizer/test_resolve_thinking_mode.py +++ b/tests/tokenizer/test_resolve_thinking_mode.py @@ -26,3 +26,13 @@ def test_explicit_chat_mode(): def test_invalid_mode_falls_back_to_chat(): assert resolve_thinking_mode({"thinking_mode": "bogus"}, None) == "chat" + + +def test_explicit_disable_overrides_tools(): + """An explicit enable_thinking=False (or thinking=False) must win even + when tools are present — the caller knows their intent better than the + tools-presumed-thinking heuristic.""" + assert resolve_thinking_mode({"enable_thinking": False}, [{"type": "function"}]) == "chat" + assert resolve_thinking_mode({"thinking": False}, [{"type": "function"}]) == "chat" + assert resolve_thinking_mode({"enable_thinking": False, "thinking": True}, [{"type": "function"}]) == "chat" + assert resolve_thinking_mode({"thinking": False, "enable_thinking": True}, [{"type": "function"}]) == "chat"