import unittest from PIL import Image from unittest import mock from approach.reflection import ( build_reflection_prompt, make_successful_detection_request, openai_compatible_advisor, run_reflection_loop, ) class ReflectionTest(unittest.TestCase): def test_pii5_crop_uses_bbox_without_resize_or_flip(self): image = Image.new("RGB", (12, 8), "black") for x in range(2, 7): for y in range(1, 5): image.putpixel((x, y), (x, y, 100)) request = make_successful_detection_request( image, {"bbox": [2, 1, 5, 4], "category_name": "button"} ) self.assertEqual(request.crop_image.size, (5, 4)) self.assertEqual(request.crop_image.getpixel((0, 0)), image.getpixel((2, 1))) self.assertEqual(request.crop_image.getpixel((4, 0)), image.getpixel((6, 1))) self.assertIs(request.original_image, image) def test_fractional_bbox_crop_contains_the_full_detected_region(self): request = make_successful_detection_request( Image.new("RGB", (10, 10), "white"), {"bbox": [1.2, 2.2, 2.2, 1.2], "category_name": "button"}, ) self.assertEqual(request.crop_image.size, (3, 2)) def test_reflection_loop_stops_when_advisor_has_no_concerns(self): calls = [] def advisor(**kwargs): calls.append(kwargs) return {"verified": [0], "needs_refinement": [], "feedback": []} result = run_reflection_loop( Image.new("RGB", (10, 10), "white"), [{"bbox": [1, 1, 3, 3], "category_name": "menu"}], miner=lambda trace: ["menu"], detector=lambda candidates, previous: previous, advisor=advisor, max_iterations=10, ) self.assertEqual(len(calls), 1) self.assertFalse(result["max_iterations_reached"]) self.assertEqual(result["trace"][0]["verified"], [0]) self.assertEqual(len(calls[0]["verification_requests"]), 1) self.assertIn("PII.6", calls[0]["pii6_prompt"]) def test_reflection_loop_marks_max_iterations(self): def advisor(**kwargs): return {"verified": [], "needs_refinement": [0], "feedback": ["retry"]} result = run_reflection_loop( Image.new("RGB", (10, 10), "white"), [{"bbox": [1, 1, 3, 3], "category_name": "menu"}], miner=lambda trace: ["menu"], detector=lambda candidates, previous: previous, advisor=advisor, max_iterations=3, ) self.assertTrue(result["max_iterations_reached"]) self.assertEqual(len(result["trace"]), 3) def test_reflection_removes_rejected_false_positive_before_redetection(self): detector_calls = [] def detector(candidates, retained): detector_calls.append((candidates, retained)) return retained advice = [ {"verified": [1], "needs_refinement": [0], "feedback": ["missing lever"]}, {"verified": [0], "needs_refinement": [], "feedback": []}, ] detections = [ {"bbox": [1, 1, 2, 2], "category_name": "decoration"}, {"bbox": [5, 5, 2, 2], "category_name": "button"}, ] result = run_reflection_loop( Image.new("RGB", (10, 10), "white"), detections, miner=lambda trace: trace[-1]["feedback"], detector=detector, advisor=lambda **kwargs: advice.pop(0), ) self.assertEqual(detector_calls[0][0], ["missing lever"]) self.assertEqual(detector_calls[0][1], [detections[1]]) self.assertEqual(result["detections"], [detections[1]]) def test_openai_compatible_advisor_sends_annotated_scene_original_and_crop(self): fake_client = mock.Mock() fake_client.complete_json.return_value = { "verified": [0], "needs_refinement": [], "feedback": [], } with mock.patch("approach.reflection.OpenAICompatibleChatClient", return_value=fake_client): advisor = openai_compatible_advisor("default") request = make_successful_detection_request( Image.new("RGB", (10, 10), "white"), {"bbox": [1, 1, 3, 3], "category_name": "button"}, ) result = advisor( verification_requests=[request], unsuccessful_detection_image=Image.new("RGB", (10, 10), "white"), pii5_prompt="PII.5", pii6_prompt="PII.6", pii7_prompt="PII.7", ) self.assertEqual(result["verified"], [0]) call = fake_client.complete_json.call_args self.assertIn("PII.5", call.args[0]) self.assertEqual(len(call.args[1]), 3) self.assertTrue(all(image.media_type == "image/png" for image in call.args[1])) self.assertIn("response_format", call.kwargs) def test_reflection_prompt_includes_detection_metadata(self): request = make_successful_detection_request( Image.new("RGB", (10, 10), "white"), {"bbox": [1, 2, 3, 4], "category_name": "slider", "score": 0.9}, ) prompt = build_reflection_prompt([request], "PII.5", "PII.6", "PII.7") self.assertIn('"category_name": "slider"', prompt) self.assertIn("PII.6", prompt) if __name__ == "__main__": unittest.main()