Centralize spell and combat-turn assets

This commit is contained in:
2026-08-05 00:43:39 +00:00
parent 2345da106a
commit 1c7291e17d
15 changed files with 71 additions and 29 deletions

View File

@@ -1,6 +0,0 @@
package combatturns
import "embed"
//go:embed assets/schemas/dnd_combat_turns_llm.v1.json assets/prompts/*.yaml assets/prompts/*.md
var embeddedAssets embed.FS

View File

@@ -1,47 +0,0 @@
id: dnd.combat_turns
version: "v1"
default_profile: dnd-extraction
inputs:
- name: transcript
required: true
content_type: application/json
- name: players
required: false
content_type: text/plain
- name: party
required: false
content_type: text/plain
- name: glossary
required: false
content_type: text/plain
- name: npcs
required: false
content_type: application/json
messages:
- role: system
content_file: ./sharedassets/common-dnd-system.md
- role: user
content_file: ./sharedassets/common-dnd-identity.md
- role: user
content_file: ./sharedassets/common-dnd-references.md
cache_control:
type: ephemeral
- role: user
content_file: ./sharedassets/common-dnd-transcript.md
cache_control:
type: ephemeral
- role: user
content_file: ./sharedassets/common-dnd-extraction-evidence.md
- role: user
content_file: ./sharedassets/common-dnd-npcs.md
- role: user
content_file: ./task.md
- role: user
content_file: ./instructions.md
cache_control:
type: ephemeral
output:
format: json
validation_mode: json_schema
schema_path: dnd_combat_turns_llm.v1.json
repair_attempts: 0

View File

@@ -1,6 +0,0 @@
Return the combat_turns array even when no combat turn is established. Return
actor, turn_kind, and source_refs for every record. For turn_kind, use exactly
one of: turn, reaction, legendary_action, lair_action, or other. Cite the
transcript ranges that establish both the actor and the combat event. Use the
players, party, and transcript context to map speakers to in-world actors. NPC
names may help disambiguate identity but do not replace transcript evidence.

View File

@@ -1,18 +0,0 @@
Extract Dungeons & Dragons combat-turn artifacts from the supplied transcript.
Include a record only when the transcript establishes that an in-world
participant takes a combat turn or performs a discrete interrupting combat
event. Interrupting events belong at the point where they occur in transcript
chronology.
Exclude initiative setup without a turn or combat event, tactical planning,
table talk, rules lookup, hypothetical events, abandoned intentions, recaps
outside the current passage, and downstream consequences.
Do not infer combat events from D&D rules knowledge. Preserve the session as
played and attribute relevant nonstandard rulings to the GM or table.
Unmatched actors remain permitted.
Place all supporting transcript ranges for a turn in its turn-level source_refs
collection.

View File

@@ -1,41 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "notarius.dnd.combat_turns.llm",
"type": "object",
"additionalProperties": false,
"required": ["combat_turns"],
"properties": {
"combat_turns": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["actor", "turn_kind", "source_refs"],
"properties": {
"actor": {
"type": "string"
},
"turn_kind": {
"type": "string"
},
"source_refs": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["start_unit_id", "end_unit_id"],
"properties": {
"start_unit_id": {
"type": "integer"
},
"end_unit_id": {
"type": "integer"
}
}
}
}
}
}
}
}
}

View File

