Move NPC occurrences to their canonical namespace
This commit is contained in:
@@ -49,8 +49,8 @@ func TestExtractMapsEnemyEventsInSourceOrder(t *testing.T) {
|
||||
if got := string(request.Inputs[CombatTurnReferenceSlot].Content); !strings.Contains(got, `"actor":"Ashfang"`) || strings.Contains(got, "source_ref") {
|
||||
t.Fatalf("combat grounding = %s", got)
|
||||
}
|
||||
if got := string(request.Inputs[NPCInteractionReferenceSlot].Content); !strings.Contains(got, `"kind":"combat_opponent"`) || strings.Contains(got, "Aria") {
|
||||
t.Fatalf("interaction grounding = %s", got)
|
||||
if got := string(request.Inputs[NPCOccurrenceReferenceSlot].Content); !strings.Contains(got, `"kind":"combat_opponent"`) || strings.Contains(got, "Aria") {
|
||||
t.Fatalf("occurrence grounding = %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
combatturncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/combatturns"
|
||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
||||
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||
scenecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
||||
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
|
||||
@@ -27,7 +27,7 @@ const (
|
||||
NPCRegistryReferenceSlot = npcregistry.ReferenceSlot
|
||||
SceneDescriptionReferenceSlot = sceneregistry.ReferenceSlot
|
||||
CombatTurnReferenceSlot = "combat_turns"
|
||||
NPCInteractionReferenceSlot = "npc_interactions"
|
||||
NPCOccurrenceReferenceSlot = "npc_occurrences"
|
||||
ReferenceMaxBytes = 1048576
|
||||
promptProjectionMediaType = "application/json"
|
||||
)
|
||||
@@ -67,10 +67,10 @@ func referenceSlots() []contracts.ReferenceSlot {
|
||||
MaxBytes: ReferenceMaxBytes,
|
||||
},
|
||||
contracts.ReferenceSlot{
|
||||
Name: NPCInteractionReferenceSlot,
|
||||
Description: "Required NPC-interaction artifact used only as source-free enemy-event grounding.",
|
||||
Name: NPCOccurrenceReferenceSlot,
|
||||
Description: "Required NPC-occurrence artifact used only as source-free enemy-event grounding.",
|
||||
Required: true,
|
||||
AcceptedMediaTypes: []string{interactioncodec.MediaType},
|
||||
AcceptedMediaTypes: []string{occurrencecodec.MediaType},
|
||||
AcceptedArtifactKinds: []contracts.ArtifactKind{dnd.NPCOccurrenceListKind},
|
||||
MaxBytes: ReferenceMaxBytes,
|
||||
},
|
||||
@@ -86,14 +86,14 @@ type groundingResolver struct {
|
||||
npcs *npcregistry.Resolver
|
||||
scenes *sceneregistry.Resolver
|
||||
|
||||
combatTurns *contracts.LLMInputMaterial
|
||||
npcInteractions *contracts.LLMInputMaterial
|
||||
combatTurns *contracts.LLMInputMaterial
|
||||
npcOccurrences *contracts.LLMInputMaterial
|
||||
}
|
||||
|
||||
type grounding struct {
|
||||
npcInput contracts.LLMInputMaterial
|
||||
combatTurnInput contracts.LLMInputMaterial
|
||||
npcInteractionInput contracts.LLMInputMaterial
|
||||
npcInput contracts.LLMInputMaterial
|
||||
combatTurnInput contracts.LLMInputMaterial
|
||||
npcOccurrenceInput contracts.LLMInputMaterial
|
||||
}
|
||||
|
||||
func newGroundingResolver(references contracts.ReferenceSet) (*groundingResolver, error) {
|
||||
@@ -109,15 +109,15 @@ func newGroundingResolver(references contracts.ReferenceSet) (*groundingResolver
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
npcInteractions, err := prepareNPCInteractionInput(references)
|
||||
npcOccurrences, err := prepareNPCOccurrenceInput(references)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &groundingResolver{
|
||||
npcs: npcs,
|
||||
scenes: scenes,
|
||||
combatTurns: combatTurns,
|
||||
npcInteractions: npcInteractions,
|
||||
npcs: npcs,
|
||||
scenes: scenes,
|
||||
combatTurns: combatTurns,
|
||||
npcOccurrences: npcOccurrences,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -136,14 +136,14 @@ func (r *groundingResolver) Resolve(references contracts.ReferenceSet) (groundin
|
||||
if err != nil {
|
||||
return grounding{}, err
|
||||
}
|
||||
npcInteractions, err := resolveInput(references, NPCInteractionReferenceSlot, r.npcInteractions, prepareNPCInteractionInput)
|
||||
npcOccurrences, err := resolveInput(references, NPCOccurrenceReferenceSlot, r.npcOccurrences, prepareNPCOccurrenceInput)
|
||||
if err != nil {
|
||||
return grounding{}, err
|
||||
}
|
||||
return grounding{
|
||||
npcInput: npcs.PromptInput(),
|
||||
combatTurnInput: combatTurns,
|
||||
npcInteractionInput: npcInteractions,
|
||||
npcInput: npcs.PromptInput(),
|
||||
combatTurnInput: combatTurns,
|
||||
npcOccurrenceInput: npcOccurrences,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -188,9 +188,9 @@ func resolveInput(references contracts.ReferenceSet, slot string, seeded *contra
|
||||
|
||||
func (g grounding) PromptInputs() contracts.LLMInputSet {
|
||||
return contracts.LLMInputSet{
|
||||
NPCRegistryReferenceSlot: g.npcInput.Clone(),
|
||||
CombatTurnReferenceSlot: g.combatTurnInput.Clone(),
|
||||
NPCInteractionReferenceSlot: g.npcInteractionInput.Clone(),
|
||||
NPCRegistryReferenceSlot: g.npcInput.Clone(),
|
||||
CombatTurnReferenceSlot: g.combatTurnInput.Clone(),
|
||||
NPCOccurrenceReferenceSlot: g.npcOccurrenceInput.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,22 +212,22 @@ func prepareCombatTurnInput(references contracts.ReferenceSet) (*contracts.LLMIn
|
||||
return newPromptInput(CombatTurnReferenceSlot, content), nil
|
||||
}
|
||||
|
||||
func prepareNPCInteractionInput(references contracts.ReferenceSet) (*contracts.LLMInputMaterial, error) {
|
||||
item, ok, err := referenceItem(references, NPCInteractionReferenceSlot, interactioncodec.MediaType)
|
||||
func prepareNPCOccurrenceInput(references contracts.ReferenceSet) (*contracts.LLMInputMaterial, error) {
|
||||
item, ok, err := referenceItem(references, NPCOccurrenceReferenceSlot, occurrencecodec.MediaType)
|
||||
if err != nil || !ok {
|
||||
return nil, err
|
||||
}
|
||||
value, err := interactioncodec.New().Decode(item.Content)
|
||||
value, err := occurrencecodec.New().Decode(item.Content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode NPC-interaction grounding: invalid approved NPC-interaction JSON")
|
||||
return nil, fmt.Errorf("decode NPC-occurrence grounding: invalid approved NPC-occurrence JSON")
|
||||
}
|
||||
content, err := json.Marshal(struct {
|
||||
Interactions []npcInteractionProjection `json:"npc_interactions"`
|
||||
}{Interactions: projectNPCInteractions(value.Occurrences)})
|
||||
Occurrences []npcOccurrenceProjection `json:"npc_occurrences"`
|
||||
}{Occurrences: projectNPCOccurrences(value.Occurrences)})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode NPC-interaction grounding: %w", err)
|
||||
return nil, fmt.Errorf("encode NPC-occurrence grounding: %w", err)
|
||||
}
|
||||
return newPromptInput(NPCInteractionReferenceSlot, content), nil
|
||||
return newPromptInput(NPCOccurrenceReferenceSlot, content), nil
|
||||
}
|
||||
|
||||
func referenceItem(references contracts.ReferenceSet, slotName, expectedMediaType string) (contracts.ReferenceItem, bool, error) {
|
||||
@@ -271,16 +271,16 @@ func projectCombatTurns(turns []dnd.CombatTurn) []combatTurnProjection {
|
||||
return projection
|
||||
}
|
||||
|
||||
type npcInteractionProjection struct {
|
||||
type npcOccurrenceProjection struct {
|
||||
Name string `json:"name"`
|
||||
Kind dnd.NPCOccurrenceKind `json:"kind"`
|
||||
}
|
||||
|
||||
func projectNPCInteractions(occurrences []dnd.NPCOccurrence) []npcInteractionProjection {
|
||||
projection := make([]npcInteractionProjection, 0, len(occurrences))
|
||||
func projectNPCOccurrences(occurrences []dnd.NPCOccurrence) []npcOccurrenceProjection {
|
||||
projection := make([]npcOccurrenceProjection, 0, len(occurrences))
|
||||
for _, occurrence := range occurrences {
|
||||
if occurrence.Kind == dnd.NPCOccurrenceKindCombatOpponent {
|
||||
projection = append(projection, npcInteractionProjection{Name: occurrence.Name, Kind: occurrence.Kind})
|
||||
projection = append(projection, npcOccurrenceProjection{Name: occurrence.Name, Kind: occurrence.Kind})
|
||||
}
|
||||
}
|
||||
return projection
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
combatturncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/combatturns"
|
||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
||||
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||
scenecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/scenedescriptions"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
|
||||
@@ -36,7 +36,7 @@ func TestReferenceSlotsDescribeRequiredTypedArtifacts(t *testing.T) {
|
||||
{NPCRegistryReferenceSlot, dnd.NPCRegistryKind},
|
||||
{SceneDescriptionReferenceSlot, dnd.SceneDescriptionListKind},
|
||||
{CombatTurnReferenceSlot, dnd.CombatTurnListKind},
|
||||
{NPCInteractionReferenceSlot, dnd.NPCOccurrenceListKind},
|
||||
{NPCOccurrenceReferenceSlot, dnd.NPCOccurrenceListKind},
|
||||
} {
|
||||
slot, ok := byName[want.name]
|
||||
if !ok || !slot.Required || slot.MaxBytes != ReferenceMaxBytes || len(slot.AcceptedMediaTypes) != 1 || slot.AcceptedMediaTypes[0] != "application/json" || len(slot.AcceptedArtifactKinds) != 1 || slot.AcceptedArtifactKinds[0] != want.kind {
|
||||
@@ -61,9 +61,9 @@ func TestGroundingProducesExactSourceFreePromptInputs(t *testing.T) {
|
||||
}
|
||||
inputs := resolved.PromptInputs()
|
||||
want := map[string]string{
|
||||
NPCRegistryReferenceSlot: `{"npcs":[{"name":"Ashfang"}]}`,
|
||||
CombatTurnReferenceSlot: `{"combat_turns":[{"actor":"Ashfang","turn_kind":"turn"},{"actor":"Aria","turn_kind":"reaction"}]}`,
|
||||
NPCInteractionReferenceSlot: `{"npc_interactions":[{"name":"Ashfang","kind":"combat_opponent"}]}`,
|
||||
NPCRegistryReferenceSlot: `{"npcs":[{"name":"Ashfang"}]}`,
|
||||
CombatTurnReferenceSlot: `{"combat_turns":[{"actor":"Ashfang","turn_kind":"turn"},{"actor":"Aria","turn_kind":"reaction"}]}`,
|
||||
NPCOccurrenceReferenceSlot: `{"npc_occurrences":[{"name":"Ashfang","kind":"combat_opponent"}]}`,
|
||||
}
|
||||
if len(inputs) != len(want) {
|
||||
t.Fatalf("PromptInputs() = %#v", inputs)
|
||||
@@ -153,7 +153,7 @@ func TestGroundingRejectsMissingAndInvalidReferences(t *testing.T) {
|
||||
{NPCRegistryReferenceSlot, "NPC registry"},
|
||||
{SceneDescriptionReferenceSlot, "scene descriptions"},
|
||||
{CombatTurnReferenceSlot, CombatTurnReferenceSlot},
|
||||
{NPCInteractionReferenceSlot, NPCInteractionReferenceSlot},
|
||||
{NPCOccurrenceReferenceSlot, NPCOccurrenceReferenceSlot},
|
||||
} {
|
||||
t.Run("missing "+test.slot, func(t *testing.T) {
|
||||
resolver, err := newGroundingResolver(withoutSlot(valid, test.slot))
|
||||
@@ -176,7 +176,7 @@ func TestGroundingRejectsMissingAndInvalidReferences(t *testing.T) {
|
||||
set contracts.ReferenceSet
|
||||
}{
|
||||
{"multiple items", replaceSlot(valid, CombatTurnReferenceSlot, contracts.ResolvedReferenceSlot{Items: []contracts.ReferenceItem{{MediaType: "application/json"}, {MediaType: "application/json"}}})},
|
||||
{"malformed durable content", replaceItem(valid, NPCInteractionReferenceSlot, contracts.ReferenceItem{MediaType: "application/json", Content: []byte(`{}`)})},
|
||||
{"malformed durable content", replaceItem(valid, NPCOccurrenceReferenceSlot, contracts.ReferenceItem{MediaType: "application/json", Content: []byte(`{}`)})},
|
||||
{"wrong media type", replaceItem(valid, CombatTurnReferenceSlot, contracts.ReferenceItem{MediaType: "text/plain", Content: valid.Slots[CombatTurnReferenceSlot].Items[0].Content})},
|
||||
{"oversize", replaceItem(valid, CombatTurnReferenceSlot, contracts.ReferenceItem{MediaType: "application/json", Content: make([]byte, ReferenceMaxBytes+1)})},
|
||||
}
|
||||
@@ -220,7 +220,7 @@ func groundingReferences(t *testing.T, enemy string, sceneKind dnd.SceneKind) co
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
interactionContent, err := interactioncodec.New().Encode(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
||||
interactionContent, err := occurrencecodec.New().Encode(dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
|
||||
{NPCID: identity.DeriveID(enemy), Name: enemy, Kind: dnd.NPCOccurrenceKindCombatOpponent, SourceRefs: []source.SourceRef{{SourceID: "combat-session", StartUnitID: 1, EndUnitID: 1}}},
|
||||
{NPCID: identity.DeriveID("Aria"), Name: "Aria", Kind: dnd.NPCOccurrenceKindCombatAlly, SourceRefs: []source.SourceRef{{SourceID: "combat-session", StartUnitID: 2, EndUnitID: 2}}},
|
||||
}})
|
||||
@@ -231,7 +231,7 @@ func groundingReferences(t *testing.T, enemy string, sceneKind dnd.SceneKind) co
|
||||
NPCRegistryReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCRegistryReferenceSlot, npcContent)}},
|
||||
SceneDescriptionReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(SceneDescriptionReferenceSlot, sceneContent)}},
|
||||
CombatTurnReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(CombatTurnReferenceSlot, turnContent)}},
|
||||
NPCInteractionReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCInteractionReferenceSlot, interactionContent)}},
|
||||
NPCOccurrenceReferenceSlot: {Items: []contracts.ReferenceItem{newReferenceItem(NPCOccurrenceReferenceSlot, interactionContent)}},
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestRegisterPromptAssetsAndPrepareEnemyEventPrompt(t *testing.T) {
|
||||
|
||||
func TestEnemyEventPromptRequiresGroundingInputs(t *testing.T) {
|
||||
engine := newEnemyEventPromptEngine(t)
|
||||
for _, inputName := range []string{"npc_registry", "combat_turns", "npc_interactions"} {
|
||||
for _, inputName := range []string{"npc_registry", "combat_turns", "npc_occurrences"} {
|
||||
t.Run(inputName, func(t *testing.T) {
|
||||
inputs := enemyEventPromptInputs()
|
||||
delete(inputs, inputName)
|
||||
@@ -79,12 +79,12 @@ func newEnemyEventPromptEngine(t *testing.T) *promptkit.Engine {
|
||||
|
||||
func enemyEventPromptInputs() map[string]promptkit.ArtifactRef {
|
||||
return map[string]promptkit.ArtifactRef{
|
||||
"transcript": promptkit.Inline(`{"value":"enemy-transcript"}`),
|
||||
"players": promptkit.Inline("enemy-player"),
|
||||
"party": promptkit.Inline("enemy-party"),
|
||||
"glossary": promptkit.Inline("enemy-glossary"),
|
||||
"npc_registry": promptkit.Inline(`{"npcs":[{"name":"enemy-npc"}]}`),
|
||||
"combat_turns": promptkit.Inline(`{"combat_turns":[{"actor":"enemy-turn","turn_kind":"turn"}]}`),
|
||||
"npc_interactions": promptkit.Inline(`{"npc_interactions":[{"name":"enemy-opponent","kind":"combat_opponent"}]}`),
|
||||
"transcript": promptkit.Inline(`{"value":"enemy-transcript"}`),
|
||||
"players": promptkit.Inline("enemy-player"),
|
||||
"party": promptkit.Inline("enemy-party"),
|
||||
"glossary": promptkit.Inline("enemy-glossary"),
|
||||
"npc_registry": promptkit.Inline(`{"npcs":[{"name":"enemy-npc"}]}`),
|
||||
"combat_turns": promptkit.Inline(`{"combat_turns":[{"actor":"enemy-turn","turn_kind":"turn"}]}`),
|
||||
"npc_occurrences": promptkit.Inline(`{"npc_occurrences":[{"name":"enemy-opponent","kind":"combat_opponent"}]}`),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user