Hardened the proposal modules to skip malformed proposals rather than hard failing the entire run
All checks were successful
ci/woodpecker/tag/release Pipeline was successful

This commit is contained in:
2026-05-17 07:19:24 -05:00
parent a84941d681
commit 9c2d8338d7
17 changed files with 436 additions and 137 deletions

View File

@@ -44,6 +44,16 @@ type StructuredCorrectionSet struct {
Corrections []StructuredCorrectionProposal `json:"corrections"`
}
type DroppedCandidate struct {
ProposalIndex int `json:"proposal_index"`
TargetSegmentID int `json:"target_segment_id"`
OriginalText string `json:"original_text"`
CorrectedText string `json:"corrected_text"`
Confidence float64 `json:"confidence"`
ReasonCode string `json:"reason_code"`
Message string `json:"message"`
}
// Request captures reusable proposal-generation inputs for future modules.
type Request struct {
ModuleKey string `json:"module_key"`
@@ -62,12 +72,14 @@ type Request struct {
Scheduler contracts.LLMScheduler
DiagnosticsDir string
DiagnosticsWriter InteractionDiagnosticsWriter
OnDroppedCandidate func(dropped DroppedCandidate)
}
// Result contains generated candidate proposals and optional diagnostics paths.
type Result struct {
Corrections []proposals.CorrectionProposal `json:"corrections"`
Enriched []proposals.EnrichedCorrectionProposal `json:"enriched"`
Dropped []DroppedCandidate `json:"dropped,omitempty"`
Artifacts InteractionArtifacts `json:"artifacts,omitempty"`
}
@@ -161,7 +173,9 @@ func GenerateCandidates(ctx context.Context, req Request) (Result, error) {
corrections := make([]proposals.CorrectionProposal, 0, len(response.Corrections))
enriched := make([]proposals.EnrichedCorrectionProposal, 0, len(response.Corrections))
dropped := make([]DroppedCandidate, 0)
for i, raw := range response.Corrections {
proposalIndex := req.StartIndex + i
candidate := proposals.CorrectionProposal{
TargetSegmentID: raw.TargetSegmentID,
OriginalText: raw.OriginalText,
@@ -169,14 +183,27 @@ func GenerateCandidates(ctx context.Context, req Request) (Result, error) {
Confidence: raw.Confidence,
}
if err := candidate.Validate(); err != nil {
return Result{}, fmt.Errorf("invalid structured correction at index %d: %w", i, err)
record := DroppedCandidate{
ProposalIndex: proposalIndex,
TargetSegmentID: raw.TargetSegmentID,
OriginalText: raw.OriginalText,
CorrectedText: raw.CorrectedText,
Confidence: raw.Confidence,
ReasonCode: "invalid_structured_correction",
Message: fmt.Sprintf("invalid structured correction at index %d: %v", i, err),
}
dropped = append(dropped, record)
if req.OnDroppedCandidate != nil {
req.OnDroppedCandidate(record)
}
continue
}
corrections = append(corrections, candidate)
enrichedCandidate := proposals.EnrichedCorrectionProposal{
CorrectionProposal: candidate,
ProposalMetadata: proposals.ProposalMetadata{
ProposalIndex: req.StartIndex + i,
ProposalIndex: proposalIndex,
ModuleKey: req.ModuleKey,
ModuleInstance: req.ModuleInstance,
},
@@ -191,6 +218,7 @@ func GenerateCandidates(ctx context.Context, req Request) (Result, error) {
return Result{
Corrections: corrections,
Enriched: enriched,
Dropped: dropped,
Artifacts: artifacts,
}, nil
}

View File

@@ -229,7 +229,7 @@ func TestGenerateCandidatesMalformedStructuredResponse(t *testing.T) {
responses: []StructuredCorrectionSet{
{
Corrections: []StructuredCorrectionProposal{
{TargetSegmentID: 1, OriginalText: "x", CorrectedText: "", Confidence: 0.9},
{TargetSegmentID: 0, OriginalText: "x", CorrectedText: "", Confidence: 0.9},
},
},
},
@@ -237,9 +237,18 @@ func TestGenerateCandidatesMalformedStructuredResponse(t *testing.T) {
req := defaultRequest(t)
req.LLMClient = client
_, err := GenerateCandidates(context.Background(), req)
if err == nil || !strings.Contains(err.Error(), "invalid structured correction") {
t.Fatalf("expected structured response validation failure, got %v", err)
got, err := GenerateCandidates(context.Background(), req)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(got.Corrections) != 0 || len(got.Enriched) != 0 {
t.Fatalf("expected invalid proposal to be dropped, got %+v", got)
}
if len(got.Dropped) != 1 {
t.Fatalf("expected one dropped candidate, got %+v", got.Dropped)
}
if got.Dropped[0].ReasonCode != "invalid_structured_correction" {
t.Fatalf("unexpected dropped reason: %+v", got.Dropped[0])
}
}