From c3a8b749475ef72998b73c1628f34cb1c62b737d Mon Sep 17 00:00:00 2001 From: mmikita95 Date: Wed, 4 Dec 2024 11:19:49 +0300 Subject: [PATCH 1/4] fix: properly process tool calls with no arguments --- src/writer/ai.py | 64 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/src/writer/ai.py b/src/writer/ai.py index c86bf1753..70ac2fc42 100644 --- a/src/writer/ai.py +++ b/src/writer/ai.py @@ -1240,7 +1240,17 @@ def _tool_calls_ready(self): def _gather_tool_calls_messages(self): return { - index: ongoing_tool_call.get("res") + index: ongoing_tool_call.get( + "res", + { + "role": "tool", + "content": "ERROR: Failed to get function call result – " + + "the function was never called. The most likely reason " + + "is LLM never issuing a `finish_reason: 'tool_calls'`. " + + "Please DO NOT RETRY the function call and inform " + + "the user about the error." + } + ) for index, ongoing_tool_call in self._ongoing_tool_calls.items() } @@ -1534,6 +1544,14 @@ def _convert_argument_to_type(self, value: Any, target_type: str) -> Any: else: raise ValueError(f"Unsupported target type: {target_type}") + def _check_if_arguments_are_required(self, function_name: str) -> bool: + callable_entry = self._callable_registry.get(function_name) + callable_parameters = callable_entry.get("parameters") + return \ + callable_parameters is not None \ + and \ + callable_parameters != {} + def _execute_function_tool_call(self, index: int) -> dict: """ Executes the function call for the specified tool call index. @@ -1547,7 +1565,14 @@ def _execute_function_tool_call(self, index: int) -> dict: # Parse arguments and execute callable try: - parsed_arguments = json.loads(arguments) + if ( + not arguments + and + not self._check_if_arguments_are_required(function_name) + ): + parsed_arguments = {} + else: + parsed_arguments = json.loads(arguments) callable_entry = self._callable_registry.get(function_name) if callable_entry: @@ -1657,12 +1682,41 @@ def _process_tool_call( tool_call_arguments # Check if we have all necessary data to execute the function + tool_call_id, tool_call_name, tool_call_arguments = \ + self._ongoing_tool_calls[index]["tool_call_id"], \ + self._ongoing_tool_calls[index]["name"], \ + self._ongoing_tool_calls[index]["arguments"] + + tool_call_id_ready = tool_call_id is not None + tool_call_name_ready = tool_call_name is not None + tool_call_arguments_ready = \ + ( + tool_call_name_ready + and + ( + ( + not tool_call_arguments + and + not self._check_if_arguments_are_required( + tool_call_name + ) + ) + or + ( + isinstance( + tool_call_arguments, str + ) + and + tool_call_arguments.endswith("}") + ) + ) + ) if ( - self._ongoing_tool_calls[index]["tool_call_id"] is not None + tool_call_id_ready and - self._ongoing_tool_calls[index]["name"] is not None + tool_call_name_ready and - self._ongoing_tool_calls[index]["arguments"].endswith("}") + tool_call_arguments_ready ): follow_up_message = self._execute_function_tool_call(index) if follow_up_message: From 7a49671da95fd3245e5ec52cbccb19fa3e4e8d3a Mon Sep 17 00:00:00 2001 From: mmikita95 Date: Wed, 4 Dec 2024 11:20:54 +0300 Subject: [PATCH 2/4] fix: optional parameters for `create_function_tool` --- src/writer/ai.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/writer/ai.py b/src/writer/ai.py index 70ac2fc42..53474c97f 100644 --- a/src/writer/ai.py +++ b/src/writer/ai.py @@ -100,7 +100,7 @@ class FunctionTool(Tool): def create_function_tool( callable: Callable, name: str, - parameters: Optional[Dict[str, Dict[str, str]]], + parameters: Optional[Dict[str, Dict[str, str]]] = None, description: Optional[str] = None ) -> FunctionTool: parameters = parameters or {} From 2792d910cd904c2f9c28a1d350a34de888b18eee Mon Sep 17 00:00:00 2001 From: mmikita95 Date: Wed, 4 Dec 2024 12:16:44 +0300 Subject: [PATCH 3/4] chore: refactor for cleaner structure --- src/writer/ai.py | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/writer/ai.py b/src/writer/ai.py index 53474c97f..f045f03c5 100644 --- a/src/writer/ai.py +++ b/src/writer/ai.py @@ -1689,28 +1689,35 @@ def _process_tool_call( tool_call_id_ready = tool_call_id is not None tool_call_name_ready = tool_call_name is not None - tool_call_arguments_ready = \ - ( - tool_call_name_ready - and + + # Check whether the arguments are prepared properly - + # either present in correct format + # or should not be used due to not being required for the function + if tool_call_name_ready: + # Function name is needed to check the function for params + tool_call_arguments_not_required = \ ( - ( - not tool_call_arguments - and - not self._check_if_arguments_are_required( - tool_call_name - ) + not tool_call_arguments + and + not self._check_if_arguments_are_required( + tool_call_name ) - or - ( - isinstance( - tool_call_arguments, str - ) - and - tool_call_arguments.endswith("}") + ) + tool_call_arguments_formatted_properly = \ + ( + isinstance( + tool_call_arguments, str ) + and + tool_call_arguments.endswith("}") ) - ) + tool_call_arguments_ready = \ + tool_call_arguments_not_required \ + or \ + tool_call_arguments_formatted_properly + else: + tool_call_arguments_ready = False + if ( tool_call_id_ready and From 9930c1306278f1fe155d71f31efd8973c6721056 Mon Sep 17 00:00:00 2001 From: mmikita95 Date: Wed, 4 Dec 2024 12:25:28 +0300 Subject: [PATCH 4/4] fix: edge case of callable entry not being retrieved --- src/writer/ai.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/writer/ai.py b/src/writer/ai.py index f045f03c5..bbdbbfbb8 100644 --- a/src/writer/ai.py +++ b/src/writer/ai.py @@ -1546,6 +1546,11 @@ def _convert_argument_to_type(self, value: Any, target_type: str) -> Any: def _check_if_arguments_are_required(self, function_name: str) -> bool: callable_entry = self._callable_registry.get(function_name) + if not callable_entry: + raise RuntimeError( + f"Tried to check arguments of function {function_name} " + + "which is not present in the conversation's callable registry." + ) callable_parameters = callable_entry.get("parameters") return \ callable_parameters is not None \