From 19e5a929c36bfa88e9bf913ad8e78ae714717698 Mon Sep 17 00:00:00 2001 From: Romonaga Date: Tue, 16 Jun 2026 22:00:03 -0400 Subject: [PATCH] Fix response finalizer JSON fallback --- backend/services/response_finalizer.py | 4 ++- tests/test_response_finalizer.py | 36 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/backend/services/response_finalizer.py b/backend/services/response_finalizer.py index e93529f..5002eb0 100644 --- a/backend/services/response_finalizer.py +++ b/backend/services/response_finalizer.py @@ -22,6 +22,7 @@ re.IGNORECASE, ) SOURCE_REFERENCE_PATTERN = re.compile(r"\b(source|page|datasheet|book|context|retrieved)\b", re.IGNORECASE) +FINALIZER_PARSE_FALLBACK_NOTE = "Response validation could not parse the finalizer output, so the original answer was kept." @dataclass @@ -166,7 +167,8 @@ def parse_response_finalizer_output( ran=True, useful=True, changed=False, - issues=deterministic_issues + ["Response finalizer did not return valid JSON."], + issues=_dedupe_strings(deterministic_issues)[:16], + notes=[FINALIZER_PARSE_FALLBACK_NOTE], ) schema_issue = [] diff --git a/tests/test_response_finalizer.py b/tests/test_response_finalizer.py index 41ec9bc..9f0696e 100644 --- a/tests/test_response_finalizer.py +++ b/tests/test_response_finalizer.py @@ -65,6 +65,42 @@ def test_parse_revised_answer(self): self.assertEqual(validation.confidence, 0.82) self.assertIn("Missing source citation.", validation.issues) + def test_parse_non_json_keeps_answer_and_deterministic_issues(self): + answer, validation = parse_response_finalizer_output( + "This answer looks fine, but I did not follow the requested schema.", + fallback_answer="Original source-backed answer.", + deterministic_issues=["The answer does not clearly reference the retrieved material."], + ) + + self.assertEqual(answer, "Original source-backed answer.") + self.assertTrue(validation.useful) + self.assertFalse(validation.changed) + self.assertIn("The answer does not clearly reference the retrieved material.", validation.issues) + self.assertFalse(any("valid JSON" in issue for issue in validation.issues)) + self.assertIn("original answer was kept", " ".join(validation.notes)) + + def test_parse_extracts_fenced_json_finalizer_output(self): + raw = """```json + { + "useful": true, + "confidence": 0.74, + "issues": [], + "notes": ["Parsed from fenced JSON."], + "revisedAnswer": "## Checked answer\\nUse the retrieved source." + } + ```""" + + answer, validation = parse_response_finalizer_output( + raw, + fallback_answer="Original answer.", + deterministic_issues=[], + ) + + self.assertEqual(answer, "## Checked answer\nUse the retrieved source.") + self.assertTrue(validation.changed) + self.assertEqual(validation.confidence, 0.74) + self.assertIn("Parsed from fenced JSON.", validation.notes) + def test_should_run_modes(self): self.assertTrue(should_run_response_finalizer(enabled=True, mode="always", confidence=0.99, build_card=None, issues=[], min_confidence=0.8)) self.assertFalse(should_run_response_finalizer(enabled=False, mode="always", confidence=0.1, build_card={}, issues=["x"], min_confidence=0.8))