@@ -2,8 +2,10 @@ package combatturns
import (
"fmt"
"io/fs"
"sync"
rootassets "gitea.maximumdirect.net/eric/notarius/assets"
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
"gitea.maximumdirect.net/eric/notarius/internal/framework/promptfs"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
@@ -14,9 +16,9 @@ const promptAssetRoot = "assets/prompts"
var promptAssetManifest = shared.PromptAssetManifest{
ModuleDir: "dnd.combat_turns",
ModuleFiles: []promptfs.ModulePromptFile{
{Name: "dnd.combat_turns.yaml", Path: "assets/prompts/dnd.combat_turns.yaml"},
{Name: "task.md", Path: "assets/prompts/task.md"},
{Name: "instructions.md", Path: "assets/prompts/instructions.md"},
{Name: "dnd.combat_turns.yaml", Path: "prompts/dnd.combat_turns.yaml"},
{Name: "task.md", Path: "prompts/task.md"},
{Name: "instructions.md", Path: "prompts/instructions.md"},
},
SharedFiles: []string{
"common-dnd-system.md",
@@ -28,20 +30,41 @@ var promptAssetManifest = shared.PromptAssetManifest{
},
}
func moduleAssetFS() (fs.FS, error) {
assets, err := fs.Sub(rootassets.FS(), "dnd/combat-turns")
if err != nil {
return nil, fmt.Errorf("scope combat-turn assets: %w", err)
}
return assets, nil
}
func RegisterPromptAssets(registry *llm.AssetRegistry) error {
promptFS, err := promptAssetManifest.PromptFS(embeddedAssets)
assets, err := moduleAssetFS()
if err != nil {
return err
}
promptFS, err := promptAssetManifest.PromptFS(assets)
if err != nil {
return fmt.Errorf("prepare combat-turn prompt assets: %w", err)
}
if err := registry.RegisterPromptFS(promptFS, promptAssetRoot); err != nil {
return err
}
return registry.RegisterSchemaFS(embeddedAssets, "assets/schemas")
schemas, err := fs.Sub(assets, "schemas")
if err != nil {
return fmt.Errorf("scope combat-turn schemas: %w", err)
}
return registry.RegisterSchemaFS(schemas, ".")
}
func promptAssetMetadata() (string, error) {
promptAssetHashOnce.Do(func() {
promptAssetHash, promptAssetHashErr = promptAssetManifest.Hash(embeddedAssets)
assets, err := moduleAssetFS()
if err != nil {
promptAssetHashErr = err
return
}
promptAssetHash, promptAssetHashErr = promptAssetManifest.Hash(assets)
})
return promptAssetHash, promptAssetHashErr
}

View File

@@ -11,11 +11,15 @@ const (
)
func loadResponseSchema() (llm.ResponseSchema, error) {
return llm.LoadResponseSchema(embeddedAssets, llm.ResponseSchemaDefinition{
assets, err := moduleAssetFS()
if err != nil {
return llm.ResponseSchema{}, err
}
return llm.LoadResponseSchema(assets, llm.ResponseSchemaDefinition{
Key: ResponseSchemaKey,
ID: ResponseSchemaID,
Version: SchemaVersion,
Name: ResponseSchemaName,
AssetPath: "assets/schemas/dnd_combat_turns_llm.v1.json",
AssetPath: "schemas/dnd_combat_turns_llm.v1.json",
})
}

View File

@@ -1,6 +0,0 @@
package spells
import "embed"
//go:embed assets/schemas/dnd_spells_llm.v1.json assets/prompts/*.yaml assets/prompts/*.md
var embeddedAssets embed.FS

View File

@@ -1,6 +0,0 @@
The canonical spell-name catalog for this extraction is provided below as JSON.
Return spell names using the catalog's canonical spelling exactly. Aliases and
other campaign reference material are not part of this catalog input and must
not be copied into the output as spell names.
{{ input "spell_catalog" }}

View File

@@ -1,52 +0,0 @@
id: dnd.spells
version: "v1"
default_profile: dnd-extraction
inputs:
- name: transcript
required: true
content_type: application/json
- name: spell_catalog
required: true
content_type: application/json
- name: players
required: false
content_type: text/plain
- name: party
required: false
content_type: text/plain
- name: glossary
required: false
content_type: text/plain
- name: npcs
required: false
content_type: application/json
messages:
- role: system
content_file: ./sharedassets/common-dnd-system.md
- role: user
content_file: ./sharedassets/common-dnd-identity.md
- role: user
content_file: ./sharedassets/common-dnd-references.md
cache_control:
type: ephemeral
- role: user
content_file: ./sharedassets/common-dnd-transcript.md
cache_control:
type: ephemeral
- role: user
content_file: ./sharedassets/common-dnd-extraction-evidence.md
- role: user
content_file: ./sharedassets/common-dnd-npcs.md
- role: user
content_file: ./catalog.md
- role: user
content_file: ./task.md
- role: user
content_file: ./instructions.md
cache_control:
type: ephemeral
output:
format: json
validation_mode: json_schema
schema_path: dnd_spells_llm.v1.json
repair_attempts: 0

View File

@@ -1,16 +0,0 @@
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.
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
canonical character name from the references. Likewise, attribute a spell
narrated by the GM to the in-world creature that casts it. If the caster cannot
be resolved, use only the most specific in-world identity supported by the
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.

View File

@@ -1,9 +0,0 @@
Extract Dungeons & Dragons spell-cast artifacts from the provided transcript.
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 and never evidence that a spell was cast.

View File

@@ -1,48 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "notarius.dnd.spells",
"type": "object",
"additionalProperties": false,
"required": ["spell_casts"],
"properties": {
"spell_casts": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"caster",
"spell",
"source_refs"
],
"properties": {
"caster": {
"type": "string",
"description": "Canonical in-world character or creature that casts the spell, never the human player, transcript speaker, or GM when the in-world caster can be identified."
},
"spell": {
"type": "string",
"description": "Canonical spell name from the provided spell-name catalog."
},
"source_refs": {
"type": "array",
"description": "Transcript ranges offered as evidence for the caster, spell name, and casting event in this spell-cast object.",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["start_unit_id", "end_unit_id"],
"properties": {
"start_unit_id": {
"type": "integer"
},
"end_unit_id": {
"type": "integer"
}
}
}
}
}
}
}
}
}

