add MiniMax tool-call wrapper stripping, JSON body fallback, <think> support

Three additions to handle MiniMax-specific quirks on top of the
existing <thinking> handling already on main from #458:

- normalize_tool_format strips <minimax:tool_call>...</minimax:tool_call>
  wrappers MiniMax wraps around its tool calls
- parse_tool_invocations falls back to JSON parsing when a
  <function=foo>{...}</function> body has no <parameter> 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 <thinking> (Claude/legacy) and <think> (MiniMax/DeepSeek)
  tags — the original regex from #458 only caught the -ing variant

Closes #439
This commit is contained in:
n1majne3 2026-05-03 19:42:36 -07:00 • committed by 0xallam
parent e1f38f8339
commit 7327ca8324
2 changed files with 22 additions and 2 deletions

View file

@ -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"<thinking[^>]*>.*?</thinking>", re.DOTALL)
_THINKING_BLOCK_OR_OPEN_RE = re.compile(r"<thinking[^>]*>.*?(?:</thinking>|\Z)", re.DOTALL)
_THINKING_BLOCK_RE = re.compile(r"<think(?:ing)?[^>]*>.*?</think(?:ing)?>", re.DOTALL)
_THINKING_BLOCK_OR_OPEN_RE = re.compile(
r"<think(?:ing)?[^>]*>.*?(?:</think(?:ing)?>|\Z)", re.DOTALL
)
def _find_end_tag_outside_thinking(content: str, end_tag: str) -> int:

View file

@ -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'<invoke\s+name=["\']([^"\']+)["\']>')
_PARAM_NAME_ATTR = re.compile(r'<parameter\s+name=["\']([^"\']+)["\']>')
_FUNCTION_CALLS_TAG = re.compile(r"</?function_calls>")
_MINIMAX_TOOL_CALL_TAG = re.compile(r"</?minimax:tool_call>")
_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:
<minimax:tool_call>...</minimax:tool_call> → stripped
<function_calls>...</function_calls> → stripped
<invoke name="X"> → <function=X>
<parameter name="X"> → <parameter=X>
@ -20,6 +23,8 @@ def normalize_tool_format(content: str) -> str:
<function="X"> → <function=X>
<parameter="X"> → <parameter=X>
"""
content = _MINIMAX_TOOL_CALL_TAG.sub("", content)
if (
"<invoke" in content
or "<function_calls" in content
@ -107,6 +112,19 @@ def parse_tool_invocations(content: str) -> 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