Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

121 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Lever D — evidence-weighted top-3 re-ranking (conservative rescue variant).
11:46 failure analysis: of the 77 a1=0 cases, 41 (53%) had the correct
``fault_object`` SOMEWHERE in the LLM's top-3, but only 29 (38%) at rank-1.
That's ~15 points of object accuracy parked in ranks 2-3 because the
LLM's own confidence ordering didn't surface the best-evidenced candidate.
Re-ranking by how many of each prediction's identifying tokens appear in
the actual investigation evidence pulls those candidates up.
Cheap deterministic variant (this module): substring count, no LLM call.
Audit-grade variant (LLM-as-judge over the same input) is a follow-up.
"""
from __future__ import annotations
import re
from typing import Any
# Stem tokens that are too common across predictions to discriminate by their
# presence in the evidence — "service" appears in every Kubernetes diagnosis,
# "fault" / "error" / "pod" are noise. Counting them inflates every prediction's
# score equally, defeating the rerank. Drop them from the token set.
_RERANK_STOPWORDS: frozenset[str] = frozenset(
{"app", "node", "namespace", "service", "fault", "error", "pod", "the", "and", "for"}
)
# Tokens shorter than this can't carry meaningful signal (single letters,
# 2-char abbreviations are too noisy to substring-match reliably).
_RERANK_MIN_TOKEN_LEN: int = 3
def _prediction_tokens(prediction: dict[str, Any]) -> set[str]:
"""Pull the identifying tokens from one prediction.
Combines ``fault_object`` (after stripping the prefix) and ``root_cause``,
splits on the structural separators that the dataset uses (``_``, ``-``,
``/``), lowercases, and drops stop-words + tokens shorter than
``_RERANK_MIN_TOKEN_LEN``. The result is the set of substrings that
should appear in the evidence if this prediction is well-supported.
"""
fields: list[str] = []
fault_obj = (prediction.get("fault_object") or "").strip().lower()
if "/" in fault_obj:
_prefix, _, name = fault_obj.partition("/")
fault_obj = name
if fault_obj:
fields.append(fault_obj)
root_cause = (prediction.get("root_cause") or "").strip().lower()
if root_cause:
fields.append(root_cause)
tokens: set[str] = set()
for field in fields:
for tok in re.split(r"[_\-/\s]+", field):
if len(tok) >= _RERANK_MIN_TOKEN_LEN and tok not in _RERANK_STOPWORDS:
tokens.add(tok)
return tokens
def rerank_predictions_by_evidence(
predictions: list[dict[str, Any]],
evidence_text: str,
) -> list[dict[str, Any]]:
"""Conservatively rescue the top-1 if it has zero evidence support.
**Empirical motivation**: a permissive "always re-sort by substring
hits" version was tested against the 11:46 case data and produced a
7.2pp regression on A@1 (103/180 → 90/180 correct triple-matches).
Cause: when the investigation discusses multiple services, multiple
predictions accumulate substring hits, and a wrong-but-multiply-cited
rank-2 was beating a correct-and-singly-cited rank-1. Substring count
alone is not strong enough signal to over-rule the LLM's confidence
ordering.
The conservative variant in this function only fires when **rank-1
has ZERO matching tokens in the evidence** (a clear "the LLM picked a
prediction the investigation never mentioned" signal). When that
fires, the highest-scoring non-rank-1 prediction is promoted. All
other cases are identity — protecting the LLM's confidence ordering
when it has any evidence backing at all.
This recovers ~2 a1 cells per 180 (from the 11:46 replay) without
regressing the 30+ cells the LLM had correctly ranked at #1.
Returns a NEW list — the input is not mutated. ``rank`` is rewritten
to match the new 1-based positions.
"""
if len(predictions) <= 1:
return list(predictions)
haystack = (evidence_text or "").lower()
if not haystack.strip():
return list(predictions)
scores: list[int] = []
for prediction in predictions:
tokens = _prediction_tokens(prediction)
scores.append(sum(1 for tok in tokens if tok in haystack))
# Conservative gate: only intervene when rank-1 has zero evidence hits.
# When the LLM's top pick IS evidenced at all, defer to its judgment —
# cross-citation noise in the substring count is too high to over-rule
# a confidence ordering that has any backing.
if scores[0] > 0:
return list(predictions)
# Find the highest-scoring non-rank-1 prediction. If none score positive,
# all predictions are unevidenced and we have no signal to act on.
best_alt_idx: int | None = None
best_alt_score = 0
for idx in range(1, len(predictions)):
if scores[idx] > best_alt_score:
best_alt_score = scores[idx]
best_alt_idx = idx
if best_alt_idx is None:
return list(predictions)
# Promote: chosen alt becomes rank-1, original rank-1 takes the alt's slot,
# everything else preserves relative order so the swap is minimally disruptive.
promoted = predictions[best_alt_idx]
new_order = [promoted, predictions[0]]
for idx, prediction in enumerate(predictions):
if idx in (0, best_alt_idx):
continue
new_order.append(prediction)
return [{**prediction, "rank": new_rank + 1} for new_rank, prediction in enumerate(new_order)]