7.8 KiB
Package scriptorium
Import path:
import "gitea.maximumdirect.net/eric/scriptorium"
The root package is the public Go facade for Scriptorium's prompt prepare/run
workflow. It exposes typed requests, results, source options, injected LLM
clients, and stable public errors while keeping internal/* packages private.
Intended Use Cases
Use the package when a Go application needs:
- in-process prompt preparation or execution;
- typed request/result structs;
- direct
context.Contextcancellation; - injected/fake LLM clients for tests;
- direct per-request
RunRequest.APIKey.
Use Subprocess integration or the HTTP API when a process or service boundary is preferred.
Construct An Engine
engine, err := scriptorium.NewEngine(scriptorium.Config{
PromptDir: "./examples/prompts",
ProfileDir: "./examples/profiles",
SchemaDir: "./examples/schemas",
})
if err != nil {
return err
}
Config fields:
| Field | Description |
|---|---|
PromptDir |
Prompt definition directory. Required unless WithPromptFS or WithPromptFile is used. |
ProfileDir |
Optional custom profile directory overlaid above built-in profiles. |
SchemaDir |
Schema directory. Defaults to . when empty. |
Timeout |
Default timeout for the built-in OpenAI-compatible client. |
HTTPClient |
Optional HTTP client for the built-in OpenAI-compatible client. |
NewEngine accepts nil options and ignores them. Invalid construction wraps
ErrInvalidConfig.
Source Options
Directory fields are the compatibility path. Explicit source options override the matching directory field.
Prompt sources:
WithPromptFS(fsys, root)WithPromptFile(path)
Profile sources:
WithProfileFS(fsys, root)WithProfileFile(path)WithProfiles(profiles...)
Schema sources:
WithSchemaFS(fsys, root)WithSchemaFile(path)
LLM source:
WithLLMClient(client)
Source behavior:
- Prompt and profile YAML use the same strict rules as directory loading.
- Prompt
content_filevalues resolve relative to the prompt file. fs.FSroots are containment boundaries for prompt content files and schema paths.- File options expose the selected file by its base name.
- Profile source precedence is in-memory profiles, then explicit profile file/FS/directory source, then built-ins.
WithLLMClient(nil)returnsErrInvalidConfig.
In-Memory Profiles
Use WithProfiles when the application already has typed model settings:
profile := scriptorium.OpenAICompatibleProfile(scriptorium.OpenAICompatibleProfileConfig{
ID: "app.default",
Endpoint: "https://openrouter.ai/api/v1",
Model: "mistralai/mistral-small-3.2-24b-instruct",
APIKeyRequired: true,
})
engine, err := scriptorium.NewEngine(cfg, scriptorium.WithProfiles(profile))
Profile and OpenAICompatibleProfileConfig include:
IDEndpointModelTemperatureMaxTokensTopPTimeoutSecondsServiceTierReasoningEffortAPIKeyRequiredExtraParams
WithProfiles rejects duplicate IDs in one call. In-memory profiles do not
store raw keys. When APIKeyRequired is true, pass the secret on each request
with RunRequest.APIKey.
ExtraParams must be JSON-compatible: strings, booleans, finite numbers,
objects with string keys, arrays/slices, and nil. Unsupported values, non-string
map keys, non-finite floats, and cycles return ErrInvalidConfig for profiles
or ErrInvalidRequest for request overrides.
Prepare Workflow
Prepare resolves prompt/profile/input/schema state and renders messages
without calling an LLM.
prepared, err := engine.Prepare(ctx, scriptorium.RunRequest{
PromptID: "generic.markdown_summary",
Inputs: map[string]scriptorium.ArtifactRef{
"transcript": scriptorium.File("./examples/fixtures/transcript.md"),
"glossary": scriptorium.File("./examples/fixtures/glossary.yml"),
},
})
if err != nil {
return err
}
_ = prepared.EffectiveModelParams
PreparedRun includes prompt ID/version/hash, selected profile, effective
model params, output contract, structured-output metadata, input hashes,
rendered prompt hash, rendered messages, and timing fields. It does not include
raw API-key values, model output, validation results, or internal target
presence metadata.
Run Workflow
Run calls Prepare, invokes the configured LLM client, builds the output
artifact, and validates the output.
result, err := engine.Run(ctx, scriptorium.RunRequest{
PromptID: "generic.markdown_summary",
APIKey: apiKey,
Inputs: map[string]scriptorium.ArtifactRef{
"transcript": scriptorium.File("./examples/fixtures/transcript.md"),
"glossary": scriptorium.File("./examples/fixtures/glossary.yml"),
},
})
if err != nil {
return err
}
_ = result.Artifact
RunResult includes run ID, output artifact, raw output, validation result,
prompt/profile/model metadata, effective model params, input hashes, usage, and
timing fields.
Generated-content validation failures return a successful RunResult with
Validation.Status == ValidationFailed. Runtime/schema validation errors
return an error that matches ErrValidation.
Inputs
Input helpers:
File(path): file-backed artifact reference.Inline(body): inline artifact body.InlineWithURI(uri, body): inline artifact body with URI metadata.
Input map keys must match the prompt's expected input names.
Injected LLM Clients
Use WithLLMClient for tests or custom model integrations:
type fakeLLM struct{}
func (fakeLLM) Generate(ctx context.Context, req scriptorium.GenerateRequest) (*scriptorium.GenerateResponse, error) {
return &scriptorium.GenerateResponse{
Content: "generated text",
Usage: scriptorium.TokenUsage{TotalTokens: 12},
}, nil
}
engine, err := scriptorium.NewEngine(cfg, scriptorium.WithLLMClient(fakeLLM{}))
Injected clients receive:
- rendered prompt;
- effective execution target;
- numeric target presence metadata;
- structured-output spec when applicable;
- direct request API key when provided.
Custom clients should not log raw prompts or API keys by default.
Overrides And API Keys
RunRequest fields:
| Field | Description |
|---|---|
PromptID |
Prompt ID. |
PromptVersion |
Optional prompt version filter. |
ProfileID |
Optional profile override. |
APIKey |
Direct per-request API key. |
Inputs |
Input artifact references. |
Vars |
Template variables. |
Execution |
Per-request model overrides. |
Validation |
Per-request output contract override. |
Metadata |
Request metadata reserved for callers. |
RunRequest.Execution uses pointer fields for numeric values so explicit zero
overrides are preserved:
zero := 0
req.Execution = &scriptorium.ExecutionTargetOverride{
MaxTokens: &zero,
}
Direct RunRequest.APIKey takes precedence over profile api_key_env for the
default OpenAI-compatible client. It is request-scoped, uses json:"-", and is
not included in PreparedRun or RunResult JSON. Normal Go string formatting
of RunRequest and GenerateRequest reports only whether a direct key is set.
Raw API keys do not belong in profile YAML, in-memory profiles, or app config.
Avoid reflection-based debug dumps of request structs because exported fields
remain visible to tools that bypass String and GoString.
Errors
Public methods wrap context while preserving stable sentinel checks with
errors.Is:
ErrInvalidConfigErrInvalidRequestErrPromptNotFoundErrProfileNotFoundErrPromptLoadErrProfileLoadErrArtifactLoadErrPromptRenderErrLLMGenerateErrValidation
Example:
if errors.Is(err, scriptorium.ErrPromptNotFound) {
return err
}
Examples
Run the maintained prepare-only example from the repository root:
go run ./examples/go-library/prepare
See also: