unsloth/studio
Daniel Han e9ea45b6a5
Studio: coerce tool_call arguments to dict before chat templating (fixes MLX tool follow-up error) (#6807)
* Studio: coerce tool_call arguments to dict before chat templating

Strict tool chat templates (e.g. mlx-community Qwen3.5 checkpoints) iterate
arguments.items() and raise "TypeError: Can only get item pairs from a mapping"
when a prior assistant tool call is re-rendered on the next turn. The agentic
loop stores arguments in the OpenAI JSON-string form (as_assistant_tool_call),
which is correct on the wire and for llama-server, but the transformers / MLX
paths apply_chat_template directly and hit the strict Jinja templates.

Normalize each assistant tool_call's function.arguments from a JSON string to a
dict inside apply_chat_template_for_generation (shared by both the MLX and
safetensors paths). A dict renders on strict and lenient templates alike;
non-JSON / non-dict values are left untouched, and the OpenAI-format
as_assistant_tool_call (used by the GGUF path + API responses) is unchanged.

Verified against the real mlx-community/Qwen3.5-2B-8bit template: string args
raised the tester's error, the fix renders cleanly, and the lenient
unsloth/Qwen3.5-0.8B template still works.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Studio: make tool-arg coercion a string-first fallback (non-regressive)

Render the original OpenAI string-arg form first and only coerce arguments to a
dict when the template raises the mapping TypeError, instead of always coercing.
Any template that already renders is now byte-identical (a template that emits
arguments verbatim keeps the JSON string, not a Python dict repr).

Verified across Llama-3, Qwen2.5, Qwen3, Qwen3.5, Phi-3.5 (byte-identical) and
mlx-community/Qwen3.5-2B-8bit (strict -> fixed). Gemma-3 / Mistral tool-template
errors are unrelated (role alternation / tool-id length) and identical with or
without the change.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Make core.inference package init lazy so dependency-light helpers import standalone

Importing any core.inference submodule ran the package __init__, which
eagerly imported orchestrator and llama_cpp; both pull loggers ->
structlog (and httpx), so a dependency-light helper like
chat_template_helpers dragged in the full heavy stack and its unit test
failed to collect in a backend env without structlog. Defer those
imports to attribute access via PEP 562 __getattr__, mirroring the lazy
pattern already in core/__init__.py. The re-exports resolve unchanged on
first access.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Retry dict-coercion for strict templates that raise non-TypeError

apply_chat_template_for_generation only retried the OpenAI JSON-string arguments
coercion when the first render raised TypeError (the arguments.items() form). The
bundled gemma-4.jinja instead rejects string arguments with raise_exception, which
surfaces as a Jinja error, so a second tool turn with string function.arguments
propagated and failed rather than retrying with the parsed dict.

Broaden the outer catch to Exception, still gated on there being a string arg to
normalize (normalized is messages -> re-raise), so unrelated template errors and
templates that already render are unaffected.

* Tighten comments in tool-call argument coercion helper and tests

* Tighten tool-call argument coercion comments

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-06 10:12:22 -07:00
..
backend Studio: coerce tool_call arguments to dict before chat templating (fixes MLX tool follow-up error) (#6807) 2026-07-06 10:12:22 -07:00
frontend studio: label RAM and VRAM readouts as GiB not GB (#6895) 2026-07-06 08:27:26 -07:00
src-tauri Speed up Studio desktop startup (#6742) 2026-06-29 15:27:39 +02:00
__init__.py Final cleanup 2026-03-12 18:28:04 +00:00
install_llama_prebuilt.py Studio: stop handing CI/user secrets to downloaded llama.cpp binaries (#6696) 2026-06-27 05:21:05 -07:00
install_node_prebuilt.py Pin isolated Node.js installer to committed sha256 digests (#6625) 2026-06-24 05:47:58 -07:00
install_python_stack.py Guard Windows ROCm torchao override skip (#6837) 2026-07-03 19:24:29 +01:00
LICENSE.AGPL-3.0 Add AGPL-3.0 license to studio folder 2026-03-09 19:36:25 +00:00
node_prebuilt_pins.json Pin isolated Node.js installer to committed sha256 digests (#6625) 2026-06-24 05:47:58 -07:00
package-lock.json ci: advisory lockfile supply-chain audit (no install-script changes) (#5604) 2026-05-19 05:56:56 -07:00
package.json ci: advisory lockfile supply-chain audit (no install-script changes) (#5604) 2026-05-19 05:56:56 -07:00
setup.bat Final cleanup 2026-03-12 18:28:04 +00:00
setup.ps1 [Studio] Add --with-llama-cpp-dir installer flag to reuse a local llama.cpp (#6472) 2026-07-02 22:11:20 +01:00
setup.sh [Studio] Add --with-llama-cpp-dir installer flag to reuse a local llama.cpp (#6472) 2026-07-02 22:11:20 +01:00
Unsloth_Studio_Colab.ipynb Studio Colab: opt-in shareable Cloudflare tunnel link (#6684) 2026-06-26 00:56:23 -07:00