Minimize D&D spell extraction contracts
This commit is contained in:
@@ -33,8 +33,6 @@ messages:
|
||||
content_file: ./sharedassets/common-dnd-references.md
|
||||
cache_control:
|
||||
type: ephemeral
|
||||
- role: user
|
||||
content_file: ./sharedassets/common-dnd-immediate-resolution.md
|
||||
- role: user
|
||||
content_file: ./sharedassets/common-dnd-npcs.md
|
||||
cache_control:
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
For each spell cast, source references must collectively support the caster,
|
||||
spell, effect, and narrative_description. If a detail is not supported by the
|
||||
cited transcript units, omit that detail or describe only the supported attempt
|
||||
or declaration.
|
||||
For each spell cast, source references must collectively support the in-world
|
||||
caster, spell name, and the fact that the cast or declared attempt occurred.
|
||||
|
||||
For spells, do not follow summoned creatures, persistent effects, or other
|
||||
downstream consequences through the rest of the scene.
|
||||
|
||||
Return only D&D spell-cast artifacts. For each spell cast, identify the
|
||||
in-world caster, spell name, effect, narrative description, and source
|
||||
references.
|
||||
Return only D&D spell-cast artifacts. For each record, identify the in-world
|
||||
caster, canonical spell name, and source references.
|
||||
|
||||
Use the player and party references together with transcript context to map
|
||||
first-person player speech to the associated player character and use the
|
||||
@@ -20,10 +14,3 @@ transcript; do not invent a name.
|
||||
Use the canonical spell-name catalog to select spell names. Do not return a
|
||||
spell name absent from that catalog, even when it is suggested by general D&D
|
||||
knowledge or reference material.
|
||||
|
||||
Effects and narrative descriptions are session records, not rules summaries.
|
||||
Report only mechanics, explanations, and outcomes established by the cited
|
||||
transcript units. Preserve the table's observed resolution without silently
|
||||
correcting it from general D&D knowledge. If the transcript gives a possibly
|
||||
nonstandard rationale, use wording such as "the GM rules" or "the table
|
||||
resolves" rather than asserting that rationale as a universal rule.
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
Extract Dungeons & Dragons spell-cast artifacts from the provided transcript.
|
||||
|
||||
Do not infer a spell cast from general D&D knowledge or from table chatter that
|
||||
does not identify a spell being cast.
|
||||
|
||||
Describe the session as it was played and adjudicated. The transcript is
|
||||
authoritative for what happened in this session, even when a table ruling may
|
||||
differ from published D&D rules. Do not correct the transcript or fill in
|
||||
unstated mechanics from general D&D knowledge. When a ruling or mechanical
|
||||
explanation matters, attribute it to the GM or table instead of presenting it
|
||||
as a universal game rule.
|
||||
Include an actual casting event or an unambiguous declared casting attempt.
|
||||
Exclude spell mentions, hypothetical plans, rules discussion, and catalog
|
||||
matches that do not establish a casting event in the transcript.
|
||||
|
||||
Use the provided canonical spell-name catalog when naming each extracted spell.
|
||||
Return the canonical catalog spelling exactly. The catalog is a recognition
|
||||
aid; it does not establish that a spell was cast or how the spell works.
|
||||
aid and never evidence that a spell was cast.
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
"required": [
|
||||
"caster",
|
||||
"spell",
|
||||
"effect",
|
||||
"narrative_description",
|
||||
"source_refs"
|
||||
],
|
||||
"properties": {
|
||||
@@ -26,17 +24,9 @@
|
||||
"type": "string",
|
||||
"description": "Canonical spell name from the provided spell-name catalog."
|
||||
},
|
||||
"effect": {
|
||||
"type": "string",
|
||||
"description": "Concise immediate effect or resolution established by the cited transcript units; do not infer mechanics from general D&D rules knowledge or follow persistent downstream consequences."
|
||||
},
|
||||
"narrative_description": {
|
||||
"type": "string",
|
||||
"description": "Short session-grounded description of the casting declaration and immediate resolution, containing only details established by the cited transcript units."
|
||||
},
|
||||
"source_refs": {
|
||||
"type": "array",
|
||||
"description": "Transcript ranges offered as evidence for factual claims about the casting declaration and immediate resolution in this spell-cast object.",
|
||||
"description": "Transcript ranges offered as evidence for the caster, spell name, and casting event in this spell-cast object.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -88,11 +88,9 @@ func canonicalSpellList(response extractionResponse, sourceID string) dnd.SpellL
|
||||
}
|
||||
}
|
||||
spellCasts[index] = dnd.SpellCast{
|
||||
Caster: spell.Caster,
|
||||
Spell: spell.Spell,
|
||||
Effect: spell.Effect,
|
||||
NarrativeDescription: spell.NarrativeDescription,
|
||||
SourceRefs: refs,
|
||||
Caster: spell.Caster,
|
||||
Spell: spell.Spell,
|
||||
SourceRefs: refs,
|
||||
}
|
||||
}
|
||||
if response.SpellCasts == nil {
|
||||
|
||||
@@ -19,11 +19,9 @@ import (
|
||||
func TestExtractReturnsCanonicalSpellListFromPrivateResponse(t *testing.T) {
|
||||
client := &fakeSpellsLLMClient{response: extractionResponse{SpellCasts: []spellCastResponse{
|
||||
{
|
||||
Caster: " Aria ",
|
||||
Spell: " Cure Wounds ",
|
||||
Effect: " Heals an injured ally. ",
|
||||
NarrativeDescription: " Aria restores the fighter after the fight. ",
|
||||
SourceRefs: responseSourceRefs(1, 2),
|
||||
Caster: " Aria ",
|
||||
Spell: " Cure Wounds ",
|
||||
SourceRefs: responseSourceRefs(1, 2),
|
||||
},
|
||||
}}}
|
||||
req := extractionRequest()
|
||||
@@ -34,11 +32,9 @@ func TestExtractReturnsCanonicalSpellListFromPrivateResponse(t *testing.T) {
|
||||
}
|
||||
want := dnd.SpellList{SpellCasts: []dnd.SpellCast{
|
||||
{
|
||||
Caster: " Aria ",
|
||||
Spell: " Cure Wounds ",
|
||||
Effect: " Heals an injured ally. ",
|
||||
NarrativeDescription: " Aria restores the fighter after the fight. ",
|
||||
SourceRefs: []source.SourceRef{{SourceID: "session-alpha", StartUnitID: 1, EndUnitID: 2}},
|
||||
Caster: " Aria ",
|
||||
Spell: " Cure Wounds ",
|
||||
SourceRefs: []source.SourceRef{{SourceID: "session-alpha", StartUnitID: 1, EndUnitID: 2}},
|
||||
},
|
||||
}}
|
||||
if !reflect.DeepEqual(result.Value, want) {
|
||||
@@ -268,9 +264,9 @@ func TestExtractRejectsInvalidRequests(t *testing.T) {
|
||||
|
||||
func TestExtractOrdersAndDeduplicatesEvidence(t *testing.T) {
|
||||
client := &fakeSpellsLLMClient{response: extractionResponse{SpellCasts: []spellCastResponse{
|
||||
{Caster: "Borin", Spell: "Fire Bolt", Effect: "Burns.", NarrativeDescription: "Second.", SourceRefs: responseSourceRefs(2, 2)},
|
||||
{Caster: "Aria", Spell: "Cure Wounds", Effect: "Heals.", NarrativeDescription: "First.", SourceRefs: []spellSourceRefResponse{{StartUnitID: 1, EndUnitID: 2}, {StartUnitID: 1, EndUnitID: 2}}},
|
||||
{Caster: "Narrator", Spell: "Unknown", Effect: "Unknown.", NarrativeDescription: "Uncited."},
|
||||
{Caster: "Borin", Spell: "Fire Bolt", SourceRefs: responseSourceRefs(2, 2)},
|
||||
{Caster: "Aria", Spell: "Cure Wounds", SourceRefs: []spellSourceRefResponse{{StartUnitID: 1, EndUnitID: 2}, {StartUnitID: 1, EndUnitID: 2}}},
|
||||
{Caster: "Narrator", Spell: "Unknown"},
|
||||
}}}
|
||||
result, err := newExtractor(t, client).Extract(context.Background(), extractionRequest())
|
||||
if err != nil {
|
||||
@@ -286,7 +282,7 @@ func TestExtractOrdersAndDeduplicatesEvidence(t *testing.T) {
|
||||
|
||||
func TestExtractPreservesInvalidEvidenceForValidators(t *testing.T) {
|
||||
client := &fakeSpellsLLMClient{response: extractionResponse{SpellCasts: []spellCastResponse{{
|
||||
Caster: "Aria", Spell: "Cure Wounds", Effect: "Heals.", NarrativeDescription: "Aria heals.",
|
||||
Caster: "Aria", Spell: "Cure Wounds",
|
||||
SourceRefs: []spellSourceRefResponse{{StartUnitID: 99, EndUnitID: 0}},
|
||||
}}}}
|
||||
result, err := newExtractor(t, client).Extract(context.Background(), extractionRequest())
|
||||
@@ -300,13 +296,13 @@ func TestExtractPreservesInvalidEvidenceForValidators(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExtractMapsRawSemanticCandidatesWithoutRepair(t *testing.T) {
|
||||
client := &fakeSpellsLLMClient{content: []byte(`{"spell_casts":[{"caster":"","spell":"Cure Wounds","effect":"","narrative_description":"","source_refs":[{"start_unit_id":0,"end_unit_id":-1}]}]}`)}
|
||||
client := &fakeSpellsLLMClient{content: []byte(`{"spell_casts":[{"caster":"","spell":"Cure Wounds","source_refs":[{"start_unit_id":0,"end_unit_id":-1}]}]}`)}
|
||||
result, err := newExtractor(t, client).Extract(context.Background(), extractionRequest())
|
||||
if err != nil {
|
||||
t.Fatalf("Extract() error = %v, want nil", err)
|
||||
}
|
||||
spell := result.Value.SpellCasts[0]
|
||||
if spell.Caster != "" || spell.Effect != "" || spell.NarrativeDescription != "" {
|
||||
if spell.Caster != "" {
|
||||
t.Fatalf("spell = %#v, want blank semantic values preserved", spell)
|
||||
}
|
||||
if refs := spell.SourceRefs; len(refs) != 1 || refs[0] != (source.SourceRef{SourceID: "session-alpha", StartUnitID: 0, EndUnitID: -1}) {
|
||||
|
||||
@@ -5,11 +5,9 @@ type extractionResponse struct {
|
||||
}
|
||||
|
||||
type spellCastResponse struct {
|
||||
Caster string `json:"caster"`
|
||||
Spell string `json:"spell"`
|
||||
Effect string `json:"effect"`
|
||||
NarrativeDescription string `json:"narrative_description"`
|
||||
SourceRefs []spellSourceRefResponse `json:"source_refs"`
|
||||
Caster string `json:"caster"`
|
||||
Spell string `json:"spell"`
|
||||
SourceRefs []spellSourceRefResponse `json:"source_refs"`
|
||||
}
|
||||
|
||||
type spellSourceRefResponse struct {
|
||||
|
||||
@@ -37,14 +37,14 @@ func TestLoadResponseSchemaUsesExtractorOwnedLLMSchema(t *testing.T) {
|
||||
{
|
||||
name: "semantic blanks and empty evidence",
|
||||
response: map[string]any{"spell_casts": []any{map[string]any{
|
||||
"caster": "", "spell": "", "effect": "", "narrative_description": "", "source_refs": []any{},
|
||||
"caster": "", "spell": "", "source_refs": []any{},
|
||||
}}},
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "nonpositive unit candidates",
|
||||
response: map[string]any{"spell_casts": []any{map[string]any{
|
||||
"caster": "Aria", "spell": "Cure Wounds", "effect": "Heals.", "narrative_description": "Aria heals.",
|
||||
"caster": "Aria", "spell": "Cure Wounds",
|
||||
"source_refs": []any{map[string]any{"start_unit_id": 0, "end_unit_id": -1}},
|
||||
}}},
|
||||
valid: true,
|
||||
@@ -52,25 +52,25 @@ func TestLoadResponseSchemaUsesExtractorOwnedLLMSchema(t *testing.T) {
|
||||
{
|
||||
name: "missing required field",
|
||||
response: map[string]any{"spell_casts": []any{map[string]any{
|
||||
"spell": "Cure Wounds", "effect": "Heals.", "narrative_description": "Aria heals.", "source_refs": []any{},
|
||||
"spell": "Cure Wounds", "source_refs": []any{},
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "unknown field",
|
||||
response: map[string]any{"spell_casts": []any{map[string]any{
|
||||
"caster": "Aria", "spell": "Cure Wounds", "effect": "Heals.", "narrative_description": "Aria heals.", "source_refs": []any{}, "id": "assigned later",
|
||||
"caster": "Aria", "spell": "Cure Wounds", "source_refs": []any{}, "id": "assigned later",
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "wrong field type",
|
||||
response: map[string]any{"spell_casts": []any{map[string]any{
|
||||
"caster": 7, "spell": "Cure Wounds", "effect": "Heals.", "narrative_description": "Aria heals.", "source_refs": []any{},
|
||||
"caster": 7, "spell": "Cure Wounds", "source_refs": []any{},
|
||||
}}},
|
||||
},
|
||||
{
|
||||
name: "noninteger source identifier",
|
||||
response: map[string]any{"spell_casts": []any{map[string]any{
|
||||
"caster": "Aria", "spell": "Cure Wounds", "effect": "Heals.", "narrative_description": "Aria heals.",
|
||||
"caster": "Aria", "spell": "Cure Wounds",
|
||||
"source_refs": []any{map[string]any{"start_unit_id": 1.5, "end_unit_id": 2}},
|
||||
}}},
|
||||
},
|
||||
@@ -131,10 +131,8 @@ func validSpellsResponse() map[string]any {
|
||||
return map[string]any{
|
||||
"spell_casts": []any{
|
||||
map[string]any{
|
||||
"caster": "Aria",
|
||||
"spell": "Cure Wounds",
|
||||
"effect": "The wounds close.",
|
||||
"narrative_description": "Aria casts Cure Wounds.",
|
||||
"caster": "Aria",
|
||||
"spell": "Cure Wounds",
|
||||
"source_refs": []any{
|
||||
map[string]any{"start_unit_id": 1, "end_unit_id": 2},
|
||||
},
|
||||
|
||||
@@ -25,7 +25,6 @@ var promptAssetManifest = shared.PromptAssetManifest{
|
||||
"common-dnd-identity.md",
|
||||
"common-dnd-transcript.md",
|
||||
"common-dnd-references.md",
|
||||
"common-dnd-immediate-resolution.md",
|
||||
"common-dnd-npcs.md",
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user