# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 """Strict-mode (Auto-Heal disabled) tool-call parsing. With ``allow_incomplete=False`` the parser must accept a well-formed ``...`` call even when the model appends prose after the closing tag -- matching the JSON-style ``...`` path, which already tolerates trailing text -- while still rejecting genuinely truncated calls that never close. """ from __future__ import annotations import json import sys from pathlib import Path _BACKEND_DIR = str(Path(__file__).resolve().parent.parent) if _BACKEND_DIR not in sys.path: sys.path.insert(0, _BACKEND_DIR) from core.inference.tool_call_parser import parse_tool_calls_from_text def _only(text: str) -> dict: calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1, f"expected exactly one call, got {len(calls)}: {calls!r}" fn = calls[0]["function"] return {"name": fn["name"], "arguments": json.loads(fn["arguments"])} class TestFunctionStyleTrailingText: def test_closed_function_with_trailing_prose_is_accepted(self): text = ( "weather london" " Let me check that for you." ) call = _only(text) assert call == {"name": "web_search", "arguments": {"query": "weather london"}} def test_closed_function_with_trailing_whitespace_is_accepted(self): text = "cats \n\n" call = _only(text) assert call == {"name": "web_search", "arguments": {"query": "cats"}} def test_closed_function_without_trailing_text_still_parses(self): text = "cats" call = _only(text) assert call == {"name": "web_search", "arguments": {"query": "cats"}} def test_multi_param_with_trailing_prose(self): text = ( "ls -la" "home running it now" ) call = _only(text) assert call == { "name": "terminal", "arguments": {"command": "ls -la", "workdir": "home"}, } def test_code_value_containing_literal_close_tag_is_preserved(self): # The real closing is the last one; the literal inside # the code argument must survive (rfind, not the first match). text = ( "" 'print("")' " all done" ) call = _only(text) assert call == {"name": "python", "arguments": {"code": 'print("")'}} def test_closed_function_with_trailing_prose_heal_path(self): # Regression: the heal path (allow_incomplete=True) must match the strict path -- # keep a clean argument and leave trailing prose outside the call span. text = "cats trailing words" calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(calls) == 1 fn = calls[0]["function"] assert fn["name"] == "web_search" assert json.loads(fn["arguments"]) == {"query": "cats"} # The trailing prose sits outside the removed span, so it stays visible. from core.tool_healing import ( parse_tool_calls_from_text as _parse_with_spans, ) _calls, spans = _parse_with_spans(text, allow_incomplete = True, with_spans = True) out = text for s, e in sorted(spans, reverse = True): out = out[:s] + out[e:] assert out == " trailing words" def test_incomplete_function_without_close_is_still_rejected(self): text = "weather london" assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] def test_param_without_close_tag_is_rejected_in_strict_mode(self): # Closing present, but the single parameter never closes. text = "weather london" assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] def test_attribute_form_literal_close_tag_is_preserved(self): # The attribute form (MiniCPM-5 / MiniMax-M2) also ends at the # LAST , so a literal close tag inside a code argument survives. text = ( '' 'print("")' " all done" ) call = _only(text) assert call == {"name": "python", "arguments": {"code": 'print("")'}} def test_closed_zero_param_attribute_call_is_accepted_in_strict_mode(self): # A closed call with no parameters is a valid zero-argument call; strict # mode must not treat the empty parameter list as a truncated call. assert _only('') == {"name": "ping", "arguments": {}} # A no-arg call that never closes is still rejected as truncated. assert parse_tool_calls_from_text('', allow_incomplete = False) == [] class TestParityWithJsonStyle: def test_json_tool_call_with_trailing_prose_is_accepted(self): text = ( '{"name":"web_search","arguments":{"query":"weather london"}}' " Let me check that for you." ) calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1 assert calls[0]["function"]["name"] == "web_search" def test_function_and_json_styles_agree_on_trailing_text(self): q = "weather london" func = parse_tool_calls_from_text( f"{q} trailing", allow_incomplete = False, ) js = parse_tool_calls_from_text( f'{{"name":"web_search","arguments":{{"query":"{q}"}}}} trailing', allow_incomplete = False, ) assert len(func) == len(js) == 1 assert json.loads(func[0]["function"]["arguments"]) == {"query": q} assert json.loads(js[0]["function"]["arguments"]) == {"query": q} class TestGemmaNativeStyle: def test_closed_native_call_with_trailing_prose_is_accepted(self): text = ( '<|tool_call>call:terminal{command:"ls -la",workdir:"."}' " running it now" ) calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1 assert calls[0]["function"]["name"] == "terminal" assert json.loads(calls[0]["function"]["arguments"]) == { "command": "ls -la", "workdir": ".", } def test_unclosed_native_call_requires_healing(self): text = '<|tool_call>call:terminal{command:"ls"}' assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(calls) == 1 assert calls[0]["function"]["name"] == "terminal" def test_hyphenated_native_argument_name_is_accepted(self): text = '<|tool_call>call:mcp__srv__create-issue{issue-title:"Bug report"}' calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1 assert calls[0]["function"]["name"] == "mcp__srv__create-issue" assert json.loads(calls[0]["function"]["arguments"]) == {"issue-title": "Bug report"} def test_native_template_quotes_preserve_windows_path(self): text = r'<|tool_call>call:ls{path:<|"|>C:\Users\wasim\repo<|"|>}' calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1 assert json.loads(calls[0]["function"]["arguments"]) == {"path": r"C:\Users\wasim\repo"} def test_bare_unquoted_string_values_are_accepted(self): # Gemma can emit enum/string args unquoted; bare JSON scalars stay typed. text = ( "<|tool_call>call:get_weather{location:Tokyo,unit:celsius,days:3,live:true}" ) calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1 assert json.loads(calls[0]["function"]["arguments"]) == { "location": "Tokyo", "unit": "celsius", "days": 3, "live": True, } class TestLlama3PythonTagStrict: def test_closed_dot_call_is_accepted(self): text = '<|python_tag|>get_weather.call(location="Tokyo")' calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1 assert calls[0]["function"]["name"] == "get_weather" assert json.loads(calls[0]["function"]["arguments"]) == {"location": "Tokyo"} def test_truncated_dot_call_is_rejected(self): # No closing paren (depth > 0 at EOF): truncated, reject in strict mode. text = '<|python_tag|>get_weather.call(location="Tokyo"' assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] # Auto-Heal still recovers it. assert len(parse_tool_calls_from_text(text, allow_incomplete = True)) == 1 class TestMistralArrayStrict: def test_closed_array_is_accepted(self): text = '[TOOL_CALLS] [{"name":"web_search","arguments":{"q":"x"}}]' calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1 assert calls[0]["function"]["name"] == "web_search" def test_unclosed_array_is_rejected(self): # Missing the closing ]; strict mode must not heal it. text = '[TOOL_CALLS] [{"name":"web_search","arguments":{"q":"x"}}' assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] # Auto-Heal still recovers the object by hand. assert len(parse_tool_calls_from_text(text, allow_incomplete = True)) == 1 class TestHealingPathUnaffected: def test_auto_heal_still_repairs_unclosed_function(self): text = "cats" calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(calls) == 1 assert calls[0]["function"]["name"] == "web_search" def test_closed_function_call_keeps_trailing_prose_out_of_arguments(self): # A call that DID close must parse identically to strict mode, leaving prose after # out of the last parameter and the removal span. from core.tool_healing import parse_tool_calls_from_text as parse_with_spans text = "cats trailing" calls, spans = parse_with_spans(text, allow_incomplete = True, with_spans = True) (call,) = calls assert json.loads(call["function"]["arguments"]) == {"query": "cats"} (span,) = spans assert text[span[0] : span[1]] == ( "cats" ) def test_wrapperless_fallback_calls_carry_spans(self): # The wrapperless function-XML fallback must report spans too, so with_spans # consumers strip exactly the promoted markup (through when closed). from core.tool_healing import parse_tool_calls_from_text as parse_with_spans closed = "before cats after" calls, spans = parse_with_spans(closed, allow_incomplete = True, with_spans = True) (call,) = calls assert json.loads(call["function"]["arguments"]) == {"query": "cats"} (span,) = spans assert closed[span[0] : span[1]] == ( "cats" ) healed = "x dogs" calls, spans = parse_with_spans(healed, allow_incomplete = True, with_spans = True) (call,) = calls assert json.loads(call["function"]["arguments"]) == {"query": "dogs"} (span,) = spans assert healed[span[0] : span[1]] == "dogs" class TestEnabledToolNameGate: """``enabled_tool_names`` disambiguates the ambiguous bare-rehearsal ``NAME[ARGS]{json}`` form (#5704): NAME is a call only when it is an active tool, otherwise it is prose. ``None`` (the default) keeps the legacy unrestricted parse so existing callers are unaffected.""" def _names(self, calls): return [c["function"]["name"] for c in calls] def test_inactive_rehearsal_before_active_call_does_not_swallow_it(self): # P1: an inactive ``foo[ARGS]{...}`` before a real call must not consume the real call. text = 'foo[ARGS]{"a":1} web_search[ARGS]{"query":"cats"}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert self._names(calls) == ["web_search"] assert json.loads(calls[0]["function"]["arguments"]) == {"query": "cats"} def test_inactive_rehearsal_alone_is_not_a_call(self): text = 'foo[ARGS]{"a":1}' assert parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) == [] def test_active_rehearsal_is_still_parsed(self): text = 'web_search[ARGS]{"query":"cats"}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert self._names(calls) == ["web_search"] def test_unrestricted_gate_none_preserves_legacy_behavior(self): # Without a gate every ``NAME[ARGS]{...}`` is parsed, as before the gate landed. text = 'foo[ARGS]{"a":1} web_search[ARGS]{"query":"cats"}' assert self._names(parse_tool_calls_from_text(text)) == ["foo", "web_search"] assert self._names(parse_tool_calls_from_text(text, enabled_tool_names = None)) == [ "foo", "web_search", ] class TestBracketCallSpans: """with_spans tiling for Mistral bracket calls: promoted markup strips exactly once, filtered calls' bytes stay visible, closers strip too.""" def test_mixed_array_filtered_first_keeps_its_bytes_only(self): from core.inference.passthrough_healing import heal_openai_message_events tools = [{"type": "function", "function": {"name": "lookup", "parameters": {}}}] content = ( '[TOOL_CALLS][{"name":"bad","arguments":{"x":1}},' '{"name":"lookup","arguments":{"q":"cats"}}]' ) events = heal_openai_message_events( {"role": "assistant", "content": content}, {"lookup"}, tools ) kinds = [k for k, _v in events] assert kinds == ["text", "tool_call"] text = events[0][1] assert '"bad"' in text # The promoted call's markup must not survive in the text event. assert '"lookup"' not in text def test_mixed_array_filtered_second_stays_visible(self): from core.inference.passthrough_healing import heal_openai_message_events tools = [{"type": "function", "function": {"name": "lookup", "parameters": {}}}] content = ( '[TOOL_CALLS][{"name":"lookup","arguments":{"q":"cats"}},' '{"name":"bad","arguments":{"x":1}}]' ) events = heal_openai_message_events( {"role": "assistant", "content": content}, {"lookup"}, tools ) assert events[0][0] == "tool_call" trailing = "".join(v for k, v in events if k == "text") assert '"bad"' in trailing def test_v11_closer_inside_span(self): from core.tool_healing import parse_tool_calls_from_text as parse_with_spans text = '[TOOL_CALLS]web_search[ARGS]{"query":"cats"}[/TOOL_CALLS] after' calls, spans = parse_with_spans(text, allow_incomplete = True, with_spans = True) (call,) = calls assert call["function"]["name"] == "web_search" (span,) = spans assert text[span[0] : span[1]].endswith("[/TOOL_CALLS]") assert text[span[1] :] == " after" def test_fully_promoted_array_strips_whole_region(self): from core.inference.passthrough_healing import heal_openai_message_events tools = [{"type": "function", "function": {"name": "lookup", "parameters": {}}}] content = ( '[TOOL_CALLS][{"name":"lookup","arguments":{"q":"a"}},' '{"name":"lookup","arguments":{"q":"b"}}] after' ) events = heal_openai_message_events( {"role": "assistant", "content": content}, {"lookup"}, tools ) assert [k for k, _v in events] == ["tool_call", "tool_call", "text"] assert events[2][1] == " after" class TestMistralArrayHealing: """Draining the whole [TOOL_CALLS] array for the shapes the repo's own Mistral/Ollama templates emit.""" def test_comma_less_multi_call_array_parses_all_calls(self): # ollama_template_mappers.py renders multi-call turns as [{...}{...}] with no # comma separator; a single json.loads of the body rejects it and dropped every # call. The element-by-element decode must recover all of them. text = '[TOOL_CALLS] [{"name":"a","arguments":{"x":1}}{"name":"b","arguments":{"y":2}}]' calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["a", "b"] assert json.loads(calls[0]["function"]["arguments"]) == {"x": 1} assert json.loads(calls[1]["function"]["arguments"]) == {"y": 2} def test_comma_separated_and_single_arrays_still_parse(self): both = parse_tool_calls_from_text( '[TOOL_CALLS] [{"name":"a","arguments":{}},{"name":"b","arguments":{}}]' ) assert [c["function"]["name"] for c in both] == ["a", "b"] one = parse_tool_calls_from_text('[TOOL_CALLS] [{"name":"a","arguments":{}}]') assert [c["function"]["name"] for c in one] == ["a"] def test_mistral_array_null_arguments_normalized_to_empty_object(self): # ``"arguments": null`` is a no-arg call; it must become {} (as the # path does), not the string "null" that auto-heal turns into {"query":"null"}. calls = parse_tool_calls_from_text('[TOOL_CALLS][{"name":"get_time","arguments":null}]') assert calls[0]["function"]["arguments"] == "{}" class TestGlmStrict: def test_closed_glm_call_is_accepted(self): text = ( "get_weather\n" "city\nParis\n" "" ) calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1 assert calls[0]["function"]["name"] == "get_weather" def test_unclosed_glm_call_is_rejected(self): # No close: truncated, reject with Auto-Heal off. text = "get_weather\ncity\nParis" assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] assert len(parse_tool_calls_from_text(text, allow_incomplete = True)) == 1 class TestKimiStrict: _SB = "<|tool_calls_section_begin|>" _KB = "<|tool_call_begin|>" _AB = "<|tool_call_argument_begin|>" _KE = "<|tool_call_end|>" _SE = "<|tool_calls_section_end|>" def test_full_kimi_call_is_accepted(self): text = self._SB + self._KB + "functions.x:0" + self._AB + '{"a":1}' + self._KE + self._SE calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1 assert calls[0]["function"]["name"] == "x" def test_kimi_call_without_call_end_is_rejected(self): # Section closed but the call lacks <|tool_call_end|>: reject in strict. text = self._SB + self._KB + "functions.x:0" + self._AB + '{"a":1}' + self._SE assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] assert len(parse_tool_calls_from_text(text, allow_incomplete = True)) == 1 def test_kimi_without_section_end_is_rejected(self): # No <|tool_calls_section_end|>: truncated section, reject in strict. text = self._SB + self._KB + "functions.x:0" + self._AB + '{"a":1}' + self._KE assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] assert len(parse_tool_calls_from_text(text, allow_incomplete = True)) == 1 class TestParserLinearity: """Llama-3 ``.call`` kwargs and Mistral-array healing must stay linear (a regex-per-offset blew up on long truncated bodies).""" def test_llama3_unterminated_call_arg_is_linear(self): import time text = '<|python_tag|>upload.call(data="' + "A" * 200_000 # no closing quote/paren t0 = time.perf_counter() parse_tool_calls_from_text(text, allow_incomplete = True) assert time.perf_counter() - t0 < 2.0 def test_llama3_huge_wordrun_call_arg_is_linear(self): import time text = "<|python_tag|>upload.call(" + "a" * 200_000 # giant word run, no '=' t0 = time.perf_counter() parse_tool_calls_from_text(text, allow_incomplete = True) assert time.perf_counter() - t0 < 2.0 def test_mistral_unclosed_array_open_braces_is_linear(self): import time text = "[TOOL_CALLS] [" + "{" * 200_000 # unclosed array, all open braces t0 = time.perf_counter() parse_tool_calls_from_text(text, allow_incomplete = True) assert time.perf_counter() - t0 < 2.0 def test_gemma_wrapperless_deep_nesting_is_linear(self): # Wrapper-less Gemma ``call:f{a:{a:{...}}}`` deep nesting must parse in linear time (no quadratic re-scan). import time def nested(d): return "call:f{a:" + "{a:" * d + "x:1" + "}" * d + "}" def best_ms(depth): text = nested(depth) best = float("inf") for _ in range(5): t0 = time.perf_counter() calls = parse_tool_calls_from_text(text) best = min(best, time.perf_counter() - t0) assert calls and json.loads(calls[0]["function"]["arguments"]), "nested args dropped" return best t200 = best_ms(200) t400 = best_ms(400) assert t400 < t200 * 3.0, (t200, t400) def test_llama3_call_kwargs_still_parse(self): text = '<|python_tag|>do.call(s="hi 😀", n=42, f=1.5, b=true, z=null)' calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(calls) == 1 assert json.loads(calls[0]["function"]["arguments"]) == { "s": "hi 😀", "n": 42, "f": 1.5, "b": True, "z": None, } def test_llama3_call_scientific_notation_args_parse(self): # Scientific notation must decode as float (the old regex truncated 1e-3 -> 1). text = "<|python_tag|>calc.call(x=1e-3, y=-2E+4, z=0.5e2, n=42)" calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args == {"x": 1e-3, "y": -2e4, "z": 50.0, "n": 42} assert isinstance(args["n"], int) and isinstance(args["x"], float) def test_mistral_unclosed_array_recovers_top_level_objects(self): text = ( '[TOOL_CALLS] [{"name":"a","arguments":{"k":1}},' '{"name":"b","arguments":{"j":2}}' # missing closing ] ) calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert [c["function"]["name"] for c in calls] == ["a", "b"] class TestLlamaBuiltinChainAndNesting: """Llama-3 ``.call`` built-ins: ``; `` chaining and nested-tag isolation.""" def test_semicolon_chained_builtin_calls_all_parse(self): # Only the first call is anchored to <|python_tag|>; the rest chain via ';'. text = "<|python_tag|>alpha.call(x=1); beta.call(y=2); gamma.call(z=3)" calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert [c["function"]["name"] for c in calls] == ["alpha", "beta", "gamma"] assert json.loads(calls[1]["function"]["arguments"]) == {"y": 2} def test_nested_python_tag_in_json_string_arg_is_not_a_call(self): # A code arg literally containing a <|python_tag|>...call(...) string: the real call is the # outer "python", not the nested "os" -- the scan stays anchored to the first tag. text = ( '<|python_tag|>{"name":"python","parameters":' '{"code":"<|python_tag|>os.call(\'rm -rf /\')"}}' ) calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(calls) == 1 assert calls[0]["function"]["name"] == "python" args = json.loads(calls[0]["function"]["arguments"]) assert args["code"] == "<|python_tag|>os.call('rm -rf /')" def test_single_builtin_call_unchanged(self): text = '<|python_tag|>web_search.call(query="cats")' calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(calls) == 1 assert calls[0]["function"]["name"] == "web_search" assert json.loads(calls[0]["function"]["arguments"]) == {"query": "cats"} def test_glm_open_does_not_parse_spaced_prose_as_tool_name(): # The GLM NAME opener must reject spaced literal prose (V10); only a # valid [\w.\-]+ name (followed by newline//) is a call. assert parse_tool_calls_from_text("not a call") == [] ok = parse_tool_calls_from_text( "get_weather\ncity\nNYC\n" ) assert [c["function"]["name"] for c in ok] == ["get_weather"] def test_deepseek_r1_missing_call_terminator_rejected_in_strict_mode(): # R1 must reject a fenced call whose closing ``` + <|tool▁call▁end|> never # arrived when Auto-Heal is off, matching V3/V3.1 strictness (V6). text = ( "<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>get_weather\n" "```json\n" '{"city":"NYC"}' "<|tool▁calls▁end|>" ) assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] assert len(parse_tool_calls_from_text(text, allow_incomplete = True)) == 1 def test_deepseek_r1_complete_call_accepted_in_strict_mode(): # A fully-terminated R1 call (close fence + per-call end) is still accepted. text = ( "<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>get_weather\n" "```json\n" '{"city":"NYC"}\n' "```<|tool▁call▁end|><|tool▁calls▁end|>" ) calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1 and calls[0]["function"]["name"] == "get_weather" def test_strip_leading_bare_json_call_drops_complete_call(): from core.inference.tool_call_parser import strip_leading_bare_json_call # A complete Llama-3.2 bare-JSON call is removed; trailing prose is kept. assert strip_leading_bare_json_call('{"name":"web_search","parameters":{"query":"cats"}}') == "" assert ( strip_leading_bare_json_call('{"name":"python","parameters":{"code":"x"}} done') == "done" ) def test_strip_leading_bare_json_call_drops_truncated_call(): from core.inference.tool_call_parser import strip_leading_bare_json_call # A truncated call (no closing brace) collapses to "" -- nothing recoverable. assert ( strip_leading_bare_json_call('{"name":"web_search","parameters":{"query":"weather in S') == "" ) def test_strip_leading_bare_json_call_preserves_plain_json_and_prose(): from core.inference.tool_call_parser import strip_leading_bare_json_call # No "name" key -> plain JSON answer, left untouched. assert ( strip_leading_bare_json_call('{"result": 42, "ok": true}') == '{"result": 42, "ok": true}' ) # Prose before the brace -> not a leading bare call, untouched. assert strip_leading_bare_json_call('here is {"name":"x"}') == 'here is {"name":"x"}' # Ordinary text untouched. assert strip_leading_bare_json_call("just a sentence.") == "just a sentence." def test_glm_literal_close_tag_in_string_arg_not_truncated(): import json from core.inference.tool_call_parser import parse_tool_calls_from_text # A GLM string argument may legitimately contain the literal close tag ````. text = ( "run_code\n" "code\n" 'print("")\n' "" ) calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args["code"] == 'print("")', args def test_glm_truncated_block_rejected_in_strict_mode_but_healed_otherwise(): from core.inference.tool_call_parser import parse_tool_calls_from_text # No close: strict mode (Auto-Heal off) rejects the truncated # block; with Auto-Heal it keeps the partial call. text = "get_weather\ncity\nNYC" assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] healed = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(healed) == 1 and healed[0]["function"]["name"] == "get_weather" def test_truncated_wrapperless_gemma_call_is_stripped(): from core.inference.tool_call_parser import strip_tool_markup # A wrapper-less Gemma ``call:NAME{...`` cut off mid-arguments (no closing # brace) must not leak the raw call into the visible stream. text = 'Sure!\ncall:web_search{"query": "weather in San Fr' stripped = strip_tool_markup(text, final = True) assert "call:web_search" not in stripped, repr(stripped) assert stripped.strip() == "Sure!" def test_complete_wrapperless_gemma_call_keeps_trailing_prose(): from core.inference.tool_call_parser import strip_tool_markup # The truncation pattern must run AFTER the closed form, so a complete call # followed by prose keeps the prose instead of eating to EOS. text = 'call:web_search{"query": "cats"} Here you go.' stripped = strip_tool_markup(text, final = True) assert "call:web_search" not in stripped assert stripped.strip() == "Here you go." def test_bare_json_gated_on_enabled_tool_names(): from core.inference.tool_call_parser import parse_tool_calls_from_text alice = '{"name":"Alice","parameters":{"age":30}}' real = '{"name":"web_search","parameters":{"query":"cats"}}' # With an enabled set, markerless JSON whose name is not a tool is NOT a call. assert parse_tool_calls_from_text(alice, enabled_tool_names = {"web_search"}) == [] # A real call (enabled name) still parses. got = parse_tool_calls_from_text(real, enabled_tool_names = {"web_search"}) assert [c["function"]["name"] for c in got] == ["web_search"] # No enabled set (None) keeps the name-agnostic behaviour for direct callers. assert [c["function"]["name"] for c in parse_tool_calls_from_text(alice)] == ["Alice"] # Marker-based forms are NOT gated (an explicit signal is a real call attempt). xml = '{"name":"Alice","arguments":{}}' assert parse_tool_calls_from_text(xml, enabled_tool_names = {"web_search"}) def test_strip_leading_bare_json_call_gated_on_enabled_tool_names(): from core.inference.tool_call_parser import strip_leading_bare_json_call alice = '{"name":"Alice","parameters":{"age":30}}' # Not an enabled tool -> ordinary JSON answer, kept verbatim. assert strip_leading_bare_json_call(alice, {"web_search"}) == alice # Enabled tool -> a real call, stripped (trailing prose kept). assert ( strip_leading_bare_json_call( '{"name":"web_search","parameters":{"q":1}} hi', {"web_search"} ) == "hi" ) def test_function_xml_strip_keeps_literal_close_tag_in_param_value(): from core.inference.tool_call_parser import strip_tool_markup # The strip uses the LAST (like the parser) so a literal in a value doesn't # truncate it; separate calls still strip independently. text = 'print("") done' assert strip_tool_markup(text, final = True) == "done" two = ( "a 1 mid " "2 end" ) assert strip_tool_markup(two, final = True) == "a mid end" def test_function_xml_strip_keeps_trailing_text_after_literal_open_tag(): from core.inference.tool_call_parser import parse_tool_calls_from_text, strip_tool_markup # A literal ```` opener inside a parameter value is data, not a call: the scan-based # strip keeps " done" (the old negative-lookahead regex ate the trailing prose). text = 'print("") done' assert parse_tool_calls_from_text(text)[0]["function"]["name"] == "python" assert strip_tool_markup(text, final = True) == "done" # Non-final (streaming) keeps an unclosed call buffered, does not eat prose early. open_text = 'pre print("")' assert strip_tool_markup(open_text, final = False) == open_text def test_final_strip_removes_magistral_think_reasoning(): from core.inference.tool_call_parser import strip_tool_markup # Magistral emits reasoning as ``[THINK]...[/THINK]`` (bracket form, not ````); # at end-of-turn it must be dropped so it doesn't leak into display / history. text = "[THINK]The user greeted me, I should say hi.[/THINK]Hello! How can I help?" assert strip_tool_markup(text, final = True) == "Hello! How can I help?" # A ``[TOOL_CALLS]`` living inside the reasoning goes with it. with_call = '[THINK]Maybe I should search.[/THINK][TOOL_CALLS]search{"q":"x"}' assert strip_tool_markup(with_call, final = True) == "" def test_streaming_strip_keeps_magistral_think_buffered(): from core.inference.tool_call_parser import strip_tool_markup # Mid-stream (final=False) the reasoning block is left intact; only the # end-of-turn pass removes it. text = "[THINK]still thinking" assert strip_tool_markup(text, final = False) == text def test_final_strip_leaves_non_magistral_bracket_text_untouched(): from core.inference.tool_call_parser import strip_tool_markup # Only a LEADING ``[THINK]`` block is reasoning; unrelated bracketed prose stays. text = "See [THINK about it] later" assert strip_tool_markup(text, final = True) == "See [THINK about it] later" def test_strip_leading_bare_json_call_ignores_nested_name(): from core.inference.tool_call_parser import strip_leading_bare_json_call # A nested ``"name"`` must NOT gate the strip (only a TOP-LEVEL enabled name is a call); the # ordinary JSON answer is kept verbatim, truncated or complete. nested_trunc = '{"result":{"name":"web_search","age":' nested_full = '{"result":{"name":"web_search","age":1}}' assert strip_leading_bare_json_call(nested_trunc, {"web_search"}) == nested_trunc assert strip_leading_bare_json_call(nested_full, {"web_search"}) == nested_full # A real top-level call (even with a top-level array before the name) still strips. assert ( strip_leading_bare_json_call( '{"data":[1,2],"name":"web_search","parameters":{}}', {"web_search"} ) == "" ) def test_mistral_single_object_call_is_stripped_for_display(): from core.inference.tool_call_parser import ( _strip_mistral_closed_calls, parse_tool_calls_from_text, ) # The parser accepts the single-object [TOOL_CALLS]{...} shape, so the display # strip must remove it too (asymmetry would leak the raw object). text = '[TOOL_CALLS]{"name":"web_search","arguments":{"filters":{"date":"2024"}}} tail' assert [c["function"]["name"] for c in parse_tool_calls_from_text(text)] == ["web_search"] assert _strip_mistral_closed_calls(text) == " tail" # A literal [TOOL_CALLS] in prose (no following object) is left untouched. assert _strip_mistral_closed_calls("See the [TOOL_CALLS] docs") == "See the [TOOL_CALLS] docs" def test_tool_call_parser_declares_future_annotations_for_py39_import(): # F1: the parser is imported standalone on python >=3.9, where its PEP 604 ``X | None`` # annotations need ``from __future__ import annotations``; guard that the import stays. from pathlib import Path src = ( Path(__file__).resolve().parent.parent / "core" / "inference" / "tool_call_parser.py" ).read_text() assert "from __future__ import annotations" in src def test_glm_strip_treats_literal_close_tag_in_arg_value_as_data(): # Core strip parity: a literal inside a GLM is argument data, so the whole call is stripped (no leaked tail). from core.inference.tool_call_parser import strip_tool_markup text = ( "web_search\nquery\n" "see tag\n tail" ) assert strip_tool_markup(text, final = True) == "tail" calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] assert json.loads(calls[0]["function"]["arguments"]) == {"query": "see tag"} def test_bare_json_function_alias_parses_and_strips_symmetrically(): # The bare-JSON parser accepts the "function" alias for the call name; # strip_leading_bare_json_call must recognise it too (parser/strip symmetry). from core.inference.tool_call_parser import ( parse_tool_calls_from_text, strip_leading_bare_json_call, _top_level_bare_json_name, ) enabled = {"web_search"} text = '{"function":"web_search","parameters":{"query":"cats"}}' calls = parse_tool_calls_from_text(text, enabled_tool_names = enabled) assert [c["function"]["name"] for c in calls] == ["web_search"] assert strip_leading_bare_json_call(text, enabled) == "" # "name" still takes precedence when both are present; nested aliases are data. assert _top_level_bare_json_name('{"function":"foo","name":"web_search"}') == "web_search" assert _top_level_bare_json_name('{"function":"web_search"}') == "web_search" assert _top_level_bare_json_name('{"result":{"function":"web_search"}}') is None # A non-enabled function-alias object is ordinary content and is preserved. assert ( strip_leading_bare_json_call('{"function":"not_a_tool","parameters":{}}', enabled) == '{"function":"not_a_tool","parameters":{}}' ) class TestMistralOuterOverXmlLiteral: """Quoted tool XML inside a [TOOL_CALLS] call's arguments is data; the outer call executes. Reverse order keeps the XML.""" def test_mistral_v11_arg_quoting_function_xml(self): text = ( '[TOOL_CALLS]web_search[ARGS]{"query":"literal ' '1"}' ) for strict in (True, False): calls = parse_tool_calls_from_text(text, allow_incomplete = not strict) assert [c["function"]["name"] for c in calls] == ["web_search"] assert "" in json.loads(calls[0]["function"]["arguments"])["query"] def test_mistral_array_arg_quoting_tool_call_json(self): text = ( '[TOOL_CALLS][{"name":"web_search","arguments":{"query":' '"see {\\"name\\":\\"evil\\"}"}}]' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_xml_outer_keeps_winning_over_mistral_literal(self): text = ( '{"name":"web_search","arguments":' '{"query":"docs say [TOOL_CALLS]evil[ARGS]{}"}}' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] class TestHealerSignalAlignment: """The healer buffers only formats its shared parser can promote. Mistral's ``[TOOL_CALLS]`` is promotable (rescued), so it is a heal signal; the loop-only text-call markers (Llama ``<|python_tag|>``, bare ``[ARGS]``) are not, so they stream through instead of stalling as prose that never yields a call.""" def test_heal_signals_subset_of_promotable_formats(self): from core.inference.passthrough_healing import _HEAL_SIGNALS assert set(_HEAL_SIGNALS) == {"", "<|tool_call>", " is not a healer-promotable format, so it streams through as text. events = list(healer.feed('<|python_tag|>web_search.call(query="cats")')) text_out = "".join(v for k, v in events if k == "text") assert "<|python_tag|>" in text_out # streamed through, not buffered assert not list(healer.finalize()) or all(k == "text" for k, _v in healer.finalize()) class TestGemmaWrapperlessLiteralMarkers: """Wrapper-less Gemma calls whose ARGUMENTS mention Gemma's own markup. The tool_healing deferral must key on an actual wrapped opener (``<|tool_call>call:...``), not the wrapper literal anywhere in content: a query about the marker has nothing tool_healing can parse, and deferring it loses the call entirely (not executed AND stripped from display).""" def test_marker_literal_in_argument_still_parses(self): text = 'call:web_search{query:"what does <|tool_call> mean"}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args["query"] == "what does <|tool_call> mean" def test_real_wrapped_call_still_deferred_to_tool_healing(self): from core.inference.tool_call_parser import _parse_gemma_tool_calls # An actual wrapped opener present: the Gemma fallback must keep # deferring to the shared tool_healing parser that owns that form. text = '<|tool_call>call:web_search{query:<|"|>cats<|"|>}' assert _parse_gemma_tool_calls(text, id_offset = 0) == [] def test_single_quoted_brace_does_not_truncate_code(self): text = "call:python{code:print('}')}" calls = parse_tool_calls_from_text(text, enabled_tool_names = {"python"}) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args["code"] == "print('}')" def test_single_quoted_brace_strip_span_covers_whole_call(self): from core.inference.tool_call_parser import strip_tool_markup text = "call:python{code:print('}')} Done." stripped = strip_tool_markup(text, final = True, enabled_tool_names = {"python"}) assert "call:python" not in stripped assert "')}" not in stripped assert stripped.strip() == "Done." class TestGlmEmbeddedClosePair: """A GLM value whose string literal embeds the full close-tag pair ```` (code documenting the GLM format) must not be truncated at the embedded pair: a structural close sits at balanced quote state, an embedded one is inside an open string literal.""" def test_embedded_pair_inside_quoted_value_not_structural(self): text = ( "python\n" "code\n" 'print("")\nx = 1\n' "" ) calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args["code"] == 'print("")\nx = 1' def test_strip_covers_the_full_call(self): from core.inference.tool_call_parser import strip_tool_markup text = ( "python\n" "code\n" 'print("")\nx = 1\n' " Done." ) stripped = strip_tool_markup(text, final = True) assert "arg_value" not in stripped assert stripped.strip() == "Done." def test_unbalanced_apostrophe_falls_back_to_first_candidate(self): # Prose-like value with an apostrophe: no candidate reaches balanced # quote state, so the first token-valid close wins (prior behavior). text = ( "web_search\n" "query\n" "it's fine\n" "" ) calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args["query"] == "it's fine" class TestPythonTagLiteralInsideMistralArgs: """A python_tag LITERAL inside a leading Mistral call's arguments is data; the outer call executes.""" def test_mistral_arg_quoting_python_tag_call(self): text = ( '[TOOL_CALLS] [{"name": "web_search", "arguments": ' '{"query": "what is <|python_tag|>evil.call(x=1)"}}]' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] args = json.loads(calls[0]["function"]["arguments"]) assert args["query"] == "what is <|python_tag|>evil.call(x=1)" class TestPythonTagOuterOverXmlLiteral: """A leading Llama-3 ``<|python_tag|>`` call owns the turn: tool XML/Mistral markup quoted in a ``.call(...)`` string argument (or in trailing prose) is data, so the outer call executes -- parity with the bare-JSON / Mistral / attribute-form leading-ownership rules. XML before the tag keeps normal order.""" def test_call_arg_quoting_complete_function_xml(self): # A closed in a .call() code arg must not beat the leading python_tag call. text = ( '<|python_tag|>python.call(code="' '1")' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["python"] args = json.loads(calls[0]["function"]["arguments"]) assert args["code"] == "1" def test_call_arg_quoting_bare_function_tag_in_query(self): # A query mentioning must search, not execute a phantom tool. text = '<|python_tag|>web_search.call(query="how do I use in llama")' calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] args = json.loads(calls[0]["function"]["arguments"]) assert args["query"] == "how do I use in llama" def test_call_arg_quoting_tool_call_json(self): text = ( "<|python_tag|>save_file.call(content=" '"{\\"name\\": \\"delete\\", \\"arguments\\": {}}")' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["save_file"] def test_json_form_code_arg_quoting_function_xml(self): # JSON emission: a in the code arg is data; the outer "python" call runs. text = ( '<|python_tag|>{"name":"python","parameters":' '{"code":"ls"}}' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["python"] args = json.loads(calls[0]["function"]["arguments"]) assert args["code"] == "ls" def test_call_arg_quoting_mistral_trigger(self): text = '<|python_tag|>web_search.call(query="see [TOOL_CALLS]evil[ARGS]{}")' calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_leading_call_wins_over_trailing_xml(self): # A leading python_tag call owns the turn even when a real XML literal follows. text = ( '<|python_tag|>web_search.call(query="cats") ' "1" ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_xml_before_python_tag_keeps_xml_order(self): # A foreign signal BEFORE the tag keeps normal document order (XML wins). text = ( "x " '<|python_tag|>python.call(code="y")' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] class TestBareJsonOuterOverXmlLiteral: """Quoted tool XML inside a leading bare-JSON call is data; XML before the JSON keeps normal order.""" def test_bare_json_code_arg_quoting_function_xml(self): text = ( '{"name": "python", "arguments": ' '{"code": "run() # ls"}}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"python"}) assert [c["function"]["name"] for c in calls] == ["python"] args = json.loads(calls[0]["function"]["arguments"]) assert args["code"] == "run() # ls" def test_bare_json_outer_unrestricted_mode(self): text = '{"name": "python", "parameters": {"code": "ls"}}' calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["python"] def test_xml_before_json_keeps_xml_order(self): text = ( "cats" ' {"name": "python", "arguments": {"code": "x"}}' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] class TestMagistralThinkRehearsal: """A call rehearsed inside [THINK]...[/THINK] is reasoning; the real call after wins, and parse agrees with strip.""" def test_function_xml_rehearsal_in_think_is_not_promoted(self): text = ( '[THINK]I could emit {"query":"x"}' ' here[/THINK][TOOL_CALLS] [{"name":"terminal","arguments":{"cmd":"ls"}}]' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["terminal"] def test_hermes_rehearsal_in_think_is_not_promoted(self): text = ( '[THINK]maybe {"name":"web_search","arguments":' '{"query":"x"}}[/THINK]' '[TOOL_CALLS] [{"name":"terminal","arguments":{"cmd":"ls"}}]' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["terminal"] def test_unclosed_think_parses_nothing(self): text = '[THINK]let me try {"query":"x"}' assert parse_tool_calls_from_text(text) == [] class TestGemmaUnquotedApostrophes: """Quotes open strings only at value-start context: an apostrophe inside an unquoted wrapper-less value (contractions, possessives) is prose, and treating it as an opener swallowed the closing brace and lost the call.""" def test_contraction_in_unquoted_query_parses(self): text = "call:web_search{query:what's the weather}" calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args["query"] == "what's the weather" def test_contraction_does_not_swallow_next_key(self): text = "call:web_search{query:what's up, n:3}" calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args["query"] == "what's up" assert args["n"] == 3 def test_contraction_strip_span_covers_whole_call(self): from core.inference.tool_call_parser import strip_tool_markup text = "call:web_search{query:what's the weather} Done." stripped = strip_tool_markup(text, final = True, enabled_tool_names = {"web_search"}) assert "call:web_search" not in stripped assert stripped.strip() == "Done." def test_quoted_values_still_hide_delimiters(self): text = 'call:web_search{query:"weather, location: Boston", n:2}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args["query"] == "weather, location: Boston" assert args["n"] == 2 class TestGlmKeyWithoutValue: """A GLM with no tag: strict mode rejects the call (same contract as an unclosed value) instead of executing it with the argument silently dropped; Auto-Heal keeps the lenient skip.""" def test_strict_rejects_key_without_value(self): text = "web_search\nquery\n" assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] def test_heal_keeps_the_lenient_skip(self): text = "web_search\nquery\n" calls = parse_tool_calls_from_text(text, allow_incomplete = True) assert len(calls) == 1 assert calls[0]["function"]["name"] == "web_search" assert json.loads(calls[0]["function"]["arguments"]) == {} class TestDisabledBareJsonLiteralNotPromoted: """A leading non-enabled-name object is content: nothing inside promotes, and a call after it still parses.""" def test_literal_inside_disabled_json_stays_data(self): text = ( '{"name": "Alice", "note": "try ' 'x"}' ) assert parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) == [] def test_python_tag_literal_inside_disabled_json_stays_data(self): text = '{"name": "Alice", "note": "<|python_tag|>web_search.call(query=1)"}' assert parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) == [] def test_real_call_after_disabled_json_still_parses(self): text = ( '{"name": "Alice", "note": "x"} ' '{"name": "web_search", "arguments": {"query": "cats"}}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert [c["function"]["name"] for c in calls] == ["web_search"] class TestDeepSeekMarkerInsideLeadingEnvelopes: """A DeepSeek/Kimi marker quoted inside a leading bare-JSON or Mistral call's argument strings is data: the pre-pass must not promote the embedded no-arg literal and drop the real outer call.""" def test_marker_inside_leading_json_call_stays_data(self): text = ( '{"name": "web_search", "arguments": ' '{"query": "what is <|tool▁calls▁begin|>...{}..."}}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert [c["function"]["name"] for c in calls] == ["web_search"] args = json.loads(calls[0]["function"]["arguments"]) assert "tool▁calls▁begin" in args["query"] def test_marker_inside_leading_mistral_call_stays_data(self): text = ( '[TOOL_CALLS] [{"name": "web_search", "arguments": ' '{"query": "docs on <|tool▁calls▁begin|> markers"}}]' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_standalone_deepseek_call_still_parses(self): text = ( "<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>web_search\n" '```json\n{"query": "cats"}\n```<|tool▁call▁end|><|tool▁calls▁end|>' ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] class TestMistralLiteralInsideLeadingJson: """A [TOOL_CALLS] literal quoted inside a leading JSON object must not be promoted over it.""" def test_outer_json_call_wins_over_mistral_literal(self): text = '{"name": "python", "arguments": {"code": "[TOOL_CALLS]web_search{}"}}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"python", "web_search"}) assert [c["function"]["name"] for c in calls] == ["python"] args = json.loads(calls[0]["function"]["arguments"]) assert args["code"] == "[TOOL_CALLS]web_search{}" def test_disabled_outer_json_keeps_mistral_literal_as_data(self): text = '{"name": "Alice", "note": "[TOOL_CALLS]web_search{}"}' assert parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) == [] class TestGemmaWrappedWhitespace: """Whitespace drift around ``call``/``:`` in wrapped Gemma calls must still parse (no fallback exists).""" def test_space_after_call_colon_parses(self): text = '<|tool_call>call: web_search{query:<|"|>cats<|"|>}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert [c["function"]["name"] for c in calls] == ["web_search"] assert json.loads(calls[0]["function"]["arguments"]) == {"query": "cats"} def test_space_around_colon_parses(self): text = '<|tool_call>call : web_search{query:<|"|>cats<|"|>}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_strict_mode_still_requires_the_closing_tag(self): text = '<|tool_call>call: web_search{query:<|"|>cats<|"|>}' assert parse_tool_calls_from_text(text, allow_incomplete = False) == [] class TestDisabledJsonBeforeDeepSeekCall: """A disabled leading bare-JSON object whose strings mention a DeepSeek/Kimi marker is dropped and the tail parsed, so a REAL DeepSeek/Kimi call after the object still executes instead of the whole message skipping the pre-pass.""" _DS = ( "<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>web_search\n" '```json\n{"query": "cats"}\n```<|tool▁call▁end|><|tool▁calls▁end|>' ) def test_real_deepseek_call_after_disabled_json_parses(self): text = '{"name": "Alice", "note": "<|tool▁calls▁begin|>"} ' + self._DS calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert [c["function"]["name"] for c in calls] == ["web_search"] assert json.loads(calls[0]["function"]["arguments"]) == {"query": "cats"} def test_disabled_json_with_marker_alone_stays_data(self): text = '{"name": "Alice", "note": "<|tool▁calls▁begin|>"}' assert parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) == [] class TestGemmaDottedArgumentKeys: """Dotted Gemma keys (namespaced schemas) must survive key-quoting or the call is lost.""" def test_dotted_key_parses(self): text = '<|tool_call>call:web_search{user.name:<|"|>bob<|"|>, query:<|"|>x<|"|>}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert [c["function"]["name"] for c in calls] == ["web_search"] args = json.loads(calls[0]["function"]["arguments"]) assert args == {"user.name": "bob", "query": "x"} class TestLeadingWrapperlessGemmaOverEmbeddedMarkers: """A leading wrapper-less Gemma call to an enabled tool owns the turn: a quoted foreign literal inside its argument (a query citing another tool syntax) is data, and tool_healing must not promote it before the Gemma fallback runs. Foreign markup leading keeps the normal order.""" def test_leading_gemma_wins_over_quoted_xml_literal(self): text = ( 'call:web_search{query:"explain ' '{"name":"evil","arguments":{}}"}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search", "evil"}) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_xml_leading_keeps_normal_order(self): text = ( '{"name":"web_search","arguments":' '{"query":"call:evil{x:1} example"}}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search", "evil"}) assert [c["function"]["name"] for c in calls] == ["web_search"] class TestLeadingMistralCallOwnsTheTurn: """A leading Mistral call wins in document order over literal XML in trailing prose.""" def test_leading_mistral_wins_over_trailing_xml_literal(self): text = ( '[TOOL_CALLS]web_search[ARGS]{"query":"cats"} ' "Note: 1" ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_function_xml_leading_keeps_normal_order(self): text = ( "x " "[TOOL_CALLS]evil[ARGS]{}" ) calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] class TestGemmaDottedKeyAfterBareValue: def test_dotted_key_after_bare_value_is_a_boundary(self): text = "<|tool_call>call:web_search{query:foo,user.name:bob}" calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert [c["function"]["name"] for c in calls] == ["web_search"] args = json.loads(calls[0]["function"]["arguments"]) assert args == {"query": "foo", "user.name": "bob"} class TestJsonAnswersAreDataForMarkerlessScans: """A whole-content JSON value is a structured answer: a quoted example of an enabled tool's syntax inside it must not execute the tool, and the display strip must not mutilate the answer.""" def test_gemma_example_inside_json_answer_not_promoted(self): text = '{"answer":"Gemma syntax is call:web_search{query:hi}"}' assert parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) == [] def test_gemma_example_inside_json_answer_not_stripped(self): from core.inference.tool_call_parser import strip_tool_markup text = '{"answer":"Gemma syntax is call:web_search{query:hi}"}' assert strip_tool_markup(text, final = True, enabled_tool_names = {"web_search"}) == text def test_kimi_marker_inside_json_answer_not_promoted(self): text = ( '{"answer":"<|tool_call_begin|>functions.web_search:0' '<|tool_call_argument_begin|>{}<|tool_call_end|>"}' ) assert parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) == [] class TestGemmaNestedQuotedLeaves: def test_nested_object_and_array_values_are_unquoted(self): text = 'call:f{loc:{city:"New York"},items:["a","b"],n:3}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"f"}) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args == {"loc": {"city": "New York"}, "items": ["a", "b"], "n": 3} class TestEarliestEnvelopeWinsAcrossDeepSeekKimi: """The DeepSeek/Kimi pre-pass dispatches by earliest envelope opener: a leading real call wins over a trailing example of the sibling format in either direction (document order, like the other leading guards).""" _DS = ( "<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>evil\n" '```json\n{"x": 1}\n```<|tool▁call▁end|><|tool▁calls▁end|>' ) _KIMI = ( "<|tool_calls_section_begin|><|tool_call_begin|>functions.web_search:0" '<|tool_call_argument_begin|>{"query": "cats"}<|tool_call_end|>' "<|tool_calls_section_end|>" ) def test_leading_kimi_wins_over_trailing_deepseek_example(self): text = self._KIMI + " For reference: " + self._DS calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_leading_deepseek_wins_over_trailing_kimi_example(self): text = self._DS + " Kimi format: " + self._KIMI calls = parse_tool_calls_from_text(text) assert [c["function"]["name"] for c in calls] == ["evil"] class TestNamelessLeadingJsonAnswerIsData: """A nameless leading JSON answer is an envelope: quoted markup stays data, and a call after it parses.""" def test_xml_literal_inside_json_answer_stays_data(self): text = '{"answer": "use x"}' assert parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) == [] def test_real_call_after_json_answer_still_parses(self): text = ( '{"answer": "docs"} {"name": "web_search", ' '"arguments": {"query": "cats"}}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert [c["function"]["name"] for c in calls] == ["web_search"] class TestClosedCallPrecedesMarkerPrePass: """A closed non-DeepSeek/Kimi call that precedes the first DS/Kimi marker owns the turn: a trailing example (or an example quoted inside a wrapped Gemma argument) must not be promoted by the pre-pass.""" _KIMI_EVIL = ( "<|tool_calls_section_begin|><|tool_call_begin|>functions.evil:0" '<|tool_call_argument_begin|>{"x": 1}<|tool_call_end|>' "<|tool_calls_section_end|>" ) def test_kimi_example_inside_wrapped_gemma_arg_stays_data(self): text = ( '<|tool_call>call:web_search{query:<|"|>explain ' + self._KIMI_EVIL + '<|"|>}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search", "evil"}) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_leading_xml_call_wins_over_trailing_kimi_example(self): text = ( '{"name":"web_search","arguments":{"query":"cats"}}' " For reference: " + self._KIMI_EVIL ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search", "evil"}) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_standalone_kimi_call_still_parses(self): calls = parse_tool_calls_from_text(self._KIMI_EVIL) assert [c["function"]["name"] for c in calls] == ["evil"] class TestTruncatedWrapperlessGemmaStopsScan: def test_call_quoted_inside_truncated_arg_not_promoted(self): text = 'call:python{code:example("call:web_search{query:hi}") and then it cut' assert parse_tool_calls_from_text(text, enabled_tool_names = {"python", "web_search"}) == [] class TestGemmaQuotedNestedDelimiters: def test_comma_inside_quoted_nested_string_not_a_split(self): text = 'call:f{loc:{city:"New, York"},n:1}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"f"}) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args == {"loc": {"city": "New, York"}, "n": 1} class TestGemmaStringMarkerLiteralInArgs: def test_string_marker_literal_does_not_lose_the_call(self): text = "call:web_search{query:'what does <|\"|> mean in Gemma'}" calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args["query"] == 'what does <|"|> mean in Gemma' class TestGemmaMidValueQuotedPhrase: def test_quoted_phrase_mid_value_hides_delimiters(self): text = 'call:web_search{query:find "weather, location: Boston", limit:3}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args == {"query": 'find "weather, location: Boston"', "limit": 3} def test_apostrophes_still_prose_mid_value(self): text = "call:web_search{query:what's on at the museum, n:2}" calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) args = json.loads(calls[0]["function"]["arguments"]) assert args == {"query": "what's on at the museum", "n": 2} class TestGlmStrictRefusesInQuoteFallback: """A truncated GLM value whose only close candidates sit inside a string literal must reject in strict mode instead of executing truncated arguments; Auto-Heal keeps the lenient partial value.""" _TRUNC = ( 'python\ncode\nprint("")' ) def test_strict_rejects_truncated_in_string_close(self): assert parse_tool_calls_from_text(self._TRUNC, allow_incomplete = False) == [] def test_heal_keeps_partial_value(self): calls = parse_tool_calls_from_text(self._TRUNC, allow_incomplete = True) assert len(calls) == 1 and calls[0]["function"]["name"] == "python" class TestGemmaGuardCoversPreambles: def test_preamble_then_gemma_call_quoting_xml_wins(self): text = ( "Sure, searching now. call:web_search{query:" '"explain {"name":"evil","arguments":{}}"}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search", "evil"}) assert [c["function"]["name"] for c in calls] == ["web_search"] class TestGlmStrictAcceptsApostrophes: def test_apostrophe_value_parses_in_strict_mode(self): text = ( "web_search\nquery\n" "what's the weather\n" ) calls = parse_tool_calls_from_text(text, allow_incomplete = False) assert len(calls) == 1 args = json.loads(calls[0]["function"]["arguments"]) assert args == {"query": "what's the weather"} class TestDisabledGemmaCallLiteralsAreData: def test_literal_inside_disabled_call_not_promoted(self): text = 'call:foo{query:"x"}' assert parse_tool_calls_from_text(text, enabled_tool_names = {"python", "web_search"}) == [] def test_real_call_after_disabled_example_still_parses(self): text = ( 'call:foo{query:"x"}' " call:web_search{query:hi}" ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"python", "web_search"}) assert [c["function"]["name"] for c in calls] == ["web_search"] class TestLeadingJsonArrayAnswerIsData: def test_kimi_marker_inside_json_array_answer_not_promoted(self): text = ( '[{"answer": "<|tool_call_begin|>functions.web_search:0' '<|tool_call_argument_begin|>{}<|tool_call_end|>"}]' ) assert parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) == [] class TestLeadingBareJsonOwnsTurnOverTrailingXml: """Document order: a leading closed bare-JSON call owns the turn even when tool XML appears AFTER it (inside-or-after, mirroring the Mistral rule).""" def test_leading_call_wins_over_trailing_xml(self): text = ( '{"name":"lookup","parameters":{"q":"first"}} Example: ' '{"name":"delete_all","arguments":{}}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"lookup", "delete_all"}) assert [c["function"]["name"] for c in calls] == ["lookup"], calls assert json.loads(calls[0]["function"]["arguments"]) == {"q": "first"} def test_chained_leading_calls_win_over_trailing_xml(self): text = ( '{"name":"lookup","parameters":{"q":"first"}};' '{"name":"lookup","parameters":{"q":"second"}} ' '{"name":"delete_all","arguments":{}}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"lookup", "delete_all"}) assert [c["function"]["name"] for c in calls] == ["lookup", "lookup"], calls def test_non_call_leading_object_defers_to_trailing_real_call(self): # Nameless answers and disabled-name objects take the decline path: # the object is dropped and the real trailing call still parses. for lead in ('{"answer": 42}', '{"name":"draft","parameters":{}}'): text = lead + ' {"name":"delete_all","arguments":{}}' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"delete_all"}) assert [c["function"]["name"] for c in calls] == ["delete_all"], (lead, calls) def test_leading_xml_call_still_wins_over_trailing_bare_json(self): text = ( '{"name":"delete_all","arguments":{}} ' 'Example: {"name":"lookup","parameters":{"q":"x"}}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"lookup", "delete_all"}) assert [c["function"]["name"] for c in calls] == ["delete_all"], calls class TestProseCloseTagAfterClosedFunctionCall: """A literal in prose after a closed call is data: the call ends at its first close that is not parameter data, so arguments never swallow the prose between the real close and the literal.""" def test_arguments_do_not_swallow_prose(self): text = ( "cats" " Done. The tag closes a call." ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert [c["function"]["name"] for c in calls] == ["web_search"], calls assert json.loads(calls[0]["function"]["arguments"]) == {"query": "cats"} def test_literal_close_inside_open_parameter_stays_data(self): text = 'print("")' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"python"}) assert [c["function"]["name"] for c in calls] == ["python"], calls assert json.loads(calls[0]["function"]["arguments"]) == {"code": 'print("")'} def test_attribute_form_arguments_do_not_swallow_prose(self): # The attribute form shares the first-balanced-close # rule: prose mentioning a literal close tag never folds into arguments. text = ( 'cats' " Done. The tag closes a call." ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert [c["function"]["name"] for c in calls] == ["web_search"], calls assert json.loads(calls[0]["function"]["arguments"]) == {"query": "cats"} def test_attribute_form_literal_close_in_open_parameter_stays_data(self): text = 'print("")' calls = parse_tool_calls_from_text(text, enabled_tool_names = {"python"}) assert json.loads(calls[0]["function"]["arguments"]) == {"code": 'print("")'} def test_attribute_form_two_calls_both_parse(self): text = ( 'cats' 'x=1' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search", "python"}) assert [c["function"]["name"] for c in calls] == ["web_search", "python"], calls class TestEnabledNameJsonAnswerIsContent: """A JSON answer whose top-level name matches an enabled tool but has no call shape is content: the parser rejects it, so the strip and the drain gate must keep it visible too.""" def test_answer_survives_strip(self): from core.inference.tool_call_parser import strip_leading_bare_json_call ans = '{"name":"web_search","result":"no call"}' assert strip_leading_bare_json_call(ans, {"web_search"}) == ans def test_answer_does_not_route_to_draining(self): from core.inference.safetensors_agentic import _looks_like_enabled_bare_json assert not _looks_like_enabled_bare_json( '{"name":"web_search","result":"no call"}', {"web_search"} ) def test_real_call_still_strips_and_drains(self): from core.inference.safetensors_agentic import _looks_like_enabled_bare_json from core.inference.tool_call_parser import strip_leading_bare_json_call real = '{"name":"web_search","parameters":{"q":"x"}}' assert strip_leading_bare_json_call(real, {"web_search"}) == "" assert _looks_like_enabled_bare_json(real, {"web_search"}) def test_arguments_string_call_still_strips(self): from core.inference.tool_call_parser import strip_leading_bare_json_call call = '{"name":"web_search","arguments":"{\\"q\\":\\"x\\"}"} tail' assert strip_leading_bare_json_call(call, {"web_search"}) == "tail" class TestAttributeFormLeadingContainment: """A leading attribute-form call owns the turn: markup quoted inside its parameter is data, not a call for the shared XML parser to promote.""" def test_quoted_tool_call_inside_param_stays_data(self): from core.inference.tool_call_parser import parse_tool_calls_from_text text = ( 'find ' '{"name":"delete","arguments":{}}' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search", "delete"}) assert [c["function"]["name"] for c in calls] == ["web_search"] assert "delete" in json.loads(calls[0]["function"]["arguments"])["query"] def test_real_xml_call_before_attribute_form_keeps_order(self): from core.inference.tool_call_parser import parse_tool_calls_from_text text = ( '{"name":"delete","arguments":{}} Example: ' 'x' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search", "delete"}) assert calls[0]["function"]["name"] == "delete" class TestParameterKeepsMultipleLiteralCloses: """A parameter that provably closes with its own tag keeps every literal function close inside it as data (regression: the first literal close was treated as ending the parameter, truncating the value).""" def test_two_literal_closes_in_one_parameter(self): from core.inference.tool_call_parser import parse_tool_calls_from_text text = ( '' "a b c " ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search"}) assert json.loads(calls[0]["function"]["arguments"]) == { "query": "a b c" } def test_strip_removes_the_whole_call(self): from core.inference.tool_call_parser import strip_tool_markup text = ( '' "a b c after" ) assert strip_tool_markup(text, final = True) == "after" def test_unclosed_parameter_still_heals_at_function_close(self): from core.inference.tool_call_parser import parse_tool_calls_from_text calls = parse_tool_calls_from_text( "val", enabled_tool_names = {"web_search"}, ) assert json.loads(calls[0]["function"]["arguments"]) == {"query": "val"} class TestMistralPreambleOwnership: """A visible preface before the first Mistral call must not hand the turn to a later XML literal: the Mistral call is first in document order.""" def test_v11_named_form_after_preface(self): from core.inference.tool_call_parser import parse_tool_calls_from_text text = ( 'pref [TOOL_CALLS]web_search[ARGS]{"query":"cats"} Note ' "1" ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search", "evil"}) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_array_form_after_preface(self): from core.inference.tool_call_parser import parse_tool_calls_from_text text = ( 'pref [TOOL_CALLS][{"name":"web_search","arguments":{"query":"cats"}}] Note ' "1" ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search", "evil"}) assert [c["function"]["name"] for c in calls] == ["web_search"] def test_xml_call_before_trigger_keeps_order(self): from core.inference.tool_call_parser import parse_tool_calls_from_text text = ( "1 then " '[TOOL_CALLS][{"name":"web_search","arguments":{}}]' ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"web_search", "evil"}) assert calls[0]["function"]["name"] == "evil" def test_prose_mention_without_call_shape_keeps_order(self): from core.inference.tool_call_parser import parse_tool_calls_from_text text = ( "See [TOOL_CALLS] docs for details. " "1" ) calls = parse_tool_calls_from_text(text, enabled_tool_names = {"evil"}) assert [c["function"]["name"] for c in calls] == ["evil"] class TestBareJsonStripRequiresTopLevelName: """The strip's shape gate requires the parser's TOP-LEVEL name in every mode: a JSON answer with only a nested name is content, even name-agnostic.""" def test_nested_name_answer_survives_name_agnostic_strip(self): from core.inference.tool_call_parser import strip_leading_bare_json_call ans = '{"parameters":{},"result":{"name":"web_search"}}' assert strip_leading_bare_json_call(ans) == ans assert strip_leading_bare_json_call(ans, {"web_search"}) == ans def test_real_call_still_strips_name_agnostic(self): from core.inference.tool_call_parser import strip_leading_bare_json_call assert strip_leading_bare_json_call('{"name":"web_search","parameters":{"q":"x"}}') == "" class TestGemmaAwareClosedBlockPrePass: """The closed JSON/function strip pre-pass must not delete across a complete Gemma span (a quoted plus a later real ).""" def test_literal_function_in_gemma_arg_with_later_real_call(self): from core.tool_healing import strip_tool_call_markup text = ( 'before <|tool_call>call:python{code:<|"|>print("")<|"|>}' " ls" " after" ) assert strip_tool_call_markup(text, final = True) == "before after" def test_literal_function_in_gemma_arg_with_prose_closer(self): from core.tool_healing import strip_tool_call_markup text = ( 'before <|tool_call>call:python{code:<|"|>print("")<|"|>}' " then use to close. after" ) out = strip_tool_call_markup(text, final = True) assert out.startswith("before") assert out.endswith("after") assert "call:python" not in out def test_gemma_opener_inside_json_arg_still_strips_block(self): from core.tool_healing import strip_tool_call_markup text = ( '{"name":"t","arguments":{"code":"<|tool_call>call:x{"}} after' ) assert strip_tool_call_markup(text, final = True) == "after" def test_gemma_opener_inside_function_param_still_strips_block(self): from core.tool_healing import strip_tool_call_markup text = ( 'x = "<|tool_call>call:t{"' " after" ) assert strip_tool_call_markup(text, final = True) == "after"