fix(checks): preserve partial trace on input generation failure - #2718
Conversation
kevinmessiaen
left a comment
There was a problem hiding this comment.
Hey @dev404ai
Thanks for the contribution
While the fix is good, I would probably let with_interaction raise an InteractionGenerationError(partial_trace) in order to keep the interaction handling the generator logic and easily apply same logic on potential future call sites
|
Thanks — reworked in that direction.
One compatibility point to confirm: direct I kept the review follow-up as a second commit so the delta is easy to inspect. Happy to squash if you prefer a single commit. |
Two follow-ups to the InteractionGenerationError work on this PR. Trace.with_interaction is recursive and its `except Exception` sat at every level, so a spec driving another spec through the trace had its inner wrapper caught and re-wrapped. The runner unwraps one level, so the generic wrapper reached the caller and the inner spec's completed interactions were dropped. The inner wrapper's partial trace is built from the outer one, so it already holds strictly more progress and the root cause: re-raise it untouched instead. ScenarioRunner re-raised with `raise error from error.__cause__`, which becomes `raise error from None` when the generator chained its error implicitly (a bare `raise` inside an `except`). That set __suppress_context__ and hid the root cause on the raise path while return_exception=True still reported it. Restore the original context links around the re-raise so both paths agree. Adds regressions for nested composition, implicit context chaining, and a generator that fails before its first yield. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Description
When an
InteractionSpecyielded an interaction and then raised,ScenarioRunnercorrectly recorded the input-generation error withreturn_exception=True, but itsfinal_tracestill pointed to the trace from before the spec started. The failing generator had already received the updated trace, so the returned error result lost observable partial progress and leftlast_interaction_indexunset.This change keeps the generator-driving loop in
Trace.with_interaction. If a later yield fails,with_interactionraisesInteractionGenerationError(partial_trace)from the original error.ScenarioRunneradopts that partial trace and continues to expose the original exception type, message, traceback, its own__cause__chain, andreturn_exception=Falsebehavior; the wrapper is suppressed from the reported context rather than shown as an extra frame.Direct
Trace.with_interaction(spec)andTrace.with_interactions(*specs)callers now receiveInteractionGenerationErrorwhere the generator's own exception used to surface, so code catching a specific error there — for exampleInputGenerationExceptionraised byLLMGenerator— needsexcept InteractionGenerationErrorand.__cause__. That is the intended consequence of keeping generator handling in one place. ConcreteInteractionarguments keep the existing fast path and never raise it, so the in-treegiskard-scancall sites are unaffected, andCancelledErrorderives fromBaseExceptionand is not wrapped, so cancellation and generator cleanup are unchanged.The existing runner regression now asserts the preserved interaction and its step index, and a second runner regression asserts that a chained generator failure reaches the caller unwrapped with its own cause intact. A direct
Trace.with_interactionsregression covers prior trace state, multiple successful yields, the original exception cause, and async-generator cleanup. This is a focused follow-up to the input-generation error handling introduced by #2523 and #2599; it does not reopen or replace either scope.Maintainer-requested architecture
The initial patch duplicated generator advancement in
ScenarioRunnerand fixed only that call site. Review #4912803161 requested an exception carrying the partial trace so the protocol remains owned byTraceand future call sites can use the same behavior. The revised patch follows that direction: there is one generator loop, direct callers can recover completed progress, and the runner unwraps the chained cause so its external error contract remains stable.AI assistance disclosure: I used AI coding assistants (Codex and Claude) to help investigate, implement, test, and review this change. I reviewed the resulting diff and verification evidence.
Related Issue
Follow-up to #2523 and #2599. There is no exact open issue for partial-trace loss.
Type of Change
Verification
make formatmake checkmake test-unit PACKAGE=giskard-checks— 807 passed, 4 skippedgit diff --checkChecklist
CODE_OF_CONDUCT.mddocument.CONTRIBUTING.mdguide.uv.lockrunninguv lock(only applicable whenpyproject.tomlhas been modified). Not applicable:pyproject.tomlwas not modified.