From 699da0decdd10a982942fa835aefc894b54e32ba Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 14:43:00 +0000 Subject: [PATCH] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- studio/backend/core/inference/llama_cpp.py | 76 +++++++++------ .../core/inference/safetensors_agentic.py | 71 +++++++------- .../tests/test_validation_retry_loop.py | 97 ++++++++++++------- 3 files changed, 142 insertions(+), 102 deletions(-) diff --git a/studio/backend/core/inference/llama_cpp.py b/studio/backend/core/inference/llama_cpp.py index 762eeb22d5..da25d91678 100644 --- a/studio/backend/core/inference/llama_cpp.py +++ b/studio/backend/core/inference/llama_cpp.py @@ -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 diff --git a/studio/backend/core/inference/safetensors_agentic.py b/studio/backend/core/inference/safetensors_agentic.py index b7fbe680e5..196caf89f1 100644 --- a/studio/backend/core/inference/safetensors_agentic.py +++ b/studio/backend/core/inference/safetensors_agentic.py @@ -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 diff --git a/studio/backend/tests/test_validation_retry_loop.py b/studio/backend/tests/test_validation_retry_loop.py index 719683e158..c137ba4271 100644 --- a/studio/backend/tests/test_validation_retry_loop.py +++ b/studio/backend/tests/test_validation_retry_loop.py @@ -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([ - ['{"name":"missing_tool","arguments":{}}'], - ['{"name":"web_search","arguments":{"query":"x"}}'], - ["Done."], - ]) + single_turn = _multi_turn( + [ + ['{"name":"missing_tool","arguments":{}}'], + [ + '{"name":"web_search","arguments":{"query":"x"}}' + ], + ["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([ - ['{"name":"missing_a","arguments":{}}'], - ['{"name":"missing_b","arguments":{}}'], - ['{"name":"missing_c","arguments":{}}'], - ["Sorry, I cannot proceed."], - ]) + single_turn = _multi_turn( + [ + ['{"name":"missing_a","arguments":{}}'], + ['{"name":"missing_b","arguments":{}}'], + ['{"name":"missing_c","arguments":{}}'], + ["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([ - ['{"name":"missing","arguments":{}}'], - ["bye"], - ]) + single_turn = _multi_turn( + [ + ['{"name":"missing","arguments":{}}'], + ["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([ - ['{"name":"web_search","arguments":"some text"}'], - ["all done"], - ]) + single_turn = _multi_turn( + [ + [ + '{"name":"web_search","arguments":"some text"}' + ], + ["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([ - ['{"name":"web_search","arguments":"not a dict"}'], - ['{"name":"web_search","arguments":{"query":"sf"}}'], - ["sunny"], - ]) + single_turn = _multi_turn( + [ + [ + '{"name":"web_search","arguments":"not a dict"}' + ], + [ + '{"name":"web_search","arguments":{"query":"sf"}}' + ], + ["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([ - ['{"name":"anything","arguments":{}}'], - ["done"], - ]) + single_turn = _multi_turn( + [ + ['{"name":"anything","arguments":{}}'], + ["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([ - ['{"name":"web_search","arguments":{"query":"x"}}'], - ["final"], - ]) + single_turn = _multi_turn( + [ + [ + '{"name":"web_search","arguments":{"query":"x"}}' + ], + ["final"], + ] + ) exec_fn = FakeExecuteTool(["res"]) events = _collect( run_safetensors_tool_loop(