1313# See the License for the specific language governing permissions and
1414# limitations under the License.
1515
16+ import json
1617from unittest .mock import patch
1718
1819import pytest
2425from tests .utils import FakeLLMModel , TestChat
2526
2627
28+ def _tool_arguments (func : dict ) -> dict :
29+ """Parse OpenAI-style tool call arguments (JSON string or dict)."""
30+ arguments = func .get ("arguments" , {})
31+ if isinstance (arguments , str ):
32+ try :
33+ arguments = json .loads (arguments )
34+ except json .JSONDecodeError :
35+ return {}
36+ return arguments if isinstance (arguments , dict ) else {}
37+
38+
2739@action (is_system_action = True )
2840async def validate_tool_parameters (tool_calls , context = None , ** kwargs ):
2941 tool_calls = tool_calls or (context .get ("tool_calls" , []) if context else [])
@@ -32,7 +44,7 @@ async def validate_tool_parameters(tool_calls, context=None, **kwargs):
3244
3345 for tool_call in tool_calls :
3446 func = tool_call .get ("function" , {})
35- args = func . get ( "arguments" , {} )
47+ args = _tool_arguments ( func )
3648 for param_value in args .values ():
3749 if isinstance (param_value , str ):
3850 if any (pattern .lower () in param_value .lower () for pattern in dangerous_patterns ):
@@ -230,7 +242,7 @@ async def test_assistant_tool_calls_run_tool_output_rails_when_dialog_disabled()
230242 "type" : "function" ,
231243 "function" : {
232244 "name" : "dangerous_tool" ,
233- "arguments" : {"param" : "eval('malicious code')" },
245+ "arguments" : ' {"param": "eval(\ ' malicious code\ ' )"}' ,
234246 },
235247 }
236248 ],
@@ -242,3 +254,62 @@ async def test_assistant_tool_calls_run_tool_output_rails_when_dialog_disabled()
242254 assert isinstance (result , GenerationResponse )
243255 assert isinstance (result .response , list )
244256 assert "parameters may be unsafe" in result .response [0 ]["content" ]
257+
258+
259+ @pytest .mark .asyncio
260+ async def test_approved_assistant_tool_calls_are_returned_when_dialog_disabled ():
261+ config = RailsConfig .from_content (
262+ """
263+ define subflow validate tool parameters
264+ $valid = execute validate_tool_parameters(tool_calls=$tool_calls)
265+
266+ if not $valid
267+ bot refuse dangerous tool parameters
268+ abort
269+
270+ define bot refuse dangerous tool parameters
271+ "I cannot execute this tool request because the parameters may be unsafe."
272+ """ ,
273+ """
274+ models: []
275+ passthrough: true
276+ rails:
277+ tool_output:
278+ flows:
279+ - validate tool parameters
280+ """ ,
281+ )
282+ rails = LLMRails (config )
283+ rails .runtime .register_action (validate_tool_parameters , name = "validate_tool_parameters" )
284+
285+ messages = [
286+ {"role" : "user" , "content" : "Use the requested tool" },
287+ {
288+ "role" : "assistant" ,
289+ "content" : "" ,
290+ "tool_calls" : [
291+ {
292+ "id" : "call_safe" ,
293+ "type" : "function" ,
294+ "function" : {
295+ "name" : "safe_tool" ,
296+ "arguments" : '{"param": "safe value"}' ,
297+ },
298+ }
299+ ],
300+ },
301+ ]
302+
303+ result = await rails .generate_async (messages = messages , options = {"rails" : {"dialog" : False }})
304+
305+ assert isinstance (result , GenerationResponse )
306+ assert result .tool_calls == [
307+ {
308+ "id" : "call_safe" ,
309+ "type" : "function" ,
310+ "function" : {
311+ "name" : "safe_tool" ,
312+ "arguments" : '{"param": "safe value"}' ,
313+ },
314+ }
315+ ]
0 commit comments