feat: Normalize alternative tool call formats (invoke/function_calls)

This commit is contained in:
0xallam
2026-02-20 07:31:45 -08:00
committed by Ahmed Allam
parent 6166be841b
commit f4d522164d
4 changed files with 70 additions and 17 deletions
+36 -7
View File
@@ -3,6 +3,29 @@ import re
from typing import Any
_INVOKE_OPEN = re.compile(r'<invoke\s+name="([^"]+)"\s*>')
_PARAM_NAME_ATTR = re.compile(r'<parameter\s+name="([^"]+)"\s*>')
_FUNCTION_CALLS_TAG = re.compile(r"</?function_calls>")
def normalize_tool_format(content: str) -> str:
"""Convert alternative tool-call XML format to the expected one.
Handles:
<function_calls>...</function_calls> → stripped
<invoke name="X"> → <function=X>
<parameter name="X"> → <parameter=X>
</invoke> → </function>
"""
if "<invoke" not in content and "<function_calls" not in content:
return content
content = _FUNCTION_CALLS_TAG.sub("", content)
content = _INVOKE_OPEN.sub(r"<function=\1>", content)
content = _PARAM_NAME_ATTR.sub(r"<parameter=\1>", content)
return content.replace("</invoke>", "</function>")
STRIX_MODEL_MAP: dict[str, str] = {
"claude-sonnet-4.6": "anthropic/claude-sonnet-4-6",
"claude-opus-4.6": "anthropic/claude-opus-4-6",
@@ -41,7 +64,9 @@ def _truncate_to_first_function(content: str) -> str:
if not content:
return content
function_starts = [match.start() for match in re.finditer(r"<function=", content)]
function_starts = [
match.start() for match in re.finditer(r"<function=|<invoke\s+name=", content)
]
if len(function_starts) >= 2:
second_function_start = function_starts[1]
@@ -52,6 +77,7 @@ def _truncate_to_first_function(content: str) -> str:
def parse_tool_invocations(content: str) -> list[dict[str, Any]] | None:
content = normalize_tool_format(content)
content = fix_incomplete_tool_call(content)
tool_invocations: list[dict[str, Any]] = []
@@ -81,12 +107,14 @@ def parse_tool_invocations(content: str) -> list[dict[str, Any]] | None:
def fix_incomplete_tool_call(content: str) -> str:
"""Fix incomplete tool calls by adding missing </function> tag."""
if (
"<function=" in content
and content.count("<function=") == 1
and "</function>" not in content
):
"""Fix incomplete tool calls by adding missing closing tag.
Handles both ``<function=…>`` and ``<invoke name="">`` formats.
"""
has_open = "<function=" in content or "<invoke " in content
count_open = content.count("<function=") + content.count("<invoke ")
has_close = "</function>" in content or "</invoke>" in content
if has_open and count_open == 1 and not has_close:
content = content.rstrip()
content = content + "function>" if content.endswith("</") else content + "\n</function>"
return content
@@ -107,6 +135,7 @@ def clean_content(content: str) -> str:
if not content:
return ""
content = normalize_tool_format(content)
content = fix_incomplete_tool_call(content)
tool_pattern = r"<function=[^>]+>.*?</function>"