Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion freerelay/data_plane/profiler/workload.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def profile(
value, confidence = classifier(request, headers)
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.

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

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.

Expand Down
Loading