fix(report): flag a partial LLM failure as degraded, not only a total one - #362
fix(report): flag a partial LLM failure as degraded, not only a total one#362AmirF194 wants to merge 2 commits into
Conversation
… one _llm_runtime_status() only set degraded when every LLM call failed (succeeded == 0). A rate-limited provider that drops a single batch (e.g. semantic_security_discovery hits a 429) still has succeeded > 0, so the scan reported a normal risk_assessment even though the security-critical analyzer never ran. Widen the condition to succeeded < attempted, so any dropped batch degrades the scan and the existing fail-closed floor (CAUTION instead of SAFE) applies to a partial pass too. Updated the two degraded-scan messages to say how many of the calls failed instead of assuming all of them did. Covers request 3 of NVIDIA#303 (surface incompleteness in the verdict). Request 1 (configurable concurrency) shipped in NVIDIA#305; request 2 (retry with backoff) is left to the already-open NVIDIA#29. Refs NVIDIA#303 Signed-off-by: Amir Fathi <amirfathi.me@gmail.com>
rng1995
left a comment
There was a problem hiding this comment.
The report-level predicate now handles mixed analyzer outcomes, but the core partial-batch path is still recorded as a success upstream, so a multi-batch analyzer can drop work and still produce SAFE. The new predicate also conflates overall coverage with whether the meta-analyzer/provider actually ran. Please address the inline findings so the degraded verdict and metadata are accurate end to end.
| attempted = len(llm_call_log) | ||
| succeeded = sum(1 for r in llm_call_log if r.get("ok")) | ||
| degraded = bool(use_llm and attempted > 0 and succeeded == 0) | ||
| degraded = bool(use_llm and attempted > 0 and succeeded < attempted) |
There was a problem hiding this comment.
[P1] Detect failures at batch granularity
llm_call_log is not a per-batch log today. The semantic analyzers and meta_analyzer emit one record with ok=bool(outcome.successful) or not outcome.failures, so a two-batch run with one success and one 429 is recorded as ok=True; test_partial_batch_failure_records_llm_success currently pins that behavior. In that exact multi-file/multi-batch case, succeeded == attempted here and the report remains SAFE, so this does not yet implement the advertised ‘any dropped batch’ behavior. Please either emit per-batch records or mark the analyzer record failed whenever outcome.failures is non-empty, then add an analyzer-to-report regression test.
| # meta_analysis_applied reflects whether the LLM meta-analysis effectively | ||
| # ran: requested, available, and not fully degraded (every call failing). | ||
| # ran in full: requested, available, and every attempted call succeeded. | ||
| meta_analysis_applied = use_llm and llm_available and not degraded |
There was a problem hiding this comment.
[P2] Keep coverage separate from meta-analysis/availability
After this change, degraded means any LLM-backed analyzer failed—not that the meta-analyzer failed or the provider was unavailable. In the new 3/4 scenario, meta_analyzer is explicitly successful, yet this forces meta_analysis_applied=False, adds filtering_mode="heuristic", and line 626 reports llm_available=False despite three successful calls. That misstates independent contracts (and #303 explicitly distinguishes throttled partial coverage from provider unavailability). Please derive meta-analysis from the meta_analyzer outcome, retain provider availability, and use llm_degraded/a coverage field for the partial loss.
…p meta-analysis fields inheriting other analyzers' failures Two gaps from review on NVIDIA#362: 1. llm_call_log records were built with ok=bool(outcome.successful) or not outcome.failures, so an analyzer with one succeeded batch and one dropped/429'd batch still recorded ok=True. In that exact case succeeded == attempted at the report layer and the scan stayed SAFE, defeating the partial-coverage fix. Now the record is ok=not outcome.failures: any dropped batch marks the whole record failed. Applied identically in the three semantic analyzers and meta_analyzer, the four call sites that build this record. 2. meta_analysis_applied and the llm_available field were derived from the aggregate `degraded` flag, which pools every LLM-backed node together. That let a different analyzer's dropped batch force meta_analysis_applied=False, filtering_mode="heuristic" and llm_available=False even when meta_analyzer's own call fully succeeded, misstating two independent contracts (meta-analysis ran vs. some coverage was lost) as one boolean. Both fields now derive from is_llm_available() plus meta_analyzer's own llm_call_log record only; the coverage loss from other analyzers still surfaces through llm_degraded / llm_calls_attempted / llm_calls_succeeded, unchanged. Verified: test_partial_batch_failure_records_llm_failure (renamed from ..._records_llm_success, now pins ok=False) and three new report-level tests, run red against the pre-fix code (3 of 4 failed) and green after. tests/nodes/test_report.py: 66 passed. Full suite in Docker (python:3.12-slim): 1947 passed, 13 skipped, 4 xfailed, 0 failed. ruff lint and format-check both pass. Signed-off-by: Amir Fathi <amirfathi.me@gmail.com>
|
Pushed a follow-up commit for both findings. P1 (line 587): P2 (line 617/626): I did not add a separate coverage field beyond the existing Verified: ran the new/changed tests against the pre-fix code first to confirm they fail (3 of 4 red, the meta_analyzer-failure case already passed under the old formula since that one case wasn't actually broken), then confirmed green after the fix. |
What
report()derivesdegradedfrom_llm_runtime_status(), which only set it when every attempted LLM call failed (succeeded == 0). A rate-limited provider that drops one batch (e.g. a 429 onsemantic_security_discovery) still hassucceeded > 0, so the existing fail-closed floor (CAUTIONinstead ofSAFEon a degraded scan) never triggered. The reported scenario is exactly this:llm_calls_attempted=4, llm_calls_succeeded=3,risk_assessmentstill SAFE, and the dropped batch happened to be the one analyzer that would have caught the malicious skill in the report.This widens the condition to
succeeded < attempted, so any dropped or throttled batch marks the scan degraded, not just a total failure. The two degraded-scan messages (_llm_degradation_notice,meta["llm_error"]) are updated to say how many of the calls failed instead of assuming all of them did, since that is no longer always true.Test
Added
test_partial_llm_failure_also_floors_recommendation_at_caution, matching the reported3/4scenario directly againstreport(). Renamed the test that pinned the old behavior (test_report_not_degraded_when_some_calls_succeeded->test_report_degraded_when_some_calls_fail) to assert the corrected one. Fulltests/nodes/test_report.pygreen (63 passed);make lintandmake format-checkclean.Scope
This covers request 3 of #303 (surface incompleteness in the verdict). Request 1 (configurable concurrency) shipped in #305; request 2 (retry with backoff) is left to the already-open #29, which this PR does not touch or conflict with (it does not modify
_llm_runtime_status).Refs #303