Files
CHORUS/pkg/prompt/loader.go

115 lines
3.0 KiB
Go

package prompt
import (
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
var (
loadedRoles map[string]RoleDefinition
defaultInstr string
)
// Initialize loads roles and default instructions from the configured directory.
// dir: base directory (e.g., /etc/chorus/prompts)
// defaultPath: optional explicit path to defaults file; if empty, will look for defaults.md or defaults.txt in dir.
func Initialize(dir string, defaultPath string) error {
loadedRoles = make(map[string]RoleDefinition)
// Load roles from all YAML files under dir
if dir != "" {
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil || d == nil || d.IsDir() {
return nil
}
name := d.Name()
if strings.HasSuffix(name, ".yaml") || strings.HasSuffix(name, ".yml") {
_ = loadRolesFile(path)
}
return nil
})
}
// Load default instructions
if defaultPath == "" && dir != "" {
// Try defaults.md then defaults.txt in the directory
tryPaths := []string{
filepath.Join(dir, "defaults.md"),
filepath.Join(dir, "defaults.txt"),
}
for _, p := range tryPaths {
if b, err := os.ReadFile(p); err == nil {
defaultInstr = string(b)
break
}
}
} else if defaultPath != "" {
if b, err := os.ReadFile(defaultPath); err == nil {
defaultInstr = string(b)
}
}
return nil
}
func loadRolesFile(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return err
}
var rf RolesFile
if err := yaml.Unmarshal(data, &rf); err != nil {
return err
}
for id, def := range rf.Roles {
def.ID = id
loadedRoles[id] = def
}
return nil
}
// GetRole returns the role definition by ID if loaded.
func GetRole(id string) (RoleDefinition, bool) {
r, ok := loadedRoles[id]
return r, ok
}
// ListRoles returns IDs of loaded roles.
func ListRoles() []string {
ids := make([]string, 0, len(loadedRoles))
for id := range loadedRoles {
ids = append(ids, id)
}
return ids
}
// GetDefaultInstructions returns the loaded default instructions (may be empty if not present on disk).
func GetDefaultInstructions() string {
return defaultInstr
}
// ComposeSystemPrompt concatenates the role system prompt (S) with default instructions (D).
func ComposeSystemPrompt(roleID string) (string, error) {
r, ok := GetRole(roleID)
if !ok {
return "", errors.New("role not found: " + roleID)
}
s := strings.TrimSpace(r.SystemPrompt)
d := strings.TrimSpace(defaultInstr)
switch {
case s != "" && d != "":
return s + "\n\n" + d, nil
case s != "":
return s, nil
case d != "":
return d, nil
default:
return "", nil
}
}