Demonstrate PromptKit catalogs and smoke tests
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Dependency upgrades, discovery, and smoke testing implementation plan
|
||||
|
||||
Status: Stages 1–14 complete; Stage 15 is next.
|
||||
Status: Stages 1–15 complete; Stage 16 is next.
|
||||
|
||||
## Authority and baseline
|
||||
|
||||
@@ -738,6 +738,17 @@ native API's success criteria.
|
||||
**Complete when:** the consumer demonstrates the new interfaces with little
|
||||
application plumbing and no model-service prerequisite.
|
||||
|
||||
**Completed:** 2026-10-17. The composed consumer now explicitly wires smoke
|
||||
testing with a nil request builder and exposes backend, namespace, collection,
|
||||
and namespace-filtered prompt discovery using one synthetic collection. Its
|
||||
shared invocation state carries an injected in-memory PromptKit client; tests
|
||||
confirm metadata and requirements leave it unused and smoke inference invokes
|
||||
it. The README documents ID-only inspection, optional version metadata, all
|
||||
catalogs, namespace filtering, typed builder customization, and single and
|
||||
aggregate smoke commands. The composed test suite passed, as did the documented
|
||||
offline discovery, inspection, requirements, and smoke commands. Native HTTP
|
||||
checks reported their configured unsupported/not-applicable observations.
|
||||
|
||||
## Stage 16: Reconcile dependency graphs and notices
|
||||
|
||||
**Primary files:** all three module manifests/sums and attribution documents.
|
||||
|
||||
@@ -12,6 +12,10 @@ go run . config diff --scope app --config fixtures/before.yml --against-config f
|
||||
go run . config diff --scope promptkit --config fixtures/before.yml --against-config fixtures/after.yml --campaign spring
|
||||
go run . promptkit list prompts --config fixtures/before.yml --campaign spring
|
||||
go run . promptkit list profiles --config fixtures/after.yml --campaign autumn --format yaml
|
||||
go run . promptkit list backends --campaign spring
|
||||
go run . promptkit list namespaces --campaign spring
|
||||
go run . promptkit list collections --campaign spring
|
||||
go run . promptkit list prompts --namespace field-notes --campaign spring
|
||||
go run . promptkit show prompt briefing --campaign spring --format yaml
|
||||
go run . promptkit show profile spring --campaign spring
|
||||
go run . promptkit requirements show briefing --campaign spring
|
||||
@@ -19,6 +23,8 @@ go run . promptkit requirements show briefing --campaign spring --var mode=full
|
||||
go run . promptkit requirements validate briefing --campaign spring --input topic=fixtures/topic.txt
|
||||
go run . promptkit requirements validate briefing --campaign spring --input topic=fixtures/topic.txt --var mode=full --input notes=fixtures/notes.txt --format yaml
|
||||
go run . promptkit requirements validate briefing --campaign spring
|
||||
go run . promptkit test text_inference --campaign spring --prompt briefing --profile spring
|
||||
go run . promptkit test all --campaign spring --prompt briefing --profile spring
|
||||
go run . session restore session-42 --campaign spring
|
||||
```
|
||||
|
||||
@@ -38,11 +44,36 @@ on both sides and reports the native engine change under the spring profile.
|
||||
The application input check requires `--limit=0` when `--campaign` is supplied.
|
||||
Catalogs use the same selection-aware loader: the prompt catalog includes
|
||||
`briefing` version `1`, and the profile catalog includes the selected `autumn`
|
||||
profile. The example's shared run cache keys successful results by the complete
|
||||
supplied configuration/overlay selection and the supplied campaign, including
|
||||
presence. The campaign override is applied through ConfigKit before loading.
|
||||
These are choices for this example's loader and do not define a CommandKit cache
|
||||
policy.
|
||||
profile. Prompt inspection uses an ID only; its optional version is descriptive
|
||||
metadata in the report, not a selector. The synthetic `field-notes` collection
|
||||
provides one backend, profile, and prompt for collection discovery and native
|
||||
namespace filtering. The example's shared run cache keys successful results by
|
||||
the complete supplied configuration/overlay selection and the supplied
|
||||
campaign, including presence. The campaign override is applied through ConfigKit
|
||||
before loading. These are choices for this example's loader and do not define a
|
||||
CommandKit cache policy.
|
||||
|
||||
Smoke testing is explicitly wired with `ckprompt.WithSmokeTesting` and its
|
||||
optional request builder is nil, so CommandKit forwards the selected native
|
||||
request. Applications that need to adjust the request can provide a typed
|
||||
builder that starts with the seed and returns the chosen native request:
|
||||
|
||||
```go
|
||||
definitions = ckprompt.WithSmokeTesting(definitions, loadEngine,
|
||||
func(ctx context.Context, seed ckprompt.SmokeRequest, input *inspectionInput, state *runCache) (promptkit.SmokeTestRequest, error) {
|
||||
request := seed.Request
|
||||
request.ProfileID = "spring"
|
||||
return request, nil
|
||||
})
|
||||
```
|
||||
|
||||
The example injects a small in-memory `promptkit.LLMClient` through its shared
|
||||
run state. Catalog, inspection, and requirements commands do not call it;
|
||||
inference smoke commands do. No model service or credentials are required.
|
||||
Native HTTP checks may retain unsupported or not-applicable observations because
|
||||
the example does not configure a network probe. An aggregate smoke command can
|
||||
return an operational outcome for a native diagnostic while still delivering
|
||||
the complete aggregate report.
|
||||
|
||||
The first requirements command reports incomplete discovery: PromptKit records
|
||||
the conditional `notes` input and a missing `mode` issue. Supplying `mode=full`
|
||||
|
||||
@@ -40,6 +40,18 @@ type applicationCacheKey struct {
|
||||
// chooses to pass it to its loaders.
|
||||
type runCache struct {
|
||||
applications map[applicationCacheKey]*configkit.Result[appConfiguration]
|
||||
llmClient promptkit.LLMClient
|
||||
}
|
||||
|
||||
type exampleLLMClient struct {
|
||||
calls *int
|
||||
}
|
||||
|
||||
func (client *exampleLLMClient) Generate(context.Context, promptkit.GenerateRequest) (*promptkit.GenerateResponse, error) {
|
||||
if client.calls != nil {
|
||||
(*client.calls)++
|
||||
}
|
||||
return &promptkit.GenerateResponse{Content: "Example response."}, nil
|
||||
}
|
||||
|
||||
type restoreInput struct {
|
||||
@@ -110,6 +122,20 @@ func loadPromptEngine(ctx context.Context, selection ckconfig.Selection, input *
|
||||
return nil, err
|
||||
}
|
||||
engineConfig := configkit.Mapping("campaign "+name, map[string]any{
|
||||
"collections": map[string]any{
|
||||
"field-notes": map[string]any{
|
||||
"release_version": "1",
|
||||
"backends": map[string]any{"demo": map[string]any{"endpoint": "https://example.invalid/v1"}},
|
||||
"profiles": map[string]any{"assistant": map[string]any{"backend": "field-notes/demo", "model": "example-model"}},
|
||||
"prompts": map[string]any{"briefing": map[string]any{
|
||||
"version": "1",
|
||||
"default_profile": "field-notes/assistant",
|
||||
"inputs": []any{map[string]any{"name": "topic", "required": true, "content_type": "text/plain", "description": "Topic to brief"}},
|
||||
"messages": []any{map[string]any{"role": "user", "content": "Write a concise briefing about {{.topic}}."}},
|
||||
"output": map[string]any{"format": "text", "validation_mode": "none"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
"profiles": map[string]any{chosen.ProfileID: map[string]any{
|
||||
"endpoint": "https://example.invalid/v1",
|
||||
"model": chosen.Model,
|
||||
@@ -125,7 +151,11 @@ func loadPromptEngine(ctx context.Context, selection ckconfig.Selection, input *
|
||||
"output": map[string]any{"format": "text", "validation_mode": "none"},
|
||||
}},
|
||||
})
|
||||
return promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{Source: engineConfig}}})
|
||||
var client promptkit.LLMClient = &exampleLLMClient{}
|
||||
if cache != nil && cache.llmClient != nil {
|
||||
client = cache.llmClient
|
||||
}
|
||||
return promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{Source: engineConfig}}}, promptkit.WithLLMClient(client))
|
||||
}
|
||||
|
||||
func selectedCampaign(configuration appConfiguration, supplied commandkit.Supplied[string]) (string, campaign, error) {
|
||||
@@ -166,11 +196,18 @@ func buildValidationRequest(ctx context.Context, seed ckprompt.ValidationRequest
|
||||
}
|
||||
|
||||
func newApp() (*commandkit.App, error) {
|
||||
return newAppWithLLMClient(&exampleLLMClient{})
|
||||
}
|
||||
|
||||
func newAppWithLLMClient(client promptkit.LLMClient) (*commandkit.App, error) {
|
||||
if client == nil {
|
||||
client = &exampleLLMClient{}
|
||||
}
|
||||
limitDefault := 0
|
||||
limitFlag := commandkit.ValueFlag(commandkit.FlagMeta{Name: "limit", Usage: "Override the application item limit."}, &limitDefault, strconv.Atoi,
|
||||
func(input **inspectionInput, value commandkit.Supplied[int]) { (*input).Limit = value })
|
||||
inputFactory := func() *inspectionInput { return &inspectionInput{} }
|
||||
stateFactory := func() *runCache { return &runCache{} }
|
||||
stateFactory := func() *runCache { return &runCache{llmClient: client} }
|
||||
|
||||
appScope := ckconfig.FromConfigKit("app", "Application settings.", loadApplicationConfig)
|
||||
engineScope := ckprompt.ConfigScope(loadPromptEngine)
|
||||
@@ -195,6 +232,7 @@ func newApp() (*commandkit.App, error) {
|
||||
definitions.NewState = stateFactory
|
||||
definitions.Selectors = []commandkit.Flag[*inspectionInput]{campaignSelector()}
|
||||
definitions = ckprompt.WithRequestValidation(definitions, loadPromptEngine, buildValidationRequest)
|
||||
definitions = ckprompt.WithSmokeTesting(definitions, loadPromptEngine, nil)
|
||||
definitionCommand := ckprompt.Command(definitions)
|
||||
|
||||
store := map[string]session{
|
||||
|
||||
@@ -311,6 +311,45 @@ func TestPromptKitScopeDiffAndCatalogsUseSelectedCampaign(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposedCatalogsAndSmokeUseInjectedClientOnlyForInference(t *testing.T) {
|
||||
calls := 0
|
||||
app, err := newAppWithLLMClient(&exampleLLMClient{calls: &calls})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
commands := [][]string{
|
||||
{"promptkit", "list", "backends", "--campaign", "spring"},
|
||||
{"promptkit", "list", "namespaces", "--campaign", "spring"},
|
||||
{"promptkit", "list", "collections", "--campaign", "spring"},
|
||||
{"promptkit", "list", "prompts", "--namespace", "field-notes", "--campaign", "spring"},
|
||||
{"promptkit", "show", "prompt", "briefing", "--campaign", "spring"},
|
||||
{"promptkit", "requirements", "show", "briefing", "--campaign", "spring"},
|
||||
}
|
||||
for _, args := range commands {
|
||||
var output bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeSuccess {
|
||||
t.Errorf("metadata command %q outcome = %#v; output %q", args, outcome, output.String())
|
||||
}
|
||||
if output.Len() == 0 {
|
||||
t.Errorf("metadata command %q produced no report", args)
|
||||
}
|
||||
}
|
||||
if calls != 0 {
|
||||
t.Fatalf("metadata and requirements commands invoked the LLM client %d times", calls)
|
||||
}
|
||||
|
||||
var output bytes.Buffer
|
||||
outcome := app.Execute(context.Background(), []string{"promptkit", "test", "all", "--campaign", "spring", "--prompt", "briefing", "--profile", "spring"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||
if output.Len() == 0 || !bytes.Contains(output.Bytes(), []byte("tests:")) {
|
||||
t.Fatalf("aggregate smoke report missing: outcome=%#v output=%q", outcome, output.String())
|
||||
}
|
||||
if calls == 0 {
|
||||
t.Fatalf("smoke command did not use the injected LLM client: outcome=%#v output=%q", outcome, output.String())
|
||||
}
|
||||
}
|
||||
|
||||
func composedReportValue(t *testing.T, data []byte, category string, fields ...string) *yaml.Node {
|
||||
t.Helper()
|
||||
var document yaml.Node
|
||||
|
||||
Reference in New Issue
Block a user