175 lines
6.0 KiB
Go
175 lines
6.0 KiB
Go
package prompts
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"embed"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
"text/template"
|
|
)
|
|
|
|
//go:embed assets/**
|
|
var embeddedAssets embed.FS
|
|
|
|
const (
|
|
SourceBuiltin = "builtin"
|
|
VersionV1 = "v1"
|
|
)
|
|
|
|
const (
|
|
PromptIDModuleGlossaryProposal = "modules.glossary.proposal"
|
|
PromptIDModuleHomophonesProposal = "modules.homophones.proposal"
|
|
PromptIDModuleSpokenWordProposal = "modules.spoken_word.proposal"
|
|
PromptIDModuleGrammarProposal = "modules.grammar.proposal"
|
|
|
|
PromptIDValidatorSpokenFormPlausibility = "validators.spoken_form_plausibility"
|
|
PromptIDValidatorMeaningReversalReview = "validators.meaning_reversal_review"
|
|
PromptIDValidatorEditorialReview = "validators.editorial_review"
|
|
PromptIDValidatorGrammarReview = "validators.grammar_review"
|
|
PromptIDValidatorSpokenWordReview = "validators.spoken_word_review"
|
|
)
|
|
|
|
type Metadata struct {
|
|
PromptID string `json:"prompt_id"`
|
|
PromptVersion string `json:"prompt_version"`
|
|
PromptSource string `json:"prompt_source"`
|
|
EmbeddedPath string `json:"embedded_path"`
|
|
SHA256 string `json:"sha256"`
|
|
}
|
|
|
|
func (m Metadata) DiagnosticsMap() map[string]any {
|
|
return map[string]any{
|
|
"prompt_id": m.PromptID,
|
|
"prompt_version": m.PromptVersion,
|
|
"prompt_source": m.PromptSource,
|
|
"embedded_path": m.EmbeddedPath,
|
|
"sha256": m.SHA256,
|
|
}
|
|
}
|
|
|
|
type definition struct {
|
|
id string
|
|
version string
|
|
embeddedDir string
|
|
systemPath string
|
|
userPath string
|
|
}
|
|
|
|
type compiledPrompt struct {
|
|
def definition
|
|
systemTmpl *template.Template
|
|
userTmpl *template.Template
|
|
metadata Metadata
|
|
}
|
|
|
|
var promptRegistry map[string]compiledPrompt
|
|
var sharedHardening string
|
|
|
|
func init() {
|
|
var err error
|
|
sharedHardening, err = readAsset("assets/shared/prompt_hardening.md")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
defs := []definition{
|
|
{id: PromptIDModuleGlossaryProposal, version: VersionV1, embeddedDir: "assets/modules/glossary", systemPath: "assets/modules/glossary/system.md", userPath: "assets/modules/glossary/user.md"},
|
|
{id: PromptIDModuleHomophonesProposal, version: VersionV1, embeddedDir: "assets/modules/homophones", systemPath: "assets/modules/homophones/system.md", userPath: "assets/modules/homophones/user.md"},
|
|
{id: PromptIDModuleSpokenWordProposal, version: VersionV1, embeddedDir: "assets/modules/spoken_word", systemPath: "assets/modules/spoken_word/system.md", userPath: "assets/modules/spoken_word/user.md"},
|
|
{id: PromptIDModuleGrammarProposal, version: VersionV1, embeddedDir: "assets/modules/grammar", systemPath: "assets/modules/grammar/system.md", userPath: "assets/modules/grammar/user.md"},
|
|
{id: PromptIDValidatorSpokenFormPlausibility, version: VersionV1, embeddedDir: "assets/validators/spoken_form_plausibility", systemPath: "assets/validators/spoken_form_plausibility/system.md", userPath: "assets/validators/spoken_form_plausibility/user.md"},
|
|
{id: PromptIDValidatorMeaningReversalReview, version: VersionV1, embeddedDir: "assets/validators/meaning_reversal_review", systemPath: "assets/validators/meaning_reversal_review/system.md", userPath: "assets/validators/meaning_reversal_review/user.md"},
|
|
{id: PromptIDValidatorEditorialReview, version: VersionV1, embeddedDir: "assets/validators/editorial_review", systemPath: "assets/validators/editorial_review/system.md", userPath: "assets/validators/editorial_review/user.md"},
|
|
{id: PromptIDValidatorGrammarReview, version: VersionV1, embeddedDir: "assets/validators/grammar_review", systemPath: "assets/validators/grammar_review/system.md", userPath: "assets/validators/grammar_review/user.md"},
|
|
{id: PromptIDValidatorSpokenWordReview, version: VersionV1, embeddedDir: "assets/validators/spoken_word_review", systemPath: "assets/validators/spoken_word_review/system.md", userPath: "assets/validators/spoken_word_review/user.md"},
|
|
}
|
|
|
|
promptRegistry = make(map[string]compiledPrompt, len(defs))
|
|
for _, def := range defs {
|
|
cp, cErr := compilePrompt(def)
|
|
if cErr != nil {
|
|
panic(cErr)
|
|
}
|
|
promptRegistry[def.id] = cp
|
|
}
|
|
}
|
|
|
|
func readAsset(assetPath string) (string, error) {
|
|
b, err := embeddedAssets.ReadFile(assetPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("read embedded prompt asset %q: %w", assetPath, err)
|
|
}
|
|
return string(b), nil
|
|
}
|
|
|
|
func compilePrompt(def definition) (compiledPrompt, error) {
|
|
systemSource, err := readAsset(def.systemPath)
|
|
if err != nil {
|
|
return compiledPrompt{}, err
|
|
}
|
|
userSource, err := readAsset(def.userPath)
|
|
if err != nil {
|
|
return compiledPrompt{}, err
|
|
}
|
|
|
|
funcs := template.FuncMap{
|
|
"hardening": func() string { return sharedHardening },
|
|
}
|
|
systemTmpl, err := template.New(path.Base(def.systemPath)).Option("missingkey=error").Funcs(funcs).Parse(systemSource)
|
|
if err != nil {
|
|
return compiledPrompt{}, fmt.Errorf("parse embedded system prompt %q: %w", def.systemPath, err)
|
|
}
|
|
userTmpl, err := template.New(path.Base(def.userPath)).Option("missingkey=error").Funcs(funcs).Parse(userSource)
|
|
if err != nil {
|
|
return compiledPrompt{}, fmt.Errorf("parse embedded user prompt %q: %w", def.userPath, err)
|
|
}
|
|
|
|
hashInput := systemSource + "\n\n" + userSource
|
|
sum := sha256.Sum256([]byte(hashInput))
|
|
meta := Metadata{
|
|
PromptID: def.id,
|
|
PromptVersion: def.version,
|
|
PromptSource: SourceBuiltin,
|
|
EmbeddedPath: def.embeddedDir,
|
|
SHA256: hex.EncodeToString(sum[:]),
|
|
}
|
|
|
|
return compiledPrompt{def: def, systemTmpl: systemTmpl, userTmpl: userTmpl, metadata: meta}, nil
|
|
}
|
|
|
|
func LookupMetadata(promptID string) (Metadata, bool) {
|
|
cp, ok := promptRegistry[strings.TrimSpace(promptID)]
|
|
if !ok {
|
|
return Metadata{}, false
|
|
}
|
|
return cp.metadata, true
|
|
}
|
|
|
|
func MustLookupMetadata(promptID string) Metadata {
|
|
m, ok := LookupMetadata(promptID)
|
|
if !ok {
|
|
panic(fmt.Sprintf("unknown prompt id %q", promptID))
|
|
}
|
|
return m
|
|
}
|
|
|
|
func RegisteredMetadata() []Metadata {
|
|
ids := make([]string, 0, len(promptRegistry))
|
|
for id := range promptRegistry {
|
|
ids = append(ids, id)
|
|
}
|
|
sort.Strings(ids)
|
|
out := make([]Metadata, 0, len(ids))
|
|
for _, id := range ids {
|
|
out = append(out, promptRegistry[id].metadata)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func HardeningText() string {
|
|
return sharedHardening
|
|
}
|