Improve item occurrence holder corrections
This commit is contained in:
@@ -6,7 +6,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
itemidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/items/identity"
|
||||
itemoccurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemoccurrences/shape"
|
||||
)
|
||||
|
||||
func TestExtractGroundsOccurrencesInRequiredRegistry(t *testing.T) {
|
||||
@@ -146,3 +148,46 @@ func TestExtractUsesOnlySupportedPromptInputs(t *testing.T) {
|
||||
t.Fatalf("unexpected prompt input: %#v", client.requests[0].Inputs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractCarriesRejectedHolderGuidanceIntoCorrectedAttempt(t *testing.T) {
|
||||
references := itemRegistryReferences(t)
|
||||
req := extractionRequest()
|
||||
req.References = references
|
||||
defectiveResponse := []byte(`{"occurrences":[{"name":"Torch","kind":"acquired","quantity":null,"from":"Chest","to":"party","source_refs":[{"start_unit_id":2,"end_unit_id":2}]}]}`)
|
||||
defective, err := newExtractor(t, &fakeItemOccurrencesLLMClient{content: defectiveResponse}, references).Extract(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
validation, err := itemoccurrenceshape.New(itemoccurrenceshape.Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.ItemOccurrenceList]{Value: defective.Value})
|
||||
if err != nil || validation.Approved {
|
||||
t.Fatalf("holder validation = %#v, %v; want rejection", validation, err)
|
||||
}
|
||||
for _, fragment := range []string{"For `acquired` occurrences", "`from` to JSON null", "item \"Torch\"", "source unit 2"} {
|
||||
if !strings.Contains(validation.CorrectionGuidance, fragment) {
|
||||
t.Fatalf("CorrectionGuidance = %q, want %q", validation.CorrectionGuidance, fragment)
|
||||
}
|
||||
}
|
||||
for _, forbidden := range []string{defective.Value.Occurrences[0].ItemID, "item_id", "sha256", itemoccurrenceshape.Key, itemoccurrenceshape.ReasonCode} {
|
||||
if strings.Contains(validation.CorrectionGuidance, forbidden) {
|
||||
t.Fatalf("CorrectionGuidance leaked implementation identifier %q: %q", forbidden, validation.CorrectionGuidance)
|
||||
}
|
||||
}
|
||||
correction, err := contracts.NewSemanticCorrection(defective.ModelCandidate.Response, validation.CorrectionGuidance)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
correctedResponse := []byte(`{"occurrences":[{"name":"Torch","kind":"acquired","quantity":null,"from":null,"to":"party","source_refs":[{"start_unit_id":2,"end_unit_id":2}]}]}`)
|
||||
client := &fakeItemOccurrencesLLMClient{content: correctedResponse}
|
||||
req.Correction = correction
|
||||
corrected, err := newExtractor(t, client, references).Extract(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
accepted, err := itemoccurrenceshape.New(itemoccurrenceshape.Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.ItemOccurrenceList]{Value: corrected.Value})
|
||||
if err != nil || !accepted.Approved {
|
||||
t.Fatalf("corrected holder validation = %#v, %v; want approval", accepted, err)
|
||||
}
|
||||
if len(client.requests) != 1 || client.requests[0].Correction == nil || string(client.requests[0].Correction.AssistantResponse) != string(defectiveResponse) || client.requests[0].Correction.UserGuidance != validation.CorrectionGuidance {
|
||||
t.Fatalf("corrected request = %#v, want byte-faithful defective response and semantic guidance", client.requests)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,17 @@ func TestPromptAssetsPrepareItemOccurrencePrompt(t *testing.T) {
|
||||
t.Fatalf("prepared prompt contains obsolete evidence field %q: %s", obsolete, content)
|
||||
}
|
||||
}
|
||||
for _, holderRule := range []string{
|
||||
"| `discovered` | `null` | `null` |",
|
||||
"| `acquired` | `null` | `party` or the named party member gaining possession |",
|
||||
"| `lost` | `party` or the named party member losing possession | `null` |",
|
||||
"| `consumed` | `party` or the named party member consuming the item | `null` |",
|
||||
"| `transferred` | one named party member | a different named party member |",
|
||||
} {
|
||||
if !strings.Contains(content, holderRule) {
|
||||
t.Fatalf("prepared prompt does not include holder rule %q", holderRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromptAssetsDoNotLeakIntoMetadata(t *testing.T) {
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestResponseSchemaIsStrictlyStructuralAndPrivate(t *testing.T) {
|
||||
}
|
||||
valid := map[string]any{"occurrences": []any{
|
||||
map[string]any{
|
||||
"name": "", "kind": "unsupported", "quantity": 0, "from": "party", "to": "Party",
|
||||
"name": "", "kind": "transferred", "quantity": 0, "from": "party", "to": "Party",
|
||||
"source_refs": []any{map[string]any{"start_unit_id": 0, "end_unit_id": -1}},
|
||||
},
|
||||
map[string]any{
|
||||
@@ -41,6 +41,7 @@ func TestResponseSchemaIsStrictlyStructuralAndPrivate(t *testing.T) {
|
||||
{"missing occurrences", map[string]any{}},
|
||||
{"missing occurrence name", map[string]any{"occurrences": []any{withoutField(responseOccurrence(), "name")}}},
|
||||
{"missing nullable field", map[string]any{"occurrences": []any{withoutField(responseOccurrence(), "quantity")}}},
|
||||
{"unsupported kind", map[string]any{"occurrences": []any{withField(responseOccurrence(), "kind", "unsupported")}}},
|
||||
{"opaque item identifier", map[string]any{"occurrences": []any{withField(responseOccurrence(), "item_id", "item:sha256:opaque")}}},
|
||||
{"unknown occurrence field", map[string]any{"occurrences": []any{withField(responseOccurrence(), "extra", true)}}},
|
||||
{"segment-named range", map[string]any{"occurrences": []any{withField(responseOccurrence(), "source_refs", []any{map[string]any{"start_segment": 1, "end_segment": 1}})}}},
|
||||
|
||||
Reference in New Issue
Block a user