From 7327ca8324b68273661e4569fc881acadc2dc75a Mon Sep 17 00:00:00 2001 From: n1majne3 <24203125+n1majne3@users.noreply.github.com> Date: Sun, 3 May 2026 19:42:36 -0700 Subject: [PATCH] add MiniMax tool-call wrapper stripping, JSON body fallback, support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three additions to handle MiniMax-specific quirks on top of the existing handling already on main from #458: - normalize_tool_format strips ... wrappers MiniMax wraps around its tool calls - parse_tool_invocations falls back to JSON parsing when a {...} body has no tags (MiniMax sometimes emits arguments as a JSON object); non-string values are serialized via json.dumps so booleans/lists end up as valid JSON ("true", ["a"]) instead of Python repr ("True", ['a']) - _THINKING_BLOCK_RE and _THINKING_BLOCK_OR_OPEN_RE extended to match both (Claude/legacy) and (MiniMax/DeepSeek) tags — the original regex from #458 only caught the -ing variant Closes #439 --- strix/llm/llm.py | 6 ++++-- strix/llm/utils.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/strix/llm/llm.py b/strix/llm/llm.py index e500d802..6fd727d5 100644 --- a/strix/llm/llm.py +++ b/strix/llm/llm.py @@ -26,8 +26,10 @@ from strix.utils.resource_paths import get_strix_resource_path litellm.drop_params = True litellm.modify_params = True -_THINKING_BLOCK_RE = re.compile(r"]*>.*?", re.DOTALL) -_THINKING_BLOCK_OR_OPEN_RE = re.compile(r"]*>.*?(?:|\Z)", re.DOTALL) +_THINKING_BLOCK_RE = re.compile(r"]*>.*?", re.DOTALL) +_THINKING_BLOCK_OR_OPEN_RE = re.compile( + r"]*>.*?(?:|\Z)", re.DOTALL +) def _find_end_tag_outside_thinking(content: str, end_tag: str) -> int: diff --git a/strix/llm/utils.py b/strix/llm/utils.py index 0f56bedf..fd7e4784 100644 --- a/strix/llm/utils.py +++ b/strix/llm/utils.py @@ -1,4 +1,5 @@ import html +import json import re from typing import Any @@ -6,6 +7,7 @@ from typing import Any _INVOKE_OPEN = re.compile(r'') _PARAM_NAME_ATTR = re.compile(r'') _FUNCTION_CALLS_TAG = re.compile(r"") +_MINIMAX_TOOL_CALL_TAG = re.compile(r"") _STRIP_TAG_QUOTES = re.compile(r"<(function|parameter)\s*=\s*([^>]*?)>") @@ -13,6 +15,7 @@ def normalize_tool_format(content: str) -> str: """Convert alternative tool-call XML formats to the expected one. Handles: + ... → stripped ... → stripped → → @@ -20,6 +23,8 @@ def normalize_tool_format(content: str) -> str: → → """ + content = _MINIMAX_TOOL_CALL_TAG.sub("", content) + if ( " list[dict[str, Any]] | None: param_value = html.unescape(param_value) args[param_name] = param_value + if not args: + body_stripped = fn_body.strip() + if body_stripped.startswith("{"): + try: + parsed = json.loads(body_stripped) + if isinstance(parsed, dict): + args = { + k: v if isinstance(v, str) else json.dumps(v) + for k, v in parsed.items() + } + except (json.JSONDecodeError, ValueError): + pass + tool_invocations.append({"toolName": fn_name, "args": args}) return tool_invocations if tool_invocations else None