Centralize scene planning and description assets

This commit is contained in:
2026-08-05 00:38:55 +00:00
parent 08954f17e2
commit 9cb9ee48a7
14 changed files with 70 additions and 28 deletions

View File

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

View File

@@ -1,36 +0,0 @@
id: dnd.scenes
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
messages:
- role: system
content_file: ./sharedassets/common-dnd-system.md
- role: user
content_file: ./sharedassets/common-dnd-references.md
cache_control:
type: ephemeral
- role: user
content_file: ./task.md
- role: user
content_file: ./instructions.md
- role: user
content_file: ./sharedassets/common-dnd-transcript.md
cache_control:
type: ephemeral
output:
format: json
validation_mode: json_schema
schema_path: dnd_scenes.v1.json
repair_attempts: 0

View File

@@ -1,8 +0,0 @@
Cover the complete provided transcript from its first source unit to its last
source unit. Return scenes in source-unit order with no gaps or overlaps. Use
only positive integer source-unit IDs from the transcript, and give every scene
one inclusive `start_unit_id` and one inclusive `end_unit_id`.
Return exactly one JSON object and no explanatory text. The object must contain
only a non-empty `scenes` array. Each scene object must contain only
`start_unit_id` and `end_unit_id`.

View File

@@ -1,18 +0,0 @@
Divide the provided transcript into coherent Dungeons & Dragons scenes for the
`dnd/scenes` chunk module.
A scene is a coherent unit of play. Start a new scene when the transcript
establishes a meaningful change in location, objective, threat, activity,
encounter, or mode of play. Good reasons include a material move, beginning or
ending combat, a substantially different encounter phase, a shift between
combat, exploration, social interaction, planning, travel, rest, or downtime,
a change in the central NPC, faction, threat, or objective, or a sustained
table-level interruption that materially changes the activity.
Do not split a scene merely because a speaker or combat round changes, a
routine turn occurs, or the table briefly digresses. Prefer fewer coherent
scenes over speculative or fine-grained boundaries.
Return only inclusive `start_unit_id` and `end_unit_id` endpoints for each
scene. Do not return titles, modes, participants, summaries, boundary notes,
confidence, caveats, final chunk IDs, or chunk indexes.

View File

@@ -1,28 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "notarius.dnd.scenes",
"type": "object",
"additionalProperties": false,
"required": ["scenes"],
"properties": {
"scenes": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["start_unit_id", "end_unit_id"],
"properties": {
"start_unit_id": {
"type": "integer",
"minimum": 1
},
"end_unit_id": {
"type": "integer",
"minimum": 1
}
}
}
}
}
}

View File

@@ -2,8 +2,10 @@ package scenes
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.scenes",
ModuleFiles: []promptfs.ModulePromptFile{
{Name: "dnd.scenes.yaml", Path: "assets/prompts/dnd.scenes.yaml"},
{Name: "task.md", Path: "assets/prompts/task.md"},
{Name: "instructions.md", Path: "assets/prompts/instructions.md"},
{Name: "dnd.scenes.yaml", Path: "prompts/dnd.scenes.yaml"},
{Name: "task.md", Path: "prompts/task.md"},
{Name: "instructions.md", Path: "prompts/instructions.md"},
},
SharedFiles: []string{
"common-dnd-system.md",
@@ -25,20 +27,41 @@ var promptAssetManifest = shared.PromptAssetManifest{
},
}
func moduleAssetFS() (fs.FS, error) {
assets, err := fs.Sub(rootassets.FS(), "dnd/scenes")
if err != nil {
return nil, fmt.Errorf("scope scene 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 scene 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 scene 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: ResponseSchemaVersion,
Name: ResponseSchemaName,
AssetPath: "assets/schemas/dnd_scenes.v1.json",
AssetPath: "schemas/dnd_scenes.v1.json",
})
}

View File

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

View File

