Spaces:
Paused
Paused
File size: 10,076 Bytes
729628a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | """MITL Copilot: advisory analysis of evaluation failures.
Routes to local Ollama/Gemma when reachable; otherwise produces a deterministic
offline analysis derived from the actual evaluation evidence. The Copilot is
advisory only: it never changes metrics, approves/rejects labels, or overrides
gates (spec §35).
"""
from __future__ import annotations
import json
from typing import Dict, List, Optional
import httpx
from sensorflow.evaluation import reporting
from sensorflow.evaluation.records import EvalStore
OLLAMA_ENDPOINTS = [
{"url": "http://localhost:11434/api/chat", "model": "gemma3:latest"},
{"url": "http://dgx-spark.tail16d8d9.ts.net:11434/api/chat", "model": "gemma4:26b"},
]
REASON_HINTS = {
"LOW_IOU": ("Geometric mismatch vs reference", "box regression head under-fitting for this class/range"),
"POSITION_ERROR": ("Center offset beyond tolerance", "LiDAR-camera extrinsic drift or late fusion latency"),
"ORIENTATION_ERROR": ("Heading estimate off", "symmetric object ambiguity / yaw flipping in the detector"),
"INSUFFICIENT_POINT_SUPPORT": ("Too few LiDAR returns inside box", "occlusion, range sparsity, or hallucinated detection"),
"ANOMALY": ("Statistical outlier vs population", "feature combination rarely seen in training distribution"),
"GRADER_DISAGREEMENT": ("Independent graders disagree", "genuinely ambiguous object class or degraded viewpoint"),
"TRACK_FRAGMENTATION": ("Track split into fragments", "missed detections breaking association continuity"),
"ID_SWITCH": ("Identity switched mid-track", "crossing objects with similar appearance/kinematics"),
"MODEL_REGRESSION": ("Model version regressed", "training data shift or a bad checkpoint promotion"),
"LOW_CONFIDENCE": ("Detector confidence very low", "borderline detection near the score threshold"),
"SENSOR_DISAGREEMENT": ("Camera and LiDAR disagree", "single-sensor failure mode (glare, rain scatter, dropout)"),
}
def _gather_context(store: EvalStore, req: Dict) -> Dict:
ctx: Dict = {"context_type": req.get("context_type", "general")}
aid = req.get("annotation_id")
if aid:
rec = reporting.evaluation_record(store, aid)
if rec:
ctx["evaluation_record"] = rec
eid = req.get("event_id")
if eid:
ev = store.get("rare_events", eid)
if ev:
ctx["rare_event"] = ev.model_dump()
if req.get("context_type") == "regression" or req.get("model_version"):
regs = sorted(store.all("regressions"), key=lambda r: r.date)
if req.get("model_version"):
regs = [r for r in regs if r.model_version == req["model_version"]] or regs
if regs:
ctx["regression"] = regs[-1].model_dump()
return ctx
def _offline_analysis(ctx: Dict) -> Dict:
"""Deterministic rule-based analysis assembled from real evidence."""
ctype = ctx.get("context_type", "general")
evidence: List[str] = []
factors: List[str] = []
investigation: List[str] = []
classification = ctype.replace("_", " ").title()
likely_cause = "Insufficient evidence gathered for a specific cause."
confidence = 0.5
rec = ctx.get("evaluation_record")
if rec:
geo = rec["geometry"]
an = rec["anomaly"]
gr = rec["grading"]
dec = rec.get("decision") or {}
reasons = dec.get("failure_reasons", [])
if reasons:
classification = reasons[0].replace("_", " ").title()
if geo.get("iou_3d") is not None:
evidence.append(f"3D IoU vs reference = {geo['iou_3d']:.3f}")
if geo.get("position_error") is not None:
evidence.append(f"Position error = {geo['position_error']:.2f} m")
if geo.get("orientation_error_deg") is not None:
evidence.append(f"Orientation error = {geo['orientation_error_deg']:.1f}°")
evidence.append(f"Anomaly ensemble score = {an['score']:.3f} "
f"(threshold {an['decision_threshold']:.2f}, strategy {an['ensemble_strategy']})")
if an.get("detector_scores"):
top = sorted(an["normalized_scores"].items(), key=lambda kv: -kv[1])[:3]
evidence.append("Highest detector votes: " + ", ".join(f"{k}={v:.2f}" for k, v in top))
if gr.get("consensus") is not None:
evidence.append(f"Grader consensus = {gr['consensus']:.2%} across {gr['grader_count']} graders")
if rec["tracking"]["id_switch"]:
evidence.append("Tracking evidence shows an identity switch on this object's track")
if rec["tracking"]["fragmentation"]:
evidence.append("Tracking evidence shows track fragmentation")
primary = dec.get("primary_failure_reason")
if primary and primary in REASON_HINTS:
headline, cause = REASON_HINTS[primary]
likely_cause = f"{headline}: most consistent with {cause}."
confidence = 0.72
for r in reasons[1:4]:
if r in REASON_HINTS:
factors.append(f"{r}: {REASON_HINTS[r][1]}")
investigation.extend([
"Open the frame in HITL review and compare camera vs LiDAR evidence",
"Check neighboring frames of the same track for consistent behavior",
"Verify the reference ground-truth type and coverage before trusting the comparison",
])
ev = ctx.get("rare_event")
if ev:
classification = f"Rare Event: {ev['scenario_type'].replace('_', ' ').title()}"
evidence.append(f"Scenario {ev['scenario_type']} severity={ev['severity']} "
f"rarity={ev['rarity_score']:.2f} anomaly={ev['anomaly_score']:.2f}")
for sensor, detail in ev.get("sensor_evidence", {}).items():
evidence.append(f"{sensor}: {detail}")
likely_cause = "Genuine rare scenario rather than a labeling defect; value lies in adding it to training."
investigation.append("Verify the event, then include its frames in the next training dataset")
confidence = 0.68
reg = ctx.get("regression")
if reg and ctype == "regression":
classification = "Model Performance Regression"
for d in reg.get("deltas", [])[:6]:
if d["regressed"]:
evidence.append(f"{d['metric']}: {d['baseline']:.3f} → {d['current']:.3f} "
f"(Δ {d['delta']:+.3f}, tolerance {d['tolerance']})")
if reg.get("affected_classes"):
factors.append("Affected classes: " + ", ".join(reg["affected_classes"]))
likely_cause = ("Training-data distribution shift or label-quality drop between model versions; "
"the regressed metrics cluster in " + ", ".join(reg.get("kinds", ["performance"])) + ".")
investigation.extend([
"Diff the training dataset lineage between the two model versions",
"Re-run the benchmark on the affected classes only",
"Inspect flagged labels from the newer model for systematic geometry errors",
])
confidence = 0.7
if not evidence:
evidence.append("No structured evidence was attached to this request.")
investigation.append("Select a specific annotation, rare event, or regression entry and retry")
hypothesis = (f"HYPOTHESIS (not a conclusion): {likely_cause} "
"This is generated from deterministic evidence rules and requires human confirmation.")
lines = [f"# {classification}", "", "## Observed evidence"]
lines += [f"- {e}" for e in evidence]
if factors:
lines += ["", "## Contributing factors"] + [f"- {f}" for f in factors]
lines += ["", "## Likely cause", likely_cause, "", "## Hypothesis", hypothesis,
"", "## Recommended investigation"] + [f"- {i}" for i in investigation]
lines += ["", f"_Advisory only — confidence {confidence:.0%}. The Copilot never changes metrics or overrides gates._"]
return {
"analysis": "\n".join(lines),
"structured": {
"failure_classification": classification,
"observed_evidence": evidence,
"likely_cause": likely_cause,
"contributing_factors": factors,
"hypothesis": hypothesis,
"recommended_investigation": investigation,
"confidence": confidence,
},
}
def explain(store: EvalStore, req: Dict) -> Dict:
"""Try Ollama first; fall back to the deterministic offline analysis."""
ctx = _gather_context(store, req)
offline = _offline_analysis(ctx)
prompt = (
"You are an advisory MITL Copilot for an autonomous-driving label evaluation platform. "
"You never change metrics or approve/reject labels; you explain evidence.\n\n"
f"CONTEXT TYPE: {ctx.get('context_type')}\n"
f"EVIDENCE (JSON):\n{json.dumps({k: v for k, v in ctx.items() if k != 'context_type'}, indent=2)[:6000]}\n\n"
"Produce: failure classification, observed evidence, likely cause, contributing factors, "
"a clearly-labeled hypothesis, recommended investigation steps, and a confidence estimate. "
"Be concise and concrete."
)
for ep in OLLAMA_ENDPOINTS:
try:
res = httpx.post(ep["url"], json={
"model": ep["model"],
"messages": [{"role": "user", "content": prompt}],
"stream": False,
}, timeout=20.0)
if res.status_code == 200:
text = res.json().get("message", {}).get("content", "")
if text:
return {
"status": "ok",
"provider": ep["url"],
"analysis": text,
"structured": offline["structured"], # deterministic evidence summary retained
}
except Exception:
continue
return {
"status": "ok",
"provider": "offline_deterministic",
"analysis": offline["analysis"],
"structured": offline["structured"],
}
|