Skip to content

Commit

Permalink
Merge pull request #458 from kreneskyp/repl_error_handling
Browse files Browse the repository at this point in the history
Better error handling for RunSkill and REPLs
  • Loading branch information
kreneskyp authored Feb 24, 2024
2 parents 2470762 + 46b1b04 commit 74f63a5
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 6 deletions.
7 changes: 7 additions & 0 deletions ix/skills/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
],
)

Expand Down
68 changes: 62 additions & 6 deletions ix/skills/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
import json
import os
import subprocess
import sys
import textwrap
import traceback
from typing import Dict, Any, Optional

from langchain_experimental.utilities import PythonREPL
from pydantic import BaseModel


class MissingTypeHintError(Exception):
Expand Down Expand Up @@ -102,24 +105,77 @@ def parse_skill(
return func_name, input_schema, description


class ErrorResponse(BaseModel):
message: str
traceback: str
line: int


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()
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
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit 74f63a5

Please sign in to comment.