Carry optional prompt labels through results
This commit is contained in:
@@ -228,7 +228,7 @@ func BenchmarkInspectionSelection(b *testing.B) {
|
||||
}
|
||||
ctx := context.Background()
|
||||
definition, hash, err := result.GetPromptSnapshot(ctx, "inspection")
|
||||
if err != nil || definition.ID != "inspection" || definition.Version != "1" || hash == "" ||
|
||||
if err != nil || definition.ID != "inspection" || definition.Version == nil || *definition.Version != "1" || hash == "" ||
|
||||
len(definition.Inputs) != 1 || definition.Inputs[0].Name != "transcript" || !definition.Inputs[0].Required {
|
||||
b.Fatalf("prompt lookup fixture is invalid: definition=%+v hash=%q err=%v", definition, hash, err)
|
||||
}
|
||||
@@ -238,7 +238,7 @@ func BenchmarkInspectionSelection(b *testing.B) {
|
||||
b.Fatalf("profile lookup fixture is invalid: profile=%+v err=%v", profile, err)
|
||||
}
|
||||
inspection, err := result.GetPromptInspection(ctx, "inspection")
|
||||
if err != nil || inspection.PromptID != "inspection" || inspection.PromptVersion != "1" ||
|
||||
if err != nil || inspection.PromptID != "inspection" || inspection.PromptVersion == nil || *inspection.PromptVersion != "1" ||
|
||||
len(inspection.Inputs) != 1 || inspection.Inputs[0].Name != "transcript" {
|
||||
b.Fatalf("prompt inspection fixture is invalid: inspection=%+v err=%v", inspection, err)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ func (state *loaded) ListPrompts(ctx context.Context) ([]domain.PromptIdentity,
|
||||
return nil, err
|
||||
}
|
||||
prompts := append([]domain.PromptIdentity(nil), state.promptIDs...)
|
||||
for index := range prompts {
|
||||
prompts[index].Version = stringPointerValue(prompts[index].Version)
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ func TestLoadedListsSortedEffectiveIdentities(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantPrompts := []domain.PromptIdentity{
|
||||
{ID: " z/prompt ", Version: "v1"},
|
||||
{ID: "a/prompt", Version: "v1"},
|
||||
{ID: "z/prompt", Version: "v1"},
|
||||
{ID: " z/prompt ", Version: stringPointer("v1")},
|
||||
{ID: "a/prompt", Version: stringPointer("v1")},
|
||||
{ID: "z/prompt", Version: stringPointer("v1")},
|
||||
}
|
||||
if !reflect.DeepEqual(prompts, wantPrompts) {
|
||||
t.Fatalf("prompt identities = %#v, want %#v", prompts, wantPrompts)
|
||||
@@ -75,7 +75,7 @@ func TestLoadedListsAreIndependent(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
prompts[0].ID = "changed prompt"
|
||||
prompts[0].Version = "changed version"
|
||||
*prompts[0].Version = "changed version"
|
||||
|
||||
profiles, err = state.ListProfiles(context.Background())
|
||||
if err != nil {
|
||||
@@ -88,7 +88,7 @@ func TestLoadedListsAreIndependent(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantPrompts := []domain.PromptIdentity{{ID: "example", Version: "v1"}}
|
||||
wantPrompts := []domain.PromptIdentity{{ID: "example", Version: stringPointer("v1")}}
|
||||
if !reflect.DeepEqual(prompts, wantPrompts) {
|
||||
t.Fatalf("caller mutation changed prompt inventory: %#v", prompts)
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
|
||||
}
|
||||
state.profileIDs = sortedKeys(state.indexedProfiles)
|
||||
for _, id := range sortedKeys(state.prompts) {
|
||||
state.promptIDs = append(state.promptIDs, domain.PromptIdentity{ID: id, Version: state.prompts[id].definition.Version})
|
||||
state.promptIDs = append(state.promptIDs, domain.PromptIdentity{ID: id, Version: stringPointerValue(state.prompts[id].definition.Version)})
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
@@ -96,11 +96,7 @@ func (state *loaded) materializePrompts(prompts map[string]promptConfig, basePat
|
||||
if strings.TrimSpace(id) == "" {
|
||||
return fmt.Errorf("%s: prompt ID must not be blank", promptBase)
|
||||
}
|
||||
version := ""
|
||||
if prompts[id].Version != nil {
|
||||
version = *prompts[id].Version
|
||||
}
|
||||
entry, err := state.materializePrompt(id, version, prompts[id], promptBase, lookupOrigin)
|
||||
entry, err := state.materializePrompt(id, prompts[id].Version, prompts[id], promptBase, lookupOrigin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -109,11 +105,11 @@ func (state *loaded) materializePrompts(prompts map[string]promptConfig, basePat
|
||||
return nil
|
||||
}
|
||||
|
||||
func (state *loaded) materializePrompt(id, version string, raw promptConfig, base configkit.Path, lookupOrigin func(configkit.Path) (configkit.FieldInfo, bool)) (*promptEntry, error) {
|
||||
func (state *loaded) materializePrompt(id string, version *string, raw promptConfig, base configkit.Path, lookupOrigin func(configkit.Path) (configkit.FieldInfo, bool)) (*promptEntry, error) {
|
||||
if len(raw.Messages) == 0 {
|
||||
return nil, fmt.Errorf("%s: at least one message is required", base.Key("messages"))
|
||||
}
|
||||
definition := domain.PromptDefinition{ID: id, Version: version}
|
||||
definition := domain.PromptDefinition{ID: id, Version: stringPointerValue(version)}
|
||||
if raw.DefaultProfile != nil {
|
||||
if strings.TrimSpace(*raw.DefaultProfile) == "" {
|
||||
return nil, fmt.Errorf("%s: default profile must not be blank", base.Key("default_profile"))
|
||||
@@ -242,7 +238,7 @@ func (state *loaded) materializePrompt(id, version string, raw promptConfig, bas
|
||||
entry.definition = definition
|
||||
entry.hash = hash
|
||||
entry.inspection = domain.PromptInspection{
|
||||
PromptID: id, PromptVersion: version, PromptHash: hash,
|
||||
PromptID: id, PromptVersion: stringPointerValue(version), PromptHash: hash,
|
||||
DefaultProfileID: definition.DefaultProfile, Inputs: append([]domain.PromptInput(nil), definition.Inputs...),
|
||||
OutputContract: contract,
|
||||
}
|
||||
@@ -254,6 +250,13 @@ func stringPointer(value string) *string {
|
||||
return ©
|
||||
}
|
||||
|
||||
func stringPointerValue(value *string) *string {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return stringPointer(*value)
|
||||
}
|
||||
|
||||
func (state *loaded) readContent(fieldPath configkit.Path, name string, lookupOrigin func(configkit.Path) (configkit.FieldInfo, bool)) (string, error) {
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return "", fmt.Errorf("%s: content_file path must not be blank", fieldPath)
|
||||
@@ -298,6 +301,7 @@ func (state *loaded) lookupPromptEntry(id string) (*promptEntry, error) {
|
||||
}
|
||||
|
||||
func ownPromptDefinition(definition domain.PromptDefinition) *domain.PromptDefinition {
|
||||
definition.Version = stringPointerValue(definition.Version)
|
||||
definition.Inputs = append([]domain.PromptInput(nil), definition.Inputs...)
|
||||
definition.Templates = append([]domain.PromptMessageTemplate(nil), definition.Templates...)
|
||||
for index := range definition.Templates {
|
||||
@@ -310,6 +314,7 @@ func ownPromptDefinition(definition domain.PromptDefinition) *domain.PromptDefin
|
||||
}
|
||||
|
||||
func ownPromptInspection(inspection domain.PromptInspection) *domain.PromptInspection {
|
||||
inspection.PromptVersion = stringPointerValue(inspection.PromptVersion)
|
||||
inspection.Inputs = append([]domain.PromptInput(nil), inspection.Inputs...)
|
||||
return &inspection
|
||||
}
|
||||
|
||||
@@ -131,11 +131,112 @@ func TestLoadRejectsMalformedStaticSyntaxAndCapturesOptionalVersion(t *testing.T
|
||||
t.Fatal(err)
|
||||
}
|
||||
definition, inspection, err := state.prompt("example")
|
||||
if err != nil || definition.Version != "release label" || inspection.PromptVersion != "release label" {
|
||||
if err != nil || definition.Version == nil || *definition.Version != "release label" ||
|
||||
inspection.PromptVersion == nil || *inspection.PromptVersion != "release label" {
|
||||
t.Fatalf("optional version metadata was not retained: definition=%+v inspection=%+v err=%v", definition, inspection, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromptVersionLabelPresenceAndOwnership(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version any
|
||||
include bool
|
||||
wantPresent bool
|
||||
wantValue string
|
||||
}{
|
||||
{name: "absent"},
|
||||
{name: "null", include: true},
|
||||
{name: "empty", version: "", include: true, wantPresent: true},
|
||||
{name: "exact text", version: " Release/v1 ", include: true, wantPresent: true, wantValue: " Release/v1 "},
|
||||
}
|
||||
|
||||
hashes := make(map[string]string, len(tests))
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
definition := map[string]any{
|
||||
"messages": []any{map[string]any{"role": "user", "content": "hello"}},
|
||||
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||
}
|
||||
if test.include {
|
||||
definition["version"] = test.version
|
||||
}
|
||||
state, err := load(mapping("prompt version label", map[string]any{
|
||||
"prompts": map[string]any{"example": definition},
|
||||
}), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
captured, inspection, err := state.prompt("example")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
identities, err := state.ListPrompts(context.Background())
|
||||
if err != nil || len(identities) != 1 {
|
||||
t.Fatalf("list prompt labels: %#v, %v", identities, err)
|
||||
}
|
||||
check := func(name string, value *string) {
|
||||
t.Helper()
|
||||
if !test.wantPresent {
|
||||
if value != nil {
|
||||
t.Errorf("%s label = %q, want absent", name, *value)
|
||||
}
|
||||
return
|
||||
}
|
||||
if value == nil || *value != test.wantValue {
|
||||
t.Errorf("%s label = %v, want %q", name, value, test.wantValue)
|
||||
}
|
||||
}
|
||||
check("definition", captured.Version)
|
||||
check("inspection", inspection.PromptVersion)
|
||||
check("listing", identities[0].Version)
|
||||
if captured.Version != nil && (captured.Version == inspection.PromptVersion || captured.Version == identities[0].Version || inspection.PromptVersion == identities[0].Version) {
|
||||
t.Fatal("returned prompt labels share mutable pointers")
|
||||
}
|
||||
hashes[test.name] = inspection.PromptHash
|
||||
|
||||
if captured.Version != nil {
|
||||
*captured.Version = "mutated definition"
|
||||
}
|
||||
if inspection.PromptVersion != nil {
|
||||
*inspection.PromptVersion = "mutated inspection"
|
||||
}
|
||||
if identities[0].Version != nil {
|
||||
*identities[0].Version = "mutated listing"
|
||||
}
|
||||
again, againInspection, err := state.prompt("example")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
check("captured definition after mutation", again.Version)
|
||||
check("captured inspection after mutation", againInspection.PromptVersion)
|
||||
})
|
||||
}
|
||||
if hashes["absent"] == hashes["empty"] {
|
||||
t.Fatal("absent and declared-empty labels produced the same definition hash")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromptVersionNullClearsInheritedLabel(t *testing.T) {
|
||||
prompt := map[string]any{
|
||||
"version": "base-label",
|
||||
"messages": []any{map[string]any{"role": "user", "content": "hello"}},
|
||||
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||
}
|
||||
state, err := load(
|
||||
mapping("clear inherited label", map[string]any{"prompts": map[string]any{"example": map[string]any{"version": nil}}}),
|
||||
[]configkit.Underlay{{Inputs: []configkit.Input{mapping("base prompt", map[string]any{"prompts": map[string]any{"example": prompt}})}}},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
definition, inspection, err := state.prompt("example")
|
||||
if err != nil || definition.Version != nil || inspection.PromptVersion != nil {
|
||||
t.Fatalf("null overlay retained inherited label: definition=%+v inspection=%+v err=%v", definition, inspection, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromptInspectionLookupMatchesSnapshotSelectionAndOwnsInputs(t *testing.T) {
|
||||
makePrompt := func(content string) map[string]any {
|
||||
return map[string]any{
|
||||
@@ -169,7 +270,8 @@ func TestPromptInspectionLookupMatchesSnapshotSelectionAndOwnsInputs(t *testing.
|
||||
if err != nil {
|
||||
t.Fatalf("snapshot prompt ID: %v", err)
|
||||
}
|
||||
if inspection.PromptID != definition.ID || inspection.PromptVersion != definition.Version ||
|
||||
if inspection.PromptID != definition.ID || inspection.PromptVersion == nil || definition.Version == nil ||
|
||||
*inspection.PromptVersion != *definition.Version ||
|
||||
inspection.PromptHash != hash || inspection.DefaultProfileID != "partial" ||
|
||||
len(inspection.Inputs) != 1 || inspection.Inputs[0].Name != "document" ||
|
||||
inspection.Inputs[0].ContentType != "text/plain" || inspection.Inputs[0].Description != "source document" {
|
||||
|
||||
Reference in New Issue
Block a user