@@ -376,6 +376,27 @@ def test_run_batches_uses_message_text_for_content_blocks(self) -> None:
376376
377377 assert results [0 ][1 ] == ["chunk" ]
378378
379+ @patch (MOCK_PATCH_TARGET , _mock_get_chat_model )
380+ def test_run_batches_isolates_raw_invoke_validation_error (self ) -> None :
381+ analyzer = _RawTextAnalyzer (base_prompt = "test" , model = self .MODEL )
382+
383+ def _invoke (prompt : str ) -> AIMessage :
384+ if "b.py" in prompt :
385+ LLMAnalysisResult .model_validate ({"findings" : 'We{"findings":[]}' })
386+ return AIMessage (content = "ok" )
387+
388+ analyzer ._llm .invoke .side_effect = _invoke
389+ batches = [
390+ Batch (file_path = "a.py" , content = "code a" ),
391+ Batch (file_path = "b.py" , content = "code b" ),
392+ Batch (file_path = "c.py" , content = "code c" ),
393+ ]
394+
395+ results = analyzer .run_batches (batches )
396+
397+ assert {batch .file_path for batch , _ in results } == {"a.py" , "c.py" }
398+ assert [items for _ , items in results ] == [["ok" ], ["ok" ]]
399+
379400 @patch (MOCK_PATCH_TARGET , _mock_get_chat_model )
380401 async def test_arun_batches_uses_message_text_for_content_blocks (self ) -> None :
381402 analyzer = _RawTextAnalyzer (base_prompt = "test" , model = self .MODEL )
@@ -431,6 +452,28 @@ def test_value_error_still_propagates(self) -> None:
431452 with pytest .raises (ValueError , match = "no API key" ):
432453 analyzer .run_batches ([Batch (file_path = "a.py" , content = "code" )])
433454
455+ @patch (MOCK_PATCH_TARGET , _mock_get_chat_model )
456+ def test_parse_validation_error_does_not_abort_the_others (self ) -> None :
457+ analyzer = LLMAnalyzerBase (base_prompt = "test" , model = self .MODEL )
458+ analyzer ._structured_llm .invoke .return_value = LLMAnalysisResult (findings = [])
459+ original_parse = analyzer .parse_response
460+
461+ def _parse (response : object , batch : Batch ) -> list [Finding ]:
462+ if batch .file_path == "b.py" :
463+ LLMAnalysisResult .model_validate ({"findings" : 'We{"findings":[]}' })
464+ return original_parse (response , batch )
465+
466+ analyzer .parse_response = _parse
467+ batches = [
468+ Batch (file_path = "a.py" , content = "code a" ),
469+ Batch (file_path = "b.py" , content = "code b" ),
470+ Batch (file_path = "c.py" , content = "code c" ),
471+ ]
472+
473+ results = analyzer .run_batches (batches )
474+
475+ assert {batch .file_path for batch , _ in results } == {"a.py" , "c.py" }
476+
434477
435478# ---------------------------------------------------------------------------
436479# LLMAnalyzerBase.arun_batches (async parallel execution)
0 commit comments