@@ -1,40 +0,0 @@
id: dnd.scene_descriptions
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
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: ./task.md
- role: user
content_file: ./instructions.md
cache_control:
type: ephemeral
output:
format: json
validation_mode: json_schema
schema_path: dnd_scene_descriptions_llm.v1.json
repair_attempts: 0

View File

@@ -1,44 +0,0 @@
Choose exactly one kind:
- combat: active combat materially organizes the scene, including
initiative-like exchanges or sustained hostile action. Planning a fight or
discussing a completed fight is not combat by itself.
- narrative: current-session in-world play that is not principally active
combat, a prior-session recap, or sustained out-of-character session
discussion. This includes exploration, travel, dialogue, investigation,
in-character planning, and aftermath.
- recap: the scene's organizing purpose is to recount events from a previous
session for the table. An in-world character recounting history during
current play remains narrative.
- meta: the scene's organizing purpose is sustained out-of-character
discussion about the game or session rather than advancing current in-world
play.
Narrative is the default for actual current-session gameplay that does not meet
another definition. When the accepted chunk is mixed:
1. use combat when active combat is a substantive central activity, even with
brief setup, rules clarification, or immediate aftermath;
2. otherwise use recap when recounting a previous session is the chunk's
primary table purpose;
3. otherwise use meta when sustained out-of-character session discussion is
primary and in-world progression is no more than incidental; and
4. use narrative for all remaining current-session in-world play.
Brief table talk, dice resolution, rules clarification, jokes, or
administrative comments do not make a gameplay scene meta. A short recollection
used to orient current action does not make a scene recap.
The title must be a short, distinguishing phrase rather than a sentence,
chapter number, or generic label such as "Scene." It may use names and places
established by the transcript or disambiguated by campaign references, but it
must not invent a proper noun.
The summary must briefly state the main activity and material transition or
outcome established within the accepted chunk. Do not add analysis, inferred
motives, hidden state, future consequences, relationship claims, or facts from
outside the chunk. Campaign references may disambiguate names but never add
events or lore.
Do not return identifiers, source identifiers, source ranges, unit identifiers,
participants, confidence, or any fields besides kind, title, and summary.

View File

@@ -1,5 +0,0 @@
Describe exactly one accepted Dungeons & Dragons scene from the supplied
transcript chunk. The complete chunk is the evidence boundary: do not split it
into multiple scenes or use facts that are not supported by it.
Return one kind, one concise title, and one concise summary.

View File

@@ -1,18 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "notarius.dnd.scene_descriptions.llm",
"type": "object",
"additionalProperties": false,
"required": ["kind", "title", "summary"],
"properties": {
"kind": {
"type": "string"
},
"title": {
"type": "string"
},
"summary": {
"type": "string"
}
}
}

View File

@@ -2,8 +2,10 @@ package scenedescriptions
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.scene_descriptions",
ModuleFiles: []promptfs.ModulePromptFile{
{Name: "dnd.scene_descriptions.yaml", Path: "assets/prompts/dnd.scene_descriptions.yaml"},
{Name: "task.md", Path: "assets/prompts/task.md"},
{Name: "instructions.md", Path: "assets/prompts/instructions.md"},
{Name: "dnd.scene_descriptions.yaml", Path: "prompts/dnd.scene_descriptions.yaml"},
{Name: "task.md", Path: "prompts/task.md"},
{Name: "instructions.md", Path: "prompts/instructions.md"},
},
SharedFiles: []string{
"common-dnd-system.md",
@@ -26,20 +28,41 @@ var promptAssetManifest = shared.PromptAssetManifest{
},
}
func moduleAssetFS() (fs.FS, error) {
assets, err := fs.Sub(rootassets.FS(), "dnd/scene-descriptions")
if err != nil {
return nil, fmt.Errorf("scope scene-description 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 scene-description 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 scene-description 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

@@ -12,11 +12,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_scene_descriptions_llm.v1.json",
AssetPath: "schemas/dnd_scene_descriptions_llm.v1.json",
})
}