Update the composed PromptKit consumer
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Shared reporting and PromptKit v0.20.0 implementation plan
|
# Shared reporting and PromptKit v0.20.0 implementation plan
|
||||||
|
|
||||||
Status: implementation in progress; Stages 1–21 complete.
|
Status: implementation in progress; Stages 1–22 complete.
|
||||||
|
|
||||||
## Scope, authority, and execution
|
## Scope, authority, and execution
|
||||||
|
|
||||||
@@ -784,6 +784,10 @@ do not initialize application runtime state.
|
|||||||
|
|
||||||
## Stage 22: Composed consumer and application-owned flag reuse
|
## Stage 22: Composed consumer and application-owned flag reuse
|
||||||
|
|
||||||
|
**Complete.** The composed consumer exercises request preview, namespaces,
|
||||||
|
reusable flags, independent overrides, and failure evidence with invocation-local
|
||||||
|
caching. The root, ckconfig, and ckprompt workspace suites passed with Go 1.27.0.
|
||||||
|
|
||||||
Update the maintained [composed consumer](../../integrations/ckprompt/examples/composed)
|
Update the maintained [composed consumer](../../integrations/ckprompt/examples/composed)
|
||||||
to install `ckprompt.Declarations`, explicit empty native namespace defaults
|
to install `ckprompt.Declarations`, explicit empty native namespace defaults
|
||||||
(enabling root defaults plus opt-in selectors), request preparation, extended
|
(enabling root defaults plus opt-in selectors), request preparation, extended
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
# Composed consumer
|
# Composed consumer
|
||||||
|
|
||||||
This example is part of the ckprompt module and uses core v0.3.0, ckconfig
|
This example is part of the ckprompt module and uses core v0.3.0, ckconfig
|
||||||
v0.3.0, ConfigKit v1.1.0, and PromptKit v0.18.0. It requires Go 1.27.0.
|
v0.3.0, ConfigKit v1.1.0, and PromptKit v0.20.0. It requires Go 1.27.0.
|
||||||
|
The current work uses the aligned local workspace until the required core and
|
||||||
|
ckconfig releases are published.
|
||||||
|
|
||||||
This example composes application configuration, PromptKit configuration,
|
This example composes application configuration, PromptKit configuration,
|
||||||
PromptKit comparison and catalogs, definition and requirements inspection, and
|
PromptKit comparison and catalogs, definition and requirements inspection, and
|
||||||
@@ -12,6 +14,7 @@ go run . config scopes
|
|||||||
go run . config validate --scope all --campaign spring --limit=0
|
go run . config validate --scope all --campaign spring --limit=0
|
||||||
go run . config diff --scope app --config fixtures/before.yml --against-config fixtures/after.yml
|
go run . config diff --scope app --config fixtures/before.yml --against-config fixtures/after.yml
|
||||||
go run . config diff --scope app --config fixtures/before.yml --against-config fixtures/after.yml --campaign spring --limit=0
|
go run . config diff --scope app --config fixtures/before.yml --against-config fixtures/after.yml --campaign spring --limit=0
|
||||||
|
go run . config diff --scope app --campaign spring --limit=0 --against-campaign autumn --against-limit=0 --set '/campaigns/spring/model="left"' --against-set '/campaigns/spring/model="right"' --details
|
||||||
go run . config diff --scope promptkit --config fixtures/before.yml --against-config fixtures/after.yml --campaign spring --against-campaign spring
|
go run . config diff --scope promptkit --config fixtures/before.yml --against-config fixtures/after.yml --campaign spring --against-campaign spring
|
||||||
go run . promptkit list prompts --config fixtures/before.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 profiles --config fixtures/after.yml --campaign autumn --format yaml
|
||||||
@@ -22,6 +25,8 @@ go run . list collections --campaign spring
|
|||||||
go run . pk list bundles --campaign spring
|
go run . pk list bundles --campaign spring
|
||||||
go run . promptkit list prompts --namespace field-notes --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 prompt briefing --campaign spring --format yaml
|
||||||
|
go run . promptkit show prompt briefing --campaign spring --prompt-namespace field-notes
|
||||||
|
go run . promptkit show prepared briefing --campaign spring --input topic=sprint --profile spring --extra-params '{"campaign":"preview"}'
|
||||||
go run . pk show prompt briefing --campaign spring --fmt yaml
|
go run . pk show prompt briefing --campaign spring --fmt yaml
|
||||||
go run . promptkit show profile spring --campaign spring
|
go run . promptkit show profile spring --campaign spring
|
||||||
go run . promptkit requirements show briefing --campaign spring
|
go run . promptkit requirements show briefing --campaign spring
|
||||||
@@ -34,6 +39,7 @@ go run . smoke text_inference --campaign spring --llm-prompt briefing --llm-prof
|
|||||||
go run . promptkit test all --campaign spring --llm-prompt briefing --llm-profile spring
|
go run . promptkit test all --campaign spring --llm-prompt briefing --llm-profile spring
|
||||||
go run . pk --hlep
|
go run . pk --hlep
|
||||||
go run . session restore session-42 --campaign spring
|
go run . session restore session-42 --campaign spring
|
||||||
|
go run . request inspect --llm-prompt briefing --llm-profile= --llm-model example --llm-input-text topic=sprint --format json
|
||||||
```
|
```
|
||||||
|
|
||||||
`newApp` installs application-owned command and flag aliases alongside the
|
`newApp` installs application-owned command and flag aliases alongside the
|
||||||
@@ -51,8 +57,11 @@ Bash, then load a shell script as described in the
|
|||||||
in `composed-example config show --config=fixtures/`. The already-entered `pk`
|
in `composed-example config show --config=fixtures/`. The already-entered `pk`
|
||||||
alias selects the PromptKit command context without loading an engine.
|
alias selects the PromptKit command context without loading an engine.
|
||||||
|
|
||||||
The composition calls `ckconfig.Command(suite)` and `ckprompt.Command(definitions)`
|
The composition installs `ckconfig.Command(suite)` and the complete
|
||||||
directly in a `[]commandkit.Declaration` with root-installed routing rules.
|
`ckprompt.Declarations(definitions)` in a `[]commandkit.Declaration` with
|
||||||
|
root-installed routing rules. The PromptKit declarations include scoped aliases;
|
||||||
|
the application-owned `request inspect` command reuses the flag collections
|
||||||
|
with their canonical names and does not install those aliases.
|
||||||
Group children remain canonical `Command` values. For command-only slices,
|
Group children remain canonical `Command` values. For command-only slices,
|
||||||
convert each `Command` to `Declaration` before appending aliases. The
|
convert each `Command` to `Declaration` before appending aliases. The
|
||||||
[consumer guide](../../../../docs/consumers/guide.md#application-owned-aliases)
|
[consumer guide](../../../../docs/consumers/guide.md#application-owned-aliases)
|
||||||
@@ -79,10 +88,13 @@ Prompt inspection uses an ID only. The default PromptKit bundle also contributes
|
|||||||
profiles and a backend to the engine inventory and source reports. The synthetic `field-notes` collection
|
profiles and a backend to the engine inventory and source reports. The synthetic `field-notes` collection
|
||||||
provides one backend, profile, and prompt for collection discovery and native
|
provides one backend, profile, and prompt for collection discovery and native
|
||||||
namespace filtering. The example's shared run cache keys successful results by
|
namespace filtering. The example's shared run cache keys successful results by
|
||||||
the complete supplied configuration/overlay selection and the supplied
|
the complete supplied configuration/overlay selection, CLI overrides, and the
|
||||||
campaign, including presence. The campaign override is applied through ConfigKit
|
supplied campaign, including presence. The campaign override and application
|
||||||
before loading. These are choices for this example's loader and do not define a
|
scope's `--set` records are applied through ConfigKit before loading. The
|
||||||
CommandKit cache policy.
|
PromptKit configuration scope does not enable `--set` because its released
|
||||||
|
configuration loader has no override handoff. An aggregate operation therefore
|
||||||
|
rejects overrides when that scope is selected. These are choices for this
|
||||||
|
example's loaders and do not define a CommandKit cache policy.
|
||||||
|
|
||||||
Smoke testing is explicitly wired with `ckprompt.WithSmokeTesting` and its
|
Smoke testing is explicitly wired with `ckprompt.WithSmokeTesting` and its
|
||||||
optional request builder is nil, so CommandKit forwards the selected native
|
optional request builder is nil, so CommandKit forwards the selected native
|
||||||
@@ -114,17 +126,19 @@ not execute the prompt or establish input, credential, or execution readiness.
|
|||||||
See the [CLI reference](../../../../docs/cli.md#promptkit-requirements) for the
|
See the [CLI reference](../../../../docs/cli.md#promptkit-requirements) for the
|
||||||
command and report contract.
|
command and report contract.
|
||||||
|
|
||||||
The validation commands exercise concrete PromptKit request preparation.
|
The validation and preview commands exercise concrete PromptKit request preparation.
|
||||||
The first uses the builder's campaign-specific default topic variable and the
|
The first uses the builder's campaign-specific default topic variable and the
|
||||||
operator's topic file. The second also selects the notes branch and supplies its
|
operator's topic file. The second also selects the notes branch and supplies its
|
||||||
file. Both succeed and report only `prompt_id` and `valid: true`; they do not
|
file. Validation reports only `prompt_id` and `valid: true`; preview returns the
|
||||||
execute the prompt. The final command intentionally fails because the prompt's
|
prepared request, including rendered messages and effective provider settings.
|
||||||
|
Neither executes the prompt. The validation command without inputs intentionally
|
||||||
|
fails because the prompt's
|
||||||
required `topic` artifact is not supplied. The builder defaults variables from
|
required `topic` artifact is not supplied. The builder defaults variables from
|
||||||
the selected campaign but leaves artifact bindings to the operator. The
|
the selected campaign but leaves artifact bindings to the operator. The
|
||||||
`requirements show` commands remain discovery reports: partial discovery can
|
`requirements show` commands remain discovery reports: partial discovery can
|
||||||
succeed even when a concrete request would fail validation.
|
succeed even when a concrete request would fail validation.
|
||||||
|
|
||||||
Run `GOWORK=off go test ./...` from this example directory to check it against
|
Use the aligned local workspace to test the three current sources, as described in
|
||||||
the module's declared dependencies. A local workspace can instead select the
|
the [developer guide](../../../../docs/development.md#documentation-validation).
|
||||||
three current sources, as described in the
|
Once released dependency requirements are aligned, `GOWORK=off go test ./...`
|
||||||
[developer guide](../../../../docs/development.md#documentation-validation).
|
from this example directory checks it against its declared dependencies.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"go.maximumdirect.net/ckconfig"
|
"go.maximumdirect.net/ckconfig"
|
||||||
@@ -31,16 +32,16 @@ type inspectionInput struct {
|
|||||||
Limit commandkit.Supplied[int]
|
Limit commandkit.Supplied[int]
|
||||||
}
|
}
|
||||||
|
|
||||||
type applicationCacheKey struct {
|
type applicationCacheEntry struct {
|
||||||
config commandkit.Supplied[string]
|
selection ckconfig.Selection
|
||||||
overlay commandkit.Supplied[string]
|
campaign commandkit.Supplied[string]
|
||||||
campaign commandkit.Supplied[string]
|
result *configkit.Result[appConfiguration]
|
||||||
}
|
}
|
||||||
|
|
||||||
// runCache belongs to one invocation and is shared only where the application
|
// runCache belongs to one invocation and is shared only where the application
|
||||||
// chooses to pass it to its loaders.
|
// chooses to pass it to its loaders.
|
||||||
type runCache struct {
|
type runCache struct {
|
||||||
applications map[applicationCacheKey]*configkit.Result[appConfiguration]
|
applications []applicationCacheEntry
|
||||||
llmClient promptkit.LLMClient
|
llmClient promptkit.LLMClient
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +61,12 @@ type restoreInput struct {
|
|||||||
Session string
|
Session string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type requestInputs struct {
|
||||||
|
Execution ckprompt.ExecutionInput `json:"execution"`
|
||||||
|
Resources ckprompt.ResourceInput `json:"resources"`
|
||||||
|
Inputs ckprompt.PromptInputs `json:"inputs"`
|
||||||
|
}
|
||||||
|
|
||||||
type session struct {
|
type session struct {
|
||||||
ID string
|
ID string
|
||||||
Campaign string
|
Campaign string
|
||||||
@@ -81,10 +88,11 @@ func loadApplicationConfig(ctx context.Context, selection ckconfig.Selection, in
|
|||||||
if input != nil {
|
if input != nil {
|
||||||
campaignInput = input.Campaign
|
campaignInput = input.Campaign
|
||||||
}
|
}
|
||||||
key := applicationCacheKey{config: selection.Config, overlay: selection.Overlay, campaign: campaignInput}
|
|
||||||
if cache != nil {
|
if cache != nil {
|
||||||
if cached, ok := cache.applications[key]; ok {
|
for _, cached := range cache.applications {
|
||||||
return cached, nil
|
if reflect.DeepEqual(cached.selection, selection) && cached.campaign == campaignInput {
|
||||||
|
return cached.result, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
source := configkit.Mapping("composed application settings", map[string]any{
|
source := configkit.Mapping("composed application settings", map[string]any{
|
||||||
@@ -105,12 +113,10 @@ func loadApplicationConfig(ctx context.Context, selection ckconfig.Selection, in
|
|||||||
if campaignInput.Set {
|
if campaignInput.Set {
|
||||||
options.Overrides = []configkit.Override{{Path: configkit.Keys("default_campaign"), Value: campaignInput.Value, Flag: "--campaign"}}
|
options.Overrides = []configkit.Override{{Path: configkit.Keys("default_campaign"), Value: campaignInput.Value, Flag: "--campaign"}}
|
||||||
}
|
}
|
||||||
|
options.Overrides = append(options.Overrides, selection.Overrides...)
|
||||||
result, err := configkit.Load(source, options)
|
result, err := configkit.Load(source, options)
|
||||||
if err == nil && result != nil && cache != nil {
|
if err == nil && result != nil && cache != nil {
|
||||||
if cache.applications == nil {
|
cache.applications = append(cache.applications, applicationCacheEntry{selection: selection, campaign: campaignInput, result: result})
|
||||||
cache.applications = make(map[applicationCacheKey]*configkit.Result[appConfiguration])
|
|
||||||
}
|
|
||||||
cache.applications[key] = result
|
|
||||||
}
|
}
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
@@ -224,6 +230,7 @@ func newAppWithEngineLoader(
|
|||||||
stateFactory := func() *runCache { return &runCache{llmClient: client} }
|
stateFactory := func() *runCache { return &runCache{llmClient: client} }
|
||||||
|
|
||||||
appScope := ckconfig.FromConfigKit("app", "Application settings.", loadApplicationConfig)
|
appScope := ckconfig.FromConfigKit("app", "Application settings.", loadApplicationConfig)
|
||||||
|
appScope.Features.Overrides = true
|
||||||
engineScope := ckprompt.ConfigScope(engineLoader)
|
engineScope := ckprompt.ConfigScope(engineLoader)
|
||||||
configCommand := ckconfig.Command(ckconfig.Suite[*inspectionInput, *runCache]{
|
configCommand := ckconfig.Command(ckconfig.Suite[*inspectionInput, *runCache]{
|
||||||
NewInput: inputFactory,
|
NewInput: inputFactory,
|
||||||
@@ -246,11 +253,28 @@ func newAppWithEngineLoader(
|
|||||||
definitions.NewState = stateFactory
|
definitions.NewState = stateFactory
|
||||||
definitions.Selectors = []commandkit.Flag[*inspectionInput]{campaignSelector()}
|
definitions.Selectors = []commandkit.Flag[*inspectionInput]{campaignSelector()}
|
||||||
definitions = ckprompt.WithRequestValidation(definitions, engineLoader, buildValidationRequest)
|
definitions = ckprompt.WithRequestValidation(definitions, engineLoader, buildValidationRequest)
|
||||||
|
definitions = ckprompt.WithRequestPreparation(definitions, engineLoader,
|
||||||
|
func(ctx context.Context, seed ckprompt.PreparationRequest, input *inspectionInput, cache *runCache) (promptkit.RunRequest, error) {
|
||||||
|
return buildValidationRequest(ctx, ckprompt.ValidationRequest{Selection: seed.Selection, Request: seed.Request, Namespaces: seed.Namespaces}, input, cache)
|
||||||
|
})
|
||||||
definitions = ckprompt.WithSmokeTesting(definitions, engineLoader, nil)
|
definitions = ckprompt.WithSmokeTesting(definitions, engineLoader, nil)
|
||||||
|
definitions = ckprompt.WithNamespaces(definitions, promptkit.Namespaces{})
|
||||||
if smokeTest != nil {
|
if smokeTest != nil {
|
||||||
definitions.SmokeTest = smokeTest
|
definitions.SmokeTest = smokeTest
|
||||||
}
|
}
|
||||||
definitionDeclarations := ckprompt.Declarations(definitions)
|
definitionDeclarations := ckprompt.Declarations(definitions)
|
||||||
|
resources := ckprompt.NewResourceFlags(func(input *requestInputs) *ckprompt.ResourceInput { return &input.Resources })
|
||||||
|
execution := ckprompt.NewExecutionFlags(func(input *requestInputs) *ckprompt.ExecutionInput { return &input.Execution })
|
||||||
|
inputs := ckprompt.NewPromptInputFlags(func(input *requestInputs) *ckprompt.PromptInputs { return &input.Inputs })
|
||||||
|
requestFlags := append([]commandkit.Flag[requestInputs]{resources.Prompt, resources.Profile}, execution.All()...)
|
||||||
|
requestFlags = append(requestFlags, inputs.All()...)
|
||||||
|
requestInspect := commandkit.NewCommand(commandkit.CommandSpec[requestInputs]{
|
||||||
|
Name: "inspect", Summary: "Inspect application-owned request inputs.", Flags: requestFlags,
|
||||||
|
Run: func(invocation commandkit.Invocation, input requestInputs) commandkit.Outcome {
|
||||||
|
_ = invocation.Reporter().Report(commandkit.Report{Value: input})
|
||||||
|
return commandkit.Success()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
store := map[string]session{
|
store := map[string]session{
|
||||||
"session-42": {ID: "session-42", Campaign: "spring", Summary: "A synthetic planning session."},
|
"session-42": {ID: "session-42", Campaign: "spring", Summary: "A synthetic planning session."},
|
||||||
@@ -294,6 +318,7 @@ func newAppWithEngineLoader(
|
|||||||
configCommand,
|
configCommand,
|
||||||
commandkit.Completion("composed-example"),
|
commandkit.Completion("composed-example"),
|
||||||
commandkit.Group("session", "Manage saved sessions.", restore),
|
commandkit.Group("session", "Manage saved sessions.", restore),
|
||||||
|
commandkit.Group("request", "Inspect application-owned request inputs.", requestInspect),
|
||||||
commandkit.Alias("list collections", "promptkit list collections"),
|
commandkit.Alias("list collections", "promptkit list collections"),
|
||||||
commandkit.Alias("promptkit list bundles", "promptkit list collections"),
|
commandkit.Alias("promptkit list bundles", "promptkit list collections"),
|
||||||
commandkit.Alias("pk", "promptkit"),
|
commandkit.Alias("pk", "promptkit"),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -101,6 +102,159 @@ func TestApplicationCacheDoesNotRetainFailedLoads(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplicationCacheDistinguishesOverrides(t *testing.T) {
|
||||||
|
cache := &runCache{}
|
||||||
|
selection := func(value string) ckconfig.Selection {
|
||||||
|
return ckconfig.Selection{Overrides: []configkit.Override{{Path: configkit.Keys("campaigns", "spring", "model"), Value: value, Flag: "--set"}}}
|
||||||
|
}
|
||||||
|
left, err := loadApplicationConfig(context.Background(), selection("left"), nil, cache)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
right, err := loadApplicationConfig(context.Background(), selection("right"), nil, cache)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
again, err := loadApplicationConfig(context.Background(), selection("left"), nil, cache)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if left == right || left != again || left.Config.Campaigns["spring"].Model != "left" || right.Config.Campaigns["spring"].Model != "right" {
|
||||||
|
t.Fatalf("override selections reused the wrong result: left=%#v right=%#v", left.Config, right.Config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposedIndependentOverridesAndFailureEvidence(t *testing.T) {
|
||||||
|
app, err := newApp()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var output bytes.Buffer
|
||||||
|
args := []string{"config", "diff", "--scope", "app", "--campaign", "spring", "--limit=0", "--against-campaign", "autumn", "--against-limit=0", "--set", `/campaigns/spring/model="left"`, "--against-set", `/campaigns/spring/model="right"`, "--details", "--format", "json"}
|
||||||
|
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||||
|
if outcome.Kind != commandkit.OutcomeSuccess {
|
||||||
|
t.Fatalf("comparison: %#v, %s", outcome, output.String())
|
||||||
|
}
|
||||||
|
var report struct {
|
||||||
|
Differences []struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Before struct {
|
||||||
|
Value any `json:"value"`
|
||||||
|
} `json:"before"`
|
||||||
|
After struct {
|
||||||
|
Value any `json:"value"`
|
||||||
|
} `json:"after"`
|
||||||
|
} `json:"differences"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(output.Bytes(), &report); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
changes := map[string][2]any{}
|
||||||
|
for _, difference := range report.Differences {
|
||||||
|
changes[difference.Path] = [2]any{difference.Before.Value, difference.After.Value}
|
||||||
|
}
|
||||||
|
if changes["/campaigns/spring/model"] != [2]any{"left", "right"} || changes["/default_campaign"] != [2]any{"spring", "autumn"} {
|
||||||
|
t.Fatalf("independent comparison: %#v", changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
output.Reset()
|
||||||
|
outcome = app.Execute(context.Background(), []string{"config", "sources", "--scope", "app", "--config", "fixtures/missing.yml", "--format", "json"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||||
|
var evidence struct {
|
||||||
|
Sources []any `json:"sources"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(output.Bytes(), &evidence); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if outcome.Kind != commandkit.OutcomeOperational || outcome.Cause == nil || len(evidence.Sources) == 0 {
|
||||||
|
t.Fatalf("failed load evidence: %#v, %s", outcome, output.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
outcome = app.Execute(context.Background(), []string{"config", "validate", "--scope", "app", "--campaign", "spring", "--limit=0", "--set", `/default_campaign="autumn"`}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if outcome.Kind != commandkit.OutcomeOperational || outcome.Cause == nil {
|
||||||
|
t.Fatalf("overlapping native overrides: %#v", outcome)
|
||||||
|
}
|
||||||
|
loads := 0
|
||||||
|
loader := func(ctx context.Context, selection ckconfig.Selection, input *inspectionInput, cache *runCache) (*promptkit.Engine, error) {
|
||||||
|
loads++
|
||||||
|
return loadPromptEngine(ctx, selection, input, cache)
|
||||||
|
}
|
||||||
|
app, err = newAppWithEngineLoader(&exampleLLMClient{}, loader, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
outcome = app.Execute(context.Background(), []string{"config", "validate", "--scope", "all", "--set", `/default_campaign="spring"`}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if outcome.Kind != commandkit.OutcomeUsage || loads != 0 {
|
||||||
|
t.Fatalf("unsupported aggregate override: %#v", outcome)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposedPreviewAndReusableFlags(t *testing.T) {
|
||||||
|
calls := 0
|
||||||
|
app, err := newAppWithLLMClient(&exampleLLMClient{calls: &calls})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var output bytes.Buffer
|
||||||
|
args := []string{"promptkit", "show", "prepared", "briefing", "--campaign", "spring", "--llm-input-file", "topic=fixtures/topic.txt", "--input", "topic=sprint", "--profile", "spring", "--extra-params", `{"campaign":"preview"}`, "--format", "json"}
|
||||||
|
outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||||
|
if outcome.Kind != commandkit.OutcomeSuccess {
|
||||||
|
t.Fatalf("preview: %#v, %s", outcome, output.String())
|
||||||
|
}
|
||||||
|
var report promptkit.PreparedRun
|
||||||
|
if err := json.Unmarshal(output.Bytes(), &report); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
input := &inspectionInput{Campaign: commandkit.Supplied[string]{Value: "spring", Set: true}}
|
||||||
|
engine, err := loadPromptEngine(context.Background(), ckconfig.Selection{}, input, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
request, err := buildValidationRequest(context.Background(), ckprompt.ValidationRequest{Request: promptkit.RunRequest{PromptID: "briefing", ProfileID: "spring", Inputs: map[string]promptkit.ArtifactRef{"topic": promptkit.Inline("sprint")}, Execution: &promptkit.ExecutionTargetOverride{ExtraParams: map[string]any{"campaign": "preview"}}}}, input, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
native, err := engine.Prepare(context.Background(), request)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if report.PromptID != native.PromptID || !reflect.DeepEqual(report.Messages, native.Messages) || !reflect.DeepEqual(report.EffectiveModelParams, native.EffectiveModelParams) || calls != 0 {
|
||||||
|
t.Fatalf("preview diverged from native result or called model: %#v", report)
|
||||||
|
}
|
||||||
|
|
||||||
|
output.Reset()
|
||||||
|
outcome = app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--prompt-namespace", "field-notes", "--format", "json"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||||
|
var namespaced promptkit.PromptInspection
|
||||||
|
if err := json.Unmarshal(output.Bytes(), &namespaced); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
view, err := engine.WithNamespaces(promptkit.Namespaces{Prompt: "field-notes"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wantPrompt, err := view.InspectPrompt(context.Background(), "briefing")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if outcome.Kind != commandkit.OutcomeSuccess || namespaced.PromptID != wantPrompt.PromptID {
|
||||||
|
t.Fatalf("namespace selection: %#v, %#v", outcome, namespaced)
|
||||||
|
}
|
||||||
|
|
||||||
|
output.Reset()
|
||||||
|
args = []string{"request", "inspect", "--llm-prompt", "briefing", "--llm-profile=", "--llm-model", "sample", "--llm-input-text", "topic=hello", "--format", "json"}
|
||||||
|
outcome = app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||||
|
var owned requestInputs
|
||||||
|
if err := json.Unmarshal(output.Bytes(), &owned); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if outcome.Kind != commandkit.OutcomeSuccess || !owned.Resources.Profile.Set || owned.Resources.Profile.Value != "" || owned.Execution.Execution.Model != "sample" || owned.Inputs.Inputs["topic"] != promptkit.Inline("hello") {
|
||||||
|
t.Fatalf("application flags: %#v, %#v", outcome, owned)
|
||||||
|
}
|
||||||
|
outcome = app.Execute(context.Background(), []string{"request", "inspect", "--model", "sample"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||||
|
if outcome.Kind != commandkit.OutcomeUsage {
|
||||||
|
t.Fatalf("unexpected application alias: %#v", outcome)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestComposedPromptKitAndDomainCommandsRemainAvailable(t *testing.T) {
|
func TestComposedPromptKitAndDomainCommandsRemainAvailable(t *testing.T) {
|
||||||
app, err := newApp()
|
app, err := newApp()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -266,15 +420,19 @@ func TestBuildValidationRequestDefaultsOnlyAbsentVariables(t *testing.T) {
|
|||||||
if !reflect.DeepEqual(got.Inputs, inputs) || !reflect.DeepEqual(seed.Request.Vars, explicit) {
|
if !reflect.DeepEqual(got.Inputs, inputs) || !reflect.DeepEqual(seed.Request.Vars, explicit) {
|
||||||
t.Fatalf("seed bindings changed: built=%#v seed=%#v", got.Inputs, explicit)
|
t.Fatalf("seed bindings changed: built=%#v seed=%#v", got.Inputs, explicit)
|
||||||
}
|
}
|
||||||
key := applicationCacheKey{config: seed.Selection.Config, overlay: seed.Selection.Overlay, campaign: input.Campaign}
|
var cached *configkit.Result[appConfiguration]
|
||||||
cached := cache.applications[key]
|
for _, entry := range cache.applications {
|
||||||
|
if reflect.DeepEqual(entry.selection, seed.Selection) && entry.campaign == input.Campaign {
|
||||||
|
cached = entry.result
|
||||||
|
}
|
||||||
|
}
|
||||||
if cached == nil {
|
if cached == nil {
|
||||||
t.Fatal("builder did not load application configuration into invocation state")
|
t.Fatal("builder did not load application configuration into invocation state")
|
||||||
}
|
}
|
||||||
if _, err := loadPromptEngine(context.Background(), seed.Selection, input, cache); err != nil {
|
if _, err := loadPromptEngine(context.Background(), seed.Selection, input, cache); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if cache.applications[key] != cached {
|
if len(cache.applications) != 1 || cache.applications[0].result != cached {
|
||||||
t.Fatal("engine loader did not reuse the builder's application configuration")
|
t.Fatal("engine loader did not reuse the builder's application configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,6 +676,11 @@ func TestComposedAliasesReachCanonicalCommands(t *testing.T) {
|
|||||||
if outcome.Kind != commandkit.OutcomeSuccess || output.Len() != 0 || engineLoads != 0 || clientCalls != 0 {
|
if outcome.Kind != commandkit.OutcomeSuccess || output.Len() != 0 || engineLoads != 0 || clientCalls != 0 {
|
||||||
t.Fatalf("quiet aliased help outcome/output/engine loads/client calls = (%#v, %q, %d, %d)", outcome, output.String(), engineLoads, clientCalls)
|
t.Fatalf("quiet aliased help outcome/output/engine loads/client calls = (%#v, %q, %d, %d)", outcome, output.String(), engineLoads, clientCalls)
|
||||||
}
|
}
|
||||||
|
output.Reset()
|
||||||
|
outcome = app.Execute(context.Background(), []string{"version"}, commandkit.Streams{Out: &output, Err: io.Discard})
|
||||||
|
if outcome.Kind != commandkit.OutcomeSuccess || engineLoads != 0 || clientCalls != 0 {
|
||||||
|
t.Fatalf("version outcome/engine loads/client calls = (%#v, %d, %d)", outcome, engineLoads, clientCalls)
|
||||||
|
}
|
||||||
|
|
||||||
for _, args := range [][]string{
|
for _, args := range [][]string{
|
||||||
{"list", "collections", "--campaign", "spring"},
|
{"list", "collections", "--campaign", "spring"},
|
||||||
|
|||||||
Reference in New Issue
Block a user