Repository navigation
feat: add an opt-in raise mode for LLM errors in the pipeline stages - #63
Merged
Merged
Conversation
When an LLM call fails, the Atomizer, Reviser, NLI extractor and Summarizer print the error and fall back to a default result, so a caller cannot tell that a run was computed from fallbacks or handle the error itself. Add a raise_errors keyword (default False) to Atomizer, Reviser, NLIExtractor, ContextSummarizer and the AtomizerReviser and NLIAdapterExtractor adapters. A shared handle_generation_error helper in fact_reasoner.utils keeps today's printed message and fallback by default, and re-raises the backend's own exception (same object, type, traceback and cause) when the option is on. In a stage batch, the error of the first failed item in input order is raised once the batch has finished. ContextRetriever summarizes contexts inside its retrieval workers and logs any worker error as a failed retrieval, which would swallow the summarizer's error again. When the summarizer's raise_errors is True, its error is now handed back to retrieve_all, which cancels the atoms still queued and re-raises it. Retrieval errors, and summarizer errors in any other mode, keep their existing log-and-skip behaviour. FactualityRunner takes raise_errors and passes it to the atomizer, reviser, NLI and summarizer stages, the CLI exposes it as --raise-errors, and the README documents both. QueryBuilder is not covered. Fixes IBM#61 Assisted-by: Claude Code Signed-off-by: Yashvardhan Thanvi <yashvt9404@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #61
Related: #62 (stage speedups) touches the same files; happy to rebase on whichever lands first.
Problem
When an LLM call fails, each stage prints the error and carries on with a default result:
Atomizer[Atomizer] Generation failed: .../Batch item failed: ...{}(no atoms)Reviser[Reviser] Batch item failed: ...NLIExtractor[NLI] Generation failed: .../Batch item failed: ...neutral, probability 1.0ContextSummarizer[Summarizer] Batch item failed: ...AtomizerReviserandNLIAdapterExtractordo the same. Nothing reaches the caller, so a script cannot tell that a score was computed from fallbacks, or retry.Change
raise_errors: bool = FalseonAtomizer,Reviser,NLIExtractor,ContextSummarizerandAtomizerReviser.NLIAdapterExtractorpasses it through its**kwargs.handle_generation_error()infact_reasoner/utils.py, replaces the print pluscount_fallbackpair in all seven places. By default it prints the same line and counts the samegeneration_errorfallback as today. Withraise_errors=Trueit re-raises the exception it was given. It is the same object, so the type, traceback and__cause__are kept andexcept openai.APIConnectionError(or whatever the backend raised) works.run_throttledis unchanged and still collects every item, then the stage raises the error of the first failed item in input order. I did not add early cancellation there, sorun_throttled(also used by the LCS miner and NLI training) stays as it is. Happy to add it if you prefer failing fast.ContextRetriever: in the runner pipeline, contexts are summarized inside the retrieval workers, andretrieve_alllogs any worker error as "Retrieval failed" and skips the atom, which would swallow the summarizer's error again. When the summarizer'sraise_errorsisTrue, its error is now handed back toretrieve_all, which cancels the atoms still queued (so they make no more search, fetch or summarizer calls) and re-raises it; atoms already running finish first. Retrieval errors, and summarizer errors in any other mode, keep their log-and-skip behaviour.FactualityRunner(raise_errors=...)passes the option to the atomizer, reviser, NLI and summarizer stages, the same wayshow_progressis passed. The CLI gets--raise-errorsbecauseassess_fileresumes by skipping inputs already in its output file, including ones scored from fallbacks during an outage; with the flag the run stops instead, and the item is retried on restart. The README has a new row under "Common options" and a short "Handling LLM errors" section.Only errors raised by the backend call are affected. Unsuccessful sampling (validation) and unparsable output keep their fallbacks in both modes, and the default behaviour, printed lines and telemetry counts do not change.
Known limits:
QueryBuilder.runhas the same print-and-fallback pattern (it returns the original text). I left it out to keep this to the stages in the issue; it is a small change with the same helper if you want it.nli_method="simbauq", if every sample fails inside the backend's_generate_from_context, the SIMBA-UQ strategy skips them and the caller getsRuntimeError("No successful samples were generated.")without the backend error as its cause. Mellea's OpenAI-compatible backends send the request after_generate_from_contextreturns and raise when the output is awaited, which is outside that guard, so their errors still propagate.The name
raise_errorsis my choice since the issue does not name one; easy to rename.Tests
New
tests/core/test_raise_errors.pyuses a real Mellea backend (aDummyBackendsubclass) whose_generate_from_contextraises a freshConnectionError("Connection error.")on every call, so the full Mellea path runs without a network. For each of 10 entry points (runandrun_batchofAtomizer,Reviser,NLIExtractorandContextSummarizer, plusNLIAdapterExtractor.runandAtomizerReviser.run):[Summarizer] Batch item failed: Connection error., as in the issue) and counts one(component, "generation_error")fallback per failed call;raise_errors=Trueraises aConnectionErrorthat is one of the exact objects the backend raised, whose traceback still reaches_generate_from_context, and prints and counts no fallback.Five
ContextRetriever.retrieve_alltests: by default the unsummarized contexts are dropped; in raise mode the summarizer's error is re-raised; a summarizer whoseraise_errorsis not exactlyTrue(a plain mock) keeps the old log-and-skip path; raise mode cancels the queued atoms (8 atoms, 1 worker: fewer than 8 searches run); a search error is still skipped in raise mode.tests/test_runner.pyandtests/test_cli.pycheck that the option defaults to False and reaches the four stages (with and without the LoRA adapter backends) and that--raise-errorsreaches the runner.The 11 full-suite failures are the same tests on main (Windows has no
os.killpgand locks the temp SQLite file; two needmellea-ibm).ruff check .andmypy -p fact_reasonerreport the same findings as on main, none new, andruff formatis clean for the new test file and every touched file that was clean before.