Cut over public engine construction to ConfigKit sources
This commit is contained in:
@@ -21,54 +21,62 @@ programs.
|
||||
|
||||
## Construct An Engine
|
||||
|
||||
Create an engine with
|
||||
[`NewEngine`](../../engine.go). A directory-backed setup supplies a prompt
|
||||
directory and may supply profile and schema directories:
|
||||
Create an engine with [`NewEngine`](../../engine.go). Select each directory
|
||||
explicitly and place its inputs in an ordered ConfigKit underlay. A group
|
||||
strictly composes its files; later groups can overlay earlier ones:
|
||||
|
||||
```go
|
||||
promptInputs, err := promptkit.DirectoryInputs("prompts")
|
||||
if err != nil { return err }
|
||||
profileInputs, err := promptkit.DirectoryInputs("profiles")
|
||||
if err != nil { return err }
|
||||
engine, err := promptkit.NewEngine(promptkit.Config{
|
||||
PromptDir: "prompts",
|
||||
ProfileDir: "profiles",
|
||||
SchemaDir: "schemas",
|
||||
Sources: promptkit.ConfigurationSources{Underlays: []configkit.Underlay{
|
||||
{Inputs: promptInputs},
|
||||
{Inputs: profileInputs},
|
||||
}},
|
||||
SchemaDir: "schemas",
|
||||
})
|
||||
```
|
||||
|
||||
Options support single-file or `fs.FS` sources, in-memory profiles,
|
||||
engine-scoped backends, and injected artifact or model clients. Consult the
|
||||
[constructor and option GoDoc](../../engine.go) for composition, precedence,
|
||||
validation, and default transport behavior. Source discovery, format
|
||||
validation, and profile precedence are defined by the
|
||||
[framework format reference](../formats.md).
|
||||
For a single highest-priority document, set `Sources.Main` to a
|
||||
`*configkit.Input`; omit it for ordinary layered construction. A single file
|
||||
can use `configkit.File` directly. `ProfileSource`, `BackendSource`, and raw
|
||||
`configkit.Mapping` supply programmatic definitions. Schema and runtime
|
||||
collaborator options remain available. Construction validates the final
|
||||
effective configuration and captures referenced prompt content; construct a
|
||||
new engine to observe configuration edits. See the [constructor GoDoc](../../engine.go)
|
||||
for the exact public contract and the [framework format reference](../formats.md)
|
||||
for definition fields.
|
||||
|
||||
## Supply Embedded Application Defaults
|
||||
|
||||
Use `WithFallbackProfileFS` when an application packages profile definitions
|
||||
that should apply unless an operator provides an ordinary configured profile
|
||||
with the same ID. For example, an application can embed its defaults while
|
||||
continuing to use `ProfileDir` for operator overrides:
|
||||
Use `FilesystemInputs` for embedded defaults and `DirectoryInputs` for an
|
||||
explicitly chosen operator directory. A later programmatic group can override
|
||||
both. No directory name has built-in precedence:
|
||||
|
||||
```go
|
||||
//go:embed profiles/*.yaml
|
||||
var applicationProfiles embed.FS
|
||||
|
||||
engine, err := promptkit.NewEngine(promptkit.Config{
|
||||
PromptDir: "prompts",
|
||||
ProfileDir: operatorProfileDir,
|
||||
},
|
||||
promptkit.WithFallbackProfileFS(applicationProfiles, "profiles"),
|
||||
)
|
||||
defaults, err := promptkit.FilesystemInputs("embedded defaults", applicationFS, "config")
|
||||
if err != nil { return err }
|
||||
operator, err := promptkit.DirectoryInputs(operatorDirectory)
|
||||
if err != nil { return err }
|
||||
profile := promptkit.ProfileSource("application choices", map[string]promptkit.ProfileConfig{
|
||||
"summary": {Model: &selectedModel},
|
||||
})
|
||||
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
||||
Underlays: []configkit.Underlay{
|
||||
{Inputs: defaults},
|
||||
{Inputs: operator},
|
||||
{Inputs: []configkit.Input{{Source: profile}}},
|
||||
},
|
||||
}})
|
||||
```
|
||||
|
||||
Keep application-owned profile IDs and definitions in the embedded source.
|
||||
Use the ordinary configured profile source for operator overrides. Leave
|
||||
`operatorProfileDir` empty when the operator did not configure an override
|
||||
directory; a non-empty path names an authoritative higher-precedence source,
|
||||
so an unavailable or unreadable directory is an error rather than a reason to
|
||||
fall back. The
|
||||
[framework format reference](../formats.md#source-and-profile-precedence)
|
||||
owns the exact profile format and lookup order; the
|
||||
[`WithFallbackProfileFS` GoDoc](../../engine.go) owns its option contract and
|
||||
validation rules.
|
||||
Only add the operator group when the application selects that directory; an
|
||||
explicitly selected missing or unreadable root is an error. Each helper finds
|
||||
`.yaml` and `.yml` files recursively and disables imports by default. The
|
||||
caller may enable imports on individual inputs. A nil `Sources.Main` is an
|
||||
empty main source, not another directory lookup.
|
||||
|
||||
## Inspect A Prompt Before Preparation
|
||||
|
||||
@@ -268,8 +276,8 @@ result, err := engine.Run(ctx, request)
|
||||
|
||||
These messages are already rendered: Promptkit does not template or resolve
|
||||
files in them, and they can contain sensitive model output or application
|
||||
feedback. Promptkit remains stateless; every `Run` call re-resolves its current
|
||||
sources and the application owns any semantic retry budget. When a
|
||||
feedback. Every `Run` call uses the engine's captured configuration; the
|
||||
application owns any semantic retry budget. When a
|
||||
pre-execution equality check is required, use `PrepareExecution` and compare
|
||||
its opaque rendered-prompt hash before invoking `RunPrepared`.
|
||||
|
||||
@@ -282,12 +290,12 @@ replace execution settings or the complete output contract.
|
||||
The [public value GoDoc](../../types.go) defines nil, empty, zero, replacement,
|
||||
copy, and credential behavior. The
|
||||
[framework format reference](../formats.md) defines how those request values
|
||||
interact with prompt definitions, file-backed and application fallback
|
||||
profiles, built-ins, schemas, and framework defaults.
|
||||
interact with prompt definitions, layered profiles, built-ins, schemas, and
|
||||
framework defaults.
|
||||
|
||||
For programmatic profiles,
|
||||
[`OpenAICompatibleProfile`](../../profiles.go) converts ordinary
|
||||
OpenAI-compatible settings into a value accepted by `WithProfiles`.
|
||||
For programmatic profiles, use [`ProfileSource`](../../profiles.go) with keyed
|
||||
`ProfileConfig` values. Nil fields are omitted; pointers preserve explicit
|
||||
zero, false, and blank values. Raw `configkit.Mapping` expresses explicit null.
|
||||
|
||||
### Choose A Profile Ownership Model
|
||||
|
||||
@@ -319,18 +327,19 @@ the application ID while inheriting the built-in target. The child can override
|
||||
only the setting it owns:
|
||||
|
||||
```go
|
||||
promptkit.WithProfiles(promptkit.Profile{
|
||||
ID: "weather-light",
|
||||
BaseProfileID: "promptkit-openrouter-standard",
|
||||
ReasoningEffort: "high",
|
||||
profile := promptkit.ProfileSource("application profiles", map[string]promptkit.ProfileConfig{
|
||||
"weather-light": {
|
||||
Extends: &standardID,
|
||||
ReasoningEffort: &high,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Select `weather-light` in a prompt or `RunRequest.ProfileID`; it remains the
|
||||
reported selected profile. See the [profile inheritance format
|
||||
reference](../formats.md#profile-inheritance) and the
|
||||
[`Profile` GoDoc](../../types.go) for exact lookup, merging, and validation
|
||||
behavior. If the base is an external profile, apply the volatility guidance
|
||||
Place `profile` in a ConfigKit underlay, with `standardID` set to
|
||||
`"promptkit-openrouter-standard"` and `high` to `"high"`. Select
|
||||
`weather-light` in a prompt or `RunRequest.ProfileID`; it remains the
|
||||
reported selected profile. Definition IDs and inheritance references are exact
|
||||
keys. If the base is an external profile, apply the volatility guidance
|
||||
above when deciding whether to use an application-owned complete definition
|
||||
instead.
|
||||
|
||||
@@ -355,9 +364,8 @@ if prepared.SelectedBackendID != promptkit.BackendRakestrawHome {
|
||||
}
|
||||
```
|
||||
|
||||
Do not register `rakestrawhome` manually. When adopting this built-in, remove
|
||||
an existing `WithBackend` registration with that exact ID; retaining it causes
|
||||
the intentional duplicate-ID configuration error. Direct request credentials
|
||||
The maintained catalog supplies this backend, and callers may overlay its
|
||||
effective configuration through a later ConfigKit source. Direct request credentials
|
||||
and runtime endpoint overrides remain supported under their ordinary GoDoc and
|
||||
format contracts.
|
||||
|
||||
@@ -424,19 +432,15 @@ Choose the smallest configuration that fits how the endpoint will be reused.
|
||||
|
||||
#### Use An Endpoint-Only Profile
|
||||
|
||||
Put the endpoint directly on an in-memory profile when only that profile needs
|
||||
Put the endpoint directly on a programmatic profile when only that profile needs
|
||||
it and shared backend identity or capacity policy is unnecessary:
|
||||
|
||||
```go
|
||||
engine, err := promptkit.NewEngine(promptkit.Config{
|
||||
PromptDir: "prompts",
|
||||
},
|
||||
promptkit.WithProfiles(promptkit.Profile{
|
||||
ID: "local-summary",
|
||||
Endpoint: "http://localhost:8000/v1",
|
||||
Model: "example-model",
|
||||
}),
|
||||
)
|
||||
endpoint, model := "http://localhost:8000/v1", "example-model"
|
||||
profile := promptkit.ProfileSource("local profile", map[string]promptkit.ProfileConfig{
|
||||
"local-summary": {Endpoint: &endpoint, Model: &model},
|
||||
})
|
||||
// Add configkit.Input{Source: profile} to a caller underlay.
|
||||
```
|
||||
|
||||
Endpoint-only profiles have an empty backend ID and remain unrestricted by
|
||||
@@ -448,59 +452,44 @@ Use `LocalBackend` when profiles should share the conventional `local`
|
||||
identity, endpoint, and concurrency limit:
|
||||
|
||||
```go
|
||||
engine, err := promptkit.NewEngine(promptkit.Config{
|
||||
PromptDir: "prompts",
|
||||
},
|
||||
promptkit.WithBackend(
|
||||
promptkit.LocalBackend("http://localhost:8000/v1", 2),
|
||||
),
|
||||
promptkit.WithProfiles(promptkit.Profile{
|
||||
ID: "local-summary",
|
||||
BackendID: promptkit.BackendLocal,
|
||||
Model: "example-model",
|
||||
}),
|
||||
)
|
||||
backendID, model := promptkit.BackendLocal, "example-model"
|
||||
backend := promptkit.BackendSource("local backend", map[string]promptkit.BackendConfig{
|
||||
backendID: promptkit.LocalBackend("http://localhost:8000/v1", 2),
|
||||
})
|
||||
profile := promptkit.ProfileSource("local profile", map[string]promptkit.ProfileConfig{
|
||||
"local-summary": {Backend: &backendID, Model: &model},
|
||||
})
|
||||
// Add both sources as inputs in a caller underlay.
|
||||
```
|
||||
|
||||
The helper is explicit: it does not pre-register a backend or read environment
|
||||
variables. Supplying a positive limit leaves queue capacity omitted, so normal
|
||||
backend registration selects the existing default waiting capacity of 1024.
|
||||
The returned value still enters the engine through `WithBackend`.
|
||||
The helper does not choose an ID or read environment variables. A positive
|
||||
concurrency limit with omitted queue capacity selects the default waiting
|
||||
capacity of 1024.
|
||||
|
||||
#### Configure A Complete Backend
|
||||
|
||||
Use a keyed `Backend` value for authentication, extra request parameters, an
|
||||
Use a keyed `BackendConfig` value for authentication, extra request parameters, an
|
||||
explicit queue capacity, a custom ID, or multiple local endpoints:
|
||||
|
||||
```go
|
||||
noWaiting := 0
|
||||
engine, err := promptkit.NewEngine(promptkit.Config{
|
||||
PromptDir: "prompts",
|
||||
},
|
||||
promptkit.WithBackend(promptkit.Backend{
|
||||
ID: "local-gpu",
|
||||
Endpoint: "http://gpu-host:8000/v1",
|
||||
APIKeyEnv: "LOCAL_GPU_API_KEY",
|
||||
ExtraParams: map[string]any{"provider_option": "enabled"},
|
||||
ConcurrencyLimit: 2,
|
||||
QueueCapacity: &noWaiting,
|
||||
}),
|
||||
promptkit.WithProfiles(promptkit.Profile{
|
||||
ID: "gpu-summary",
|
||||
BackendID: "local-gpu",
|
||||
Model: "example-model",
|
||||
}),
|
||||
)
|
||||
endpoint, keyName, limit, noWaiting := "http://gpu-host:8000/v1", "LOCAL_GPU_API_KEY", 2, 0
|
||||
backend := promptkit.BackendSource("GPU backend", map[string]promptkit.BackendConfig{
|
||||
"local-gpu": {
|
||||
Endpoint: &endpoint, APIKeyEnv: &keyName,
|
||||
ConcurrencyLimit: &limit, QueueCapacity: &noWaiting,
|
||||
ExtraParams: map[string]any{"provider_option": "enabled"},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Use distinct custom IDs when registering multiple local endpoints.
|
||||
Registrations belong to one engine and custom IDs cannot replace built-ins.
|
||||
The [`Backend`, `LocalBackend`, and `WithBackend` GoDoc](../../backends.go)
|
||||
defines exact construction, validation, copying, uniqueness, concurrency, and
|
||||
request-default behavior.
|
||||
Place `backend` in a caller underlay and select `"local-gpu"` from a
|
||||
`ProfileConfig.Backend` pointer or a keyed file profile. All backend
|
||||
contributions use the same ConfigKit layering rules, including overlays of
|
||||
maintained definitions. The [`BackendConfig` and `LocalBackend` GoDoc](../../backends.go)
|
||||
defines exact omission and construction behavior.
|
||||
|
||||
Both file-backed and in-memory profiles select a registration through
|
||||
`backend` or `Profile.BackendID`. Profile and request endpoint overrides retain
|
||||
Both file-backed and programmatic profiles select a backend through
|
||||
`backend` or `ProfileConfig.Backend`. Profile and request endpoint overrides retain
|
||||
that routing and capacity identity. `PreparedRun.SelectedBackendID`,
|
||||
`RunResult.SelectedBackendID`, and the effective `ExecutionTarget.BackendID`
|
||||
expose it to consumers and injected model clients. Endpoint-only profiles
|
||||
@@ -508,7 +497,7 @@ remain supported and expose an empty backend ID.
|
||||
|
||||
### Limit Backend Concurrency
|
||||
|
||||
Set `Backend.ConcurrencyLimit` when a backend needs protection from too many
|
||||
Set `BackendConfig.ConcurrencyLimit` when a backend needs protection from too many
|
||||
simultaneous model calls. Leaving `QueueCapacity` nil, as in the local-backend
|
||||
example above, selects the default waiting capacity of 1024.
|
||||
|
||||
@@ -517,24 +506,23 @@ zero:
|
||||
|
||||
```go
|
||||
noWaiting := 0
|
||||
backend := promptkit.Backend{
|
||||
ID: "local-gpu",
|
||||
Endpoint: "http://gpu-host:8000/v1",
|
||||
ConcurrencyLimit: 2,
|
||||
QueueCapacity: &noWaiting,
|
||||
backend := promptkit.BackendConfig{
|
||||
Endpoint: &endpoint,
|
||||
ConcurrencyLimit: &limit,
|
||||
QueueCapacity: &noWaiting,
|
||||
}
|
||||
```
|
||||
|
||||
The pointer distinguishes an explicit zero from omission. Keep using keyed
|
||||
`Backend` literals so additive configuration fields remain source-compatible.
|
||||
The pointer distinguishes an explicit zero from omission. Use keyed
|
||||
`BackendConfig` literals inside `BackendSource` for programmatic definitions.
|
||||
Capacity belongs to one engine and the selected backend ID; endpoint-only
|
||||
profiles and custom backends without a configured limit remain unrestricted.
|
||||
Exact validation, defaulting, ownership, and concurrency semantics belong to
|
||||
the [`Backend` GoDoc](../../backends.go).
|
||||
the [`BackendConfig` GoDoc](../../backends.go).
|
||||
|
||||
## Credentials
|
||||
|
||||
File-backed profiles name an environment variable; in-memory profiles can
|
||||
File-backed and programmatic profiles can name an environment variable or
|
||||
require a direct request key. Direct keys are request-scoped and are excluded
|
||||
from supported JSON values and the package's `String` and `GoString`
|
||||
summaries. The exact precedence and redaction guarantees belong to
|
||||
@@ -568,12 +556,13 @@ the application policy that motivated the injection.
|
||||
|
||||
Use `errors.Is` with the
|
||||
[public error sentinels and operation GoDoc](../../engine.go). The declarations
|
||||
distinguish invalid construction, invalid requests, absent sources,
|
||||
source-loading failures, collaborator failures, and operational validation
|
||||
distinguish invalid construction, invalid requests, absent selections,
|
||||
collaborator failures, and operational validation
|
||||
failures. Specific request conditions may also match the broader
|
||||
`ErrInvalidRequest`, and injected collaborator identities are preserved where
|
||||
documented. Invalid or duplicate backend registrations match
|
||||
`ErrInvalidConfig`; selecting an unknown backend matches `ErrProfileLoad`.
|
||||
documented. Invalid effective definitions, including unknown backend
|
||||
references, match `ErrInvalidConfig` during construction. ConfigKit load
|
||||
errors remain available through `errors.As`.
|
||||
|
||||
When a limited backend has admitted all active and waiting calls, handle
|
||||
`ErrCapacityExceeded` separately from request errors and provider failures:
|
||||
|
||||
@@ -465,6 +465,8 @@ pass against released modules with `GOWORK=off` and no replacements.
|
||||
|
||||
## Stage 7 — Cut Over Engine Assembly And Public Construction
|
||||
|
||||
**Completed:** 2026-09-30.
|
||||
|
||||
Scope: `engine.go`, configuration facade/helpers, `internal/engine`, facade
|
||||
adapters, and migration of their direct test setup.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user