# Package scriptorium Import path: ```go 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](../integrations/subprocess.md) or the [HTTP API](../api.md) when a process or service boundary is preferred. ## Construct An Engine ```go 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: ```go 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. ```go 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. ```go 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: ```go 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: ```go 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: ```go if errors.Is(err, scriptorium.ErrPromptNotFound) { return err } ``` ## Examples Run the maintained prepare-only example from the repository root: ```bash go run ./examples/go-library/prepare ``` See also: - [Configuration reference](../config.md) - [Consumer integration overview](api.md)