@@ -374,7 +374,9 @@ def run_batches(
374374
375375 The element type of the inner list depends on the subclass: the default
376376 :meth:`parse_response` returns :class:`Finding` objects; subclasses may
377- return dicts or other types.
377+ return dicts or other types. Malformed structured responses are isolated
378+ per batch and omitted from the result so callers can detect partial
379+ analysis by comparing submitted and returned batches.
378380 """
379381 results : list [tuple [Batch , list ]] = []
380382 for batch in batches :
@@ -399,6 +401,9 @@ def run_batches(
399401 else :
400402 try :
401403 response = _message_text (self ._llm .invoke (prompt ))
404+ except ValidationError as exc :
405+ logger .warning ("LLM batch failed for %s: %s" , batch .file_label , exc )
406+ continue
402407 except (ValueError , NotImplementedError ):
403408 raise
404409 except Exception as exc :
@@ -432,12 +437,12 @@ async def arun_batches(
432437 cross-chunk batches are parallelized in a single gather call.
433438
434439 Failures are isolated per batch: a transient error (timeout, 429,
435- oversized-chunk 400, ...) costs only its own batch, which is logged
436- and omitted from the result, so one bad call cannot cancel the rest
437- of the fan-out. Callers can detect partial results by comparing the
438- returned batches against the submitted ones. ``ValueError`` and
439- ``NotImplementedError`` signal misconfiguration rather than infra
440- trouble and keep propagating.
440+ oversized-chunk 400, malformed structured response, ...) costs only
441+ its own batch, which is logged and omitted from the result, so one bad
442+ call cannot cancel the rest of the fan-out. Callers can detect partial
443+ results by comparing the returned batches against the submitted ones.
444+ ``ValueError`` and `` NotImplementedError`` signal misconfiguration
445+ rather than infra trouble and keep propagating.
441446
442447 The return type mirrors :meth:`run_batches`.
443448 """
@@ -462,6 +467,9 @@ async def _process(batch: Batch) -> tuple[Batch, list]:
462467 results = await asyncio .gather (* [_process (b ) for b in batches ], return_exceptions = True )
463468 successful : list [tuple [Batch , list ]] = []
464469 for batch , result in zip (batches , results , strict = True ):
470+ if isinstance (result , ValidationError ):
471+ logger .warning ("LLM batch failed for %s: %s" , batch .file_label , result )
472+ continue
465473 if isinstance (result , (ValueError , NotImplementedError )):
466474 raise result
467475 if isinstance (result , BaseException ):
0 commit comments