feat(prompts): load system prompts and defaults from Docker volume; set runtime system prompt; add BACKBEAT standards
This commit is contained in:
114
pkg/prompt/loader.go
Normal file
114
pkg/prompt/loader.go
Normal file
@@ -0,0 +1,114 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
22
pkg/prompt/types.go
Normal file
22
pkg/prompt/types.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package prompt
|
||||
|
||||
// RoleDefinition represents a single agent role loaded from YAML.
|
||||
type RoleDefinition struct {
|
||||
ID string `yaml:"id"`
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description"`
|
||||
Tags []string `yaml:"tags"`
|
||||
SystemPrompt string `yaml:"system_prompt"`
|
||||
Defaults struct {
|
||||
Models []string `yaml:"models"`
|
||||
Capabilities []string `yaml:"capabilities"`
|
||||
Expertise []string `yaml:"expertise"`
|
||||
MaxTasks int `yaml:"max_tasks"`
|
||||
} `yaml:"defaults"`
|
||||
}
|
||||
|
||||
// RolesFile is the top-level structure for a roles YAML file.
|
||||
type RolesFile struct {
|
||||
Roles map[string]RoleDefinition `yaml:"roles"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user