From 03d4f27d2bece61f5ed31d29a569d9c1ecea6cd0 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 4 Jul 2026 14:24:06 +0000 Subject: [PATCH] Document public library usage --- README.md | 2 + docs/consumers/api.md | 13 +++ docs/consumers/pkg-scriptorium.md | 129 ++++++++++++++++++++++++++++ docs/internal/adapters.md | 8 ++ examples/go-library/prepare/main.go | 50 +++++++++++ 5 files changed, 202 insertions(+) create mode 100644 docs/consumers/api.md create mode 100644 docs/consumers/pkg-scriptorium.md create mode 100644 examples/go-library/prepare/main.go diff --git a/README.md b/README.md index 8522dfc..2ce6cc9 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ This command renders the prepared prompt and effective runtime settings without - [Configuration reference](docs/config.md) - [Operations guide](docs/operations.md) - [Troubleshooting](docs/troubleshooting.md) +- [Go library package](docs/consumers/pkg-scriptorium.md) - [HTTP API integration](docs/integrations/http-api.md) - [OpenAI-compatible chat integration](docs/integrations/openai-compatible-chat.md) - [Narratio subprocess integration](docs/integrations/narratio.md) @@ -34,3 +35,4 @@ This command renders the prepared prompt and effective runtime settings without - `examples/render-markdown-summary.sh` - `examples/http-run.json` +- `examples/go-library/prepare` diff --git a/docs/consumers/api.md b/docs/consumers/api.md new file mode 100644 index 0000000..3607f37 --- /dev/null +++ b/docs/consumers/api.md @@ -0,0 +1,13 @@ +# Consumer API Overview + +Scriptorium can be used by consumers through three implemented surfaces: + +- CLI commands, documented in [CLI reference](../cli.md). +- HTTP `POST /v1/runs`, documented in [HTTP API integration](../integrations/http-api.md). +- Go package `gitea.maximumdirect.net/eric/scriptorium`, documented in [pkg-scriptorium](pkg-scriptorium.md). + +The Go package is the typed in-process API. It prepares prompts, runs prompts, accepts file or inline artifacts, supports per-request execution overrides, and exposes stable public errors for `errors.Is`. + +Use the Go package when the caller is a Go program that wants typed requests/results, context cancellation, repeated calls without subprocess overhead, or fake LLM injection for tests. Use the CLI or HTTP surfaces when process isolation, language neutrality, or an HTTP boundary is preferred. + +Raw API key values are not accepted in public payloads and are not returned in prepared or run results. Execution profiles may reference an environment variable name through `api_key_env`. diff --git a/docs/consumers/pkg-scriptorium.md b/docs/consumers/pkg-scriptorium.md new file mode 100644 index 0000000..140ba8a --- /dev/null +++ b/docs/consumers/pkg-scriptorium.md @@ -0,0 +1,129 @@ +# Package scriptorium + +Import path: + +```go +import "gitea.maximumdirect.net/eric/scriptorium" +``` + +The root package is a public facade over Scriptorium's prompt execution use case. It keeps `internal/*` packages private while exposing typed construction, preparation, execution, inputs, results, and errors. + +## Construct An Engine + +```go +engine, err := scriptorium.NewEngine(scriptorium.Config{ + PromptDir: "./examples/prompts", + ProfileDir: "./examples/profiles", + SchemaDir: "./examples/schemas", +}) +if err != nil { + return err +} +``` + +`PromptDir` and `ProfileDir` are required. `SchemaDir` defaults to the built-in schema directory. `Timeout` and `HTTPClient` configure the default OpenAI-compatible client used by `Run` when no custom LLM client is supplied. + +## Prepare A Prompt + +`Prepare` resolves the prompt definition, profile, inputs, variables, output contract, structured-output metadata, and rendered 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.Messages +``` + +Input helpers: + +- `scriptorium.File(path)` loads an input artifact from a file. +- `scriptorium.Inline(body)` passes inline input content. +- `scriptorium.InlineWithURI(uri, body)` passes inline content with URI metadata. + +## Run A Prompt + +`Run` prepares the prompt, calls the configured LLM client, builds the output artifact, and validates the output. + +```go +result, err := engine.Run(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 +} +_ = result.Artifact +``` + +`RunResult` includes the run ID, output artifact, raw output, validation result, prompt/profile/model metadata, effective model parameters, input hashes, token/cache usage, and timing fields. Validation content failures return a successful `RunResult` with failed validation status. Runtime validation errors return `ErrValidation`. + +## Inject An LLM Client + +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{})) +``` + +The injected client receives the rendered prompt, effective execution target, target presence metadata for explicit numeric overrides, and structured-output spec. `WithLLMClient(nil)` returns `ErrInvalidConfig`. + +## Request Overrides + +`RunRequest.Execution` accepts per-request overrides. Numeric override fields are pointers so explicit zero values are preserved: + +```go +zero := 0 +req.Execution = &scriptorium.ExecutionTargetOverride{ + MaxTokens: &zero, +} +``` + +## 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 prepare-only example from the repository root: + +```bash +go run ./examples/go-library/prepare +``` diff --git a/docs/internal/adapters.md b/docs/internal/adapters.md index 745e997..0c09106 100644 --- a/docs/internal/adapters.md +++ b/docs/internal/adapters.md @@ -8,6 +8,7 @@ This document describes implemented adapter/repository boundaries and their curr - `internal/adapter/cli`: CLI command parsing, app wiring, stdout/stderr handling, exit codes. - `internal/adapter/http`: HTTP request/response mapping for `POST /v1/runs`. +- root package `scriptorium`: public Go library facade for preparing and running prompt requests. - `internal/promptdef`: filesystem prompt-definition repository. - `internal/profile`: filesystem execution-profile repository. - `internal/artifact`: input artifact reader. @@ -30,6 +31,13 @@ HTTP adapter: - Output: JSON success/error body with mapped status codes. - Success metadata includes token usage plus cache usage counters. +Public library facade: + +- Input: typed `scriptorium.RunRequest` values. +- Output: typed `PreparedRun` and `RunResult` values plus public sentinel errors. +- Custom LLM behavior is injected with `WithLLMClient`; otherwise the default OpenAI-compatible client is used. +- Public types are facade types converted at the package boundary; internal domain types remain internal. + Filesystem repositories: - Input: prompt/profile YAML files under configured directories. diff --git a/examples/go-library/prepare/main.go b/examples/go-library/prepare/main.go new file mode 100644 index 0000000..4b3397d --- /dev/null +++ b/examples/go-library/prepare/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "encoding/json" + "log" + "os" + + "gitea.maximumdirect.net/eric/scriptorium" +) + +func main() { + engine, err := scriptorium.NewEngine(scriptorium.Config{ + PromptDir: "./examples/prompts", + ProfileDir: "./examples/profiles", + SchemaDir: "./examples/schemas", + }) + if err != nil { + log.Fatal(err) + } + + prepared, err := engine.Prepare(context.Background(), 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 { + log.Fatal(err) + } + + summary := struct { + PromptID string `json:"prompt_id"` + SelectedProfileID string `json:"selected_profile_id"` + Model string `json:"model"` + MessageCount int `json:"message_count"` + InputHashes map[string]string `json:"input_hashes"` + }{ + PromptID: prepared.PromptID, + SelectedProfileID: prepared.SelectedProfileID, + Model: prepared.EffectiveModelParams.Model, + MessageCount: len(prepared.Messages), + InputHashes: prepared.InputHashes, + } + + if err := json.NewEncoder(os.Stdout).Encode(summary); err != nil { + log.Fatal(err) + } +}