narrow WorkloadProfiler.profile classifier except to (AttributeError, TypeError, ValueError, KeyError)#12
Conversation
… TypeError, ValueError, KeyError) The 10 axis classifiers operate on a ChatCompletionRequest: request.messages iteration, msg.role / msg.content attribute access, request.estimate_tokens(), request.get_content_text(), and string operations on content. Real failure modes for those operations are AttributeError (msg missing role/content or a list-content part missing .type), TypeError (non-iterable messages or non-string content parts), ValueError (custom validator ValueErrors in some axes), and KeyError (dict-style content parts). The previous bare 'except Exception:' also caught KeyboardInterrupt when the user cancelled an in-flight classify call, SystemExit from a plugin doing sys.exit(), MemoryError from a malformed request body, and any programmer error in the surrounding loop body — none of which should silently degrade the axis to 'unknown' confidence 0.0; the axis should be skipped with the real error visible, and a Ctrl-C should actually interrupt the request. logger.exception is preserved so the failure is still logged with the full traceback at the same severity as before; only the catch list is narrowed.
Reviewer's guide (collapsed on small PRs)Reviewer's GuideNarrows the exception handling in WorkloadProfiler.profile so that axis classifier failures only degrade the axis result for expected input/validation errors, while no longer masking system-level or programmer exceptions. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
📝 WalkthroughWalkthroughIn ChangesWorkloadProfiler Exception Narrowing
Estimated code review effort🎯 2 (Simple) | ⏱️ ~5 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@freerelay/data_plane/profiler/workload.py`:
- Line 137: The exception handler at line 137 catches only a narrow tuple of
exception types (AttributeError, TypeError, ValueError, KeyError), but
classifier implementations that access nested attributes on message objects
(like msg.content.type, request.response_format.type, msg.tool_calls) could
legitimately raise additional exceptions such as RuntimeError or custom
validation exceptions when these attributes don't exist or have unexpected
types. Expand the exception tuple in the except clause to include Exception or a
broader set of exception types so that any unhandled exceptions from attribute
access failures produce fallback values instead of propagating and breaking the
profiling pipeline.
- Around line 137-140: The narrowed exception handling in the profile() method
only catches specific exception types, allowing other exceptions to propagate
uncaught through the call stack. Add error handling either by broadening the
exception handling in the profile() method to catch all exceptions while
preserving fallback values (setting axis values to "unknown" and confidences to
0.0), or by wrapping the self.profiler.profile(request) call in
_prepare_context() method of the routing engine with a try-catch block to
gracefully handle any unexpected exceptions that escape from the profile()
method before they reach the FastAPI endpoint handler.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: de3daef3-67f5-4f1b-a14e-ab77d8b72803
📒 Files selected for processing (1)
freerelay/data_plane/profiler/workload.py
| values[axis_name] = value | ||
| axis_confidences[axis_name] = confidence | ||
| except Exception: | ||
| except (AttributeError, TypeError, ValueError, KeyError): |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Search for raise statements in axis classifier implementations
# Find all raise statements in axis classifiers
rg -nP -C 3 '^\s*raise\s+' --type=py -g '**/axes/*.py'
# Also check for exception handling within classifiers that might re-raise
rg -nP -C 3 '^\s*except\s+' --type=py -g '**/axes/*.py'Repository: HrachShah/FreeRelay
Length of output: 45
🏁 Script executed:
#!/bin/bash
# First, let's explore the repository structure
echo "=== Repository structure ==="
find . -type f -name "*.py" | grep -E "(workload|profiler|axes)" | head -20
# Also list files in common locations
echo -e "\n=== Files at repo root ==="
ls -la | grep -E "\.py$|\.md$|^d"
# Look for the workload.py file
echo -e "\n=== Finding workload.py ==="
find . -name "workload.py" -type fRepository: HrachShah/FreeRelay
Length of output: 2104
🏁 Script executed:
#!/bin/bash
# Let's check the directory structure for profiler
echo "=== Looking for profiler directory ==="
find . -type d -name profiler
echo -e "\n=== Looking for axes directory ==="
find . -type d -name axesRepository: HrachShah/FreeRelay
Length of output: 207
🏁 Script executed:
#!/bin/bash
# Read workload.py around line 137
echo "=== workload.py around line 137 ==="
sed -n '125,150p' freerelay/data_plane/profiler/workload.py
echo -e "\n=== Full context of exception handling ==="
sed -n '100,160p' freerelay/data_plane/profiler/workload.pyRepository: HrachShah/FreeRelay
Length of output: 4159
🏁 Script executed:
#!/bin/bash
# Search for raise statements in all axis classifiers
echo "=== Searching for raise statements in axes classifiers ==="
for file in freerelay/data_plane/profiler/axes/*.py; do
echo "--- $file ---"
rg -n 'raise ' "$file"
doneRepository: HrachShah/FreeRelay
Length of output: 798
🏁 Script executed:
#!/bin/bash
# Search for exception types being caught or raised
echo "=== Searching for except statements in axes classifiers ==="
for file in freerelay/data_plane/profiler/axes/*.py; do
echo "--- $file ---"
rg -n 'except ' "$file"
doneRepository: HrachShah/FreeRelay
Length of output: 799
🏁 Script executed:
#!/bin/bash
# Read all axis classifier files to understand their implementation
for file in freerelay/data_plane/profiler/axes/*.py; do
if [[ "$file" != *"__init__"* ]]; then
echo "===== $file ====="
cat "$file"
echo ""
fi
doneRepository: HrachShah/FreeRelay
Length of output: 24575
Verify that classifier implementations don't raise other legitimate exception types.
The exception tuple (AttributeError, TypeError, ValueError, KeyError) is too narrow. While the 10 axis classifiers don't explicitly raise exceptions, they perform operations that could legitimately raise unhandled exceptions for valid input failures:
- AttributeError: Accessing nested attributes like
msg.content.typewhenmsg.contentis a string instead of a list, orrequest.response_format.typewhenresponse_formatis None - RuntimeError or custom validation exceptions: Future classifier changes may add validation logic raising these types
- Other attribute access failures: Accessing attributes that may not exist on message objects (e.g.,
msg.tool_calls)
When these exceptions occur, they will propagate instead of producing fallback "unknown"/0.0 values, potentially breaking the profiling pipeline.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@freerelay/data_plane/profiler/workload.py` at line 137, The exception handler
at line 137 catches only a narrow tuple of exception types (AttributeError,
TypeError, ValueError, KeyError), but classifier implementations that access
nested attributes on message objects (like msg.content.type,
request.response_format.type, msg.tool_calls) could legitimately raise
additional exceptions such as RuntimeError or custom validation exceptions when
these attributes don't exist or have unexpected types. Expand the exception
tuple in the except clause to include Exception or a broader set of exception
types so that any unhandled exceptions from attribute access failures produce
fallback values instead of propagating and breaking the profiling pipeline.
| except (AttributeError, TypeError, ValueError, KeyError): | ||
| logger.exception("Axis classifier %s failed", axis_name) | ||
| values[axis_name] = "unknown" | ||
| axis_confidences[axis_name] = 0.0 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check error handling around profiler.profile() call in routing engine
# Search for the profile() call with surrounding context to see error handling
rg -nP -A 10 -B 5 'self\.profiler\.profile\(' --type=py -g 'engine.py'Repository: HrachShah/FreeRelay
Length of output: 1278
🏁 Script executed:
#!/bin/bash
# Check the full _prepare_request_context method to see broader error handling context
rg -n 'def _prepare_request_context' --type=py freerelay/core/routing/engine.py -A 25Repository: HrachShah/FreeRelay
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Check for callers of _prepare_request_context to see if they handle exceptions
rg -n '_prepare_request_context' --type=py freerelay/core/routing/engine.py -B 2 -A 2 | head -30Repository: HrachShah/FreeRelay
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Find axis classifier implementations to check what exceptions they can raise
fd -t f -e py freerelay/data_plane/profiler/ | xargs grep -l 'def classify_axis\|class.*Classifier' | head -5Repository: HrachShah/FreeRelay
Length of output: 489
🏁 Script executed:
#!/bin/bash
# Get the full _prepare_request_context method
rg -n 'def _prepare_request_context' --type=py -A 30 freerelay/core/routing/engine.pyRepository: HrachShah/FreeRelay
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Find classifiers in the profiler directory
fd . freerelay/data_plane/profiler/ --type=f --extension=py | head -10Repository: HrachShah/FreeRelay
Length of output: 589
🏁 Script executed:
#!/bin/bash
# Check workload.py profile method to see the full try-except structure
rg -n 'def profile' freerelay/data_plane/profiler/workload.py -A 50 | head -80Repository: HrachShah/FreeRelay
Length of output: 2484
🏁 Script executed:
#!/bin/bash
# Look at one classifier implementation to see what exceptions it raises
cat -n freerelay/data_plane/profiler/axes/latency_class.pyRepository: HrachShah/FreeRelay
Length of output: 2095
🏁 Script executed:
#!/bin/bash
# Find what method contains the profile() call at line 210
rg -n 'def ' freerelay/core/routing/engine.py | grep -B1 -A1 '210'Repository: HrachShah/FreeRelay
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Search for any method that calls _prepare_request_context or wraps it
rg -n '_prepare_request_context\|prepare_request_context' freerelay/core/routing/engine.py -B 3 -A 3Repository: HrachShah/FreeRelay
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Get full engine.py to find the method containing line 210
wc -l freerelay/core/routing/engine.pyRepository: HrachShah/FreeRelay
Length of output: 100
🏁 Script executed:
#!/bin/bash
# Look at lines around 210 in engine.py
sed -n '200,230p' freerelay/core/routing/engine.pyRepository: HrachShah/FreeRelay
Length of output: 1234
🏁 Script executed:
#!/bin/bash
# Search for 'prepare_request_context' more broadly (without underscore)
rg 'prepare_request_context' freerelay/core/routing/engine.pyRepository: HrachShah/FreeRelay
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Look for the method signature that contains line 210 by searching backwards
sed -n '180,220p' freerelay/core/routing/engine.pyRepository: HrachShah/FreeRelay
Length of output: 1539
🏁 Script executed:
#!/bin/bash
# Find all callers of _prepare_context
rg '_prepare_context' freerelay/core/routing/engine.py -B 2 -A 2Repository: HrachShah/FreeRelay
Length of output: 601
🏁 Script executed:
#!/bin/bash
# Check the full methods that call _prepare_context
rg -n 'def ' freerelay/core/routing/engine.py | head -20Repository: HrachShah/FreeRelay
Length of output: 968
🏁 Script executed:
#!/bin/bash
# Look at classifier implementations to see if they raise exceptions beyond the 4 caught types
# Check a few classifiers for exception patterns
rg 'raise\|assert\|\.get\(\)\[' freerelay/data_plane/profiler/axes/ --type=pyRepository: HrachShah/FreeRelay
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Check the full route() method to see error handling context around _prepare_context call
sed -n '332,370p' freerelay/core/routing/engine.pyRepository: HrachShah/FreeRelay
Length of output: 1512
🏁 Script executed:
#!/bin/bash
# Check the full route_stream() method
sed -n '437,475p' freerelay/core/routing/engine.pyRepository: HrachShah/FreeRelay
Length of output: 1665
🏁 Script executed:
#!/bin/bash
# Look at all classifiers to see if any raise exceptions explicitly
fd . freerelay/data_plane/profiler/axes/ --type=f --extension=py -x grep -l 'raise ' {} \;Repository: HrachShah/FreeRelay
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Check if route() methods are wrapped in any middleware or error handlers
# Look for FastAPI/async handlers or middleware patterns
rg -n 'async def' freerelay/core/routing/engine.py | grep -E 'route|execute'Repository: HrachShah/FreeRelay
Length of output: 153
🏁 Script executed:
#!/bin/bash
# Search for how route() is called from outside the engine class (likely in an API handler)
rg -n '\.route\(' freerelay/ --type=py | head -10Repository: HrachShah/FreeRelay
Length of output: 1190
🏁 Script executed:
#!/bin/bash
# Check if there's a higher-level error handler in the app or API layer
fd . freerelay -name 'main.py' -o -name 'app.py' -o -name 'api.py' | head -5Repository: HrachShah/FreeRelay
Length of output: 294
🏁 Script executed:
#!/bin/bash
# Look at main.py around line 242 to see error handling context
sed -n '235,260p' freerelay/main.pyRepository: HrachShah/FreeRelay
Length of output: 875
🏁 Script executed:
#!/bin/bash
# Check broader context in main.py for the endpoint handler
sed -n '220,280p' freerelay/main.pyRepository: HrachShah/FreeRelay
Length of output: 2211
🏁 Script executed:
#!/bin/bash
# Check one of the strategy implementations to see if they have error handling
sed -n '60,75p' freerelay/data_plane/execution/strategies/cheap_first_escalate.pyRepository: HrachShah/FreeRelay
Length of output: 611
Add error handling around the profiler.profile() call in the routing engine.
With the narrowed exception handling at line 137, exceptions other than AttributeError, TypeError, ValueError, and KeyError will now propagate out of this method. The routing engine at freerelay/core/routing/engine.py calls self.profiler.profile(request) in _prepare_context() without try-catch, and the endpoint handler in main.py:242 calls engine.route() without error handling. Any unexpected exception from a classifier will propagate uncaught to the FastAPI endpoint, causing an unhandled 500 error.
Either catch and log unexpected exceptions from classifiers (preserving fallback values), or add error handling at the call site in the routing engine to gracefully handle exceptions that escape profile().
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@freerelay/data_plane/profiler/workload.py` around lines 137 - 140, The
narrowed exception handling in the profile() method only catches specific
exception types, allowing other exceptions to propagate uncaught through the
call stack. Add error handling either by broadening the exception handling in
the profile() method to catch all exceptions while preserving fallback values
(setting axis values to "unknown" and confidences to 0.0), or by wrapping the
self.profiler.profile(request) call in _prepare_context() method of the routing
engine with a try-catch block to gracefully handle any unexpected exceptions
that escape from the profile() method before they reach the FastAPI endpoint
handler.
The 10 axis classifiers in freerelay/data_plane/profiler/axes/ each take a ChatCompletionRequest and a headers dict and return (value, confidence). Real failure modes for those calls are:
The previous bare
except Exception:also caught KeyboardInterrupt (the user cancelling an in-flight classify call should actually cancel the request, not silently degrade the axis to "unknown" confidence 0.0), SystemExit (a classifier plugin doingsys.exit()), MemoryError from a malformed request body, and any programmer error in the surrounding loop body — none of which should be masked.logger.exceptionis preserved so the failure is still logged with the full traceback at the same severity as before; only the catch list is narrowed.Verified:
python3 -c "import ast; ast.parse(open('freerelay/data_plane/profiler/workload.py').read())"parses cleanly.Summary by Sourcery
Bug Fixes:
Summary by CodeRabbit
Release Notes