[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
This commit is contained in:
parent
09ca2a1777
commit
699da0decd
3 changed files with 142 additions and 102 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue