From 17ed7947d30d79280dce6682ea3224378c2bdaa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=97=E5=B9=B3?= Date: Wed, 1 Jul 2026 11:51:50 +0000 Subject: [PATCH] fix(eval): parse_anomaly_segments must tolerate non-numeric junk entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full-scale eval crashed in the pure-LLM QA-judge path: pure_llm emits free-form JSON whose 'segments' list can contain entries like ["CPU Usage", 30] (channel name instead of an int). _seg_from_entry called int() unconditionally and raised ValueError, killing the whole report run (model predictor had already finished; result lost). Fix: wrap _seg_from_entry in try/except (ValueError, TypeError) → return None for any unparseable entry, so good segments alongside junk are still extracted. Verified: {["CPU Usage",30],[10,20]} → [(10,20)]. + unit test. Also archive logs/train_full_run.log (stage1 31250 EMA1.26, stage2 9375 EMA0.75). --- src/tsmm/eval/parse_answer.py | 23 ++++++++++++++--------- tests/test_parse_answer.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/tsmm/eval/parse_answer.py b/src/tsmm/eval/parse_answer.py index 3906ed1..9921d0f 100644 --- a/src/tsmm/eval/parse_answer.py +++ b/src/tsmm/eval/parse_answer.py @@ -79,17 +79,22 @@ def _seg_from_entry(s): Accepts either ``[start, end]`` lists/tuples or ``{"start": s, "end": e}`` objects (the schema emitted by ``data/instruct.py``). Returns None if no - usable pair is found. + usable pair is found, or if the values aren't ints (e.g. a pure-LLM emits + ``["CPU Usage", 30]`` — junk that must not crash the eval pipeline). """ - if isinstance(s, dict): - if "start" in s and "end" in s: - return (int(s["start"]), int(s["end"])) + try: + if isinstance(s, dict): + if "start" in s and "end" in s: + return (int(s["start"]), int(s["end"])) + return None + if isinstance(s, (list, tuple)): + if len(s) >= 2: + return _normalize_seg(s) + if len(s) == 1: + v = int(s[0]); return (v, v) + except (ValueError, TypeError): return None - if isinstance(s, (list, tuple)): - if len(s) >= 2: - return _normalize_seg(s) - if len(s) == 1: - v = int(s[0]); return (v, v) + return None return None diff --git a/tests/test_parse_answer.py b/tests/test_parse_answer.py index 160c9ce..c90b311 100644 --- a/tests/test_parse_answer.py +++ b/tests/test_parse_answer.py @@ -73,6 +73,18 @@ class TestParseSegments: assert any('"a"' in b and '"b"' in b for b in blobs) assert '{"d": 4}' in blobs + def test_non_numeric_segment_entries_are_skipped(self): + # a pure-LLM may emit junk like ["CPU Usage", 30] — must not crash, just skip + from tsmm.eval.parse_answer import _seg_from_entry + assert _seg_from_entry(["CPU Usage", 30]) is None + assert _seg_from_entry({"start": "foo", "end": "bar"}) is None + assert _seg_from_entry("not a segment") is None + # good entries alongside junk are still extracted + text = '{"segments": [["CPU Usage", 30], [10, 20]]}' + segs, ok = parse_anomaly_segments(text) + assert ok is True + assert segs == [(10, 20)] + def test_empty_segments_list(self): text = '{"segments": [], "type": "none"}' segs, ok = parse_anomaly_segments(text)