Skip to content

Commit

Permalink
[Error Handling] reduce unknown rate for error category and target (#…
Browse files Browse the repository at this point in the history
…1944)

# Description
Please add an informative description that covers that changes made by
the pull request and link all relevant issues.
**Optimization exception error handling:**
1. The error category defaults to system error, and only when displayed
as user error exception is a user error
2. Add judgment logic to the error target to determine which module the
thrown error comes from
3. Add more detailed error details to facilitate subsequent positioning
and repair of errors


![image](https://github.com/microsoft/promptflow/assets/23182548/ef683a6c-1cda-48b6-b41b-d356fab43632)


# All Promptflow Contribution checklist:
- [x] **The pull request does not introduce [breaking changes].**
- [ ] **CHANGELOG is updated for new features, bug fixes or other
significant changes.**
- [ ] **I have read the [contribution guidelines](../CONTRIBUTING.md).**
- [ ] **Create an issue and link to the pull request to get dedicated
review from promptflow team. Learn more: [suggested
workflow](../CONTRIBUTING.md#suggested-workflow).**

## General Guidelines and Best Practices
- [x] Title of the pull request is clear and informative.
- [ ] There are a small number of commits, each of which have an
informative message. This means that previously merged commits do not
appear in the history of the PR. For more information on cleaning up the
commits in your PR, [see this
page](https://github.com/Azure/azure-powershell/blob/master/documentation/development-docs/cleaning-up-commits.md).

### Testing Guidelines
- [x] Pull request includes test coverage for the included changes.
  • Loading branch information
Stephen1993 authored Feb 4, 2024
1 parent 284825e commit 5dc4210
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 126 deletions.
120 changes: 74 additions & 46 deletions src/promptflow/promptflow/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import traceback
from enum import Enum
from functools import cached_property
from typing import Dict

from azure.core.exceptions import HttpResponseError


class ErrorCategory(str, Enum):
Expand Down Expand Up @@ -223,19 +226,10 @@ class ValidationException(UserErrorException):

class _ErrorInfo:
@classmethod
def get_error_info(cls, e: Exception):
if not isinstance(e, Exception):
return None, None, None, None, None
def get_error_info(cls, e: BaseException):
if not isinstance(e, BaseException):
return ErrorCategory.UNKNOWN, type(e).__name__, ErrorTarget.UNKNOWN, "", ""

e = cls.select_exception(e)
if cls._is_system_error(e):
return (
ErrorCategory.SYSTEM_ERROR,
cls._error_type(e),
cls._error_target(e),
cls._error_message(e),
cls._error_detail(e),
)
if cls._is_user_error(e):
return (
ErrorCategory.USER_ERROR,
Expand All @@ -245,37 +239,35 @@ def get_error_info(cls, e: Exception):
cls._error_detail(e),
)

return ErrorCategory.UNKNOWN, cls._error_type(e), ErrorTarget.UNKNOWN, "", cls._error_detail(e)

@classmethod
def select_exception(cls, e: Exception):
"""Select the exception in e and e.__cause__, and prioritize the Exception defined in the promptflow."""

if isinstance(e, PromptflowException):
return e

# raise Exception("message") from PromptflowException("message")
if e.__cause__ and isinstance(e.__cause__, PromptflowException):
return e.__cause__

return e
return (
ErrorCategory.SYSTEM_ERROR,
cls._error_type(e),
cls._error_target(e),
cls._error_message(e),
cls._error_detail(e),
)

@classmethod
def _is_system_error(cls, e: Exception):
def _is_system_error(cls, e: BaseException):
if isinstance(e, SystemErrorException):
return True
if isinstance(e, HttpResponseError):
status_code = str(e.status_code)
# Except for 429, 400-499 are all client errors.
if not status_code.startswith("4") and status_code != "429":
return True

return False

@classmethod
def _is_user_error(cls, e: Exception):
def _is_user_error(cls, e: BaseException):
if isinstance(e, UserErrorException):
return True

return False

@classmethod
def _error_type(cls, e: Exception):
def _error_type(cls, e: BaseException):
"""Return exception type.
Note:
For PromptflowException(error=ValueError(message="xxx")) or
Expand All @@ -292,31 +284,67 @@ def _error_type(cls, e: Exception):
return error_type

@classmethod
def _error_target(cls, e: Exception):
return getattr(e, "target", ErrorTarget.UNKNOWN)
def _error_target(cls, e: BaseException):
error_target = getattr(e, "target", ErrorTarget.UNKNOWN)
if error_target != ErrorTarget.UNKNOWN:
return error_target

module_target_map = cls._module_target_map()
exception_codes = cls._get_exception_codes(e)
for exception_code in exception_codes[::-1]:
for module_name, target in module_target_map.items():
# For example: "promptflow.executor" in "promptflow.executor._errors"
if module_name in exception_code["module"]:
return target

return ErrorTarget.EXECUTOR

@classmethod
def _module_target_map(cls) -> Dict[str, ErrorTarget]:
return {
"promptflow._sdk": ErrorTarget.CONTROL_PLANE_SDK,
"promptflow._cli": ErrorTarget.CONTROL_PLANE_SDK,
"promptflow.azure": ErrorTarget.CONTROL_PLANE_SDK,
"promptflow.connections": ErrorTarget.CONTROL_PLANE_SDK,
"promptflow.entities": ErrorTarget.CONTROL_PLANE_SDK,
"promptflow.operations": ErrorTarget.CONTROL_PLANE_SDK,
"promptflow.executor": ErrorTarget.EXECUTOR,
"promptflow._core": ErrorTarget.EXECUTOR,
"promptflow.batch": ErrorTarget.EXECUTOR,
"promptflow.contracts": ErrorTarget.EXECUTOR,
"promptflow._utils": ErrorTarget.EXECUTOR,
"promptflow._internal": ErrorTarget.EXECUTOR,
"promptflow.integrations": ErrorTarget.EXECUTOR,
"promptflow.storage": ErrorTarget.EXECUTOR,
"promptflow.tools": ErrorTarget.TOOL,
}

@classmethod
def _error_message(cls, e: Exception):
def _error_message(cls, e: BaseException):
return getattr(e, "message_format", "")

@classmethod
def _error_detail(cls, e: Exception):
def _error_detail(cls, e: BaseException):
promptflow_codes = cls._promptflow_error_traceback(e)
inner_exception = e.inner_exception if isinstance(e, PromptflowException) else e.__cause__
if inner_exception:
promptflow_codes += "The above exception was the direct cause of the following exception:\n"
promptflow_codes += cls._promptflow_error_traceback(inner_exception)

return promptflow_codes

@classmethod
def _promptflow_error_traceback(cls, e: BaseException):
exception_codes = cls._get_exception_codes(e)
exception_code = None
for item in exception_codes[::-1]:
if "promptflow" in item["module"]: # Only record information within the promptflow package
exception_code = item
break
if not exception_code:
return ""
return (
f"module={exception_code['module']}, "
f"code={exception_code['exception_code']}, "
f"lineno={exception_code['lineno']}."
)
promptflow_codes = ""
for item in exception_codes:
if "promptflow" in item["module"]: # Only record the promptflow package and code.
promptflow_codes += f"{item['module']}, line {item['lineno']}, {item['exception_code']}\n"

return promptflow_codes

@classmethod
def _get_exception_codes(cls, e: Exception) -> list:
def _get_exception_codes(cls, e: BaseException) -> list:
"""
Obtain information on each line of the traceback, including the module name,
exception code and lineno where the error occurred.
Expand Down
Loading

0 comments on commit 5dc4210

Please sign in to comment.