Files
scriptorium/docs/consumers/pkg-scriptorium.md

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.Context cancellation;
  • 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_file values resolve relative to the prompt file.
  • fs.FS roots 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) returns ErrInvalidConfig.

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:

  • ID
  • Endpoint
  • Model
  • Temperature
  • MaxTokens
  • TopP
  • TimeoutSeconds
  • ServiceTier
  • ReasoningEffort
  • APIKeyRequired
  • ExtraParams

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:

  • ErrInvalidConfig
  • ErrInvalidRequest
  • ErrPromptNotFound
  • ErrProfileNotFound
  • ErrPromptLoad
  • ErrProfileLoad
  • ErrArtifactLoad
  • ErrPromptRender
  • ErrLLMGenerate
  • ErrValidation

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: