Files
promptkit/profile_inheritance_contract_test.go

175 lines
7.3 KiB
Go

package promptkit_test
import (
"context"
"errors"
"io/fs"
"sync"
"testing"
"testing/fstest"
"gitea.maximumdirect.net/eric/promptkit"
"go.maximumdirect.net/configkit"
)
func aliasPrompt() map[string]any {
return map[string]any{"prompts": []any{map[string]any{
"id": "prepared",
"default_profile": "child",
"messages": []any{map[string]any{"role": "user", "content": "hello"}},
"output": map[string]any{"format": "text", "validation_mode": "none"},
}}}
}
func TestProfileInheritanceConfiguredBaseAliasWorkflow(t *testing.T) {
baseID, baseEndpoint, baseModel, reasoning, timeout := "base", "https://profile.example/v1", "synthetic-model", "high", 120
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: promptkit.ProfileSource("application alias", []promptkit.ProfileConfig{
{ID: "base", Endpoint: &baseEndpoint, Model: &baseModel},
{ID: "child", Extends: &baseID, ReasoningEffort: &reasoning, TimeoutSeconds: &timeout},
})}}}},
Main: &configkit.Input{Source: configkit.Mapping("prompt", aliasPrompt())},
}})
if err != nil {
t.Fatalf("construct alias engine: %v", err)
}
base, err := engine.InspectProfile(context.Background(), baseID)
if err != nil {
t.Fatalf("inspect base: %v", err)
}
child, err := engine.InspectProfile(context.Background(), "child")
if err != nil {
t.Fatalf("inspect alias: %v", err)
}
if child.ProfileID != "child" || child.EffectiveModelParams.BackendID != base.EffectiveModelParams.BackendID ||
child.EffectiveModelParams.Model != base.EffectiveModelParams.Model ||
child.EffectiveModelParams.ReasoningEffort != reasoning || child.EffectiveModelParams.TimeoutSeconds != timeout {
t.Fatalf("alias inspection = %+v, base = %+v", child, base)
}
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{PromptID: "prepared"})
if err != nil || prepared.SelectedProfileID != "child" {
t.Fatalf("prepare alias = (%+v, %v)", prepared, err)
}
shorter, lower := 15, "low"
overridden, err := engine.Prepare(context.Background(), promptkit.RunRequest{PromptID: "prepared", Execution: &promptkit.ExecutionTargetOverride{
TimeoutSeconds: &shorter, ReasoningEffort: &lower,
}})
if err != nil || overridden.EffectiveModelParams.TimeoutSeconds != shorter || overridden.EffectiveModelParams.ReasoningEffort != lower {
t.Fatalf("runtime override = (%+v, %v)", overridden, err)
}
}
func TestProfileInheritanceYAMLAliasOfConfiguredProfile(t *testing.T) {
inputs, err := promptkit.FilesystemInputs("profile alias", fstest.MapFS{
"config/alias.yaml": &fstest.MapFile{Data: []byte("profiles:\n - id: base\n model: synthetic-model\n - id: yaml-alias\n extends: {id: base}\n")},
}, "config")
if err != nil {
t.Fatal(err)
}
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Underlays: []configkit.Underlay{{Inputs: inputs}}}})
if err != nil {
t.Fatal(err)
}
inspection, err := engine.InspectProfile(context.Background(), "yaml-alias")
if err != nil || inspection.ProfileID != "yaml-alias" || inspection.EffectiveModelParams.Model == "" {
t.Fatalf("YAML alias inspection = (%+v, %v)", inspection, err)
}
}
func TestProfileInheritanceRetainsRequiredCredentialBehavior(t *testing.T) {
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{
Source: configkit.Mapping("credentials", map[string]any{
"backends": []any{map[string]any{"id": "credential-backend",
"endpoint": "https://credential.example/v1", "api_key_env": "OPTIONAL_BACKEND_KEY",
}},
"profiles": []any{
map[string]any{"id": "credential-base", "backend": "credential-backend", "model": "model", "api_key_required": true},
map[string]any{"id": "credential-child", "extends": map[string]any{"id": "credential-base"}},
},
}),
}}})
if err != nil {
t.Fatal(err)
}
inspection, err := engine.InspectProfile(context.Background(), "credential-child")
if err != nil || !inspection.APIKeyRequired || inspection.EffectiveModelParams.APIKeyEnv != "" {
t.Fatalf("credential inspection = (%+v, %v)", inspection, err)
}
}
func TestProfileInheritancePreservesPublicErrorIdentities(t *testing.T) {
engine, err := promptkit.NewEngine(promptkit.Config{})
if err != nil {
t.Fatal(err)
}
if inspection, err := engine.InspectProfile(context.Background(), "missing"); inspection != nil || !errors.Is(err, promptkit.ErrProfileNotFound) {
t.Fatalf("missing profile = (%+v, %v)", inspection, err)
}
for _, document := range []map[string]any{
{"profiles": []any{map[string]any{"id": "child", "extends": map[string]any{"id": "missing"}}}},
{"profiles": []any{map[string]any{"id": "a", "extends": map[string]any{"id": "b"}}, map[string]any{"id": "b", "extends": map[string]any{"id": "a"}}}},
} {
invalid, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{
Source: configkit.Mapping("invalid inheritance", document),
}}})
if invalid != nil || !errors.Is(err, promptkit.ErrInvalidConfig) {
t.Fatalf("invalid inheritance = (%+v, %v)", invalid, err)
}
}
}
func TestProfileInheritanceFreezesPreparedExecution(t *testing.T) {
profiles := &mutableInheritanceProfileFS{files: fstest.MapFS{
"config/profiles.yaml": &fstest.MapFile{Data: []byte("profiles:\n - id: base\n endpoint: https://base.example/v1\n model: first-model\n - id: child\n extends: {id: base}\n")},
}}
inputs, err := promptkit.FilesystemInputs("profiles", profiles, "config")
if err != nil {
t.Fatal(err)
}
client := &recordingProfileClient{}
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
Underlays: []configkit.Underlay{{Inputs: inputs}},
Main: &configkit.Input{Source: configkit.Mapping("prompt", aliasPrompt())},
}}, promptkit.WithLLMClient(client))
if err != nil {
t.Fatal(err)
}
prepared, err := engine.PrepareExecution(context.Background(), promptkit.RunRequest{PromptID: "prepared"})
if err != nil {
t.Fatal(err)
}
profiles.set("config/profiles.yaml", "profiles:\n - id: base\n endpoint: https://base.example/v1\n model: second-model\n - id: child\n extends: {id: base}\n")
result, err := engine.RunPrepared(context.Background(), prepared)
if err != nil || result == nil || client.model != "first-model" {
t.Fatalf("prepared execution = (%+v, %v), model %q", result, err, client.model)
}
inspection, err := engine.InspectProfile(context.Background(), "child")
if err != nil || inspection.EffectiveModelParams.Model != "first-model" {
t.Fatalf("existing engine inspection = (%+v, %v)", inspection, err)
}
}
type recordingProfileClient struct{ model string }
func (client *recordingProfileClient) Generate(ctx context.Context, request promptkit.GenerateRequest) (*promptkit.GenerateResponse, error) {
client.model = request.Target.Model
return &promptkit.GenerateResponse{Content: "ok"}, ctx.Err()
}
type mutableInheritanceProfileFS struct {
mu sync.RWMutex
files fstest.MapFS
}
func (source *mutableInheritanceProfileFS) Open(name string) (fs.File, error) {
source.mu.RLock()
defer source.mu.RUnlock()
return source.files.Open(name)
}
func (source *mutableInheritanceProfileFS) set(name, content string) {
source.mu.Lock()
defer source.mu.Unlock()
source.files[name] = &fstest.MapFile{Data: []byte(content)}
}