Capture prompt requirement metadata and templates
This commit is contained in:
@@ -35,7 +35,12 @@ The configuration adapter indexes each prompt by exact ID and version,
|
|||||||
materializes file-backed message content using the exact effective field's
|
materializes file-backed message content using the exact effective field's
|
||||||
origin and resource context, and checks static message/session template syntax
|
origin and resource context, and checks static message/session template syntax
|
||||||
at construction. It stores captured content, compiled templates, declared
|
at construction. It stores captured content, compiled templates, declared
|
||||||
input metadata, and a definition hash. It closes content-file handles before
|
input metadata, and a definition hash. Alongside the unchanged hashed
|
||||||
|
definition, it retains content-type and description presence for each declared
|
||||||
|
input and provides an internal requirement snapshot with those declarations
|
||||||
|
and the captured parses. Metadata values come from the final normalized
|
||||||
|
declaration; absent and null fields remain distinguishable from explicit
|
||||||
|
strings, including empty strings. It closes content-file handles before
|
||||||
returning and retains no source capability for public inspection.
|
returning and retains no source capability for public inspection.
|
||||||
|
|
||||||
Role and cache-control normalization is applied to execution-owned message
|
Role and cache-control normalization is applied to execution-owned message
|
||||||
@@ -43,11 +48,13 @@ copies. The retained ConfigKit configuration keeps effective input message
|
|||||||
metadata and its resource origins: recording a sequence-wide normalization in
|
metadata and its resource origins: recording a sequence-wide normalization in
|
||||||
ConfigKit v0.5.0 would replace origins of unchanged content-file fields.
|
ConfigKit v0.5.0 would replace origins of unchanged content-file fields.
|
||||||
|
|
||||||
The runner selects from this index for preparation and `InspectPrompt`.
|
The runner selects from this index for preparation and `InspectPrompt`; the
|
||||||
Neither path rescans configuration or reopens message files. `InspectPrompt`
|
internal requirement snapshot lookup selects by exact prompt ID and version.
|
||||||
|
These paths do not rescan configuration or reopen message files. `InspectPrompt`
|
||||||
returns declared metadata without rendering or resolving profile, input, or
|
returns declared metadata without rendering or resolving profile, input, or
|
||||||
schema resources. Runtime rendering still checks request variables and input
|
schema resources, and its public shape and prompt hashes do not include the
|
||||||
helpers. The [prompt adapter tests](../../internal/configuration/prompts_test.go)
|
captured presence state. Runtime rendering still checks request variables and
|
||||||
|
input helpers. The [prompt adapter tests](../../internal/configuration/prompts_test.go)
|
||||||
cover resource origins, context containment, capture, and selection.
|
cover resource origins, context containment, capture, and selection.
|
||||||
|
|
||||||
## Profiles And Built-Ins
|
## Profiles And Built-Ins
|
||||||
|
|||||||
@@ -203,6 +203,10 @@ result is not a rendering/readiness/provider guarantee.
|
|||||||
|
|
||||||
## Stage 1 — Capture Effective Metadata And Template Snapshots
|
## Stage 1 — Capture Effective Metadata And Template Snapshots
|
||||||
|
|
||||||
|
**Status: Complete (2026-10-08).** Effective input metadata presence and
|
||||||
|
construction-time templates are available through an internal snapshot without
|
||||||
|
changing prompt hashes or the existing inspection shape.
|
||||||
|
|
||||||
**Outcome:** The analyzer can obtain existing compiled templates and declaration
|
**Outcome:** The analyzer can obtain existing compiled templates and declaration
|
||||||
metadata without artifact reads or changing old inspection/execution behavior.
|
metadata without artifact reads or changing old inspection/execution behavior.
|
||||||
|
|
||||||
|
|||||||
@@ -54,10 +54,10 @@ type promptConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type promptInputConfig struct {
|
type promptInputConfig struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Required bool `yaml:"required"`
|
Required bool `yaml:"required"`
|
||||||
ContentType string `yaml:"content_type"`
|
ContentType *string `yaml:"content_type"`
|
||||||
Description string `yaml:"description"`
|
Description *string `yaml:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type promptMessageConfig struct {
|
type promptMessageConfig struct {
|
||||||
@@ -93,6 +93,7 @@ type loaded struct {
|
|||||||
result *configkit.Result[document]
|
result *configkit.Result[document]
|
||||||
backends map[string]map[string]fieldState
|
backends map[string]map[string]fieldState
|
||||||
profiles map[string]map[string]fieldState
|
profiles map[string]map[string]fieldState
|
||||||
|
promptMetadata map[string]map[string][]promptInputPresence
|
||||||
prompts map[string]map[string]*promptEntry
|
prompts map[string]map[string]*promptEntry
|
||||||
ambiguous map[string]bool
|
ambiguous map[string]bool
|
||||||
registry *backend.Registry
|
registry *backend.Registry
|
||||||
@@ -112,8 +113,9 @@ func Load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
|
|||||||
// load performs one ConfigKit lifecycle over the selected source tiers.
|
// load performs one ConfigKit lifecycle over the selected source tiers.
|
||||||
func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error) {
|
func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error) {
|
||||||
state := &loaded{
|
state := &loaded{
|
||||||
backends: make(map[string]map[string]fieldState),
|
backends: make(map[string]map[string]fieldState),
|
||||||
profiles: make(map[string]map[string]fieldState),
|
profiles: make(map[string]map[string]fieldState),
|
||||||
|
promptMetadata: make(map[string]map[string][]promptInputPresence),
|
||||||
}
|
}
|
||||||
result, err := configkit.LoadLayered(main, configkit.LayeredOptions[document]{
|
result, err := configkit.LoadLayered(main, configkit.LayeredOptions[document]{
|
||||||
Underlays: underlays,
|
Underlays: underlays,
|
||||||
@@ -122,6 +124,7 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
|
|||||||
Resolve: func(config *document, resolution *configkit.Resolution) error {
|
Resolve: func(config *document, resolution *configkit.Resolution) error {
|
||||||
captureFields(config.Backends, "backends", backendFields, resolution, state.backends)
|
captureFields(config.Backends, "backends", backendFields, resolution, state.backends)
|
||||||
captureFields(config.Profiles, "profiles", profileFields, resolution, state.profiles)
|
captureFields(config.Profiles, "profiles", profileFields, resolution, state.profiles)
|
||||||
|
capturePromptMetadata(config.Prompts, resolution, state.promptMetadata)
|
||||||
resolveBackends(config.Backends, resolution)
|
resolveBackends(config.Backends, resolution)
|
||||||
resolveProfiles(config.Profiles, resolution)
|
resolveProfiles(config.Profiles, resolution)
|
||||||
resolvePrompts(config.Prompts, resolution)
|
resolvePrompts(config.Prompts, resolution)
|
||||||
@@ -143,6 +146,44 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
|
|||||||
return state, nil
|
return state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type promptInputPresence struct {
|
||||||
|
contentType fieldState
|
||||||
|
description fieldState
|
||||||
|
}
|
||||||
|
|
||||||
|
func capturePromptMetadata(prompts map[string]map[string]promptConfig, resolution *configkit.Resolution, destination map[string]map[string][]promptInputPresence) {
|
||||||
|
for _, id := range sortedKeys(prompts) {
|
||||||
|
if destination[id] == nil {
|
||||||
|
destination[id] = make(map[string][]promptInputPresence, len(prompts[id]))
|
||||||
|
}
|
||||||
|
for _, version := range sortedKeys(prompts[id]) {
|
||||||
|
inputs := prompts[id][version].Inputs
|
||||||
|
states := make([]promptInputPresence, len(inputs))
|
||||||
|
base := configkit.Keys("prompts", id, version, "inputs")
|
||||||
|
for index := range inputs {
|
||||||
|
path := base.Index(index)
|
||||||
|
states[index] = promptInputPresence{
|
||||||
|
contentType: captureFieldState(path.Key("content_type"), resolution),
|
||||||
|
description: captureFieldState(path.Key("description"), resolution),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destination[id][version] = states
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func captureFieldState(path configkit.Path, resolution *configkit.Resolution) fieldState {
|
||||||
|
info, exists := resolution.InputInfo(path)
|
||||||
|
switch {
|
||||||
|
case !exists || !info.Explicit:
|
||||||
|
return fieldAbsent
|
||||||
|
case info.Null:
|
||||||
|
return fieldNull
|
||||||
|
default:
|
||||||
|
return fieldValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func captureFields[T any](entries map[string]T, collection string, fields []string, resolution *configkit.Resolution, destination map[string]map[string]fieldState) {
|
func captureFields[T any](entries map[string]T, collection string, fields []string, resolution *configkit.Resolution, destination map[string]map[string]fieldState) {
|
||||||
for id := range entries {
|
for id := range entries {
|
||||||
states := make(map[string]fieldState, len(fields))
|
states := make(map[string]fieldState, len(fields))
|
||||||
|
|||||||
@@ -21,11 +21,12 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type promptEntry struct {
|
type promptEntry struct {
|
||||||
definition domain.PromptDefinition
|
definition domain.PromptDefinition
|
||||||
inspection domain.PromptInspection
|
inspection domain.PromptInspection
|
||||||
hash string
|
requirementInputs []prompt.InputMetadata
|
||||||
session *template.Template
|
hash string
|
||||||
messages []*template.Template
|
session *template.Template
|
||||||
|
messages []*template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolvePrompts(prompts map[string]map[string]promptConfig, resolution *configkit.Resolution) {
|
func resolvePrompts(prompts map[string]map[string]promptConfig, resolution *configkit.Resolution) {
|
||||||
@@ -48,18 +49,24 @@ func resolvePrompts(prompts map[string]map[string]promptConfig, resolution *conf
|
|||||||
for index := range definition.Inputs {
|
for index := range definition.Inputs {
|
||||||
input := &definition.Inputs[index]
|
input := &definition.Inputs[index]
|
||||||
inputPath := base.Key("inputs").Index(index)
|
inputPath := base.Key("inputs").Index(index)
|
||||||
|
normalizedName := strings.TrimSpace(input.Name)
|
||||||
|
if normalizedName != input.Name {
|
||||||
|
configkit.Normalize(resolution, inputPath.Key("name"), &input.Name, normalizedName, "normalize input metadata")
|
||||||
|
inputsChanged = true
|
||||||
|
}
|
||||||
for _, field := range []struct {
|
for _, field := range []struct {
|
||||||
name string
|
name string
|
||||||
value *string
|
value **string
|
||||||
}{
|
}{
|
||||||
{"name", &input.Name},
|
|
||||||
{"content_type", &input.ContentType},
|
{"content_type", &input.ContentType},
|
||||||
{"description", &input.Description},
|
{"description", &input.Description},
|
||||||
} {
|
} {
|
||||||
normalized := strings.TrimSpace(*field.value)
|
if *field.value != nil {
|
||||||
if normalized != *field.value {
|
normalized := strings.TrimSpace(**field.value)
|
||||||
configkit.Normalize(resolution, inputPath.Key(field.name), field.value, normalized, "normalize input metadata")
|
if normalized != **field.value {
|
||||||
inputsChanged = true
|
configkit.Normalize(resolution, inputPath.Key(field.name), field.value, &normalized, "normalize input metadata")
|
||||||
|
inputsChanged = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,6 +139,10 @@ func (state *loaded) materializePrompt(id, version string, raw promptConfig, loo
|
|||||||
}
|
}
|
||||||
seenInputs := make(map[string]bool, len(raw.Inputs))
|
seenInputs := make(map[string]bool, len(raw.Inputs))
|
||||||
definition.Inputs = make([]domain.PromptInput, 0, len(raw.Inputs))
|
definition.Inputs = make([]domain.PromptInput, 0, len(raw.Inputs))
|
||||||
|
entry := &promptEntry{
|
||||||
|
messages: make([]*template.Template, 0, len(raw.Messages)),
|
||||||
|
requirementInputs: make([]prompt.InputMetadata, 0, len(raw.Inputs)),
|
||||||
|
}
|
||||||
for index, input := range raw.Inputs {
|
for index, input := range raw.Inputs {
|
||||||
path := base.Key("inputs").Index(index)
|
path := base.Key("inputs").Index(index)
|
||||||
if input.Name == "" {
|
if input.Name == "" {
|
||||||
@@ -141,11 +152,27 @@ func (state *loaded) materializePrompt(id, version string, raw promptConfig, loo
|
|||||||
return nil, fmt.Errorf("%s: input name is duplicated", path.Key("name"))
|
return nil, fmt.Errorf("%s: input name is duplicated", path.Key("name"))
|
||||||
}
|
}
|
||||||
seenInputs[input.Name] = true
|
seenInputs[input.Name] = true
|
||||||
|
contentType := ""
|
||||||
|
if input.ContentType != nil {
|
||||||
|
contentType = *input.ContentType
|
||||||
|
}
|
||||||
|
description := ""
|
||||||
|
if input.Description != nil {
|
||||||
|
description = *input.Description
|
||||||
|
}
|
||||||
definition.Inputs = append(definition.Inputs, domain.PromptInput{
|
definition.Inputs = append(definition.Inputs, domain.PromptInput{
|
||||||
Name: input.Name, Required: input.Required, ContentType: input.ContentType, Description: input.Description,
|
Name: input.Name, Required: input.Required, ContentType: contentType, Description: description,
|
||||||
})
|
})
|
||||||
|
presence := state.promptMetadata[id][version][index]
|
||||||
|
metadata := prompt.InputMetadata{Name: input.Name, Required: input.Required}
|
||||||
|
if presence.contentType == fieldValue {
|
||||||
|
metadata.ContentType = stringPointer(contentType)
|
||||||
|
}
|
||||||
|
if presence.description == fieldValue {
|
||||||
|
metadata.Description = stringPointer(description)
|
||||||
|
}
|
||||||
|
entry.requirementInputs = append(entry.requirementInputs, metadata)
|
||||||
}
|
}
|
||||||
entry := &promptEntry{messages: make([]*template.Template, 0, len(raw.Messages))}
|
|
||||||
definition.Templates = make([]domain.PromptMessageTemplate, 0, len(raw.Messages))
|
definition.Templates = make([]domain.PromptMessageTemplate, 0, len(raw.Messages))
|
||||||
for index, message := range raw.Messages {
|
for index, message := range raw.Messages {
|
||||||
path := base.Key("messages").Index(index)
|
path := base.Key("messages").Index(index)
|
||||||
@@ -233,6 +260,11 @@ func (state *loaded) materializePrompt(id, version string, raw promptConfig, loo
|
|||||||
return entry, nil
|
return entry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stringPointer(value string) *string {
|
||||||
|
copy := value
|
||||||
|
return ©
|
||||||
|
}
|
||||||
|
|
||||||
func (state *loaded) readContent(fieldPath configkit.Path, name string, lookupOrigin func(configkit.Path) (configkit.FieldInfo, bool)) (string, error) {
|
func (state *loaded) readContent(fieldPath configkit.Path, name string, lookupOrigin func(configkit.Path) (configkit.FieldInfo, bool)) (string, error) {
|
||||||
if strings.TrimSpace(name) == "" {
|
if strings.TrimSpace(name) == "" {
|
||||||
return "", fmt.Errorf("%s: content_file path must not be blank", fieldPath)
|
return "", fmt.Errorf("%s: content_file path must not be blank", fieldPath)
|
||||||
@@ -329,3 +361,33 @@ func (state *loaded) GetCompiledTemplates(id, version string) (prompt.CompiledTe
|
|||||||
}
|
}
|
||||||
return prompt.CompiledTemplates{Session: entry.session, Messages: entry.messages}, true
|
return prompt.CompiledTemplates{Session: entry.session, Messages: entry.messages}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRequirementSnapshot returns captured templates and input metadata for
|
||||||
|
// one exact prompt identity. Returned metadata and slices are independently
|
||||||
|
// owned; the compiled templates are shared immutable construction-time parses.
|
||||||
|
func (state *loaded) GetRequirementSnapshot(id, version string) (prompt.RequirementSnapshot, bool) {
|
||||||
|
if state == nil {
|
||||||
|
return prompt.RequirementSnapshot{}, false
|
||||||
|
}
|
||||||
|
entry := state.prompts[id][version]
|
||||||
|
if entry == nil {
|
||||||
|
return prompt.RequirementSnapshot{}, false
|
||||||
|
}
|
||||||
|
snapshot := prompt.RequirementSnapshot{
|
||||||
|
Templates: prompt.CompiledTemplates{
|
||||||
|
Session: entry.session,
|
||||||
|
Messages: append([]*template.Template(nil), entry.messages...),
|
||||||
|
},
|
||||||
|
Inputs: make([]prompt.InputMetadata, len(entry.requirementInputs)),
|
||||||
|
}
|
||||||
|
for index, input := range entry.requirementInputs {
|
||||||
|
snapshot.Inputs[index] = input
|
||||||
|
if input.ContentType != nil {
|
||||||
|
snapshot.Inputs[index].ContentType = stringPointer(*input.ContentType)
|
||||||
|
}
|
||||||
|
if input.Description != nil {
|
||||||
|
snapshot.Inputs[index].Description = stringPointer(*input.Description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return snapshot, true
|
||||||
|
}
|
||||||
|
|||||||
@@ -170,6 +170,113 @@ func TestLoadAcceptsEmptyInlineContentAndRejectsInvalidPromptFields(t *testing.T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRequirementSnapshotCapturesMetadataPresenceAndNormalizedValues(t *testing.T) {
|
||||||
|
state, err := load(mapping("requirement-metadata", map[string]any{
|
||||||
|
"prompts": map[string]any{"example": map[string]any{"v1": map[string]any{
|
||||||
|
"session_id": "{{.session}}",
|
||||||
|
"inputs": []any{
|
||||||
|
map[string]any{"name": "absent"},
|
||||||
|
map[string]any{"name": "null", "content_type": nil, "description": nil},
|
||||||
|
map[string]any{"name": "empty", "content_type": "", "description": ""},
|
||||||
|
map[string]any{"name": "normalized", "content_type": " text/plain ", "description": " final text "},
|
||||||
|
},
|
||||||
|
"messages": []any{map[string]any{"role": "user", "content": "{{input \"absent\"}}"}},
|
||||||
|
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||||
|
}}},
|
||||||
|
}), nil)
|
||||||
|
if err != nil {
|
||||||
|
var loadError *configkit.LoadError
|
||||||
|
if errors.As(err, &loadError) {
|
||||||
|
t.Fatalf("%v: %+v", err, loadError.Diagnostics())
|
||||||
|
}
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot, ok := state.GetRequirementSnapshot("example", "v1")
|
||||||
|
if !ok || len(snapshot.Inputs) != 4 || len(snapshot.Templates.Messages) != 1 || snapshot.Templates.Session == nil {
|
||||||
|
t.Fatalf("requirement snapshot was not captured: %+v, %t", snapshot, ok)
|
||||||
|
}
|
||||||
|
for _, index := range []int{0, 1} {
|
||||||
|
if snapshot.Inputs[index].ContentType != nil || snapshot.Inputs[index].Description != nil {
|
||||||
|
t.Errorf("input %q should not have explicit string metadata: %+v", snapshot.Inputs[index].Name, snapshot.Inputs[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if snapshot.Inputs[2].ContentType == nil || *snapshot.Inputs[2].ContentType != "" ||
|
||||||
|
snapshot.Inputs[2].Description == nil || *snapshot.Inputs[2].Description != "" {
|
||||||
|
t.Errorf("explicit empty metadata presence was lost: %+v", snapshot.Inputs[2])
|
||||||
|
}
|
||||||
|
if snapshot.Inputs[3].ContentType == nil || *snapshot.Inputs[3].ContentType != "text/plain" ||
|
||||||
|
snapshot.Inputs[3].Description == nil || *snapshot.Inputs[3].Description != "final text" {
|
||||||
|
t.Errorf("snapshot did not use normalized final metadata: %+v", snapshot.Inputs[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
definition, inspection, err := state.prompt("example", "v1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(definition.Inputs) != 4 || len(inspection.Inputs) != 4 || inspection.Inputs[2].ContentType != "" || inspection.Inputs[2].Description != "" ||
|
||||||
|
inspection.Inputs[3].ContentType != "text/plain" || inspection.Inputs[3].Description != "final text" {
|
||||||
|
t.Fatalf("existing prompt inspection shape or normalized values changed: %+v", inspection)
|
||||||
|
}
|
||||||
|
|
||||||
|
*snapshot.Inputs[2].ContentType = "caller mutation"
|
||||||
|
snapshot.Inputs[3].Name = "caller mutation"
|
||||||
|
snapshot.Templates.Messages[0] = nil
|
||||||
|
again, ok := state.GetRequirementSnapshot("example", "v1")
|
||||||
|
if !ok || *again.Inputs[2].ContentType != "" || again.Inputs[3].Name != "normalized" || again.Templates.Messages[0] == nil {
|
||||||
|
t.Fatalf("snapshot mutation escaped into captured state: %+v, %t", again, ok)
|
||||||
|
}
|
||||||
|
if _, ok := state.GetRequirementSnapshot("example", "V1"); ok {
|
||||||
|
t.Fatal("snapshot lookup unexpectedly changed exact version matching")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInputMetadataPresenceDoesNotChangePromptHashOrInspection(t *testing.T) {
|
||||||
|
variants := []struct {
|
||||||
|
name string
|
||||||
|
fields map[string]any
|
||||||
|
}{
|
||||||
|
{name: "absent"},
|
||||||
|
{name: "null", fields: map[string]any{"content_type": nil, "description": nil}},
|
||||||
|
{name: "empty", fields: map[string]any{"content_type": "", "description": ""}},
|
||||||
|
}
|
||||||
|
var expectedHash string
|
||||||
|
for _, variant := range variants {
|
||||||
|
t.Run(variant.name, func(t *testing.T) {
|
||||||
|
input := map[string]any{"name": "document"}
|
||||||
|
for name, value := range variant.fields {
|
||||||
|
input[name] = value
|
||||||
|
}
|
||||||
|
state, err := load(mapping("hash-"+variant.name, map[string]any{
|
||||||
|
"prompts": map[string]any{"same": map[string]any{"v1": map[string]any{
|
||||||
|
"inputs": []any{input},
|
||||||
|
"messages": []any{map[string]any{"role": "user", "content": "hello"}},
|
||||||
|
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||||
|
}}},
|
||||||
|
}), nil)
|
||||||
|
if err != nil {
|
||||||
|
var loadError *configkit.LoadError
|
||||||
|
if errors.As(err, &loadError) {
|
||||||
|
t.Fatalf("%v: %+v", err, loadError.Diagnostics())
|
||||||
|
}
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, inspection, err := state.prompt("same", "v1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(inspection.Inputs) != 1 || inspection.Inputs[0].ContentType != "" || inspection.Inputs[0].Description != "" {
|
||||||
|
t.Fatalf("old inspection metadata changed: %+v", inspection.Inputs)
|
||||||
|
}
|
||||||
|
if expectedHash == "" {
|
||||||
|
expectedHash = inspection.PromptHash
|
||||||
|
} else if inspection.PromptHash != expectedHash {
|
||||||
|
t.Fatalf("metadata presence changed the definition hash: got %q, want %q", inspection.PromptHash, expectedHash)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func promptWithFile(name string) map[string]any {
|
func promptWithFile(name string) map[string]any {
|
||||||
return map[string]any{"prompts": map[string]any{"example": map[string]any{"v1": map[string]any{
|
return map[string]any{"prompts": map[string]any{"example": map[string]any{"v1": map[string]any{
|
||||||
"messages": []any{map[string]any{"role": "user", "content_file": name}},
|
"messages": []any{map[string]any{"role": "user", "content_file": name}},
|
||||||
|
|||||||
@@ -9,6 +9,22 @@ type CompiledTemplates struct {
|
|||||||
Messages []*template.Template
|
Messages []*template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InputMetadata is the captured declaration metadata for one prompt input.
|
||||||
|
// Nil metadata pointers mean the corresponding field was absent or null.
|
||||||
|
type InputMetadata struct {
|
||||||
|
Name string
|
||||||
|
Required bool
|
||||||
|
ContentType *string
|
||||||
|
Description *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequirementSnapshot provides the captured syntax and declarations needed
|
||||||
|
// for requirement inspection without reopening prompt resources.
|
||||||
|
type RequirementSnapshot struct {
|
||||||
|
Templates CompiledTemplates
|
||||||
|
Inputs []InputMetadata
|
||||||
|
}
|
||||||
|
|
||||||
// TemplateLookup provides parsed templates for a captured prompt identity.
|
// TemplateLookup provides parsed templates for a captured prompt identity.
|
||||||
type TemplateLookup interface {
|
type TemplateLookup interface {
|
||||||
GetCompiledTemplates(id, version string) (CompiledTemplates, bool)
|
GetCompiledTemplates(id, version string) (CompiledTemplates, bool)
|
||||||
|
|||||||
Reference in New Issue
Block a user