[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
This commit is contained in:
pre-commit-ci[bot] 2026-05-22 14:43:00 +00:00
commit 699da0decd
3 changed files with 142 additions and 102 deletions

View file

@ -5040,12 +5040,16 @@ class LlamaCppBackend:
_vdec = None
if not isinstance(_vdec, dict):
_validation_problem = (
"malformed_args", _vtc, _vname,
"malformed_args",
_vtc,
_vname,
)
break
elif not isinstance(_vraw, dict):
_validation_problem = (
"malformed_args", _vtc, _vname,
"malformed_args",
_vtc,
_vname,
)
break
@ -5055,42 +5059,50 @@ class LlamaCppBackend:
_vcall_id = _vtc.get("id")
if _kind == "unknown_tool" and _vcall_id:
_allowed_list = ", ".join(sorted(_allowed_tool_names))
conversation.append({
"role": "tool",
"tool_call_id": _vcall_id,
"name": _vname,
"content": (
f"Error: tool '{_vname}' is not available. "
f"Available tools: {_allowed_list}."
),
})
conversation.append(
{
"role": "tool",
"tool_call_id": _vcall_id,
"name": _vname,
"content": (
f"Error: tool '{_vname}' is not available. "
f"Available tools: {_allowed_list}."
),
}
)
elif _kind == "malformed_args" and _vcall_id:
conversation.append({
"role": "tool",
"tool_call_id": _vcall_id,
"name": _vname,
"content": (
f"Error: arguments to '{_vname}' could not "
"be parsed as a JSON object. Call "
f"'{_vname}' again with valid JSON object "
"arguments."
),
})
conversation.append(
{
"role": "tool",
"tool_call_id": _vcall_id,
"name": _vname,
"content": (
f"Error: arguments to '{_vname}' could not "
"be parsed as a JSON object. Call "
f"'{_vname}' again with valid JSON object "
"arguments."
),
}
)
else:
# No usable call id: user-role correction since
# OpenAI tool messages require a matching id.
conversation.append({
"role": "user",
"content": (
"Your last tool call was malformed "
"(missing id or function). Please "
"re-issue it as a valid OpenAI "
"function call."
),
})
conversation.append(
{
"role": "user",
"content": (
"Your last tool call was malformed "
"(missing id or function). Please "
"re-issue it as a valid OpenAI "
"function call."
),
}
)
logger.info(
"validation_retry kind=%s retries=%d/%d",
_kind, _validation_retries, max_validation_retries,
_kind,
_validation_retries,
max_validation_retries,
)
yield {"type": "status", "text": ""}
continue

View file

@ -331,10 +331,7 @@ def run_safetensors_tool_loop(
v_decoded = json.loads(v_raw)
except (json.JSONDecodeError, ValueError):
v_decoded = None
if (
not isinstance(v_decoded, dict)
and not auto_heal_tool_calls
):
if not isinstance(v_decoded, dict) and not auto_heal_tool_calls:
validation_problem = ("malformed_args", v_tc, v_name)
break
elif not isinstance(v_raw, dict):
@ -348,39 +345,47 @@ def run_safetensors_tool_loop(
v_call_id = v_tc.get("id")
if v_kind == "unknown_tool" and v_call_id:
allowed_list = ", ".join(sorted(allowed_tool_names))
conversation.append({
"role": "tool",
"tool_call_id": v_call_id,
"name": v_name,
"content": (
f"Error: tool '{v_name}' is not available. "
f"Available tools: {allowed_list}."
),
})
conversation.append(
{
"role": "tool",
"tool_call_id": v_call_id,
"name": v_name,
"content": (
f"Error: tool '{v_name}' is not available. "
f"Available tools: {allowed_list}."
),
}
)
elif v_kind == "malformed_args" and v_call_id:
conversation.append({
"role": "tool",
"tool_call_id": v_call_id,
"name": v_name,
"content": (
f"Error: arguments to '{v_name}' could not be "
"parsed as a JSON object. Call "
f"'{v_name}' again with valid JSON object "
"arguments."
),
})
conversation.append(
{
"role": "tool",
"tool_call_id": v_call_id,
"name": v_name,
"content": (
f"Error: arguments to '{v_name}' could not be "
"parsed as a JSON object. Call "
f"'{v_name}' again with valid JSON object "
"arguments."
),
}
)
else:
conversation.append({
"role": "user",
"content": (
"Your last tool call was malformed (missing id "
"or function). Please re-issue it as a valid "
"OpenAI function call."
),
})
conversation.append(
{
"role": "user",
"content": (
"Your last tool call was malformed (missing id "
"or function). Please re-issue it as a valid "
"OpenAI function call."
),
}
)
logger.info(
"validation_retry kind=%s retries=%d/%d",
v_kind, validation_retries, max_validation_retries,
v_kind,
validation_retries,
max_validation_retries,
)
yield {"type": "status", "text": ""}
continue

View file

@ -21,7 +21,9 @@ class FakeExecuteTool:
self.results = list(results or [])
self.calls = []
def __call__(self, name, arguments, *, cancel_event = None, timeout = None, session_id = None):
def __call__(
self, name, arguments, *, cancel_event = None, timeout = None, session_id = None
):
self.calls.append((name, arguments))
return self.results.pop(0) if self.results else "OK"
@ -62,11 +64,15 @@ class TestUnknownTool:
def test_unknown_tool_triggers_retry(self):
# Turn 1: hallucinated tool name "missing_tool".
# Turn 2: valid call after the corrective nudge.
single_turn = _multi_turn([
['<tool_call>{"name":"missing_tool","arguments":{}}</tool_call>'],
['<tool_call>{"name":"web_search","arguments":{"query":"x"}}</tool_call>'],
["Done."],
])
single_turn = _multi_turn(
[
['<tool_call>{"name":"missing_tool","arguments":{}}</tool_call>'],
[
'<tool_call>{"name":"web_search","arguments":{"query":"x"}}</tool_call>'
],
["Done."],
]
)
exec_fn = FakeExecuteTool(["result"])
events = _collect(
run_safetensors_tool_loop(
@ -87,12 +93,14 @@ class TestUnknownTool:
# the first two are retried; the third falls through to the
# existing per-tool error path (which never executes a real
# tool but emits an error result to the model).
single_turn = _multi_turn([
['<tool_call>{"name":"missing_a","arguments":{}}</tool_call>'],
['<tool_call>{"name":"missing_b","arguments":{}}</tool_call>'],
['<tool_call>{"name":"missing_c","arguments":{}}</tool_call>'],
["Sorry, I cannot proceed."],
])
single_turn = _multi_turn(
[
['<tool_call>{"name":"missing_a","arguments":{}}</tool_call>'],
['<tool_call>{"name":"missing_b","arguments":{}}</tool_call>'],
['<tool_call>{"name":"missing_c","arguments":{}}</tool_call>'],
["Sorry, I cannot proceed."],
]
)
exec_fn = FakeExecuteTool([])
events = _collect(
run_safetensors_tool_loop(
@ -115,10 +123,12 @@ class TestUnknownTool:
# With max_validation_retries=0, the F3 arm never engages and
# behavior matches the pre-F3 path: the existing per-tool
# error message is emitted but no corrective re-entry happens.
single_turn = _multi_turn([
['<tool_call>{"name":"missing","arguments":{}}</tool_call>'],
["bye"],
])
single_turn = _multi_turn(
[
['<tool_call>{"name":"missing","arguments":{}}</tool_call>'],
["bye"],
]
)
exec_fn = FakeExecuteTool([])
events = _collect(
run_safetensors_tool_loop(
@ -135,10 +145,7 @@ class TestUnknownTool:
tool_ends = [e for e in events if e["type"] == "tool_end"]
# Existing per-tool error path emits a tool_end with an Error
# result so the model sees the failure.
assert any(
"not enabled" in str(e.get("result", ""))
for e in tool_ends
)
assert any("not enabled" in str(e.get("result", "")) for e in tool_ends)
class TestMalformedArgs:
@ -146,10 +153,14 @@ class TestMalformedArgs:
# With auto_heal_tool_calls=True (the default), string args are
# healed to {"query": "..."} for web_search. F3 leaves heal
# behavior intact and only catches strictly-impossible shapes.
single_turn = _multi_turn([
['<tool_call>{"name":"web_search","arguments":"some text"}</tool_call>'],
["all done"],
])
single_turn = _multi_turn(
[
[
'<tool_call>{"name":"web_search","arguments":"some text"}</tool_call>'
],
["all done"],
]
)
exec_fn = FakeExecuteTool(["ok"])
events = _collect(
run_safetensors_tool_loop(
@ -168,11 +179,17 @@ class TestMalformedArgs:
def test_malformed_args_caught_when_heal_off(self):
# With auto_heal off, a non-dict arguments value is a hard
# malformed-args failure and the F3 arm catches it.
single_turn = _multi_turn([
['<tool_call>{"name":"web_search","arguments":"not a dict"}</tool_call>'],
['<tool_call>{"name":"web_search","arguments":{"query":"sf"}}</tool_call>'],
["sunny"],
])
single_turn = _multi_turn(
[
[
'<tool_call>{"name":"web_search","arguments":"not a dict"}</tool_call>'
],
[
'<tool_call>{"name":"web_search","arguments":{"query":"sf"}}</tool_call>'
],
["sunny"],
]
)
exec_fn = FakeExecuteTool(["sunny in sf"])
events = _collect(
run_safetensors_tool_loop(
@ -192,10 +209,12 @@ class TestNoOpCases:
def test_no_tools_no_validation(self):
# Empty tools list means allowed_tool_names is empty, so the
# F3 arm never engages.
single_turn = _multi_turn([
['<tool_call>{"name":"anything","arguments":{}}</tool_call>'],
["done"],
])
single_turn = _multi_turn(
[
['<tool_call>{"name":"anything","arguments":{}}</tool_call>'],
["done"],
]
)
exec_fn = FakeExecuteTool([])
events = _collect(
run_safetensors_tool_loop(
@ -211,10 +230,14 @@ class TestNoOpCases:
def test_valid_call_no_retry_overhead(self):
# A clean valid call does not trigger F3 at all.
single_turn = _multi_turn([
['<tool_call>{"name":"web_search","arguments":{"query":"x"}}</tool_call>'],
["final"],
])
single_turn = _multi_turn(
[
[
'<tool_call>{"name":"web_search","arguments":{"query":"x"}}</tool_call>'
],
["final"],
]
)
exec_fn = FakeExecuteTool(["res"])
events = _collect(
run_safetensors_tool_loop(