From 74fb223087a18de442e010618fce34f43744c985 Mon Sep 17 00:00:00 2001 From: Peter Krenesky Date: Sat, 24 Feb 2024 12:40:04 -0800 Subject: [PATCH 1/2] Better error handling for RunSkill and REPLs --- ix/skills/components.py | 7 ++ ix/skills/utils.py | 67 +++++++++++++++++-- .../ix.skills.runnable.RunSkill.from_db.json | 23 +++++++ 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/ix/skills/components.py b/ix/skills/components.py index 48b37690..ff2052ad 100644 --- a/ix/skills/components.py +++ b/ix/skills/components.py @@ -41,6 +41,13 @@ description="Skill to run", input_type="IX:skill", ), + NodeTypeField( + name="raise_error", + label="Halt on error", + type="boolean", + description="Halt on error by raising exception", + default = False, + ) ], ) diff --git a/ix/skills/utils.py b/ix/skills/utils.py index 305e66ac..bf3e1854 100644 --- a/ix/skills/utils.py +++ b/ix/skills/utils.py @@ -6,6 +6,7 @@ from typing import Dict, Any, Optional from langchain_experimental.utilities import PythonREPL +from pydantic import BaseModel class MissingTypeHintError(Exception): @@ -102,24 +103,78 @@ def parse_skill( return func_name, input_schema, description + +class ErrorResponse(BaseModel): + message: str + traceback: str + line: int + + +import traceback +import sys + +def execute_function(function, raise_errors: bool = False): + try: + result = function() + return result + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + fname = exc_tb.tb_frame.f_code.co_filename + line_number = exc_tb.tb_lineno + error_response = ErrorResponse( + message=str(e), + traceback=''.join(traceback.format_tb(exc_tb)), + line=line_number + ) + if raise_errors: + raise e + else: + return error_response + + def run_code_with_repl( - code: str, function: str, input: Dict[str, Any], timeout: Optional[int] = None -) -> str: + code: str, function: str, input: Dict[str, Any], timeout: Optional[int] = None, raise_errors: bool = False +) -> Any: # HAX: use globals for I/O with the REPL. Hacky way to avoid serialization. func_output = [] repl = PythonREPL( - _globals={"func_input": input, "func_output": func_output, "json": json} + _globals={ + "func_input": input, + "func_output": func_output, + "json": json, + "ErrorResponse": ErrorResponse, + } ) # Prepare the command to run in the REPL command = textwrap.dedent( f""" +import traceback +import sys + {code} -output = {function}(**func_input) -func_output.append(output) + +try: + output = {function}(**func_input) + func_output.append(output) +except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + error_response = ErrorResponse( + message=str(e), + traceback=''.join(traceback.format_tb(exc_tb)), + line=exc_tb.tb_lineno + ) + func_output.append(error_response) + if raise_errors: + raise e """ ) # Run the command in the PythonREPL response = repl.run(command, timeout) - return func_output[0] if func_output else response + output = func_output[0] if func_output else response + + if isinstance(output, ErrorResponse) and raise_errors: + raise Exception(output.message) + + return output diff --git a/test_data/snapshots/components/ix.skills.runnable.RunSkill.from_db.json b/test_data/snapshots/components/ix.skills.runnable.RunSkill.from_db.json index 42e31f2d..431bf6b1 100644 --- a/test_data/snapshots/components/ix.skills.runnable.RunSkill.from_db.json +++ b/test_data/snapshots/components/ix.skills.runnable.RunSkill.from_db.json @@ -4,6 +4,12 @@ "config_schema": { "display_groups": null, "properties": { + "raise_error": { + "default": false, + "description": "Halt on error by raising exception", + "label": "Halt on error", + "type": "boolean" + }, "skill_id": { "description": "Skill to run", "input_type": "IX:skill", @@ -35,6 +41,23 @@ "step": null, "style": null, "type": "string" + }, + { + "choices": null, + "default": false, + "description": "Halt on error by raising exception", + "init_type": "init", + "input_type": null, + "label": "Halt on error", + "max": null, + "min": null, + "name": "raise_error", + "parent": null, + "required": false, + "secret_key": null, + "step": null, + "style": null, + "type": "boolean" } ], "name": "Run Skill", From 46b1b043ffad4e16869c4dd78782b0c5eed4f265 Mon Sep 17 00:00:00 2001 From: Peter Krenesky Date: Sat, 24 Feb 2024 14:15:05 -0800 Subject: [PATCH 2/2] linting --- ix/skills/components.py | 4 ++-- ix/skills/utils.py | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ix/skills/components.py b/ix/skills/components.py index ff2052ad..b9d9952c 100644 --- a/ix/skills/components.py +++ b/ix/skills/components.py @@ -46,8 +46,8 @@ label="Halt on error", type="boolean", description="Halt on error by raising exception", - default = False, - ) + default=False, + ), ], ) diff --git a/ix/skills/utils.py b/ix/skills/utils.py index bf3e1854..200d8d05 100644 --- a/ix/skills/utils.py +++ b/ix/skills/utils.py @@ -2,7 +2,9 @@ import json import os import subprocess +import sys import textwrap +import traceback from typing import Dict, Any, Optional from langchain_experimental.utilities import PythonREPL @@ -103,28 +105,23 @@ def parse_skill( return func_name, input_schema, description - class ErrorResponse(BaseModel): message: str traceback: str line: int -import traceback -import sys - def execute_function(function, raise_errors: bool = False): try: result = function() return result except Exception as e: exc_type, exc_obj, exc_tb = sys.exc_info() - fname = exc_tb.tb_frame.f_code.co_filename line_number = exc_tb.tb_lineno error_response = ErrorResponse( message=str(e), - traceback=''.join(traceback.format_tb(exc_tb)), - line=line_number + traceback="".join(traceback.format_tb(exc_tb)), + line=line_number, ) if raise_errors: raise e @@ -133,7 +130,11 @@ def execute_function(function, raise_errors: bool = False): def run_code_with_repl( - code: str, function: str, input: Dict[str, Any], timeout: Optional[int] = None, raise_errors: bool = False + code: str, + function: str, + input: Dict[str, Any], + timeout: Optional[int] = None, + raise_errors: bool = False, ) -> Any: # HAX: use globals for I/O with the REPL. Hacky way to avoid serialization. func_output = []