Make enemy event prompt tests resilient to refactoring
This commit is contained in:
@@ -28,69 +28,52 @@ func TestRegisterPromptAssetsAndPrepareEnemyEventPrompt(t *testing.T) {
|
|||||||
if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_enemy_events_llm.v1.json" || prepared.SelectedProfileID != "dnd-extraction" {
|
if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_enemy_events_llm.v1.json" || prepared.SelectedProfileID != "dnd-extraction" {
|
||||||
t.Fatalf("prepared prompt = %#v", prepared)
|
t.Fatalf("prepared prompt = %#v", prepared)
|
||||||
}
|
}
|
||||||
transcriptIndex := -1
|
transcriptIndex := renderedMessageIndex(t, prepared.Messages, "enemy-transcript")
|
||||||
for index, message := range prepared.Messages {
|
for _, sentinel := range []string{"enemy-npc", "enemy-turn", "enemy-opponent", "Extract Dungeons & Dragons enemy events"} {
|
||||||
if strings.Contains(message.Content, "enemy-transcript") {
|
if index := renderedMessageIndex(t, prepared.Messages, sentinel); index <= transcriptIndex {
|
||||||
transcriptIndex = index
|
t.Fatalf("message containing %q has index %d, want after transcript index %d", sentinel, index, transcriptIndex)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if transcriptIndex != 3 {
|
instructionIndex := renderedMessageIndex(t, prepared.Messages, "Return the `events` array")
|
||||||
t.Fatalf("transcript message index = %d, want shared-prefix index 3", transcriptIndex)
|
if instructionIndex <= transcriptIndex {
|
||||||
|
t.Fatalf("instruction index = %d, want after transcript index %d", instructionIndex, transcriptIndex)
|
||||||
}
|
}
|
||||||
for index, role := range []string{"system", "user", "user", "user"} {
|
if instructionIndex != len(prepared.Messages)-1 {
|
||||||
if prepared.Messages[index].Role != role {
|
t.Fatalf("instruction message index = %d, want final message", instructionIndex)
|
||||||
t.Fatalf("message %d role = %q, want %q", index, prepared.Messages[index].Role, role)
|
|
||||||
}
|
}
|
||||||
}
|
if cache := prepared.Messages[instructionIndex].CacheControl; cache == nil || cache.Type != promptkit.CacheControlEphemeral {
|
||||||
for _, index := range []int{2, 3} {
|
t.Fatalf("final instruction cache control = %#v", cache)
|
||||||
if cache := prepared.Messages[index].CacheControl; cache == nil || cache.Type != promptkit.CacheControlEphemeral {
|
|
||||||
t.Fatalf("message %d cache control = %#v", index, cache)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, index := range []int{0, 1} {
|
|
||||||
if prepared.Messages[index].CacheControl != nil {
|
|
||||||
t.Fatalf("message %d cache control = %#v, want nil", index, prepared.Messages[index].CacheControl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !strings.Contains(prepared.Messages[transcriptIndex+3].Content, "enemy-turn") || !strings.Contains(prepared.Messages[transcriptIndex+3].Content, "enemy-opponent") {
|
|
||||||
t.Fatalf("combat grounding message = %q", prepared.Messages[transcriptIndex+3].Content)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnemyEventPromptRequiresGroundingInputs(t *testing.T) {
|
func TestEnemyEventPromptRequiresGroundingInputs(t *testing.T) {
|
||||||
registry := llm.NewAssetRegistry()
|
engine := newEnemyEventPromptEngine(t)
|
||||||
if err := RegisterPromptAssets(registry); err != nil {
|
for _, inputName := range []string{"npcs", "combat_turns", "npc_interactions"} {
|
||||||
t.Fatal(err)
|
t.Run(inputName, func(t *testing.T) {
|
||||||
}
|
inputs := enemyEventPromptInputs()
|
||||||
options, err := registry.PromptKitOptions()
|
delete(inputs, inputName)
|
||||||
if err != nil {
|
_, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
||||||
t.Fatal(err)
|
PromptID: PromptID, PromptVersion: SchemaVersion, Inputs: inputs,
|
||||||
}
|
})
|
||||||
options = append(options, promptkit.WithProfiles(promptkit.OpenAICompatibleProfile(promptkit.OpenAICompatibleProfileConfig{
|
if err == nil || !strings.Contains(err.Error(), inputName) {
|
||||||
ID: "dnd-extraction", Endpoint: "http://127.0.0.1:1/v1", Model: "enemy-test-model",
|
t.Fatalf("Prepare() error = %v, want required %q input", err, inputName)
|
||||||
})))
|
}
|
||||||
engine, err := promptkit.NewEngine(promptkit.Config{Timeout: time.Second}, options...)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = engine.Prepare(context.Background(), promptkit.RunRequest{
|
|
||||||
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "dnd-extraction",
|
|
||||||
Inputs: map[string]promptkit.ArtifactRef{
|
|
||||||
"transcript": promptkit.Inline(`{"value":"enemy-transcript"}`),
|
|
||||||
"players": promptkit.Inline(" "),
|
|
||||||
"party": promptkit.Inline(" "),
|
|
||||||
"glossary": promptkit.Inline(" "),
|
|
||||||
"npcs": promptkit.Inline(`{"npcs":[]}`),
|
|
||||||
"npc_interactions": promptkit.Inline(`{"npc_interactions":[]}`),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
if err == nil || !strings.Contains(err.Error(), "combat_turns") {
|
|
||||||
t.Fatalf("Prepare() error = %v, want required combat-turn input", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareEnemyEventPrompt(t *testing.T) *promptkit.PreparedRun {
|
func prepareEnemyEventPrompt(t *testing.T) *promptkit.PreparedRun {
|
||||||
|
t.Helper()
|
||||||
|
prepared, err := newEnemyEventPromptEngine(t).Prepare(context.Background(), promptkit.RunRequest{
|
||||||
|
PromptID: PromptID, PromptVersion: SchemaVersion, Inputs: enemyEventPromptInputs(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return prepared
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEnemyEventPromptEngine(t *testing.T) *promptkit.Engine {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
registry := llm.NewAssetRegistry()
|
registry := llm.NewAssetRegistry()
|
||||||
if err := RegisterPromptAssets(registry); err != nil {
|
if err := RegisterPromptAssets(registry); err != nil {
|
||||||
@@ -107,9 +90,11 @@ func prepareEnemyEventPrompt(t *testing.T) *promptkit.PreparedRun {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
|
return engine
|
||||||
PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "dnd-extraction",
|
}
|
||||||
Inputs: map[string]promptkit.ArtifactRef{
|
|
||||||
|
func enemyEventPromptInputs() map[string]promptkit.ArtifactRef {
|
||||||
|
return map[string]promptkit.ArtifactRef{
|
||||||
"transcript": promptkit.Inline(`{"value":"enemy-transcript"}`),
|
"transcript": promptkit.Inline(`{"value":"enemy-transcript"}`),
|
||||||
"players": promptkit.Inline("enemy-player"),
|
"players": promptkit.Inline("enemy-player"),
|
||||||
"party": promptkit.Inline("enemy-party"),
|
"party": promptkit.Inline("enemy-party"),
|
||||||
@@ -117,10 +102,22 @@ func prepareEnemyEventPrompt(t *testing.T) *promptkit.PreparedRun {
|
|||||||
"npcs": promptkit.Inline(`{"npcs":[{"name":"enemy-npc"}]}`),
|
"npcs": promptkit.Inline(`{"npcs":[{"name":"enemy-npc"}]}`),
|
||||||
"combat_turns": promptkit.Inline(`{"combat_turns":[{"actor":"enemy-turn","turn_kind":"turn"}]}`),
|
"combat_turns": promptkit.Inline(`{"combat_turns":[{"actor":"enemy-turn","turn_kind":"turn"}]}`),
|
||||||
"npc_interactions": promptkit.Inline(`{"npc_interactions":[{"name":"enemy-opponent","kind":"combat_opponent"}]}`),
|
"npc_interactions": promptkit.Inline(`{"npc_interactions":[{"name":"enemy-opponent","kind":"combat_opponent"}]}`),
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
return prepared
|
}
|
||||||
|
|
||||||
|
func renderedMessageIndex(t *testing.T, messages []promptkit.RenderedMessage, sentinel string) int {
|
||||||
|
t.Helper()
|
||||||
|
index := -1
|
||||||
|
occurrences := 0
|
||||||
|
for messageIndex, message := range messages {
|
||||||
|
count := strings.Count(message.Content, sentinel)
|
||||||
|
if count > 0 {
|
||||||
|
index = messageIndex
|
||||||
|
occurrences += count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if occurrences != 1 {
|
||||||
|
t.Fatalf("message sentinel %q rendered %d times, want exactly once", sentinel, occurrences)
|
||||||
|
}
|
||||||
|
return index
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,7 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
promptID string
|
promptID string
|
||||||
promptVersion string
|
promptVersion string
|
||||||
inputs map[string]promptkit.ArtifactRef
|
inputs map[string]promptkit.ArtifactRef
|
||||||
npcInput bool
|
inputSentinels []string
|
||||||
spellCatalogInput bool
|
|
||||||
}{
|
}{
|
||||||
{name: "npcs", promptID: npcextract.PromptID, promptVersion: npcextract.SchemaVersion, inputs: commonInputs},
|
{name: "npcs", promptID: npcextract.PromptID, promptVersion: npcextract.SchemaVersion, inputs: commonInputs},
|
||||||
{name: "item events", promptID: itemeventextract.PromptID, promptVersion: itemeventextract.SchemaVersion, inputs: commonInputs},
|
{name: "item events", promptID: itemeventextract.PromptID, promptVersion: itemeventextract.SchemaVersion, inputs: commonInputs},
|
||||||
@@ -56,7 +55,7 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
||||||
"npcs": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
"npcs": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||||
}),
|
}),
|
||||||
npcInput: true,
|
inputSentinels: []string{npcSentinel},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "enemy events",
|
name: "enemy events",
|
||||||
@@ -67,7 +66,7 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
"combat_turns": promptkit.Inline(`{"sentinel":"combat-turns-sentinel"}`),
|
"combat_turns": promptkit.Inline(`{"sentinel":"combat-turns-sentinel"}`),
|
||||||
"npc_interactions": promptkit.Inline(`{"sentinel":"npc-interactions-sentinel"}`),
|
"npc_interactions": promptkit.Inline(`{"sentinel":"npc-interactions-sentinel"}`),
|
||||||
}),
|
}),
|
||||||
npcInput: true,
|
inputSentinels: []string{npcSentinel, "combat-turns-sentinel", "npc-interactions-sentinel"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "npc interactions",
|
name: "npc interactions",
|
||||||
@@ -76,7 +75,7 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
inputs: withPromptInputs(commonInputs, map[string]promptkit.ArtifactRef{
|
||||||
"npcs": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
"npcs": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||||
}),
|
}),
|
||||||
npcInput: true,
|
inputSentinels: []string{npcSentinel},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "spells",
|
name: "spells",
|
||||||
@@ -86,8 +85,7 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
"npcs": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
"npcs": promptkit.Inline(`{"sentinel":"` + npcSentinel + `"}`),
|
||||||
"spell_catalog": promptkit.Inline(`{"sentinel":"` + catalogSentinel + `"}`),
|
"spell_catalog": promptkit.Inline(`{"sentinel":"` + catalogSentinel + `"}`),
|
||||||
}),
|
}),
|
||||||
npcInput: true,
|
inputSentinels: []string{npcSentinel, catalogSentinel},
|
||||||
spellCatalogInput: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,9 +103,6 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
}
|
}
|
||||||
transcriptIndex := renderedInputMessageIndex(t, prepared.Messages, transcriptSentinel)
|
transcriptIndex := renderedInputMessageIndex(t, prepared.Messages, transcriptSentinel)
|
||||||
prefix := prepared.Messages[:transcriptIndex+1]
|
prefix := prepared.Messages[:transcriptIndex+1]
|
||||||
if len(prefix) != 4 {
|
|
||||||
t.Fatalf("messages through transcript = %d, want 4", len(prefix))
|
|
||||||
}
|
|
||||||
if len(prepared.Messages) <= len(prefix) {
|
if len(prepared.Messages) <= len(prefix) {
|
||||||
t.Fatalf("prepared prompt has %d messages, want lane-specific suffix after transcript", len(prepared.Messages))
|
t.Fatalf("prepared prompt has %d messages, want lane-specific suffix after transcript", len(prepared.Messages))
|
||||||
}
|
}
|
||||||
@@ -116,11 +111,8 @@ func TestExtractionPromptsShareRenderedPrefix(t *testing.T) {
|
|||||||
} else if !reflect.DeepEqual(prefix, sharedPrefix) {
|
} else if !reflect.DeepEqual(prefix, sharedPrefix) {
|
||||||
t.Fatalf("rendered prefix = %#v, want %#v", prefix, sharedPrefix)
|
t.Fatalf("rendered prefix = %#v, want %#v", prefix, sharedPrefix)
|
||||||
}
|
}
|
||||||
if testCase.npcInput {
|
for _, sentinel := range testCase.inputSentinels {
|
||||||
assertRenderedInputAfter(t, prepared.Messages, npcSentinel, transcriptIndex)
|
assertRenderedInputAfter(t, prepared.Messages, sentinel, transcriptIndex)
|
||||||
}
|
|
||||||
if testCase.spellCatalogInput {
|
|
||||||
assertRenderedInputAfter(t, prepared.Messages, catalogSentinel, transcriptIndex)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user