fix: degrade to empty result on LLM retry exhaustion instead of aborting

llm_completion/llm_acompletion raised RuntimeError once retries were
exhausted, which propagated through the unprotected sync TOC-detection
chain (find_toc_pages -> toc_transformer -> toc_extractor -> ...) and
aborted the whole document index — a regression vs. the pre-SDK behavior
that returned "" and let callers degrade (extract_json('') -> {} ->
.get(default), falling back to no-TOC indexing).

Restore the empty-result contract ("" / ("", "error") with
return_finish_reason), now logged at WARNING so the failure is visible
rather than silent. The async gather sites keep return_exceptions=True to
absorb any non-LLM error. A single persistently-failing call no longer
blocks indexing the rest of the document.
This commit is contained in:
mountain 2026-07-10 10:49:58 +08:00
parent d3ea9b9f2e
commit 9ad7c687e0
2 changed files with 54 additions and 4 deletions

View file

@ -165,8 +165,17 @@ def llm_completion(model, prompt, chat_history=None, return_finish_reason=False)
if i < max_retries - 1:
time.sleep(1)
else:
logger.error('Max retries reached for prompt: ' + prompt)
raise RuntimeError(f"LLM call failed after {max_retries} retries") from e
# Degrade gracefully instead of aborting the whole index: a single
# persistently-failing call returns an empty result so callers can
# skip that step (extract_json('') -> {} -> .get(default)) and the
# rest of the document still gets indexed. Logged at WARNING so the
# failure is visible, not silent.
logger.warning(
"LLM completion failed after %d retries; degrading to an empty "
"result so the caller can skip this step. Last error: %s",
max_retries, e,
)
return ("", "error") if return_finish_reason else ""
@ -192,8 +201,16 @@ async def llm_acompletion(model, prompt):
if i < max_retries - 1:
await asyncio.sleep(1)
else:
logger.error('Max retries reached for prompt: ' + prompt)
raise RuntimeError(f"LLM call failed after {max_retries} retries") from e
# Degrade gracefully (see llm_completion): return an empty result
# so the caller skips this step and the rest of the document still
# indexes. The gather sites still keep return_exceptions=True to
# absorb any non-LLM error. WARNING so it's visible, not silent.
logger.warning(
"Async LLM completion failed after %d retries; degrading to an "
"empty result so the caller can skip this step. Last error: %s",
max_retries, e,
)
return ""
def extract_json(content):