Centralize shared D&D LLM assets
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
id: dnd-extraction
|
||||
backend: openrouter
|
||||
model: openai/gpt-5.6-luna
|
||||
timeout_seconds: 240
|
||||
service_tier: flex
|
||||
@@ -1,14 +1,17 @@
|
||||
package register
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
|
||||
rootassets "gitea.maximumdirect.net/eric/notarius/assets"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||
)
|
||||
|
||||
//go:embed assets/profiles/*.yaml
|
||||
var embeddedProfileAssets embed.FS
|
||||
|
||||
func registerFallbackProfiles(assets *llm.AssetRegistry) error {
|
||||
return assets.RegisterFallbackProfileFS(embeddedProfileAssets, "assets/profiles")
|
||||
profiles, err := fs.Sub(rootassets.FS(), "dnd/profiles")
|
||||
if err != nil {
|
||||
return fmt.Errorf("scope D&D fallback profiles: %w", err)
|
||||
}
|
||||
return assets.RegisterFallbackProfileFS(profiles, ".")
|
||||
}
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"strings"
|
||||
|
||||
rootassets "gitea.maximumdirect.net/eric/notarius/assets"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/promptfs"
|
||||
)
|
||||
|
||||
//go:embed assets/prompts/*.md
|
||||
var embeddedAssets embed.FS
|
||||
|
||||
// PromptAssetManifest is the ordered set of assets that make up one prompt.
|
||||
// Module files are addressed in the owning module filesystem; shared files use
|
||||
// the names in sharedPromptPaths and are mounted beneath sharedassets.
|
||||
@@ -23,13 +20,21 @@ type PromptAssetManifest struct {
|
||||
}
|
||||
|
||||
var sharedPromptPaths = map[string]string{
|
||||
"common-dnd-system.md": "assets/prompts/common-dnd-system.md",
|
||||
"common-dnd-extraction-evidence.md": "assets/prompts/common-dnd-extraction-evidence.md",
|
||||
"common-dnd-identity.md": "assets/prompts/common-dnd-identity.md",
|
||||
"common-dnd-transcript.md": "assets/prompts/common-dnd-transcript.md",
|
||||
"common-dnd-references.md": "assets/prompts/common-dnd-references.md",
|
||||
"common-dnd-npcs.md": "assets/prompts/common-dnd-npcs.md",
|
||||
"common-dnd-entity-reconciliation.md": "assets/prompts/common-dnd-entity-reconciliation.md",
|
||||
"common-dnd-system.md": "prompts/common-dnd-system.md",
|
||||
"common-dnd-extraction-evidence.md": "prompts/common-dnd-extraction-evidence.md",
|
||||
"common-dnd-identity.md": "prompts/common-dnd-identity.md",
|
||||
"common-dnd-transcript.md": "prompts/common-dnd-transcript.md",
|
||||
"common-dnd-references.md": "prompts/common-dnd-references.md",
|
||||
"common-dnd-npcs.md": "prompts/common-dnd-npcs.md",
|
||||
"common-dnd-entity-reconciliation.md": "prompts/common-dnd-entity-reconciliation.md",
|
||||
}
|
||||
|
||||
func sharedAssetFS() (fs.FS, error) {
|
||||
assets, err := fs.Sub(rootassets.FS(), "dnd/shared")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scope shared D&D prompt assets: %w", err)
|
||||
}
|
||||
return assets, nil
|
||||
}
|
||||
|
||||
func (manifest PromptAssetManifest) PromptFS(moduleFS fs.FS) (fs.FS, error) {
|
||||
@@ -57,6 +62,11 @@ func (manifest PromptAssetManifest) Hash(moduleFS fs.FS) (string, error) {
|
||||
}
|
||||
|
||||
func resolveSharedPromptFiles(names []string) ([]promptfs.SharedPromptFile, error) {
|
||||
assets, err := sharedAssetFS()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := make([]promptfs.SharedPromptFile, 0, len(names))
|
||||
seen := make(map[string]struct{}, len(names))
|
||||
for _, name := range names {
|
||||
@@ -76,7 +86,7 @@ func resolveSharedPromptFiles(names []string) ([]promptfs.SharedPromptFile, erro
|
||||
seen[name] = struct{}{}
|
||||
files = append(files, promptfs.SharedPromptFile{
|
||||
Name: name,
|
||||
FS: embeddedAssets,
|
||||
FS: assets,
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
Identify only well-supported duplicate groups among the supplied candidates.
|
||||
|
||||
Candidate keys are opaque identifiers. Copy each selected key exactly. A group
|
||||
must contain at least two supplied keys, and its `canonical` key must be one of
|
||||
its members. Do not create keys, records, names, source references, evidence,
|
||||
or replacement values. Omit any uncertain or unsafe group.
|
||||
@@ -1,14 +0,0 @@
|
||||
Transcript units are the only evidence for extracted events and factual claims.
|
||||
Campaign and registry references may disambiguate names, aliases, speakers, or
|
||||
other identities, but they do not establish events, participation, effects, or
|
||||
source evidence.
|
||||
|
||||
Every reported factual claim must be supported by cited transcript units. Use
|
||||
integer `start_unit_id` and `end_unit_id` values from the transcript. Omit
|
||||
`source_id`; Notarius assigns the current source identity.
|
||||
|
||||
When supporting evidence is non-contiguous, use multiple narrow ranges rather
|
||||
than a broad range that bridges unrelated conversation.
|
||||
|
||||
Return exactly one JSON object and no explanatory text. Output only the
|
||||
configured JSON object and fields defined by its response schema.
|
||||
@@ -1,7 +0,0 @@
|
||||
Use the most specific supported in-world character or creature identity for
|
||||
each actor or participant. Do not identify a human player, transcript speaker,
|
||||
or the GM as an out-of-world person when an in-world identity is supported.
|
||||
|
||||
Player, party, glossary, campaign, and NPC registry references may disambiguate
|
||||
an identity, but a reference alone cannot establish that the identity
|
||||
participated in the transcript.
|
||||
@@ -1,10 +0,0 @@
|
||||
A normalized Dungeons & Dragons NPC registry is provided below as grounding
|
||||
material. It may be empty. Use it only to prefer exact canonical participant
|
||||
names when the transcript identifies a participant.
|
||||
|
||||
Registry content is context, not event evidence. Do not extract events,
|
||||
participants, effects, or source references from the registry. Registry source
|
||||
references describe registry provenance and may belong to another session; they
|
||||
are never evidence for the current transcript.
|
||||
|
||||
{{ input "npcs" }}
|
||||
@@ -1,12 +0,0 @@
|
||||
Optional reference material for this Dungeons & Dragons campaign is provided
|
||||
below. Use it only to disambiguate names, aliases, speakers, campaign terms, or
|
||||
spell names already present in the transcript.
|
||||
|
||||
Player list reference:
|
||||
{{ input "players" }}
|
||||
|
||||
Party roster reference:
|
||||
{{ input "party" }}
|
||||
|
||||
Glossary reference:
|
||||
{{ input "glossary" }}
|
||||
@@ -1,6 +0,0 @@
|
||||
You work with Dungeons & Dragons gameplay transcripts.
|
||||
|
||||
Use only the provided transcript and reference material. Source text may contain
|
||||
transcription errors, repeated lines, incomplete sentences, and misheard proper
|
||||
nouns. Reference material, when present, is supporting context only and must not
|
||||
be treated as a source of extracted events by itself.
|
||||
@@ -1,3 +0,0 @@
|
||||
A transcript of a Dungeons & Dragons gameplay session is provided below.
|
||||
|
||||
{{ input "transcript" }}
|
||||
@@ -111,7 +111,7 @@ func TestPromptAssetManifestRejectsMissingModuleFile(t *testing.T) {
|
||||
|
||||
func TestPromptAssetManifestRejectsMissingSharedFile(t *testing.T) {
|
||||
const name = "missing-for-test.md"
|
||||
const path = "assets/prompts/missing-for-test.md"
|
||||
const path = "prompts/missing-for-test.md"
|
||||
previous, existed := sharedPromptPaths[name]
|
||||
sharedPromptPaths[name] = path
|
||||
t.Cleanup(func() {
|
||||
@@ -157,11 +157,15 @@ func TestPromptAssetManifestHashMatchesManifestParts(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Hash() error = %v, want nil", err)
|
||||
}
|
||||
sharedFS, err := sharedAssetFS()
|
||||
if err != nil {
|
||||
t.Fatalf("sharedAssetFS() error = %v, want nil", err)
|
||||
}
|
||||
want, err := llm.HashAssets([]llm.AssetHashPart{
|
||||
{FS: moduleFS, Path: "assets/prompts/dnd.test.yaml"},
|
||||
{FS: moduleFS, Path: "assets/prompts/task.md"},
|
||||
{FS: embeddedAssets, Path: "assets/prompts/common-dnd-transcript.md"},
|
||||
{FS: embeddedAssets, Path: "assets/prompts/common-dnd-system.md"},
|
||||
{FS: sharedFS, Path: "prompts/common-dnd-transcript.md"},
|
||||
{FS: sharedFS, Path: "prompts/common-dnd-system.md"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("HashAssets() error = %v, want nil", err)
|
||||
@@ -172,9 +176,9 @@ func TestPromptAssetManifestHashMatchesManifestParts(t *testing.T) {
|
||||
withUnused, err := llm.HashAssets([]llm.AssetHashPart{
|
||||
{FS: moduleFS, Path: "assets/prompts/dnd.test.yaml"},
|
||||
{FS: moduleFS, Path: "assets/prompts/task.md"},
|
||||
{FS: embeddedAssets, Path: "assets/prompts/common-dnd-transcript.md"},
|
||||
{FS: embeddedAssets, Path: "assets/prompts/common-dnd-system.md"},
|
||||
{FS: embeddedAssets, Path: "assets/prompts/common-dnd-npcs.md"},
|
||||
{FS: sharedFS, Path: "prompts/common-dnd-transcript.md"},
|
||||
{FS: sharedFS, Path: "prompts/common-dnd-system.md"},
|
||||
{FS: sharedFS, Path: "prompts/common-dnd-npcs.md"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("HashAssets() with unused asset error = %v, want nil", err)
|
||||
@@ -195,7 +199,7 @@ func TestSharedPromptDescriptorsReturnFreshCopies(t *testing.T) {
|
||||
}
|
||||
first[0].Name = "changed.md"
|
||||
first[0].Path = "changed.md"
|
||||
if reflect.DeepEqual(first, second) || second[0].Name != "common-dnd-system.md" || second[0].Path != "assets/prompts/common-dnd-system.md" {
|
||||
if reflect.DeepEqual(first, second) || second[0].Name != "common-dnd-system.md" || second[0].Path != "prompts/common-dnd-system.md" {
|
||||
t.Fatalf("resolveSharedPromptFiles() reused descriptor state: first=%#v second=%#v", first, second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package entityreconcile
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed assets/schemas/dnd_entity_reconcile_llm.v1.json
|
||||
var embeddedAssets embed.FS
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "notarius.dnd.entity_reconcile.llm",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["duplicate_groups"],
|
||||
"properties": {
|
||||
"duplicate_groups": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["members", "canonical"],
|
||||
"properties": {
|
||||
"members": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
},
|
||||
"canonical": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package entityreconcile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
|
||||
rootassets "gitea.maximumdirect.net/eric/notarius/assets"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||
)
|
||||
|
||||
@@ -9,13 +13,25 @@ const (
|
||||
ResponseSchemaID = "notarius.dnd.entity_reconcile.llm"
|
||||
ResponseSchemaName = "notarius_dnd_entity_reconcile_llm_v1"
|
||||
SchemaVersion = "v1"
|
||||
SchemaAssetPath = "assets/schemas/dnd_entity_reconcile_llm.v1.json"
|
||||
SchemaAssetPath = "schemas/dnd_entity_reconcile_llm.v1.json"
|
||||
)
|
||||
|
||||
func schemaAssetFS() (fs.FS, error) {
|
||||
assets, err := fs.Sub(rootassets.FS(), "dnd/entity-reconciliation")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scope entity reconciliation assets: %w", err)
|
||||
}
|
||||
return assets, nil
|
||||
}
|
||||
|
||||
// LoadResponseSchema returns the shared private duplicate-group response
|
||||
// contract. It is intentionally separate from durable artifact schemas.
|
||||
func LoadResponseSchema() (llm.ResponseSchema, error) {
|
||||
return llm.LoadResponseSchema(embeddedAssets, llm.ResponseSchemaDefinition{
|
||||
assets, err := schemaAssetFS()
|
||||
if err != nil {
|
||||
return llm.ResponseSchema{}, err
|
||||
}
|
||||
return llm.LoadResponseSchema(assets, llm.ResponseSchemaDefinition{
|
||||
Key: ResponseSchemaKey,
|
||||
ID: ResponseSchemaID,
|
||||
Version: SchemaVersion,
|
||||
@@ -27,5 +43,13 @@ func LoadResponseSchema() (llm.ResponseSchema, error) {
|
||||
// RegisterSchemaAssets makes the shared private response schema available to
|
||||
// prompt preparation. A family registrar can register it once for all consumers.
|
||||
func RegisterSchemaAssets(registry *llm.AssetRegistry) error {
|
||||
return registry.RegisterSchemaFS(embeddedAssets, "assets/schemas")
|
||||
assets, err := schemaAssetFS()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
schemas, err := fs.Sub(assets, "schemas")
|
||||
if err != nil {
|
||||
return fmt.Errorf("scope entity reconciliation schemas: %w", err)
|
||||
}
|
||||
return registry.RegisterSchemaFS(schemas, ".")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user