View File

@@ -2,8 +2,10 @@ package spells
import (
"fmt"
"io/fs"
"sync"
rootassets "gitea.maximumdirect.net/eric/notarius/assets"
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
"gitea.maximumdirect.net/eric/notarius/internal/framework/promptfs"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
@@ -14,10 +16,10 @@ const promptAssetRoot = "assets/prompts"
var promptAssetManifest = shared.PromptAssetManifest{
ModuleDir: "dnd.spells",
ModuleFiles: []promptfs.ModulePromptFile{
{Name: "dnd.spells.yaml", Path: "assets/prompts/dnd.spells.yaml"},
{Name: "catalog.md", Path: "assets/prompts/catalog.md"},
{Name: "task.md", Path: "assets/prompts/task.md"},
{Name: "instructions.md", Path: "assets/prompts/instructions.md"},
{Name: "dnd.spells.yaml", Path: "prompts/dnd.spells.yaml"},
{Name: "catalog.md", Path: "prompts/catalog.md"},
{Name: "task.md", Path: "prompts/task.md"},
{Name: "instructions.md", Path: "prompts/instructions.md"},
},
SharedFiles: []string{
"common-dnd-system.md",
@@ -29,20 +31,41 @@ var promptAssetManifest = shared.PromptAssetManifest{
},
}
func moduleAssetFS() (fs.FS, error) {
assets, err := fs.Sub(rootassets.FS(), "dnd/spells")
if err != nil {
return nil, fmt.Errorf("scope spell assets: %w", err)
}
return assets, nil
}
func RegisterPromptAssets(registry *llm.AssetRegistry) error {
promptFS, err := promptAssetManifest.PromptFS(embeddedAssets)
assets, err := moduleAssetFS()
if err != nil {
return err
}
promptFS, err := promptAssetManifest.PromptFS(assets)
if err != nil {
return fmt.Errorf("prepare spell prompt assets: %w", err)
}
if err := registry.RegisterPromptFS(promptFS, promptAssetRoot); err != nil {
return err
}
return registry.RegisterSchemaFS(embeddedAssets, "assets/schemas")
schemas, err := fs.Sub(assets, "schemas")
if err != nil {
return fmt.Errorf("scope spell schemas: %w", err)
}
return registry.RegisterSchemaFS(schemas, ".")
}
func promptAssetMetadata() (string, error) {
promptAssetHashOnce.Do(func() {
promptAssetHash, promptAssetHashErr = promptAssetManifest.Hash(embeddedAssets)
assets, err := moduleAssetFS()
if err != nil {
promptAssetHashErr = err
return
}
promptAssetHash, promptAssetHashErr = promptAssetManifest.Hash(assets)
})
return promptAssetHash, promptAssetHashErr
}

View File

@@ -10,11 +10,15 @@ const (
)
func loadResponseSchema() (llm.ResponseSchema, error) {
return llm.LoadResponseSchema(embeddedAssets, llm.ResponseSchemaDefinition{
assets, err := moduleAssetFS()
if err != nil {
return llm.ResponseSchema{}, err
}
return llm.LoadResponseSchema(assets, llm.ResponseSchemaDefinition{
Key: ResponseSchemaKey,
ID: ResponseSchemaID,
Version: SchemaVersion,
Name: ResponseSchemaName,
AssetPath: "assets/schemas/dnd_spells_llm.v1.json",
AssetPath: "schemas/dnd_spells_llm.v1.json",
})
}