Studio: correct tool-call parser docstrings for PR #5811
Three label corrections surfaced while cross-checking the parser
against official HF chat templates. Comment-only, no behaviour
change; 104/104 tests still pass.
1. ``<function=...>`` XML was labelled "Qwen3.5 xml". The
canonical emitter is Qwen3-Coder and it always wraps the
<function=...> block inside an outer <tool_call>...</tool_call>,
never bare. Updated labels in the module docstring and the
three inline comments.
2. Mistral grouping was wrong on two counts: Ministral-8B-2410
uses Tekken V3 and emits the ``[TOOL_CALLS] [...]`` array
form, not ``[ARGS]``. Mistral-Large-2411 ships
tokenizer.model.v7 (no ``[ARGS]`` token) and also emits the
array form. ``[ARGS]`` only enters with Tekken V13 (Devstral,
Magistral-Small-2509). Regrouped the doc lines accordingly.
3. Gemma 4 ``<|tool_call>`` is forward-looking. Neither
gemma-3-12b-it nor gemma-3n-E4B-it emit this shape in their
chat templates today. Noted that the capability gate
correctly suppresses the tools pill on real Gemma 3
templates, so the parser stays as ready-when-Google-ships
scaffolding.
This commit is contained in:
parent
4d69577d97
commit
518d0a5570
1 changed files with 18 additions and 13 deletions
|
|
@ -8,14 +8,14 @@ Covers the emission formats so the safetensors + MLX agentic loop sees
|
|||
the same call shape llama-server normalises for GGUF:
|
||||
|
||||
- ``<tool_call>{json}</tool_call>`` (Qwen / Hermes)
|
||||
- ``<function=name><parameter=k>v</parameter></function>`` (Qwen3.5 xml)
|
||||
- ``<function=name><parameter=k>v</parameter></function>`` (Qwen3-Coder XML; nested in <tool_call>)
|
||||
- ``<|python_tag|>NAME.call(k="v", ...)`` (Llama-3 built-in tools)
|
||||
- ``<|python_tag|>{"name":..., "parameters":...}`` (Llama-3 custom)
|
||||
- ``{"name":..., "parameters":...}`` (Llama-3.2 bare JSON)
|
||||
- ``[TOOL_CALLS] [{...}, ...]`` (Mistral v0.3 / Nemo / Small)
|
||||
- ``[TOOL_CALLS]name{json}`` (Mistral v11+ / Magistral)
|
||||
- ``[TOOL_CALLS]name[ARGS]{json}`` (Ministral / Mistral Large 3)
|
||||
- ``<|tool_call>call:NAME{k:<|"|>v<|"|>}<tool_call|>`` (Gemma 4)
|
||||
- ``[TOOL_CALLS] [{...}, ...]`` (Mistral V3 tokenizer: v0.3, Nemo, Small, Ministral-8B-2410, Large-2411)
|
||||
- ``[TOOL_CALLS]name{json}`` (Mistral V11+ Tekken: Magistral)
|
||||
- ``[TOOL_CALLS]name[ARGS]{json}`` (Mistral V13 Tekken: Devstral, Magistral-Small-2509)
|
||||
- ``<|tool_call>call:NAME{k:<|"|>v<|"|>}<tool_call|>`` (Gemma 4; forward-looking, no shipping Gemma model emits this yet)
|
||||
|
||||
Closing tags / brackets are tolerated when missing because models
|
||||
frequently truncate them mid-stream.
|
||||
|
|
@ -85,7 +85,7 @@ BUDGET_EXHAUSTED_NUDGE = (
|
|||
|
||||
# Qwen / Hermes ``<tool_call>{json}``.
|
||||
_TC_JSON_START_RE = re.compile(r"<tool_call>\s*\{")
|
||||
# Qwen3.5 / Hermes XML ``<function=name><parameter=k>v``.
|
||||
# Qwen3-Coder XML ``<function=name><parameter=k>v``, nested inside <tool_call>.
|
||||
_TC_FUNC_START_RE = re.compile(r"<function=([\w\.\-]+)>\s*")
|
||||
_TC_END_TAG_RE = re.compile(r"</tool_call>")
|
||||
_TC_FUNC_CLOSE_RE = re.compile(r"\s*</function>\s*$")
|
||||
|
|
@ -102,14 +102,17 @@ _LLAMA3_KV_RE = re.compile(
|
|||
re.VERBOSE,
|
||||
)
|
||||
|
||||
# Mistral ``[TOOL_CALLS]`` trigger. v11+ chains them, each followed by
|
||||
# a bare name plus ``{json}`` (Magistral) or ``[ARGS]{json}`` (Ministral
|
||||
# / Large 3).
|
||||
# Mistral ``[TOOL_CALLS]`` trigger. V11+ Tekken chains them, each
|
||||
# followed by a bare name plus ``{json}`` (Magistral) or
|
||||
# ``[ARGS]{json}`` (V13 Tekken: Devstral, Magistral-Small-2509).
|
||||
# V3 tokenizer models (v0.3, Nemo, Small, Ministral-8B-2410,
|
||||
# Large-2411) emit the ``[TOOL_CALLS] [...]`` array form instead.
|
||||
_MISTRAL_TRIGGER = "[TOOL_CALLS]"
|
||||
_MISTRAL_ARGS_MARKER = "[ARGS]"
|
||||
_MISTRAL_V11_NAME_RE = re.compile(r"\s*([\w\.\-]+)\s*")
|
||||
|
||||
# Gemma 4: ``<|tool_call>call:NAME{...}<tool_call|>``, ``<|"|>`` wraps strings.
|
||||
# Gemma 4 (forward-looking; Gemma 3 does not emit this shape yet):
|
||||
# ``<|tool_call>call:NAME{...}<tool_call|>``, ``<|"|>`` wraps strings.
|
||||
_GEMMA_TC_RE = re.compile(r"<\|tool_call>\s*call\s*:\s*([\w\.\-]+)\s*\{")
|
||||
_GEMMA_STR_BEGIN = '<|"|>'
|
||||
_GEMMA_STR_END = '<|"|>'
|
||||
|
|
@ -223,10 +226,10 @@ def parse_tool_calls_from_text(content: str, *, id_offset: int = 0) -> list[dict
|
|||
as soon as one matches so we never double-count."""
|
||||
for parser in (
|
||||
_parse_tool_call_json, # Qwen / Hermes
|
||||
_parse_function_xml, # Qwen3.5 / Hermes XML
|
||||
_parse_function_xml, # Qwen3-Coder XML
|
||||
_parse_llama3_python_tag, # Llama-3
|
||||
_parse_mistral_tool_calls, # Mistral
|
||||
_parse_gemma_tool_calls, # Gemma 4
|
||||
_parse_gemma_tool_calls, # Gemma 4 (forward-looking)
|
||||
):
|
||||
calls = parser(content, id_offset = id_offset)
|
||||
if calls:
|
||||
|
|
@ -642,7 +645,9 @@ def _consume_mistral_call(obj_text: str, out: list[dict], id_offset: int) -> Non
|
|||
|
||||
|
||||
def _parse_gemma_tool_calls(content: str, *, id_offset: int) -> list[dict]:
|
||||
"""Gemma 4: ``<|tool_call>call:NAME{k:<|"|>v<|"|>, ...}<tool_call|>``."""
|
||||
"""Gemma 4 (forward-looking; no shipping Gemma 3 model emits this):
|
||||
``<|tool_call>call:NAME{k:<|"|>v<|"|>, ...}<tool_call|>``.
|
||||
Capability gate suppresses the tools pill on real Gemma 3 templates."""
|
||||
out: list[dict] = []
|
||||
for m in _GEMMA_TC_RE.finditer(content):
|
||||
name = m.group(1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue