Minimize D&D NPC extraction contracts
This commit is contained in:
@@ -49,9 +49,9 @@ func TestProductionCombatPipelineRetriesMergesNormalizesAndWritesJSON(t *testing
|
||||
|
||||
client := &fakeCombatLLMClient{responses: []string{
|
||||
combatTestInvalidEnumResponse("unsupported", "attack"),
|
||||
combatTestTurnResponse("The Greencloak", "turn", "watches", "The Greencloak", 1, 1),
|
||||
combatTestTurnResponse("mira thorn", "turn", "watches", "mira thorn", 1, 1),
|
||||
combatTestTurnResponse("Mira Thorn", "reaction", "asks", "Hooded Guard", 2, 2),
|
||||
combatTestTurnResponse("Hooded Guard", "turn", "attacks", "The Greencloak", 3, 3),
|
||||
combatTestTurnResponse("Hooded Guard", "turn", "attacks", "mira thorn", 3, 3),
|
||||
}}
|
||||
prepared, err := pipeline.Prepare(materialized, registries, pipeline.ModuleDependencies{LLM: client})
|
||||
if err != nil {
|
||||
@@ -250,8 +250,8 @@ type combatNPCPayload struct {
|
||||
func combatTestNPCPayload(t *testing.T) combatNPCPayload {
|
||||
t.Helper()
|
||||
value := dnd.NPCList{NPCs: []dnd.NPC{
|
||||
{ID: identity.DeriveID("Mira Thorn"), Name: "Mira Thorn", Aliases: []string{"The Greencloak", "Mira"}, Description: "A ranger.", Relationships: []dnd.NPCRelationship{}, SourceRefs: []source.SourceRef{{SourceID: "prior-npc-session", StartUnitID: 1, EndUnitID: 1}}},
|
||||
{ID: identity.DeriveID("Hooded Guard"), Name: "Hooded Guard", Aliases: []string{}, Description: "A sentry.", Relationships: []dnd.NPCRelationship{}, SourceRefs: []source.SourceRef{{SourceID: "prior-npc-session", StartUnitID: 3, EndUnitID: 3}}},
|
||||
{ID: identity.DeriveID("Mira Thorn"), Name: "Mira Thorn", SourceRefs: []source.SourceRef{{SourceID: "prior-npc-session", StartUnitID: 1, EndUnitID: 1}}},
|
||||
{ID: identity.DeriveID("Hooded Guard"), Name: "Hooded Guard", SourceRefs: []source.SourceRef{{SourceID: "prior-npc-session", StartUnitID: 3, EndUnitID: 3}}},
|
||||
}}
|
||||
content, err := npccodec.New().Encode(value)
|
||||
if err != nil {
|
||||
|
||||
@@ -65,14 +65,16 @@ func TestNPCOutputGroundsSpellAndCombatConsumersThroughOneOperation(t *testing.T
|
||||
t.Fatalf("encode canonical NPC producer payload: %v", err)
|
||||
}
|
||||
canonicalDigest := digestBytes(npcPayload)
|
||||
projection := []byte(`{"npcs":[{"name":"Mira Thorn"},{"name":"Hooded Guard"}]}`)
|
||||
projectionDigest := digestBytes(projection)
|
||||
seenConsumers := map[string]bool{}
|
||||
for _, request := range client.requestsSnapshot() {
|
||||
if request.PromptID != spells.PromptID && request.PromptID != combatextract.PromptID {
|
||||
continue
|
||||
}
|
||||
input := request.Inputs["npcs"]
|
||||
if input.MediaType != npccodec.MediaType || input.Digest != canonicalDigest || string(input.Content) != string(npcPayload) || input.OriginURI != "" {
|
||||
t.Fatalf("%s NPC prompt input = %#v, want canonical generated registry without provenance", request.PromptID, input)
|
||||
if input.MediaType != npccodec.MediaType || input.Digest != projectionDigest || string(input.Content) != string(projection) || input.OriginURI != "" {
|
||||
t.Fatalf("%s NPC prompt input = %#v, want names-only generated registry", request.PromptID, input)
|
||||
}
|
||||
seenConsumers[request.PromptID] = true
|
||||
}
|
||||
@@ -107,7 +109,7 @@ func TestNPCOutputGroundsSpellAndCombatConsumersThroughOneOperation(t *testing.T
|
||||
switch serialized.LaneID {
|
||||
case "spells":
|
||||
spellValue := decodeRunnerSpellResponse(t, serialized.Artifact.Content)
|
||||
if len(spellValue.SpellCasts) != 1 || spellValue.SpellCasts[0].Caster != "The Greencloak" {
|
||||
if len(spellValue.SpellCasts) != 1 || spellValue.SpellCasts[0].Caster != "Mira Thorn" {
|
||||
t.Fatalf("spell output = %#v, want one registry-grounded-context spell", spellValue)
|
||||
}
|
||||
assertSpellEvidence(t, spellValue.SpellCasts[0].SourceRefs)
|
||||
@@ -174,25 +176,23 @@ func (client *groundedDNDLLMClient) CompleteStructured(ctx context.Context, requ
|
||||
case npcs.PromptID:
|
||||
payload = map[string]any{"npcs": []any{
|
||||
map[string]any{
|
||||
"name": "Mira Thorn", "aliases": []string{"The Greencloak"}, "description": "A guarded ranger.",
|
||||
"relationships": []any{}, "source_refs": []any{map[string]int{"start_unit_id": 1, "end_unit_id": 1}},
|
||||
"name": "Mira Thorn", "source_refs": []any{map[string]int{"start_unit_id": 1, "end_unit_id": 1}},
|
||||
},
|
||||
map[string]any{
|
||||
"name": "Hooded Guard", "aliases": []string{}, "description": "A sentry.",
|
||||
"relationships": []any{}, "source_refs": []any{map[string]int{"start_unit_id": 3, "end_unit_id": 3}},
|
||||
"name": "Hooded Guard", "source_refs": []any{map[string]int{"start_unit_id": 3, "end_unit_id": 3}},
|
||||
},
|
||||
}}
|
||||
case spells.PromptID:
|
||||
payload = map[string]any{"spell_casts": []any{map[string]any{
|
||||
"caster": "The Greencloak", "spell": "Cure Wounds", "effect": "Restores an ally.",
|
||||
"narrative_description": "The Greencloak restores an ally.",
|
||||
"caster": "Mira Thorn", "spell": "Cure Wounds", "effect": "Restores an ally.",
|
||||
"narrative_description": "Mira Thorn restores an ally.",
|
||||
"source_refs": []any{map[string]int{"start_unit_id": 1, "end_unit_id": 1}},
|
||||
}}}
|
||||
case combatextract.PromptID:
|
||||
payload = map[string]any{"combat_turns": []any{map[string]any{
|
||||
"actor": "The Greencloak", "turn_kind": "turn", "round": 1,
|
||||
"actor": "Mira Thorn", "turn_kind": "turn", "round": 1,
|
||||
"actions": []any{map[string]any{"category": "attack", "declaration": "watches", "targets": []string{"Hooded Guard"}, "resolution": "observed"}},
|
||||
"summary": "The Greencloak watches the gate.",
|
||||
"summary": "Mira Thorn watches the gate.",
|
||||
"source_refs": []any{map[string]int{"start_unit_id": 1, "end_unit_id": 1}},
|
||||
}}}
|
||||
default:
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -40,30 +39,15 @@ func TestRunnerProcessesSeriatimInputWithProductionDNDNPCPipeline(t *testing.T)
|
||||
client := &fakeNPCProductionLLMClient{response: npcProductionResponse{
|
||||
NPCs: []npcProductionRecord{
|
||||
{
|
||||
Name: "Mira Thorn",
|
||||
Aliases: []string{"The Greencloak"},
|
||||
Description: "The first named NPC encountered.",
|
||||
Relationships: []npcProductionRelationship{
|
||||
{Target: "Hooded Guard", Relationship: "works with"},
|
||||
},
|
||||
Name: "Mira Thorn",
|
||||
SourceRefs: []npcProductionSourceRef{{StartUnitID: 1, EndUnitID: 1}},
|
||||
},
|
||||
{
|
||||
Name: "The Greencloak",
|
||||
Aliases: []string{"Mira"},
|
||||
Description: "A later description that must not replace the first.",
|
||||
Relationships: []npcProductionRelationship{
|
||||
{Target: "Hooded Guard", Relationship: "trusts"},
|
||||
},
|
||||
Name: " mira thorn ",
|
||||
SourceRefs: []npcProductionSourceRef{{StartUnitID: 2, EndUnitID: 2}},
|
||||
},
|
||||
{
|
||||
Name: "Hooded Guard",
|
||||
Aliases: []string{},
|
||||
Description: "An unnamed but distinguishable sentry.",
|
||||
Relationships: []npcProductionRelationship{
|
||||
{Target: "The Greencloak", Relationship: "reports to"},
|
||||
},
|
||||
Name: "Hooded Guard",
|
||||
SourceRefs: []npcProductionSourceRef{{StartUnitID: 3, EndUnitID: 3}},
|
||||
},
|
||||
}},
|
||||
@@ -87,15 +71,12 @@ func TestRunnerProcessesSeriatimInputWithProductionDNDNPCPipeline(t *testing.T)
|
||||
t.Fatalf("NPC output = %#v, want repeated name consolidated and group/PC omitted", value.NPCs)
|
||||
}
|
||||
first, second := value.NPCs[0], value.NPCs[1]
|
||||
if first.Name != "Mira Thorn" || first.Description != "The first named NPC encountered." || !reflect.DeepEqual(first.Aliases, []string{"The Greencloak", "Mira"}) {
|
||||
if first.Name != "Mira Thorn" || len(first.SourceRefs) != 2 {
|
||||
t.Fatalf("first NPC = %#v, want consolidated Mira identity", first)
|
||||
}
|
||||
if first.ID != identity.DeriveID(first.Name) || second.Name != "Hooded Guard" || second.ID != identity.DeriveID(second.Name) {
|
||||
t.Fatalf("NPC IDs = %q/%q, want derived IDs", first.ID, second.ID)
|
||||
}
|
||||
if first.Relationships[0].Target != "Hooded Guard" || second.Relationships[0].Target != "Mira Thorn" {
|
||||
t.Fatalf("relationship targets = %q/%q, want canonical target rewrite", first.Relationships[0].Target, second.Relationships[0].Target)
|
||||
}
|
||||
for _, npc := range value.NPCs {
|
||||
for _, ref := range npc.SourceRefs {
|
||||
if ref.SourceID != doc.ID {
|
||||
@@ -103,8 +84,8 @@ func TestRunnerProcessesSeriatimInputWithProductionDNDNPCPipeline(t *testing.T)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasNPCWarning(output.Warnings, "duplicate_npc_collapsed") || !hasNPCWarning(output.Warnings, "relationship_target_canonicalized") {
|
||||
t.Fatalf("warnings = %#v, want consolidation and target warnings", output.Warnings)
|
||||
if !hasNPCWarning(output.Warnings, "duplicate_npc_collapsed") {
|
||||
t.Fatalf("warnings = %#v, want name-only consolidation warning", output.Warnings)
|
||||
}
|
||||
if output.Manifest.ValidationStatus != "approved" || len(output.Manifest.ArtifactLanes) != 1 {
|
||||
t.Fatalf("manifest = %#v, want approved NPC lane", output.Manifest)
|
||||
@@ -149,25 +130,25 @@ func TestProductionNPCPipelineRoutesSemanticCandidatesToDeterministicValidators(
|
||||
}{
|
||||
{
|
||||
name: "blank string",
|
||||
response: []byte(`{"npcs":[{"name":"","aliases":[],"description":"A participant.","relationships":[],"source_refs":[{"start_unit_id":1,"end_unit_id":1}]}]}`),
|
||||
response: []byte(`{"npcs":[{"name":"","source_refs":[{"start_unit_id":1,"end_unit_id":1}]}]}`),
|
||||
reasonCode: npcshape.ReasonCode,
|
||||
validatorName: npcshape.Key,
|
||||
},
|
||||
{
|
||||
name: "empty evidence",
|
||||
response: []byte(`{"npcs":[{"name":"Mira Thorn","aliases":[],"description":"A participant.","relationships":[],"source_refs":[]}]}`),
|
||||
response: []byte(`{"npcs":[{"name":"Mira Thorn","source_refs":[]}]}`),
|
||||
reasonCode: npcshape.ReasonCode,
|
||||
validatorName: npcshape.Key,
|
||||
},
|
||||
{
|
||||
name: "nonpositive unit candidate",
|
||||
response: []byte(`{"npcs":[{"name":"Mira Thorn","aliases":[],"description":"A participant.","relationships":[],"source_refs":[{"start_unit_id":0,"end_unit_id":1}]}]}`),
|
||||
response: []byte(`{"npcs":[{"name":"Mira Thorn","source_refs":[{"start_unit_id":0,"end_unit_id":1}]}]}`),
|
||||
reasonCode: npcsourcerefs.ReasonCode,
|
||||
validatorName: npcsourcerefs.Key,
|
||||
},
|
||||
{
|
||||
name: "unknown unit candidate",
|
||||
response: []byte(`{"npcs":[{"name":"Mira Thorn","aliases":[],"description":"A participant.","relationships":[],"source_refs":[{"start_unit_id":99,"end_unit_id":99}]}]}`),
|
||||
response: []byte(`{"npcs":[{"name":"Mira Thorn","source_refs":[{"start_unit_id":99,"end_unit_id":99}]}]}`),
|
||||
reasonCode: npcsourcerefs.ReasonCode,
|
||||
validatorName: npcsourcerefs.Key,
|
||||
},
|
||||
@@ -194,16 +175,8 @@ type npcProductionResponse struct {
|
||||
}
|
||||
|
||||
type npcProductionRecord struct {
|
||||
Name string `json:"name"`
|
||||
Aliases []string `json:"aliases"`
|
||||
Description string `json:"description"`
|
||||
Relationships []npcProductionRelationship `json:"relationships"`
|
||||
SourceRefs []npcProductionSourceRef `json:"source_refs"`
|
||||
}
|
||||
|
||||
type npcProductionRelationship struct {
|
||||
Target string `json:"target"`
|
||||
Relationship string `json:"relationship"`
|
||||
Name string `json:"name"`
|
||||
SourceRefs []npcProductionSourceRef `json:"source_refs"`
|
||||
}
|
||||
|
||||
type npcProductionSourceRef struct {
|
||||
|
||||
Reference in New Issue
Block a user