Centralize NPC LLM assets

This commit is contained in:
2026-08-05 00:40:47 +00:00
parent 9cb9ee48a7
commit 03761c97dd
19 changed files with 94 additions and 39 deletions

View File

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

View File

@@ -1,47 +0,0 @@
id: dnd.npc_interactions
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: true
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_npc_interactions_llm.v1.json
repair_attempts: 0

View File

@@ -1,18 +0,0 @@
Return the interactions array even when no interaction is established. Every
record must contain name, kind, and source_refs. Cite transcript ranges that
support both the NPC identity and the interaction kind.
Use exactly one kind per occurrence:
- mentioned: the NPC is referred to but is not established as present or communicating;
- noncombat_presence: the NPC is present and relevant but does not meaningfully participate in dialogue or combat;
- dialogue: the NPC speaks, responds, or is directly engaged in a meaningful non-combat exchange;
- combat_ally: the NPC actively participates in combat on the party's side;
- combat_opponent: the NPC actively participates in combat against the party; or
- other: the transcript clearly establishes a direct NPC occurrence that fits none of the preceding kinds.
When activities overlap, active combat participation outranks dialogue,
presence, and mention; dialogue outranks noncombat presence and mention; and
noncombat presence outranks mention. Other is only for directly evidenced
activity outside those categories. Split an occurrence rather than assigning
both combat alignments.

View File

@@ -1,16 +0,0 @@
Extract Dungeons & Dragons NPC interaction occurrences from the supplied
transcript.
Include an occurrence only when the transcript establishes one supplied NPC,
one interaction kind, and a coherent passage supporting both. Use only names
from the supplied NPC registry. The registry helps ground identity but never
proves that an interaction occurred.
Do not summarize, infer relationships, sentiment, factions, motives, aliases,
or persistent state. Do not identify player characters, anonymous groups, or
invented NPCs. Return an empty interactions array when no supplied NPC has an
evidenced interaction in this transcript passage.
Keep occurrences within this transcript chunk. Split records when an NPC's
interaction kind changes, when combat alignment changes, or when an NPC is
first mentioned and later becomes present.

View File

@@ -1,41 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "notarius.dnd.npc_interactions.llm",
"type": "object",
"additionalProperties": false,
"required": ["interactions"],
"properties": {
"interactions": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["name", "kind", "source_refs"],
"properties": {
"name": {
"type": "string"
},
"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 npcinteractions
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.npc_interactions",
ModuleFiles: []promptfs.ModulePromptFile{
{Name: "dnd.npc_interactions.yaml", Path: "assets/prompts/dnd.npc_interactions.yaml"},
{Name: "task.md", Path: "assets/prompts/task.md"},
{Name: "instructions.md", Path: "assets/prompts/instructions.md"},
{Name: "dnd.npc_interactions.yaml", Path: "prompts/dnd.npc_interactions.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/npc-interactions")
if err != nil {
return nil, fmt.Errorf("scope NPC-interaction 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 NPC-interaction 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 NPC-interaction 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_npc_interactions_llm.v1.json",
AssetPath: "schemas/dnd_npc_interactions_llm.v1.json",
})
}