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
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user