Update D&D schemas to require integer unit_id values
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
Source references must use the source-unit IDs exactly as provided.
|
||||
Source references must use 1-based integer source-unit numbers from the
|
||||
transcript, where 1 is the first provided source unit.
|
||||
|
||||
Return only D&D spell-cast artifacts. For each spell cast, identify the in-world
|
||||
caster, spell name, effect, narrative description, and source references using
|
||||
|
||||
@@ -47,12 +47,12 @@
|
||||
"minLength": 1
|
||||
},
|
||||
"start_unit_id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"end_unit_id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.ExtractionRequest
|
||||
}
|
||||
candidates = append(candidates, artifacts.ArtifactCandidate{
|
||||
Payload: payload,
|
||||
SourceRefs: append([]source.SourceRef(nil), spellCast.SourceRefs...),
|
||||
SourceRefs: sourceRefCandidates(req.Source, spellCast.SourceRefs),
|
||||
})
|
||||
}
|
||||
return contracts.ExtractionResult{Candidates: candidates}, nil
|
||||
@@ -164,6 +164,17 @@ func spellCastPayload(spellCast spellCastResponse) (json.RawMessage, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func sourceRefCandidates(doc *source.SourceDocument, refs []dnd.SourceRefResponse) []source.SourceRef {
|
||||
if len(refs) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]source.SourceRef, 0, len(refs))
|
||||
for _, ref := range refs {
|
||||
out = append(out, dnd.SourceRefCandidate(doc, ref))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func extractorErrorf(format string, args ...any) error {
|
||||
return fmt.Errorf("dnd spells extractor: "+format, args...)
|
||||
}
|
||||
|
||||
@@ -21,9 +21,7 @@ func TestExtractReturnsSpellCandidateFromStructuredOutput(t *testing.T) {
|
||||
Spell: " Cure Wounds ",
|
||||
Effect: " Heals an injured ally. ",
|
||||
NarrativeDescription: " Aria restores the fighter after the fight. ",
|
||||
SourceRefs: []source.SourceRef{
|
||||
{SourceID: "session-alpha", StartUnitID: "seg-001", EndUnitID: "seg-002"},
|
||||
},
|
||||
SourceRefs: responseSourceRefsInt("session-alpha", 1, 2),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -256,14 +254,14 @@ func TestExtractPreservesResponseOrder(t *testing.T) {
|
||||
Spell: "Cure Wounds",
|
||||
Effect: "Heals.",
|
||||
NarrativeDescription: "First spell.",
|
||||
SourceRefs: []source.SourceRef{{SourceID: "session-alpha", StartUnitID: "seg-001", EndUnitID: "seg-001"}},
|
||||
SourceRefs: responseSourceRefs("session-alpha", "seg-001", "seg-001"),
|
||||
},
|
||||
{
|
||||
Caster: "Bandit Shaman",
|
||||
Spell: "Fire Bolt",
|
||||
Effect: "Burns.",
|
||||
NarrativeDescription: "Second spell.",
|
||||
SourceRefs: []source.SourceRef{{SourceID: "session-alpha", StartUnitID: "seg-002", EndUnitID: "seg-002"}},
|
||||
SourceRefs: responseSourceRefs("session-alpha", "seg-002", "seg-002"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -298,7 +296,7 @@ func TestExtractCopiesCandidateSourceRefs(t *testing.T) {
|
||||
Spell: "Cure Wounds",
|
||||
Effect: "Heals.",
|
||||
NarrativeDescription: "Aria heals.",
|
||||
SourceRefs: []source.SourceRef{{SourceID: "session-alpha", StartUnitID: "seg-001", EndUnitID: "seg-002"}},
|
||||
SourceRefs: responseSourceRefs("session-alpha", "seg-001", "seg-002"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -308,7 +306,7 @@ func TestExtractCopiesCandidateSourceRefs(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Extract() error = %v, want nil", err)
|
||||
}
|
||||
client.response.SpellCasts[0].SourceRefs[0].StartUnitID = "mutated"
|
||||
client.response.SpellCasts[0].SourceRefs[0].StartUnitID = dnd.UnitRefFromString("mutated")
|
||||
|
||||
if got := result.Candidates[0].SourceRefs[0].StartUnitID; got != "seg-001" {
|
||||
t.Fatalf("candidate source ref start = %q, want copied seg-001", got)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package spells
|
||||
|
||||
import "gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||
import "gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd"
|
||||
|
||||
type SpellCast struct {
|
||||
Caster string `json:"caster"`
|
||||
@@ -14,9 +14,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 []source.SourceRef `json:"source_refs"`
|
||||
Caster string `json:"caster"`
|
||||
Spell string `json:"spell"`
|
||||
Effect string `json:"effect"`
|
||||
NarrativeDescription string `json:"narrative_description"`
|
||||
SourceRefs []dnd.SourceRefResponse `json:"source_refs"`
|
||||
}
|
||||
|
||||
@@ -26,18 +26,14 @@ func TestRunnerProcessesSeriatimInputWithDNDSpellsExtractor(t *testing.T) {
|
||||
Spell: "Cure Wounds",
|
||||
Effect: "Heals an injured ally.",
|
||||
NarrativeDescription: "Aria restores the fighter after the fight.",
|
||||
SourceRefs: []source.SourceRef{
|
||||
{SourceID: expectedDoc.ID, StartUnitID: "seg-001", EndUnitID: "seg-001"},
|
||||
},
|
||||
SourceRefs: responseSourceRefs(expectedDoc.ID, "seg-001", "seg-001"),
|
||||
},
|
||||
{
|
||||
Caster: "Borin",
|
||||
Spell: "Fire Bolt",
|
||||
Effect: "Scorches the wight.",
|
||||
NarrativeDescription: "Borin hurls fire at the wight.",
|
||||
SourceRefs: []source.SourceRef{
|
||||
{SourceID: expectedDoc.ID, StartUnitID: "seg-003", EndUnitID: "seg-003"},
|
||||
},
|
||||
SourceRefs: responseSourceRefs(expectedDoc.ID, "seg-003", "seg-003"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -126,9 +122,7 @@ func TestRunnerPassesPartyAndGlossaryReferencesToDNDSpellsPrompt(t *testing.T) {
|
||||
Spell: "Fire Bolt",
|
||||
Effect: "Scorches the wight.",
|
||||
NarrativeDescription: "Borin hurls fire at the wight.",
|
||||
SourceRefs: []source.SourceRef{
|
||||
{SourceID: expectedDoc.ID, StartUnitID: "seg-003", EndUnitID: "seg-003"},
|
||||
},
|
||||
SourceRefs: responseSourceRefs(expectedDoc.ID, "seg-003", "seg-003"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -213,9 +207,7 @@ func TestRunnerRejectsDNDSpellCastWithInvalidSourceRef(t *testing.T) {
|
||||
Spell: "Cure Wounds",
|
||||
Effect: "Heals an injured ally.",
|
||||
NarrativeDescription: "Aria restores the fighter after the fight.",
|
||||
SourceRefs: []source.SourceRef{
|
||||
{SourceID: "spell-session", StartUnitID: "seg-999", EndUnitID: "seg-999"},
|
||||
},
|
||||
SourceRefs: responseSourceRefs("spell-session", "seg-999", "seg-999"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -29,6 +29,23 @@ func TestLoadResponseSchemaForSpells(t *testing.T) {
|
||||
if !json.Valid(schema.JSONSchema) {
|
||||
t.Fatalf("schema.JSONSchema is invalid JSON: %s", schema.JSONSchema)
|
||||
}
|
||||
|
||||
var decoded map[string]any
|
||||
if err := json.Unmarshal(schema.JSONSchema, &decoded); err != nil {
|
||||
t.Fatalf("Unmarshal(schema.JSONSchema) error = %v, want nil", err)
|
||||
}
|
||||
properties := decoded["properties"].(map[string]any)
|
||||
spellCastProperties := properties["spell_casts"].(map[string]any)["items"].(map[string]any)["properties"].(map[string]any)
|
||||
sourceRefProperties := spellCastProperties["source_refs"].(map[string]any)["items"].(map[string]any)["properties"].(map[string]any)
|
||||
for _, field := range []string{"start_unit_id", "end_unit_id"} {
|
||||
property := sourceRefProperties[field].(map[string]any)
|
||||
if property["type"] != "integer" {
|
||||
t.Fatalf("%s type = %#v, want integer", field, property["type"])
|
||||
}
|
||||
if property["minimum"] != float64(1) {
|
||||
t.Fatalf("%s minimum = %#v, want 1", field, property["minimum"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseSchemaJSONIsMutationSafe(t *testing.T) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd"
|
||||
)
|
||||
|
||||
func promptExtractionRequest() contracts.ExtractionRequest {
|
||||
@@ -59,3 +60,23 @@ func mustJSON(t *testing.T, value any) string {
|
||||
}
|
||||
return string(encoded)
|
||||
}
|
||||
|
||||
func responseSourceRefs(sourceID string, startUnitID string, endUnitID string) []dnd.SourceRefResponse {
|
||||
return []dnd.SourceRefResponse{
|
||||
{
|
||||
SourceID: sourceID,
|
||||
StartUnitID: dnd.UnitRefFromString(startUnitID),
|
||||
EndUnitID: dnd.UnitRefFromString(endUnitID),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func responseSourceRefsInt(sourceID string, startUnitID int, endUnitID int) []dnd.SourceRefResponse {
|
||||
return []dnd.SourceRefResponse{
|
||||
{
|
||||
SourceID: sourceID,
|
||||
StartUnitID: dnd.UnitRefFromInt(startUnitID),
|
||||
EndUnitID: dnd.UnitRefFromInt(endUnitID),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user