285 lines
7.8 KiB
Markdown
285 lines
7.8 KiB
Markdown
# 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)
|