Use shared DnD prompt helpers in modules
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Key = "dnd/scenes"
|
const Key = "dnd/scenes"
|
||||||
@@ -21,35 +22,11 @@ var providedCapabilities = []string{
|
|||||||
"chunks.scenes",
|
"chunks.scenes",
|
||||||
}
|
}
|
||||||
|
|
||||||
var acceptedReferenceMediaTypes = []string{
|
var referenceSlotDescriptions = dnd.ReferenceSlotDescriptions{
|
||||||
"application/json",
|
Glossary: "Optional campaign glossary reference material used only for scene disambiguation.",
|
||||||
"application/x-yaml",
|
Party: "Optional party roster reference material used only for scene disambiguation.",
|
||||||
"application/yaml",
|
Players: "Optional player list reference material used only for scene disambiguation.",
|
||||||
"text/markdown",
|
Roster: "Deprecated alias for party roster reference material used only for scene disambiguation.",
|
||||||
"text/plain",
|
|
||||||
}
|
|
||||||
|
|
||||||
var referenceSlots = []contracts.ReferenceSlot{
|
|
||||||
{
|
|
||||||
Name: "glossary",
|
|
||||||
Description: "Optional campaign glossary reference material used only for scene disambiguation.",
|
|
||||||
AcceptedMediaTypes: append([]string(nil), acceptedReferenceMediaTypes...),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "party",
|
|
||||||
Description: "Optional party roster reference material used only for scene disambiguation.",
|
|
||||||
AcceptedMediaTypes: append([]string(nil), acceptedReferenceMediaTypes...),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "players",
|
|
||||||
Description: "Optional player list reference material used only for scene disambiguation.",
|
|
||||||
AcceptedMediaTypes: append([]string(nil), acceptedReferenceMediaTypes...),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "roster",
|
|
||||||
Description: "Deprecated alias for party roster reference material used only for scene disambiguation.",
|
|
||||||
AcceptedMediaTypes: append([]string(nil), acceptedReferenceMediaTypes...),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ contracts.Chunker = (*Chunker)(nil)
|
var _ contracts.Chunker = (*Chunker)(nil)
|
||||||
@@ -66,7 +43,7 @@ func (c *Chunker) Key() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chunker) ReferenceSlots() []contracts.ReferenceSlot {
|
func (c *Chunker) ReferenceSlots() []contracts.ReferenceSlot {
|
||||||
return cloneReferenceSlots(referenceSlots)
|
return dnd.ReferenceSlots(referenceSlotDescriptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chunker) ManifestMetadata() map[string]any {
|
func (c *Chunker) ManifestMetadata() map[string]any {
|
||||||
@@ -122,7 +99,7 @@ func (c *Chunker) Chunk(ctx context.Context, req contracts.ChunkRequest) (contra
|
|||||||
PromptVersion: ResponseSchemaVersion,
|
PromptVersion: ResponseSchemaVersion,
|
||||||
ProfileID: req.LLMProfile,
|
ProfileID: req.LLMProfile,
|
||||||
SessionID: req.SessionID,
|
SessionID: req.SessionID,
|
||||||
Inputs: promptInputs(req),
|
Inputs: dnd.PromptInputs(req.SourceInput, req.References),
|
||||||
}, &response); err != nil {
|
}, &response); err != nil {
|
||||||
return contracts.ChunkResult{}, chunkerErrorf("complete structured output: %w", err)
|
return contracts.ChunkResult{}, chunkerErrorf("complete structured output: %w", err)
|
||||||
}
|
}
|
||||||
@@ -147,7 +124,7 @@ func ModuleSpec() pipeline.ModuleSpec {
|
|||||||
Stage: pipeline.StageChunk,
|
Stage: pipeline.StageChunk,
|
||||||
Requires: append([]string(nil), requiredCapabilities...),
|
Requires: append([]string(nil), requiredCapabilities...),
|
||||||
Provides: append([]string(nil), providedCapabilities...),
|
Provides: append([]string(nil), providedCapabilities...),
|
||||||
ReferenceSlots: cloneReferenceSlots(referenceSlots),
|
ReferenceSlots: dnd.ReferenceSlots(referenceSlotDescriptions),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,15 +315,3 @@ func cloneMetadata(metadata map[string]any) map[string]any {
|
|||||||
func chunkerErrorf(format string, args ...any) error {
|
func chunkerErrorf(format string, args ...any) error {
|
||||||
return fmt.Errorf("dnd scenes chunker: "+format, args...)
|
return fmt.Errorf("dnd scenes chunker: "+format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneReferenceSlots(slots []contracts.ReferenceSlot) []contracts.ReferenceSlot {
|
|
||||||
if len(slots) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := make([]contracts.ReferenceSlot, len(slots))
|
|
||||||
for i, slot := range slots {
|
|
||||||
slot.AcceptedMediaTypes = append([]string(nil), slot.AcceptedMediaTypes...)
|
|
||||||
out[i] = slot
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewModuleSpecAndRegister(t *testing.T) {
|
func TestNewModuleSpecAndRegister(t *testing.T) {
|
||||||
@@ -257,15 +258,12 @@ func TestChunkPassesReferencesAsPromptInputs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPromptInputsMapLegacyRosterReferenceToParty(t *testing.T) {
|
func TestPromptInputsMapLegacyRosterReferenceToParty(t *testing.T) {
|
||||||
inputs := promptInputs(contracts.ChunkRequest{
|
inputs := dnd.PromptInputs(sceneSourceInput(), contracts.ReferenceSet{
|
||||||
SourceInput: sceneSourceInput(),
|
Slots: map[string]contracts.ResolvedReferenceSlot{
|
||||||
References: contracts.ReferenceSet{
|
"roster": {
|
||||||
Slots: map[string]contracts.ResolvedReferenceSlot{
|
Slot: contracts.ReferenceSlot{Name: "roster"},
|
||||||
"roster": {
|
Items: []contracts.ReferenceItem{
|
||||||
Slot: contracts.ReferenceSlot{Name: "roster"},
|
{SlotName: "roster", Content: []byte("Legacy roster text")},
|
||||||
Items: []contracts.ReferenceItem{
|
|
||||||
{SlotName: "roster", Content: []byte("Legacy roster text")},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package scenes
|
package scenes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd"
|
||||||
@@ -29,36 +26,6 @@ func RegisterPromptAssets(registry *llm.AssetRegistry) error {
|
|||||||
return registry.RegisterSchemaFS(embeddedAssets, "assets/schemas")
|
return registry.RegisterSchemaFS(embeddedAssets, "assets/schemas")
|
||||||
}
|
}
|
||||||
|
|
||||||
func promptInputs(req contracts.ChunkRequest) contracts.LLMInputSet {
|
|
||||||
partySlot := req.References.Slots["party"]
|
|
||||||
if len(partySlot.Items) == 0 {
|
|
||||||
partySlot = req.References.Slots["roster"]
|
|
||||||
}
|
|
||||||
return contracts.LLMInputSet{
|
|
||||||
"transcript": transcriptPromptInput(req.SourceInput),
|
|
||||||
"players": referencePromptMaterial("players", req.References.Slots["players"]),
|
|
||||||
"party": referencePromptMaterial("party", partySlot),
|
|
||||||
"glossary": referencePromptMaterial("glossary", req.References.Slots["glossary"]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func transcriptPromptInput(material contracts.LLMInputMaterial) contracts.LLMInputMaterial {
|
|
||||||
out := material.Clone()
|
|
||||||
out.Name = "transcript"
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func referencePromptMaterial(name string, slot contracts.ResolvedReferenceSlot) contracts.LLMInputMaterial {
|
|
||||||
body := referencePromptInput(slot)
|
|
||||||
digest := ""
|
|
||||||
originURI := ""
|
|
||||||
if len(slot.Items) == 1 {
|
|
||||||
digest = slot.Items[0].Digest
|
|
||||||
originURI = slot.Items[0].Origin.URI
|
|
||||||
}
|
|
||||||
return contracts.NewLLMInputMaterial(name, "text/plain", body, digest, originURI)
|
|
||||||
}
|
|
||||||
|
|
||||||
func scriptoriumPromptMetadata() (string, error) {
|
func scriptoriumPromptMetadata() (string, error) {
|
||||||
scriptoriumPromptHashOnce.Do(func() {
|
scriptoriumPromptHashOnce.Do(func() {
|
||||||
parts := append([]llm.AssetHashPart{
|
parts := append([]llm.AssetHashPart{
|
||||||
@@ -71,51 +38,6 @@ func scriptoriumPromptMetadata() (string, error) {
|
|||||||
return scriptoriumPromptHash, scriptoriumPromptHashErr
|
return scriptoriumPromptHash, scriptoriumPromptHashErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func referencePromptInput(slot contracts.ResolvedReferenceSlot) []byte {
|
|
||||||
if len(slot.Items) == 0 {
|
|
||||||
return []byte(" ")
|
|
||||||
}
|
|
||||||
items := append([]contracts.ReferenceItem(nil), slot.Items...)
|
|
||||||
sort.SliceStable(items, func(i, j int) bool {
|
|
||||||
if items[i].Origin.URI != items[j].Origin.URI {
|
|
||||||
return items[i].Origin.URI < items[j].Origin.URI
|
|
||||||
}
|
|
||||||
if items[i].Digest != items[j].Digest {
|
|
||||||
return items[i].Digest < items[j].Digest
|
|
||||||
}
|
|
||||||
return string(items[i].Content) < string(items[j].Content)
|
|
||||||
})
|
|
||||||
if len(items) == 1 {
|
|
||||||
return append([]byte(nil), items[0].Content...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
for i, item := range items {
|
|
||||||
if i > 0 {
|
|
||||||
b.WriteString("\n\n")
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&b, "Reference %d\n", i+1)
|
|
||||||
if item.Origin.Type != "" {
|
|
||||||
fmt.Fprintf(&b, "Origin-Type: %s\n", item.Origin.Type)
|
|
||||||
}
|
|
||||||
if item.Origin.URI != "" {
|
|
||||||
fmt.Fprintf(&b, "Origin-URI: %s\n", item.Origin.URI)
|
|
||||||
}
|
|
||||||
if item.Digest != "" {
|
|
||||||
fmt.Fprintf(&b, "Digest: %s\n", item.Digest)
|
|
||||||
}
|
|
||||||
if item.MediaType != "" {
|
|
||||||
fmt.Fprintf(&b, "Media-Type: %s\n", item.MediaType)
|
|
||||||
}
|
|
||||||
if item.SizeBytes > 0 {
|
|
||||||
fmt.Fprintf(&b, "Size-Bytes: %d\n", item.SizeBytes)
|
|
||||||
}
|
|
||||||
b.WriteString("\n")
|
|
||||||
b.Write(item.Content)
|
|
||||||
}
|
|
||||||
return b.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
scriptoriumPromptHashOnce sync.Once
|
scriptoriumPromptHashOnce sync.Once
|
||||||
scriptoriumPromptHash string
|
scriptoriumPromptHash string
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Key = "dnd/spells"
|
const Key = "dnd/spells"
|
||||||
@@ -25,35 +26,11 @@ var providedCapabilities = []string{
|
|||||||
"dnd.spell_casts",
|
"dnd.spell_casts",
|
||||||
}
|
}
|
||||||
|
|
||||||
var acceptedReferenceMediaTypes = []string{
|
var referenceSlotDescriptions = dnd.ReferenceSlotDescriptions{
|
||||||
"application/json",
|
Glossary: "Optional campaign glossary reference material used only for disambiguation.",
|
||||||
"application/x-yaml",
|
Party: "Optional party roster reference material used only for disambiguation.",
|
||||||
"application/yaml",
|
Players: "Optional player list reference material used only for disambiguation.",
|
||||||
"text/markdown",
|
Roster: "Deprecated alias for party roster reference material used only for disambiguation.",
|
||||||
"text/plain",
|
|
||||||
}
|
|
||||||
|
|
||||||
var referenceSlots = []contracts.ReferenceSlot{
|
|
||||||
{
|
|
||||||
Name: "glossary",
|
|
||||||
Description: "Optional campaign glossary reference material used only for disambiguation.",
|
|
||||||
AcceptedMediaTypes: append([]string(nil), acceptedReferenceMediaTypes...),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "party",
|
|
||||||
Description: "Optional party roster reference material used only for disambiguation.",
|
|
||||||
AcceptedMediaTypes: append([]string(nil), acceptedReferenceMediaTypes...),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "players",
|
|
||||||
Description: "Optional player list reference material used only for disambiguation.",
|
|
||||||
AcceptedMediaTypes: append([]string(nil), acceptedReferenceMediaTypes...),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "roster",
|
|
||||||
Description: "Deprecated alias for party roster reference material used only for disambiguation.",
|
|
||||||
AcceptedMediaTypes: append([]string(nil), acceptedReferenceMediaTypes...),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ contracts.Extractor = (*Extractor)(nil)
|
var _ contracts.Extractor = (*Extractor)(nil)
|
||||||
@@ -77,7 +54,7 @@ func (e *Extractor) SchemaVersion() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Extractor) ReferenceSlots() []contracts.ReferenceSlot {
|
func (e *Extractor) ReferenceSlots() []contracts.ReferenceSlot {
|
||||||
return cloneReferenceSlots(referenceSlots)
|
return dnd.ReferenceSlots(referenceSlotDescriptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Extractor) ManifestMetadata() map[string]any {
|
func (e *Extractor) ManifestMetadata() map[string]any {
|
||||||
@@ -137,7 +114,7 @@ func (e *Extractor) Extract(ctx context.Context, req contracts.ExtractionRequest
|
|||||||
PromptVersion: SchemaVersion,
|
PromptVersion: SchemaVersion,
|
||||||
ProfileID: req.LLMProfile,
|
ProfileID: req.LLMProfile,
|
||||||
SessionID: req.SessionID,
|
SessionID: req.SessionID,
|
||||||
Inputs: promptInputs(req),
|
Inputs: dnd.PromptInputs(req.SourceInput, req.References),
|
||||||
}, &response); err != nil {
|
}, &response); err != nil {
|
||||||
return contracts.ExtractionResult{}, extractorErrorf("complete structured output: %w", err)
|
return contracts.ExtractionResult{}, extractorErrorf("complete structured output: %w", err)
|
||||||
}
|
}
|
||||||
@@ -168,7 +145,7 @@ func ModuleSpec() pipeline.ModuleSpec {
|
|||||||
Stage: pipeline.StageExtract,
|
Stage: pipeline.StageExtract,
|
||||||
Requires: append([]string(nil), requiredCapabilities...),
|
Requires: append([]string(nil), requiredCapabilities...),
|
||||||
Provides: append([]string(nil), providedCapabilities...),
|
Provides: append([]string(nil), providedCapabilities...),
|
||||||
ReferenceSlots: cloneReferenceSlots(referenceSlots),
|
ReferenceSlots: dnd.ReferenceSlots(referenceSlotDescriptions),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,15 +167,3 @@ func spellCastPayload(spellCast spellCastResponse) (json.RawMessage, error) {
|
|||||||
func extractorErrorf(format string, args ...any) error {
|
func extractorErrorf(format string, args ...any) error {
|
||||||
return fmt.Errorf("dnd spells extractor: "+format, args...)
|
return fmt.Errorf("dnd spells extractor: "+format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneReferenceSlots(slots []contracts.ReferenceSlot) []contracts.ReferenceSlot {
|
|
||||||
if len(slots) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := make([]contracts.ReferenceSlot, len(slots))
|
|
||||||
for i, slot := range slots {
|
|
||||||
slot.AcceptedMediaTypes = append([]string(nil), slot.AcceptedMediaTypes...)
|
|
||||||
out[i] = slot
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExtractReturnsSpellCandidateFromStructuredOutput(t *testing.T) {
|
func TestExtractReturnsSpellCandidateFromStructuredOutput(t *testing.T) {
|
||||||
@@ -156,15 +157,12 @@ func TestExtractPassesReferencesAsPromptInputs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPromptInputsMapLegacyRosterReferenceToParty(t *testing.T) {
|
func TestPromptInputsMapLegacyRosterReferenceToParty(t *testing.T) {
|
||||||
inputs := promptInputs(contracts.ExtractionRequest{
|
inputs := dnd.PromptInputs(spellSourceInput(), contracts.ReferenceSet{
|
||||||
SourceInput: spellSourceInput(),
|
Slots: map[string]contracts.ResolvedReferenceSlot{
|
||||||
References: contracts.ReferenceSet{
|
"roster": {
|
||||||
Slots: map[string]contracts.ResolvedReferenceSlot{
|
Slot: contracts.ReferenceSlot{Name: "roster"},
|
||||||
"roster": {
|
Items: []contracts.ReferenceItem{
|
||||||
Slot: contracts.ReferenceSlot{Name: "roster"},
|
{SlotName: "roster", Content: []byte("Legacy roster text")},
|
||||||
Items: []contracts.ReferenceItem{
|
|
||||||
{SlotName: "roster", Content: []byte("Legacy roster text")},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package spells
|
package spells
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd"
|
||||||
@@ -29,36 +26,6 @@ func RegisterPromptAssets(registry *llm.AssetRegistry) error {
|
|||||||
return registry.RegisterSchemaFS(embeddedAssets, "assets/schemas")
|
return registry.RegisterSchemaFS(embeddedAssets, "assets/schemas")
|
||||||
}
|
}
|
||||||
|
|
||||||
func promptInputs(req contracts.ExtractionRequest) contracts.LLMInputSet {
|
|
||||||
partySlot := req.References.Slots["party"]
|
|
||||||
if len(partySlot.Items) == 0 {
|
|
||||||
partySlot = req.References.Slots["roster"]
|
|
||||||
}
|
|
||||||
return contracts.LLMInputSet{
|
|
||||||
"transcript": transcriptPromptInput(req.SourceInput),
|
|
||||||
"players": referencePromptMaterial("players", req.References.Slots["players"]),
|
|
||||||
"party": referencePromptMaterial("party", partySlot),
|
|
||||||
"glossary": referencePromptMaterial("glossary", req.References.Slots["glossary"]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func transcriptPromptInput(material contracts.LLMInputMaterial) contracts.LLMInputMaterial {
|
|
||||||
out := material.Clone()
|
|
||||||
out.Name = "transcript"
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func referencePromptMaterial(name string, slot contracts.ResolvedReferenceSlot) contracts.LLMInputMaterial {
|
|
||||||
body := referencePromptInput(slot)
|
|
||||||
digest := ""
|
|
||||||
originURI := ""
|
|
||||||
if len(slot.Items) == 1 {
|
|
||||||
digest = slot.Items[0].Digest
|
|
||||||
originURI = slot.Items[0].Origin.URI
|
|
||||||
}
|
|
||||||
return contracts.NewLLMInputMaterial(name, "text/plain", body, digest, originURI)
|
|
||||||
}
|
|
||||||
|
|
||||||
func scriptoriumPromptMetadata() (string, error) {
|
func scriptoriumPromptMetadata() (string, error) {
|
||||||
scriptoriumPromptHashOnce.Do(func() {
|
scriptoriumPromptHashOnce.Do(func() {
|
||||||
parts := append([]llm.AssetHashPart{
|
parts := append([]llm.AssetHashPart{
|
||||||
@@ -71,51 +38,6 @@ func scriptoriumPromptMetadata() (string, error) {
|
|||||||
return scriptoriumPromptHash, scriptoriumPromptHashErr
|
return scriptoriumPromptHash, scriptoriumPromptHashErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func referencePromptInput(slot contracts.ResolvedReferenceSlot) []byte {
|
|
||||||
if len(slot.Items) == 0 {
|
|
||||||
return []byte(" ")
|
|
||||||
}
|
|
||||||
items := append([]contracts.ReferenceItem(nil), slot.Items...)
|
|
||||||
sort.SliceStable(items, func(i, j int) bool {
|
|
||||||
if items[i].Origin.URI != items[j].Origin.URI {
|
|
||||||
return items[i].Origin.URI < items[j].Origin.URI
|
|
||||||
}
|
|
||||||
if items[i].Digest != items[j].Digest {
|
|
||||||
return items[i].Digest < items[j].Digest
|
|
||||||
}
|
|
||||||
return string(items[i].Content) < string(items[j].Content)
|
|
||||||
})
|
|
||||||
if len(items) == 1 {
|
|
||||||
return append([]byte(nil), items[0].Content...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
for i, item := range items {
|
|
||||||
if i > 0 {
|
|
||||||
b.WriteString("\n\n")
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&b, "Reference %d\n", i+1)
|
|
||||||
if item.Origin.Type != "" {
|
|
||||||
fmt.Fprintf(&b, "Origin-Type: %s\n", item.Origin.Type)
|
|
||||||
}
|
|
||||||
if item.Origin.URI != "" {
|
|
||||||
fmt.Fprintf(&b, "Origin-URI: %s\n", item.Origin.URI)
|
|
||||||
}
|
|
||||||
if item.Digest != "" {
|
|
||||||
fmt.Fprintf(&b, "Digest: %s\n", item.Digest)
|
|
||||||
}
|
|
||||||
if item.MediaType != "" {
|
|
||||||
fmt.Fprintf(&b, "Media-Type: %s\n", item.MediaType)
|
|
||||||
}
|
|
||||||
if item.SizeBytes > 0 {
|
|
||||||
fmt.Fprintf(&b, "Size-Bytes: %d\n", item.SizeBytes)
|
|
||||||
}
|
|
||||||
b.WriteString("\n")
|
|
||||||
b.Write(item.Content)
|
|
||||||
}
|
|
||||||
return b.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
scriptoriumPromptHashOnce sync.Once
|
scriptoriumPromptHashOnce sync.Once
|
||||||
scriptoriumPromptHash string
|
scriptoriumPromptHash string
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium"
|
"gitea.maximumdirect.net/eric/scriptorium"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,8 +73,8 @@ func TestReferencePromptInputRenderingIsDeterministic(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
first := string(referencePromptInput(slot))
|
first := string(dnd.ReferencePromptInput(slot))
|
||||||
second := string(referencePromptInput(slot))
|
second := string(dnd.ReferencePromptInput(slot))
|
||||||
if first != second {
|
if first != second {
|
||||||
t.Fatalf("reference rendering was not deterministic:\nfirst=%q\nsecond=%q", first, second)
|
t.Fatalf("reference rendering was not deterministic:\nfirst=%q\nsecond=%q", first, second)
|
||||||
}
|
}
|
||||||
@@ -86,7 +87,7 @@ func TestReferencePromptInputRenderingIsDeterministic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSingleReferencePromptInputKeepsContentOnly(t *testing.T) {
|
func TestSingleReferencePromptInputKeepsContentOnly(t *testing.T) {
|
||||||
got := string(referencePromptInput(contracts.ResolvedReferenceSlot{
|
got := string(dnd.ReferencePromptInput(contracts.ResolvedReferenceSlot{
|
||||||
Items: []contracts.ReferenceItem{{Content: []byte("single reference")}},
|
Items: []contracts.ReferenceItem{{Content: []byte("single reference")}},
|
||||||
}))
|
}))
|
||||||
if got != "single reference" {
|
if got != "single reference" {
|
||||||
|
|||||||
Reference in New Issue
Block a user