160 lines
6.2 KiB
Go
160 lines
6.2 KiB
Go
package consumers
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"path"
|
|
|
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
|
)
|
|
|
|
// CollectionBundleConfig is a representative application-owned collection
|
|
// shape. Collection and prompt version labels remain ordinary domain fields.
|
|
type CollectionBundleConfig struct {
|
|
Version string `yaml:"version"`
|
|
Collections map[string]CollectionDefinition `yaml:"collections"`
|
|
}
|
|
|
|
type CollectionDefinition struct {
|
|
Name string `yaml:"name"`
|
|
Version string `yaml:"version"`
|
|
Description string `yaml:"description"`
|
|
Prompts map[string]CollectionPrompt `yaml:"prompts"`
|
|
Profiles map[string]CollectionProfile `yaml:"profiles"`
|
|
}
|
|
|
|
type CollectionPrompt struct {
|
|
Version string `yaml:"version"`
|
|
DefaultProfile string `yaml:"default_profile"`
|
|
ContentFile string `yaml:"content_file"`
|
|
ResourceText string `yaml:"resource_text"`
|
|
Messages []CollectionMessage `yaml:"messages"`
|
|
}
|
|
|
|
type CollectionMessage struct {
|
|
Role string `yaml:"role"`
|
|
Content string `yaml:"content"`
|
|
}
|
|
|
|
type CollectionProfile struct {
|
|
Format string `yaml:"format"`
|
|
Temperature float64 `yaml:"temperature"`
|
|
}
|
|
|
|
// LoadCollectionBundle demonstrates one layered collection load. Its main
|
|
// document owns the application schema version; imported collection metadata,
|
|
// prompt/profile peers, and an operator prompt-version override use ordinary
|
|
// destinations and domain fields.
|
|
func LoadCollectionBundle() (*configkit.Result[CollectionBundleConfig], error) {
|
|
files, err := fs.Sub(bundledFiles, "testdata/collection-bundle")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bundle := configkit.NewFilesystem("collection bundle", files)
|
|
collectionDestination := configkit.Keys("collections", "weatherreporter")
|
|
promptDestination := collectionDestination.Key("prompts")
|
|
profileDestination := collectionDestination.Key("profiles")
|
|
return configkit.LoadLayered(
|
|
configkit.Input{Source: configkit.Bytes("application main", []byte("version: 1\n"))},
|
|
configkit.LayeredOptions[CollectionBundleConfig]{
|
|
Definition: configkit.Definition[CollectionBundleConfig]{
|
|
Version: &configkit.VersionRule{Path: configkit.Keys("version"), Check: func(version configkit.Version, _ *configkit.Validation) error {
|
|
if !version.Present || version.Text != "1" {
|
|
return errors.New("unsupported application schema version")
|
|
}
|
|
return nil
|
|
}},
|
|
Resolve: resolveCollectionResources,
|
|
Validate: validateCollectionBundle,
|
|
},
|
|
Underlays: []configkit.Underlay{
|
|
{Inputs: []configkit.Input{
|
|
{Source: bundle.File("collections/weather/collection.yml"), Imports: true, Destination: collectionDestination},
|
|
{Source: bundle.File("collections/weather/prompts/brief.yml"), Destination: promptDestination},
|
|
{Source: bundle.File("collections/weather/prompts/custom.yml"), Destination: promptDestination},
|
|
{Source: bundle.File("collections/weather/profiles/compact.yml"), Destination: profileDestination},
|
|
{Source: bundle.File("collections/weather/profiles/weather.yml"), Destination: profileDestination},
|
|
}},
|
|
{Inputs: []configkit.Input{{
|
|
Source: bundle.File("operators/weather-prompt-version.yml"), Destination: promptDestination,
|
|
}}},
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
func resolveCollectionResources(configuration *CollectionBundleConfig, resolution *configkit.Resolution) error {
|
|
for collectionID, collection := range configuration.Collections {
|
|
for promptID, prompt := range collection.Prompts {
|
|
if prompt.ContentFile == "" {
|
|
continue
|
|
}
|
|
contentPath := configkit.Keys("collections", collectionID, "prompts", promptID, "content_file")
|
|
info, found := resolution.InputInfo(contentPath)
|
|
if !found || len(info.Origin.Locations) != 1 {
|
|
return fmt.Errorf("prompt resource has no single declaring source")
|
|
}
|
|
resourceSource, found := resolution.Source(info.Origin.Locations[0].SourceID)
|
|
if !found {
|
|
return fmt.Errorf("prompt resource source is unavailable")
|
|
}
|
|
context, found := resolution.SourceContext(resourceSource.ID)
|
|
if !found {
|
|
return fmt.Errorf("prompt resource context is unavailable")
|
|
}
|
|
data, err := fs.ReadFile(context.FS, path.Join(context.BaseDir, prompt.ContentFile))
|
|
if err != nil {
|
|
return fmt.Errorf("read prompt resource: %w", err)
|
|
}
|
|
prompt.ResourceText = string(data)
|
|
collection.Prompts[promptID] = prompt
|
|
resolution.Record(
|
|
configkit.Keys("collections", collectionID, "prompts", promptID, "resource_text"),
|
|
configkit.ChangeNormalization, "read source-relative prompt content", contentPath,
|
|
)
|
|
}
|
|
configuration.Collections[collectionID] = collection
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateCollectionBundle(configuration *CollectionBundleConfig, validation *configkit.Validation) {
|
|
for collectionID, collection := range configuration.Collections {
|
|
for promptID, prompt := range collection.Prompts {
|
|
if prompt.DefaultProfile == "" {
|
|
continue
|
|
}
|
|
if _, found := collection.Profiles[prompt.DefaultProfile]; !found {
|
|
validation.Error(configkit.Keys("collections", collectionID, "prompts", promptID, "default_profile"), "must name a profile in this collection")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ReadCollectionPromptContent follows the effective content field's physical
|
|
// source, including when it was inherited from a peer prompt.
|
|
func ReadCollectionPromptContent(result *configkit.Result[CollectionBundleConfig], collectionID, promptID string) (string, error) {
|
|
if result == nil {
|
|
return "", errors.New("collection result is nil")
|
|
}
|
|
contentPath := configkit.Keys("collections", collectionID, "prompts", promptID, "content_file")
|
|
info, found := result.Metadata().Lookup(contentPath)
|
|
if !found || len(info.Origin.Locations) != 1 {
|
|
return "", errors.New("prompt resource has no single declaring source")
|
|
}
|
|
context, found := result.SourceContext(info.Origin.Locations[0].SourceID)
|
|
if !found {
|
|
return "", errors.New("prompt resource context is unavailable")
|
|
}
|
|
prompt, found := result.Config.Collections[collectionID].Prompts[promptID]
|
|
if !found {
|
|
return "", errors.New("prompt is unavailable")
|
|
}
|
|
data, err := fs.ReadFile(context.FS, path.Join(context.BaseDir, prompt.ContentFile))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|