66
77from __future__ import annotations
88
9+ import json
910import logging
1011from typing import TYPE_CHECKING , Any , TypeVar
1112
2122from instructor .v2 .core .mode import Mode
2223from instructor .v2 .core .providers import Provider
2324from instructor .v2 .core .errors import (
25+ AsyncValidationError ,
2426 FailedAttempt ,
2527 IncompleteOutputException ,
2628 InstructorRetryException ,
29+ ResponseParsingError ,
2730)
2831from instructor .v2 .dsl .iterable import IterableBase
2932from instructor .v2 .dsl .response_list import ListResponse
4144logger = logging .getLogger ("instructor.v2.retry" )
4245
4346T_Model = TypeVar ("T_Model" , bound = BaseModel )
47+ _RETRYABLE_PARSE_ERRORS = (
48+ ValidationError ,
49+ json .JSONDecodeError ,
50+ AsyncValidationError ,
51+ ResponseParsingError ,
52+ )
4453
4554
4655def _max_attempts (max_retries : int | Retrying | AsyncRetrying ) -> int | None :
47- return max (max_retries , 1 ) if isinstance (max_retries , int ) else None
56+ return max (max_retries , 0 ) + 1 if isinstance (max_retries , int ) else None
4857
4958
5059def _attempt_metadata (
@@ -121,7 +130,7 @@ def retry_sync_v2(
121130 provider: Provider enum
122131 mode: Mode enum
123132 context: Validation context
124- max_retries: Max retry attempts or Retrying instance
133+ max_retries: Maximum retries after the initial attempt, or Retrying instance
125134 args: Positional args for func
126135 kwargs: Keyword args for func
127136 strict: Strict validation mode
@@ -143,13 +152,13 @@ def retry_sync_v2(
143152
144153 # Setup retrying
145154 if isinstance (max_retries , int ):
146- stop_condition = stop_after_attempt (max (max_retries , 1 ) )
155+ stop_condition = stop_after_attempt (max (max_retries , 0 ) + 1 )
147156 timeout = kwargs .get ("timeout" )
148157 if isinstance (timeout , (int , float )):
149158 stop_condition = stop_condition | stop_after_delay (timeout )
150159 max_retries_instance = Retrying (
151160 stop = stop_condition ,
152- retry = retry_if_exception_type (ValidationError ),
161+ retry = retry_if_exception_type (_RETRYABLE_PARSE_ERRORS ),
153162 reraise = True ,
154163 )
155164 else :
@@ -217,7 +226,7 @@ def retry_sync_v2(
217226
218227 except IncompleteOutputException :
219228 raise
220- except ValidationError as e :
229+ except _RETRYABLE_PARSE_ERRORS as e :
221230 logger .debug (f"Validation error on attempt { attempt_number } : { e } " )
222231 failed_attempts .append (
223232 FailedAttempt (
@@ -237,15 +246,6 @@ def retry_sync_v2(
237246 is_last_attempt = max_attempts == attempt_number ,
238247 ),
239248 )
240- hooks .emit_completion_error (
241- e ,
242- ** _attempt_metadata (
243- attempt_number = attempt_number ,
244- max_attempts = max_attempts ,
245- is_last_attempt = max_attempts == attempt_number ,
246- ),
247- )
248-
249249 # Prepare reask using registry
250250 kwargs = handlers .reask_handler (
251251 kwargs = kwargs ,
@@ -260,11 +260,10 @@ def retry_sync_v2(
260260 raise
261261 except Exception as e :
262262 # Max retries exceeded or non-validation error occurred
263- if last_exception is None :
264- last_exception = e
263+ last_exception = e
265264
266265 logger .error (
267- f"Max retries exceeded. Total attempts: { len ( failed_attempts ) } , "
266+ f"Max retries exceeded. Total attempts: { last_attempt_number } , "
268267 f"Last error: { last_exception } "
269268 )
270269 if hooks :
@@ -280,7 +279,7 @@ def retry_sync_v2(
280279 raise InstructorRetryException (
281280 str (last_exception ),
282281 last_completion = failed_attempts [- 1 ].completion if failed_attempts else None ,
283- n_attempts = len ( failed_attempts ) ,
282+ n_attempts = last_attempt_number ,
284283 total_usage = total_usage ,
285284 messages = extract_messages (kwargs ),
286285 create_kwargs = kwargs ,
@@ -292,7 +291,7 @@ def retry_sync_v2(
292291 raise InstructorRetryException (
293292 str (last_exception ) if last_exception else "Unknown error" ,
294293 last_completion = failed_attempts [- 1 ].completion if failed_attempts else None ,
295- n_attempts = len ( failed_attempts ) ,
294+ n_attempts = last_attempt_number ,
296295 total_usage = total_usage ,
297296 messages = extract_messages (kwargs ),
298297 create_kwargs = kwargs ,
@@ -376,7 +375,7 @@ async def retry_async_v2(
376375 provider: Provider enum
377376 mode: Mode enum
378377 context: Validation context
379- max_retries: Max retry attempts or AsyncRetrying instance
378+ max_retries: Maximum retries after the initial attempt, or AsyncRetrying instance
380379 args: Positional args for func
381380 kwargs: Keyword args for func
382381 strict: Strict validation mode
@@ -398,13 +397,13 @@ async def retry_async_v2(
398397
399398 # Setup retrying
400399 if isinstance (max_retries , int ):
401- stop_condition = stop_after_attempt (max (max_retries , 1 ) )
400+ stop_condition = stop_after_attempt (max (max_retries , 0 ) + 1 )
402401 timeout = kwargs .get ("timeout" )
403402 if isinstance (timeout , (int , float )):
404403 stop_condition = stop_condition | stop_after_delay (timeout )
405404 max_retries_instance = AsyncRetrying (
406405 stop = stop_condition ,
407- retry = retry_if_exception_type (ValidationError ),
406+ retry = retry_if_exception_type (_RETRYABLE_PARSE_ERRORS ),
408407 reraise = True ,
409408 )
410409 else :
@@ -472,7 +471,7 @@ async def retry_async_v2(
472471
473472 except IncompleteOutputException :
474473 raise
475- except ValidationError as e :
474+ except _RETRYABLE_PARSE_ERRORS as e :
476475 logger .debug (f"Validation error on attempt { attempt_number } : { e } " )
477476 failed_attempts .append (
478477 FailedAttempt (
@@ -492,15 +491,6 @@ async def retry_async_v2(
492491 is_last_attempt = max_attempts == attempt_number ,
493492 ),
494493 )
495- hooks .emit_completion_error (
496- e ,
497- ** _attempt_metadata (
498- attempt_number = attempt_number ,
499- max_attempts = max_attempts ,
500- is_last_attempt = max_attempts == attempt_number ,
501- ),
502- )
503-
504494 # Prepare reask using registry
505495 kwargs = handlers .reask_handler (
506496 kwargs = kwargs ,
@@ -515,11 +505,10 @@ async def retry_async_v2(
515505 raise
516506 except Exception as e :
517507 # Max retries exceeded or non-validation error occurred
518- if last_exception is None :
519- last_exception = e
508+ last_exception = e
520509
521510 logger .error (
522- f"Max retries exceeded. Total attempts: { len ( failed_attempts ) } , "
511+ f"Max retries exceeded. Total attempts: { last_attempt_number } , "
523512 f"Last error: { last_exception } "
524513 )
525514 if hooks :
@@ -535,7 +524,7 @@ async def retry_async_v2(
535524 raise InstructorRetryException (
536525 str (last_exception ),
537526 last_completion = failed_attempts [- 1 ].completion if failed_attempts else None ,
538- n_attempts = len ( failed_attempts ) ,
527+ n_attempts = last_attempt_number ,
539528 total_usage = total_usage ,
540529 messages = extract_messages (kwargs ),
541530 create_kwargs = kwargs ,
@@ -547,7 +536,7 @@ async def retry_async_v2(
547536 raise InstructorRetryException (
548537 str (last_exception ) if last_exception else "Unknown error" ,
549538 last_completion = failed_attempts [- 1 ].completion if failed_attempts else None ,
550- n_attempts = len ( failed_attempts ) ,
539+ n_attempts = last_attempt_number ,
551540 total_usage = total_usage ,
552541 messages = extract_messages (kwargs ),
553542 create_kwargs = kwargs ,
0 commit comments