@@ -32,6 +32,7 @@ class LLMTranslator(Translator):
3232 """
3333
3434 CHUNK_SIZE = 30
35+ RETRY_STREAK = 10 # Number of consecutive chunks to use retry model after a failure
3536
3637 def __init__ (
3738 self ,
@@ -86,6 +87,11 @@ def make_chunks(texts: list[str], chunk_size: int = 30) -> list[list[tuple[int,
8687
8788 return chunks
8889
90+ @staticmethod
91+ def _is_valid_translation (translated : list [str ] | None , expected_len : int ) -> bool :
92+ """Check whether a translation result is usable (non-empty and correct line count)."""
93+ return translated is not None and len (translated ) == expected_len
94+
8995 def _translate_chunk (
9096 self ,
9197 translator_agent : ChunkedTranslatorAgent ,
@@ -97,56 +103,65 @@ def _translate_chunk(
97103 """
98104 Translate a single chunk of text, with retry mechanism.
99105
100- This method attempts to translate the chunk using the primary translator agent.
101- If the translation fails or is inconsistent, it may use a retry agent or remove the glossary.
106+ Tries the primary agent first, then optionally falls back to a retry agent.
107+ Each agent attempt includes a glossary-removal retry when the line count
108+ is inconsistent.
102109
103- Args:
104- translator_agent (ChunkedTranslatorAgent): Primary agent for translation.
105- chunk (List[Tuple[int, str]]): Chunk of text to translate.
106- context (TranslationContext): Current translation context.
107- chunk_id (int): ID of the current chunk.
108- retry_agent (Optional[ChunkedTranslatorAgent]): Agent for retry attempts.
109-
110- Returns:
111- Tuple[List[str], TranslationContext]: Translated texts and updated context.
110+ Returns the best available result. The caller should check whether
111+ ``len(translated) == len(chunk)`` to decide if further fallback
112+ (e.g. split or atomic translation) is needed.
112113 """
114+ expected = len (chunk )
113115
114- def handle_translation (agent : ChunkedTranslatorAgent ) -> tuple [list [str ] | None , TranslationContext | None ]:
116+ def _try_agent (agent : ChunkedTranslatorAgent ) -> tuple [list [str ] | None , TranslationContext | None ]:
117+ """Single agent attempt: translate, then retry without glossary if line count mismatches."""
115118 trans : list [str ] | None = None
116- updated_context : TranslationContext | None = None
119+ ctx : TranslationContext | None = None
117120 try :
118- trans , updated_context = agent .translate_chunk (chunk_id , chunk , context )
121+ trans , ctx = agent .translate_chunk (chunk_id , chunk , context )
119122 except ChatBotException :
120123 logger .error (f"Failed to translate chunk { chunk_id } ." )
124+ return None , None
121125
122- if trans is not None and len (trans ) != len (chunk ) and agent .info .glossary :
126+ if self ._is_valid_translation (trans , expected ):
127+ return trans , ctx
128+
129+ # Line count mismatch — retry without glossary if applicable.
130+ if trans is not None and agent .info .glossary :
123131 logger .warning (
124132 f"Agent { agent } : Removing glossary for chunk { chunk_id } due to inconsistent translation."
125133 )
126134 try :
127- trans , updated_context = agent .translate_chunk (chunk_id , chunk , context , use_glossary = False )
135+ trans , ctx = agent .translate_chunk (chunk_id , chunk , context , use_glossary = False )
128136 except ChatBotException :
129137 logger .error (f"Failed to translate chunk { chunk_id } ." )
130138
131- return trans , updated_context
139+ return trans , ctx
132140
133- translated : list [str ] | None
134- updated_ctx : TranslationContext | None
141+ translated : list [str ] | None = None
142+ updated_ctx : TranslationContext | None = None
135143
144+ # Step 1: Try primary or retry agent based on retry streak.
136145 if self .use_retry_cnt == 0 or not retry_agent :
137- translated , updated_ctx = handle_translation (translator_agent )
138-
139- if retry_agent and (translated is None or len (translated ) != len (chunk )):
140- self .use_retry_cnt = 10 # Use retry_agent for the next 10 chunks
141- logger .warning (
142- f"Using retry agent { retry_agent } for chunk { chunk_id } , and next { self .use_retry_cnt } chunks."
143- )
144- translated , updated_ctx = handle_translation (retry_agent )
146+ translated , updated_ctx = _try_agent (translator_agent )
145147 else :
146148 logger .info (f"Using retry agent for chunk { chunk_id } , remaining retries: { self .use_retry_cnt } " )
147- translated , updated_ctx = handle_translation (retry_agent )
149+ translated , updated_ctx = _try_agent (retry_agent )
148150 self .use_retry_cnt -= 1
149151
152+ # Step 2: If primary failed and retry agent is available, switch to it.
153+ if not self ._is_valid_translation (translated , expected ) and retry_agent and self .use_retry_cnt == 0 :
154+ self .use_retry_cnt = self .RETRY_STREAK
155+ logger .warning (
156+ f"Using retry agent { retry_agent } for chunk { chunk_id } , and next { self .use_retry_cnt } chunks."
157+ )
158+ translated , updated_ctx = _try_agent (retry_agent )
159+
160+ # Retry agent also failed — reset streak so next chunk tries primary first.
161+ if not self ._is_valid_translation (translated , expected ):
162+ logger .warning (f"Retry agent also failed for chunk { chunk_id } , resetting retry streak." )
163+ self .use_retry_cnt = 0
164+
150165 if not translated :
151166 raise ChatBotException (f"Failed to translate chunk { chunk_id } ." )
152167
@@ -220,12 +235,8 @@ def translate(
220235 atomic = False
221236 translated , context = self ._translate_chunk (translator_agent , chunk , context , i , retry_agent = retry_agent )
222237 chunk_texts = [c [1 ] for c in chunk ]
223- # Proofreader Not fully tested
224- # localized_trans = proofreader.proofread(
225- # texts=chunk_texts, translations=translated, context=context
226- # )
227238
228- if len (translated ) != len (chunk ):
239+ if not self . _is_valid_translation (translated , len (chunk ) ):
229240 logger .warning (
230241 f"Chunk { i } translation length inconsistent: { len (translated )} vs { len (chunk )} ,"
231242 f" Attempting atomic translation."
0 commit comments