499 lines
18 KiB
Markdown
499 lines
18 KiB
Markdown
# Package `promptkit`
|
|
|
|
## Purpose
|
|
|
|
This guide helps Go consumers assemble Promptkit and choose the main
|
|
preparation or execution workflow. The declarations and GoDoc in the
|
|
[root package](../../doc.go) own exact field, option, serialization,
|
|
concurrency, ownership, failure, and cancellation semantics. The
|
|
[framework format reference](../formats.md) owns prompt, profile, and schema
|
|
file contracts.
|
|
|
|
Import the package as:
|
|
|
|
```go
|
|
import "gitea.maximumdirect.net/eric/promptkit"
|
|
```
|
|
|
|
The following Go fragments are illustrative and omit surrounding package,
|
|
import, and error-handling code. Use the maintained examples for complete
|
|
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:
|
|
|
|
```go
|
|
engine, err := promptkit.NewEngine(promptkit.Config{
|
|
PromptDir: "prompts",
|
|
ProfileDir: "profiles",
|
|
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).
|
|
|
|
## 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:
|
|
|
|
```go
|
|
//go:embed profiles/*.yaml
|
|
var applicationProfiles embed.FS
|
|
|
|
engine, err := promptkit.NewEngine(promptkit.Config{
|
|
PromptDir: "prompts",
|
|
ProfileDir: operatorProfileDir,
|
|
},
|
|
promptkit.WithFallbackProfileFS(applicationProfiles, "profiles"),
|
|
)
|
|
```
|
|
|
|
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.
|
|
|
|
## Inspect A Prompt Before Preparation
|
|
|
|
Use [`Engine.InspectPrompt`](../../engine.go) to check one configured prompt's
|
|
declared inputs and output workflow without creating placeholder inputs or
|
|
resolving a profile:
|
|
|
|
```go
|
|
inspection, err := engine.InspectPrompt(ctx, "meeting.summary", "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, input := range inspection.Inputs {
|
|
// Compare the declared input with application configuration.
|
|
}
|
|
```
|
|
|
|
Use this configuration-time boundary when the application needs only the
|
|
declared prompt interface. Use `InspectProfile` separately when it must also
|
|
check a configured profile. Use `Prepare` when it needs inputs, schemas, or
|
|
rendered messages, and use prepared execution when that work must remain tied
|
|
to later execution. The method's [GoDoc](../../engine.go) owns exact fields,
|
|
hash, ownership, and error semantics.
|
|
|
|
## Prepare Without Model Execution
|
|
|
|
[`Engine.Prepare`](../../engine.go) resolves the selected prompt and profile,
|
|
loads inputs and any structured-output schema, and renders messages without
|
|
calling a model client. Choose it when the prepared value is the final
|
|
inspection or persistence result and no later execution must be tied to that
|
|
exact snapshot:
|
|
|
|
```go
|
|
prepared, err := engine.Prepare(ctx, promptkit.RunRequest{
|
|
PromptID: "meeting.summary",
|
|
Inputs: map[string]promptkit.ArtifactRef{
|
|
"note": promptkit.Inline("Synthetic meeting notes"),
|
|
},
|
|
})
|
|
```
|
|
|
|
The maintained
|
|
[offline preparation example](../../examples/go-library/prepare/main.go)
|
|
shows a complete runnable setup with a prompt file, in-memory profile, and
|
|
inline input. Exact request requirements and prepared-result fields belong to
|
|
the [`RunRequest` and `PreparedRun` GoDoc](../../types.go).
|
|
|
|
## Prepare Now And Execute The Same Snapshot Later
|
|
|
|
Use [`Engine.PrepareExecution`](../../engine.go) when an application must
|
|
inspect or persist preflight details before deciding whether to start model
|
|
work, while ensuring that later execution uses those exact rendered messages,
|
|
inputs, target settings, and validation resources:
|
|
|
|
```go
|
|
preparedExecution, err := engine.PrepareExecution(ctx, promptkit.RunRequest{
|
|
PromptID: "meeting.summary",
|
|
Inputs: map[string]promptkit.ArtifactRef{
|
|
"note": promptkit.Inline("Synthetic meeting notes"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer preparedExecution.Discard()
|
|
|
|
details := preparedExecution.Details()
|
|
// Inspect or persist an application-selected safe subset of details.
|
|
|
|
result, err := engine.RunPrepared(ctx, preparedExecution)
|
|
```
|
|
|
|
Preparation does not call the model or reserve backend capacity.
|
|
`RunPrepared` executes from the retained snapshot rather than reloading
|
|
consumer sources. The handle is opaque in-process state, while `Details`
|
|
contains rendered content and remains subject to the application's data
|
|
handling policy. The
|
|
[`PreparedExecution` and method GoDoc](../../prepared_execution.go) and
|
|
[engine operation GoDoc](../../engine.go) own exact lifecycle, engine-binding,
|
|
credential, cancellation, timing, and error semantics.
|
|
|
|
## Execute And Validate
|
|
|
|
[`Engine.Run`](../../engine.go) performs the same preparation, invokes the
|
|
configured model client, classifies the generated artifact, and validates the
|
|
content in one call. Choose it when the application does not need a preflight
|
|
boundary tied to the eventual execution. A completed content check may return
|
|
`ValidationFailed` in the result; an operational inability to validate returns
|
|
an error.
|
|
|
|
The maintained
|
|
[offline execution example](../../examples/go-library/run/main.go) injects a
|
|
deterministic model client and exercises `Run` without credentials, network
|
|
access, or paid calls. It is intentionally separate from the preparation
|
|
example so each workflow and its small prompt fixture can be copied and run on
|
|
its own.
|
|
|
|
Use the [`RunResult` and `ValidationResult` GoDoc](../../types.go) for the
|
|
returned data and the `Engine.Run` GoDoc for failure and cancellation
|
|
semantics. The
|
|
[OpenAI-compatible integration contract](../integrations/openai-compatible-chat.md)
|
|
owns the built-in client's outbound HTTP behavior.
|
|
|
|
## Inputs, Profiles, And Overrides
|
|
|
|
Use `File`, `Inline`, or `InlineWithURI` to construct request inputs. A request
|
|
can select a profile explicitly or use the prompt's default profile, and can
|
|
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.
|
|
|
|
For programmatic profiles,
|
|
[`OpenAICompatibleProfile`](../../profiles.go) converts ordinary
|
|
OpenAI-compatible settings into a value accepted by `WithProfiles`.
|
|
|
|
### Alias A Built-In Profile
|
|
|
|
Give an application-owned profile ID a built-in base when prompts should select
|
|
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: "deepseek-4-flash",
|
|
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.
|
|
|
|
### Use The Rakestrawhome Built-In Profile
|
|
|
|
Set `RAKESTRAWHOME_INFERENCE_API_KEY` in the application environment, then
|
|
select `rakestrawhome-gemma-4-31b` as an ordinary profile ID. For example, a
|
|
prepared result identifies the selected built-in through
|
|
`BackendRakestrawHome`:
|
|
|
|
```go
|
|
prepared, err := engine.Prepare(ctx, promptkit.RunRequest{
|
|
PromptID: "meeting.summary",
|
|
ProfileID: "rakestrawhome-gemma-4-31b",
|
|
Inputs: inputs,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if prepared.SelectedBackendID != promptkit.BackendRakestrawHome {
|
|
return fmt.Errorf("unexpected backend %q", prepared.SelectedBackendID)
|
|
}
|
|
```
|
|
|
|
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
|
|
and runtime endpoint overrides remain supported under their ordinary GoDoc and
|
|
format contracts.
|
|
|
|
### Inspect A Profile Before Prompt Work
|
|
|
|
Use [`Engine.InspectProfile`](../../engine.go) to validate one configured
|
|
profile without constructing a synthetic prompt or placeholder inputs. It
|
|
resolves the profile's effective target but does not prepare or execute a
|
|
prompt:
|
|
|
|
```go
|
|
inspection, err := engine.InspectProfile(ctx, profileID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
target := inspection.EffectiveModelParams
|
|
if target.APIKeyEnv != "" {
|
|
// This is a configured optional environment lookup source.
|
|
} else if inspection.APIKeyRequired {
|
|
// Arrange a direct credential before later execution.
|
|
}
|
|
```
|
|
|
|
Use this configuration-time boundary when only the profile and its target need
|
|
checking. Use `Prepare` when the application also needs prompt, input, schema,
|
|
or rendering work; use prepared execution when that work must remain tied to a
|
|
later execution. A reported `APIKeyEnv` is a configured optional source, while
|
|
`APIKeyRequired` is the explicit local requirement. The
|
|
[credential format reference](../formats.md#credentials) and the method's
|
|
[GoDoc](../../engine.go) own the exact precedence, timing, result, and error
|
|
contracts.
|
|
|
|
### Set A Per-Run Session And Reasoning
|
|
|
|
Supply a direct session ID when one prompt should be correlated with a
|
|
consumer-managed conversation or workflow without changing prompt variables:
|
|
|
|
```go
|
|
reasoning := "high"
|
|
result, err := engine.Run(ctx, promptkit.RunRequest{
|
|
PromptID: "meeting.summary",
|
|
SessionID: "conversation-42",
|
|
Inputs: map[string]promptkit.ArtifactRef{
|
|
"note": promptkit.Inline("Synthetic meeting notes"),
|
|
},
|
|
Execution: &promptkit.ExecutionTargetOverride{
|
|
ReasoningEffort: &reasoning,
|
|
},
|
|
})
|
|
```
|
|
|
|
A nil reasoning pointer inherits the selected profile, a pointer to a
|
|
nonblank string replaces it, and a pointer to a blank string disables
|
|
reasoning for that run. Session IDs are correlation metadata, not credentials;
|
|
use stable, non-secret values that are safe to expose to collaborators and
|
|
providers. The
|
|
[`RunRequest` and `ExecutionTargetOverride` GoDoc](../../types.go) owns the
|
|
exact normalization, precedence, error, copying, and exposure contract.
|
|
|
|
### Configure A Local OpenAI-Compatible Endpoint
|
|
|
|
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
|
|
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-only profiles have an empty backend ID and remain unrestricted by
|
|
backend capacity policy.
|
|
|
|
#### Use The Conventional Local Backend
|
|
|
|
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",
|
|
}),
|
|
)
|
|
```
|
|
|
|
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`.
|
|
|
|
#### Configure A Complete Backend
|
|
|
|
Use a keyed `Backend` 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",
|
|
}),
|
|
)
|
|
```
|
|
|
|
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.
|
|
|
|
Both file-backed and in-memory profiles select a registration through
|
|
`backend` or `Profile.BackendID`. 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
|
|
remain supported and expose an empty backend ID.
|
|
|
|
### Limit Backend Concurrency
|
|
|
|
Set `Backend.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.
|
|
|
|
To accept no waiting backlog beyond the active calls, provide an explicit
|
|
zero:
|
|
|
|
```go
|
|
noWaiting := 0
|
|
backend := promptkit.Backend{
|
|
ID: "local-gpu",
|
|
Endpoint: "http://gpu-host:8000/v1",
|
|
ConcurrencyLimit: 2,
|
|
QueueCapacity: &noWaiting,
|
|
}
|
|
```
|
|
|
|
The pointer distinguishes an explicit zero from omission. Keep using keyed
|
|
`Backend` literals so additive configuration fields remain source-compatible.
|
|
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).
|
|
|
|
## Credentials
|
|
|
|
File-backed profiles name an environment variable; in-memory profiles can
|
|
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
|
|
[`RunRequest`, `GenerateRequest`, and the profile GoDoc](../../types.go).
|
|
|
|
## Protect Files And Generated Data
|
|
|
|
The default artifact reader opens a `File` reference as a caller-selected
|
|
operating-system path. It does not constrain paths to an application root,
|
|
impose an inbound request-size policy, or establish an untrusted-input security
|
|
boundary. Applications must validate and restrict untrusted paths and payloads
|
|
before constructing a request, or inject an artifact reader that enforces
|
|
their filesystem, authorization, and size policies.
|
|
|
|
Rendered messages, input and output artifact bodies, raw model output, and
|
|
validation diagnostics can contain sensitive data. API-key redaction does not
|
|
sanitize those values. Treat prepared values, results, collaborator requests,
|
|
errors, and logs according to the application's data-access, retention, and
|
|
secret-handling policies.
|
|
|
|
## Extension Interfaces
|
|
|
|
Inject an [`LLMClient` or `ArtifactReader`](../../types.go) when the built-in
|
|
behavior does not fit the application. Their GoDoc defines concurrent use,
|
|
context handling, ownership of copied values, nil responses, and preservation
|
|
of collaborator errors. Implementations must honor cancellation, safely manage
|
|
copies they retain, avoid unsafe logging of content or credentials, and enforce
|
|
the application policy that motivated the injection.
|
|
|
|
## Handle Errors
|
|
|
|
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
|
|
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`.
|
|
|
|
When a limited backend has admitted all active and waiting calls, handle
|
|
`ErrCapacityExceeded` separately from request errors and provider failures:
|
|
|
|
```go
|
|
result, err := engine.Run(ctx, request)
|
|
if errors.Is(err, promptkit.ErrCapacityExceeded) {
|
|
var capacityErr *promptkit.CapacityError
|
|
if errors.As(err, &capacityErr) {
|
|
// Record capacityErr.BackendID using application-owned diagnostics.
|
|
}
|
|
|
|
// Apply application policy: shed work, report overload, or retry later.
|
|
}
|
|
```
|
|
|
|
A rejected call returns no partial result and does not invoke the model
|
|
client. Promptkit does not prescribe retries or map this error to an HTTP
|
|
status; those choices remain with the consuming application. The
|
|
[`CapacityError` GoDoc](../../capacity_error.go) owns the exact typed-error
|
|
contract, while the [`Engine.Run` and error GoDoc](../../engine.go) owns broad
|
|
error and cancellation identities.
|
|
|
|
For a non-2xx response from the built-in OpenAI-compatible client, inspect the
|
|
status and deliberately selected provider diagnostic when useful:
|
|
|
|
```go
|
|
var generationErr *promptkit.GenerationError
|
|
if errors.As(err, &generationErr) {
|
|
status := generationErr.StatusCode()
|
|
message := generationErr.ProviderMessage()
|
|
_, _ = status, message // Apply application retry and presentation policy.
|
|
}
|
|
```
|
|
|
|
All provider fields are untrusted and can contain sensitive request or schema
|
|
fragments. Do not log, display, or return them without an application-specific
|
|
disclosure policy. Promptkit does not assign retry or presentation behavior.
|
|
The [`GenerationError` GoDoc](../../generation_error.go) owns the exact typed
|
|
error contract.
|
|
|
|
## Application Boundary
|
|
|
|
Promptkit is an importable library. It does not own a command, inbound HTTP
|
|
API, process configuration, or deployment policy. Applications map the root
|
|
package's results and errors into those concerns, including inbound size and
|
|
trust policy.
|