Enable bounded output repair in the engine
This commit is contained in:
@@ -844,7 +844,7 @@ func TestBackendExtraParamsAreDeeplyCopiedAtConstructionAndLookup(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineValidationIsSinglePass(t *testing.T) {
|
||||
func TestEngineValidationWithZeroRepairBudgetIsSinglePass(t *testing.T) {
|
||||
client := &fakeLLMClient{
|
||||
response: &promptkit.GenerateResponse{Content: "not-json"},
|
||||
}
|
||||
@@ -859,7 +859,7 @@ func TestEngineValidationIsSinglePass(t *testing.T) {
|
||||
Validation: &promptkit.OutputContract{
|
||||
Format: promptkit.FormatJSON,
|
||||
ValidationMode: promptkit.ValidationJSON,
|
||||
RepairAttempts: 3,
|
||||
RepairAttempts: 0,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -874,6 +874,87 @@ func TestEngineValidationIsSinglePass(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineRunRepairsJSONSchemaOutput(t *testing.T) {
|
||||
client := &fakeLLMClient{responses: []*promptkit.GenerateResponse{
|
||||
{
|
||||
Content: "{}",
|
||||
Usage: promptkit.TokenUsage{PromptTokens: 2, CompletionTokens: 3, TotalTokens: 5, CachedTokens: 7, CacheWriteTokens: 11},
|
||||
},
|
||||
{
|
||||
Content: `{"events":[{"title":"Repaired event"}]}`,
|
||||
Usage: promptkit.TokenUsage{PromptTokens: 13, CompletionTokens: 17, TotalTokens: 19, CachedTokens: 23, CacheWriteTokens: 29},
|
||||
},
|
||||
}}
|
||||
engine := newContractEngineWithOptions(t, frameworkSchemaDir, promptkit.WithLLMClient(client))
|
||||
|
||||
result, err := engine.Run(context.Background(), promptkit.RunRequest{
|
||||
PromptID: frameworkStructuredEventsPromptID,
|
||||
SessionID: " repair-session ",
|
||||
Inputs: map[string]promptkit.ArtifactRef{
|
||||
"transcript": promptkit.Inline("Rin opens the gate."),
|
||||
"glossary": promptkit.Inline("gate: A guarded passage."),
|
||||
},
|
||||
Validation: &promptkit.OutputContract{
|
||||
Format: promptkit.FormatJSON,
|
||||
ValidationMode: promptkit.ValidationJSONSchema,
|
||||
SchemaPath: "structured_events.schema.json",
|
||||
RepairAttempts: 1,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("run: %v", err)
|
||||
}
|
||||
if result.RawOutput != client.responses[1].Content || result.Validation.Status != promptkit.ValidationPassed ||
|
||||
result.Validation.RepairAttempts != 1 {
|
||||
t.Fatalf("repaired result = %+v", result)
|
||||
}
|
||||
wantUsage := promptkit.TokenUsage{PromptTokens: 15, CompletionTokens: 20, TotalTokens: 24, CachedTokens: 30, CacheWriteTokens: 40}
|
||||
if result.Usage != wantUsage {
|
||||
t.Fatalf("usage = %+v, want %+v", result.Usage, wantUsage)
|
||||
}
|
||||
if len(client.requests) != 2 {
|
||||
t.Fatalf("generation calls = %d, want 2", len(client.requests))
|
||||
}
|
||||
initial, repaired := client.requests[0], client.requests[1]
|
||||
if initial.Prompt.SessionID != "repair-session" || repaired.Prompt.SessionID != initial.Prompt.SessionID ||
|
||||
!reflect.DeepEqual(repaired.Target, initial.Target) || repaired.TargetPresence != initial.TargetPresence ||
|
||||
!reflect.DeepEqual(repaired.StructuredOutput, initial.StructuredOutput) {
|
||||
t.Fatalf("generation request state drifted: initial=%+v repaired=%+v", initial, repaired)
|
||||
}
|
||||
if initial.StructuredOutput == nil || initial.StructuredOutput.JSONSchema == nil {
|
||||
t.Fatalf("expected structured output on initial request: %+v", initial)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineRunReturnsFinalResultAfterRepairExhaustion(t *testing.T) {
|
||||
client := &fakeLLMClient{responses: []*promptkit.GenerateResponse{
|
||||
{Content: "not-json", Usage: promptkit.TokenUsage{PromptTokens: 2, CompletionTokens: 3, TotalTokens: 5}},
|
||||
{Content: "still-not-json", Usage: promptkit.TokenUsage{PromptTokens: 7, CompletionTokens: 11, TotalTokens: 18}},
|
||||
}}
|
||||
engine := newContractEngineWithOptions(t, frameworkSchemaDir, promptkit.WithLLMClient(client))
|
||||
|
||||
result, err := engine.Run(context.Background(), promptkit.RunRequest{
|
||||
PromptID: frameworkMarkdownSummaryPromptID,
|
||||
Inputs: map[string]promptkit.ArtifactRef{
|
||||
"transcript": promptkit.Inline("Rin opens the gate."),
|
||||
"glossary": promptkit.Inline("gate: A guarded passage."),
|
||||
},
|
||||
Validation: &promptkit.OutputContract{
|
||||
Format: promptkit.FormatJSON,
|
||||
ValidationMode: promptkit.ValidationJSON,
|
||||
RepairAttempts: 1,
|
||||
},
|
||||
})
|
||||
if err != nil || result == nil {
|
||||
t.Fatalf("run = (%+v, %v), want exhausted result", result, err)
|
||||
}
|
||||
if result.RawOutput != "still-not-json" || result.Validation.Status != promptkit.ValidationFailed ||
|
||||
result.Validation.RepairAttempts != 1 || len(result.Validation.Errors) == 0 ||
|
||||
result.Usage != (promptkit.TokenUsage{PromptTokens: 9, CompletionTokens: 14, TotalTokens: 23}) {
|
||||
t.Fatalf("exhausted result = %+v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepeatedOptionsUseLastValueInEachCategory(t *testing.T) {
|
||||
profile := promptkit.Profile{ID: "profile", Endpoint: "http://example.test/v1", Model: "model"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user