Skip to content

Commit

Permalink
wrap user facing exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
irgolic committed Dec 5, 2023
1 parent 28355fa commit b437d85
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 124 deletions.
7 changes: 7 additions & 0 deletions guardrails/classes/history/call.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,13 @@ def error(self) -> Optional[str]:
return None
return self.iterations.last.error # type: ignore

@property
def exception(self) -> Optional[Exception]:
"""The exception that interrupted the run."""
if self.iterations.empty():
return None
return self.iterations.last.exception # type: ignore

@property
def failed_validations(self) -> Stack[ValidatorLogs]:
"""The validator logs for any validations that failed during the
Expand Down
5 changes: 5 additions & 0 deletions guardrails/classes/history/iteration.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ def error(self) -> Optional[str]:
this iteration."""
return self.outputs.error

@property
def exception(self) -> Optional[Exception]:
"""The exception that interrupted this iteration."""
return self.outputs.exception

@property
def failed_validations(self) -> List[ValidatorLogs]:
"""The validator logs for any validations that failed during this
Expand Down
3 changes: 3 additions & 0 deletions guardrails/classes/history/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class Outputs(ArbitraryModel):
"that raised and interrupted the process.",
default=None,
)
exception: Optional[Exception] = Field(
description="The exception that interrupted the process.", default=None
)

def _all_empty(self) -> bool:
return (
Expand Down
36 changes: 23 additions & 13 deletions guardrails/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from guardrails.logger import logger, set_scope
from guardrails.prompt import Instructions, Prompt
from guardrails.schema import Schema, StringSchema
from guardrails.utils.exception_utils import UserFacingException
from guardrails.utils.llm_response import LLMResponse
from guardrails.utils.reask_utils import NonParseableReAsk, ReAsk, reasks_to_dict
from guardrails.validator_base import ValidatorError
Expand Down Expand Up @@ -185,9 +186,8 @@ def __call__(
prompt_params=prompt_params,
include_instructions=include_instructions,
)
# TODO decide how to handle errors
except (ValidatorError, ValueError) as e:
raise e
except UserFacingException as e:
raise e.original_exception
except Exception as e:
error_message = str(e)
return call_log, error_message
Expand Down Expand Up @@ -273,6 +273,7 @@ def step(
index, raw_output, output_schema
)
if parsing_error:
iteration.outputs.exception = parsing_error
iteration.outputs.error = str(parsing_error)

iteration.outputs.parsed_output = parsed_output
Expand All @@ -298,6 +299,7 @@ def step(
except Exception as e:
error_message = str(e)
iteration.outputs.error = error_message
iteration.outputs.exception = e
raise e
return iteration

Expand All @@ -322,16 +324,18 @@ def prepare(
"""
with start_action(action_type="prepare", index=index) as action:
if api is None:
raise ValueError("API must be provided.")
raise UserFacingException(ValueError("API must be provided."))

if prompt_params is None:
prompt_params = {}

if msg_history:
if prompt_schema is not None or instructions_schema is not None:
raise ValueError(
"Prompt and instructions validation are "
"not supported when using message history."
raise UserFacingException(
ValueError(
"Prompt and instructions validation are "
"not supported when using message history."
)
)
msg_history = copy.deepcopy(msg_history)
# Format any variables in the message history with the prompt params.
Expand Down Expand Up @@ -361,9 +365,11 @@ def prepare(
raise ValidatorError("Message history validation failed")
elif prompt is not None:
if msg_history_schema is not None:
raise ValueError(
"Message history validation is "
"not supported when using prompt/instructions."
raise UserFacingException(
ValueError(
"Message history validation is "
"not supported when using prompt/instructions."
)
)
if isinstance(prompt, str):
prompt = Prompt(prompt)
Expand Down Expand Up @@ -417,7 +423,9 @@ def prepare(
)
instructions = Instructions(validated_instructions)
else:
raise ValueError("Prompt or message history must be provided.")
raise UserFacingException(
ValueError("Prompt or message history must be provided.")
)

action.log(
message_type="info",
Expand Down Expand Up @@ -692,8 +700,8 @@ async def async_run(
output_schema,
prompt_params=prompt_params,
)
except (ValidatorError, ValueError) as e:
raise e
except UserFacingException as e:
raise e.original_exception
except Exception as e:
error_message = str(e)

Expand Down Expand Up @@ -777,6 +785,7 @@ async def async_step(
# Parse: parse the output.
parsed_output, parsing_error = self.parse(index, output, output_schema)
if parsing_error:
iteration.outputs.exception = parsing_error
iteration.outputs.error = str(parsing_error)

iteration.outputs.parsed_output = parsed_output
Expand All @@ -801,6 +810,7 @@ async def async_step(
except Exception as e:
error_message = str(e)
iteration.outputs.error = error_message
iteration.outputs.exception = e
raise e
return iteration

Expand Down
9 changes: 9 additions & 0 deletions guardrails/utils/exception_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class UserFacingException(Exception):
"""Wraps an exception to denote it as user-facing.
It will be unwrapped in runner.
"""

def __init__(self, original_exception: Exception):
super().__init__()
self.original_exception = original_exception
4 changes: 4 additions & 0 deletions tests/integration_tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ async def test_sync_async_step_equivalence(mocker):
None,
{},
None,
None,
None,
OUTPUT_SCHEMA,
call_log,
OUTPUT,
Expand All @@ -150,6 +152,8 @@ async def test_sync_async_step_equivalence(mocker):
None,
{},
None,
None,
None,
OUTPUT_SCHEMA,
call_log,
OUTPUT,
Expand Down
Loading

0 comments on commit b437d85

Please sign in to comment.