-
Notifications
You must be signed in to change notification settings - Fork 259
Bug/dg 314 show nicer errors webrtc #2103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bccd7db
2fd67da
e93ca4b
6a5fec1
f4749c2
0a66375
ec78811
579551d
990f96e
d77d39d
f6639c5
1ab89db
000c06a
538ca8f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,12 +3,21 @@ | |
| from pydantic import BaseModel, Field | ||
|
|
||
|
|
||
| class BlockTraceback(BaseModel): | ||
| traceback: Optional[str] = None | ||
| error_line: Optional[int] = None | ||
| code_snippet: Optional[str] = None | ||
| stdout: Optional[str] = None | ||
| stderr: Optional[str] = None | ||
|
|
||
|
|
||
| class WorkflowBlockError(BaseModel): | ||
| block_id: Optional[str] = None | ||
| block_type: Optional[str] = None | ||
| block_details: Optional[str] = None | ||
| property_name: Optional[str] = None | ||
| property_details: Optional[str] = None | ||
| block_traceback: Optional[BlockTraceback] = None | ||
|
|
||
|
|
||
| class WorkflowError(Exception): | ||
|
|
@@ -152,6 +161,45 @@ class WorkflowExecutionEngineError(WorkflowError): | |
| pass | ||
|
|
||
|
|
||
| class DynamicBlockCodeError(WorkflowExecutionEngineError): | ||
| """Exception for dynamic block code execution errors (errors provoked by user's code).""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| public_message: str, | ||
| context: str = "dynamic_block_code_execution", | ||
| inner_error: Optional[Exception] = None, | ||
| block_type_name: Optional[str] = None, | ||
| error_line: Optional[int] = None, | ||
| code_snippet: Optional[str] = None, | ||
| traceback_str: Optional[str] = None, | ||
| stdout: Optional[str] = None, | ||
| stderr: Optional[str] = None, | ||
| ): | ||
| super().__init__( | ||
| public_message=public_message, context=context, inner_error=inner_error | ||
| ) | ||
| self.block_type_name = block_type_name | ||
| self.error_line = error_line | ||
| self.code_snippet = code_snippet | ||
| self.traceback_str = traceback_str | ||
| self.stdout = stdout | ||
| self.stderr = stderr | ||
|
|
||
| @property | ||
| def block_traceback(self) -> Optional[BlockTraceback]: | ||
| """Construct BlockTraceback from error fields if any are present.""" | ||
| if not any([self.error_line, self.traceback_str, self.stdout, self.stderr]): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. code_snippet omitted from block_traceback existence checkLow Severity The |
||
| return None | ||
| return BlockTraceback( | ||
| error_line=self.error_line, | ||
| code_snippet=self.code_snippet, | ||
| traceback=self.traceback_str, | ||
| stdout=self.stdout, | ||
| stderr=self.stderr, | ||
| ) | ||
|
|
||
|
|
||
| class NotSupportedExecutionEngineError(WorkflowExecutionEngineError): | ||
| pass | ||
|
|
||
|
|
@@ -165,12 +213,14 @@ def __init__( | |
| self, | ||
| block_id: str, | ||
| block_type: str, | ||
| block_traceback: Optional[BlockTraceback] = None, | ||
| *args, | ||
| **kwargs, | ||
| ): | ||
| super().__init__(*args, **kwargs) | ||
| self.block_id = block_id | ||
| self.block_type = block_type | ||
| self.block_traceback = block_traceback | ||
|
|
||
|
|
||
| class ClientCausedStepExecutionError(WorkflowExecutionEngineError): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,5 @@ | ||
| import traceback | ||
| import types | ||
| from typing import List, Optional, Type | ||
| from typing import Any, Dict, List, Optional, Type | ||
|
|
||
| from inference.core.env import ( | ||
| ALLOW_CUSTOM_PYTHON_EXECUTION_IN_WORKFLOWS, | ||
|
|
@@ -10,6 +9,7 @@ | |
| from inference.core.exceptions import WorkspaceLoadError | ||
| from inference.core.roboflow_api import get_roboflow_workspace | ||
| from inference.core.workflows.errors import ( | ||
| DynamicBlockCodeError, | ||
| DynamicBlockError, | ||
| WorkflowEnvironmentConfigurationError, | ||
| ) | ||
|
|
@@ -40,6 +40,12 @@ | |
| # Shared globals dict for all custom python blocks in local mode | ||
| _LOCAL_SHARED_GLOBALS = {} | ||
|
|
||
| from inference.core.workflows.execution_engine.v1.dynamic_blocks.error_utils import ( | ||
| capture_output, | ||
| create_dynamic_block_code_error, | ||
| extract_code_snippet, | ||
| ) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mid-module import breaks convention and ordering expectationsLow Severity The import of |
||
|
|
||
|
|
||
| def assembly_custom_python_block( | ||
| block_type_name: str, | ||
|
|
@@ -49,13 +55,15 @@ def assembly_custom_python_block( | |
| api_key: Optional[str] = None, | ||
| skip_class_eval: Optional[bool] = False, | ||
| ) -> Type[WorkflowBlock]: | ||
|
|
||
| code_module = create_dynamic_module( | ||
| block_type_name=block_type_name, | ||
| python_code=python_code, | ||
| module_name=f"dynamic_module_{unique_identifier}", | ||
| api_key=api_key, | ||
| skip_class_eval=skip_class_eval, | ||
| ) | ||
|
|
||
| if not hasattr(code_module, python_code.run_function_name): | ||
| raise DynamicBlockError( | ||
| public_message=f"Cannot find function: {python_code.run_function_name} in declared code for " | ||
|
|
@@ -97,20 +105,19 @@ def run(self, *args, **kwargs) -> BlockResult: | |
| "`ALLOW_CUSTOM_PYTHON_EXECUTION_IN_WORKFLOWS=True`", | ||
| context="workflow_execution | step_execution | dynamic_step", | ||
| ) | ||
| import_lines_count = len(_get_python_code_imports(python_code).splitlines()) | ||
| try: | ||
| return run_function(self, *args, **kwargs) | ||
| with capture_output() as (stdout_buf, stderr_buf): | ||
| return run_function(self, *args, **kwargs) | ||
| except Exception as error: | ||
| tb = traceback.extract_tb(error.__traceback__) | ||
| if tb: | ||
| frame = tb[-1] | ||
| line_number = frame.lineno - len( | ||
| _get_python_code_imports(python_code).splitlines() | ||
| ) | ||
| function_name = frame.name | ||
| message = f"Error in line {line_number}, in {function_name}: {error.__class__.__name__}: {error}" | ||
| else: | ||
| message = f"{error.__class__.__name__}: {error}" | ||
| raise Exception(message) from error | ||
| raise create_dynamic_block_code_error( | ||
| error=error, | ||
| user_code=python_code.run_function_code or "", | ||
| import_lines_count=import_lines_count, | ||
| stdout=stdout_buf.getvalue() or None, | ||
| stderr=stderr_buf.getvalue() or None, | ||
| block_type_name=block_type_name, | ||
| ) from error | ||
|
|
||
| if python_code.init_function_code is not None and not hasattr( | ||
| code_module, python_code.init_function_name | ||
|
|
@@ -213,9 +220,23 @@ def create_dynamic_module( | |
| exec(code, dynamic_module.__dict__) | ||
| return dynamic_module | ||
| except Exception as error: | ||
| raise DynamicBlockError( | ||
| public_message=f"Error of type `{error.__class__.__name__}` encountered while attempting to " | ||
| f"create Python module with code for block: {block_type_name}. Error message: {error}. Full code:\n{code}", | ||
| context="workflow_compilation | dynamic_block_compilation | dynamic_module_creation", | ||
| error_line = getattr(error, "lineno", None) | ||
| code_snippet = None | ||
| if error_line and python_code.run_function_code: | ||
| import_lines_offset = len( | ||
| _get_python_code_imports(python_code).splitlines() | ||
| ) | ||
| error_line -= import_lines_offset | ||
| snippet = extract_code_snippet( | ||
| python_code.run_function_code, error_line | ||
| ) | ||
| code_snippet = snippet.lstrip("\n") if snippet else None | ||
|
|
||
| raise DynamicBlockCodeError( | ||
| public_message=f"{error.__class__.__name__}: {error}", | ||
| context="dynamic_block_code_compilation", | ||
| inner_error=error, | ||
| block_type_name=block_type_name, | ||
| error_line=error_line, | ||
| code_snippet=code_snippet, | ||
| ) from error | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DynamicBlockCodeError not handled in WebRTC worker path
High Severity
DynamicBlockCodeError(extendingWorkflowExecutionEngineError→WorkflowError) has no dedicated handler inwebrtc.py, so it falls through to the genericexcept WorkflowErrorhandler. This sendsexception_type="WorkflowError"instead of"DynamicBlockCodeError", discarding all structured error data (code snippet, traceback, stdout, stderr, block_type_name). On the receiving side inhttp_api.py, it's reconstructed as a plainWorkflowErrorand caught by the generic handler with a 500 status, rather than theDynamicBlockCodeError-specific handler that returns 400 with rich error details. Given the PR title is specifically about showing nicer errors in WebRTC, this defeats the purpose for dynamic block code errors in that path.Additional Locations (1)
inference/core/interfaces/http/http_api.py#L1831-L1836