Skip to content

narrow WorkloadProfiler.profile classifier except to (AttributeError, TypeError, ValueError, KeyError)#12

Open
HrachShah wants to merge 1 commit into
mainfrom
fix/workload-profiler-classifier-exception-narrow
Open

narrow WorkloadProfiler.profile classifier except to (AttributeError, TypeError, ValueError, KeyError)#12
HrachShah wants to merge 1 commit into
mainfrom
fix/workload-profiler-classifier-exception-narrow

Conversation

@HrachShah

@HrachShah HrachShah commented Jun 19, 2026

Copy link
Copy Markdown
Owner

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:

  • AttributeError: msg.role / msg.content missing on a malformed message, or a list-content part missing .type on access in classify_context_topology
  • TypeError: request.messages is not iterable, or msg.content is a non-string / non-list value that breaks the isinstance check or .lower() call
  • ValueError: a custom validator inside one of the axes raising ValueError on bad input
  • KeyError: dict-style content parts that don't have the expected key

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 doing sys.exit()), MemoryError from a malformed request body, and any programmer error in the surrounding loop body — none of which should be masked.

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.

Verified: python3 -c "import ast; ast.parse(open('freerelay/data_plane/profiler/workload.py').read())" parses cleanly.

Summary by Sourcery

Bug Fixes:

  • Prevent masking of critical and programmer exceptions in workload axis classification by restricting the caught exceptions to known input/validation errors.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved workload profiler error handling to properly surface unexpected exceptions instead of silently masking them, enabling better visibility into potential issues.

… 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.
@sourcery-ai

sourcery-ai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Narrows 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

Change Details Files
Restrict axis classifier error handling in WorkloadProfiler.profile to expected input/validation exceptions instead of catching all Exceptions.
  • Replace broad except Exception: around axis classifier invocation with except (AttributeError, TypeError, ValueError, KeyError): to only catch realistic classifier failure modes tied to malformed requests or validation errors.
  • Preserve existing behavior of logging full tracebacks via logger.exception when these specific exceptions occur.
  • Keep fallback axis behavior of setting value to "unknown" and confidence to 0.0 when a classifier raises one of the expected exceptions, while allowing other exceptions (e.g., KeyboardInterrupt, SystemExit, MemoryError, programmer errors) to propagate instead of being silently masked.
freerelay/data_plane/profiler/workload.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

In WorkloadProfiler.profile, the per-axis exception handler is narrowed from catching all Exception subclasses to only AttributeError, TypeError, ValueError, and KeyError. Any other runtime exception type will now propagate out of the profiling method instead of being silently logged and replaced with "unknown" / 0.0.

Changes

WorkloadProfiler Exception Narrowing

Layer / File(s) Summary
Narrow axis-classifier exception catch
freerelay/data_plane/profiler/workload.py
except Exception replaced with except (AttributeError, TypeError, ValueError, KeyError), restricting which failures fall back to "unknown"/0.0 vs. propagating out of the profiling loop.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

Poem

A rabbit once caught every bug in its net,
But some slipped through, and that's not done yet!
Now only four types get the silent "unknown,"
The rest bubble up — let their errors be known. 🐇
A tighter catch keeps surprises in sight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: narrowing the exception handler in WorkloadProfiler.profile to catch only four specific exception types instead of all exceptions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/workload-profiler-classifier-exception-narrow

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between ec9d17f and af91f7a.

📒 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):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 f

Repository: 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 axes

Repository: 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.py

Repository: 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"
done

Repository: 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"
done

Repository: 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
done

Repository: 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.type when msg.content is a string instead of a list, or request.response_format.type when response_format is 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.

Comment on lines +137 to 140
except (AttributeError, TypeError, ValueError, KeyError):
logger.exception("Axis classifier %s failed", axis_name)
values[axis_name] = "unknown"
axis_confidences[axis_name] = 0.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 25

Repository: 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 -30

Repository: 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 -5

Repository: 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.py

Repository: 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 -10

Repository: 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 -80

Repository: 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.py

Repository: 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 3

Repository: 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.py

Repository: 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.py

Repository: 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.py

Repository: 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.py

Repository: 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 2

Repository: 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 -20

Repository: 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=py

Repository: 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.py

Repository: HrachShah/FreeRelay

Length of output: 1512


🏁 Script executed:

#!/bin/bash
# Check the full route_stream() method
sed -n '437,475p' freerelay/core/routing/engine.py

Repository: 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 -10

Repository: 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 -5

Repository: 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.py

Repository: 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.py

Repository: 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.py

Repository: 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant