diff --git a/README.md b/README.md index ea0f46e..1e27278 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,21 @@ The root `promptkit` package provides the supported public engine. Consumers can configure filesystem or in-memory prompt, profile, and schema sources, prepare requests without generation, run requests with the built-in OpenAI-compatible client, or inject their own model client and artifact reader. -See the [Go package consumer guide](docs/consumers/pkg-promptkit.md) for the -public workflow and contract. + +## Quickstart + +Run the maintained offline preparation example from the repository root: + +```sh +go run ./examples/go-library/prepare +``` + +It loads a repository-local prompt, supplies an in-memory profile and inline +input, and prints deterministic preparation metadata without contacting a +provider or requiring credentials. Read the +[example source](examples/go-library/prepare/main.go), the +[Go package consumer guide](docs/consumers/pkg-promptkit.md), and the +[framework format reference](docs/formats.md) to build a consumer workflow. Contributors should start with the [development guide](docs/development.md). The [architecture policy](docs/policy/architecture.md) defines the library diff --git a/docs/consumers/pkg-promptkit.md b/docs/consumers/pkg-promptkit.md index c867f3e..989e887 100644 --- a/docs/consumers/pkg-promptkit.md +++ b/docs/consumers/pkg-promptkit.md @@ -9,7 +9,8 @@ import "gitea.maximumdirect.net/eric/promptkit" Package `promptkit` is the supported Go contract for in-process prompt preparation and execution. The declarations and their GoDoc in the [root package](../../doc.go) own the exact API; this guide explains how the -pieces are used together. +pieces are used together. The [framework format reference](../formats.md) owns +prompt, profile, and schema file contracts. ## Engine Construction And Sources @@ -32,10 +33,9 @@ The [source options](../../engine.go) replace their matching directory source: - `WithLLMClient` replaces the built-in model client; and - `WithArtifactReader` replaces the default reader for every input. -Prompt-content and schema paths from an `fs.FS` stay within the configured -root. Single-file prompt and profile sources select definitions by YAML ID. -Relative prompt content resolves from its prompt file, while a single schema -is addressed by its base name. +Source selection, path resolution, strict decoding, profile overlays, and +file-to-request precedence are defined in the +[framework format reference](../formats.md). Per-generation timeout values from profiles or requests are independent of the transport cap and caller context. An explicit request value of zero @@ -49,28 +49,10 @@ defines the complete timeout layering. [`RunRequest`](../../types.go). `Prepare` resolves the prompt, profile, input artifacts, validation contract, and rendered messages without calling an LLM. `Run` performs the same preparation, calls the configured client, and validates -the generated content. - -```go -engine, err := promptkit.NewEngine(promptkit.Config{ - PromptDir: "./prompts", - ProfileDir: "./profiles", -}) -if err != nil { - return err -} - -prepared, err := engine.Prepare(ctx, promptkit.RunRequest{ - PromptID: "meeting.summary", - Inputs: map[string]promptkit.ArtifactRef{ - "transcript": promptkit.File("./transcript.md"), - }, -}) -if err != nil { - return err -} -_ = prepared.Messages -``` +the generated content. The maintained +[offline preparation example](../../examples/go-library/prepare/main.go) +provides a complete runnable workflow using a prompt file, in-memory profile, +and inline input. [`PreparedRun` and `RunResult`](../../types.go) expose copied public values. Preparation returns effective settings, hashes, rendered messages, selected @@ -86,8 +68,10 @@ returns an error matching `ErrValidation`. The [request and value declarations](../../types.go) own the available fields, serialized constants, and result shapes. Use `File`, `Inline`, or -`InlineWithURI` to construct artifact references. Required declared inputs and -every input referenced by a template must be supplied. +`InlineWithURI` to construct artifact references. The +[framework format reference](../formats.md) defines declared inputs, template +references, output contracts, and the relationship between file values and +request overrides. `ExecutionTargetOverride` uses pointers for numeric settings so an explicit zero remains distinct from no override. `ExtraParams` accepts JSON-compatible @@ -110,7 +94,9 @@ precedence over explicit file sources and built-ins. Raw API keys do not belong in profiles. File-backed profiles may name an environment variable, while an in-memory profile can require a request key. A direct `RunRequest.APIKey` is request-scoped and takes precedence over an -environment lookup for the built-in client. +environment lookup for the built-in client. Profile fields, ranges, built-ins, +precedence, and credential rules are owned by the +[framework format reference](../formats.md). API keys are excluded from JSON, prepared values, and results. The public `String` and `GoString` methods report only whether a direct key is present. diff --git a/docs/development.md b/docs/development.md index 1ae4fcb..de73a71 100644 --- a/docs/development.md +++ b/docs/development.md @@ -31,11 +31,14 @@ Start with: | Task | Read before changing | | --- | --- | -| Documentation or examples | The [documentation policy](policy/documentation.md) and the canonical owner of the affected contract. | -| Tests or test fixtures | The [testing policy](policy/testing.md), the owning package, and any focused internal document listed by the component overview. | -| Root public API, once implemented | The [architecture policy](policy/architecture.md), [root package declaration](../doc.go), [testing policy](policy/testing.md), and existing GoDoc. | -| Internal package implementation, once introduced | The [architecture policy](policy/architecture.md), [internal component overview](internal/overview.md), and any focused internal document that the overview lists for that package. | -| Integration behavior, once introduced | The [architecture policy](policy/architecture.md), [documentation policy](policy/documentation.md), and the integration's owning contract under `docs/integrations/`. | +| Root public API | The [architecture policy](policy/architecture.md), [consumer guide](consumers/pkg-promptkit.md), [testing policy](policy/testing.md), and existing GoDoc. | +| Prompt, profile, or schema formats | The [framework format reference](formats.md), owning parser or validator package, and [documentation policy](policy/documentation.md). | +| Source loading or validation | The [framework format reference](formats.md), [internal source document](internal/sources.md), and owning package tests. | +| Model-client behavior | The [OpenAI-compatible integration contract](integrations/openai-compatible-chat.md), [internal model-client document](internal/llm.md), and owning package tests. | +| Internal package implementation | The [architecture policy](policy/architecture.md), [internal component overview](internal/overview.md), and focused internal document listed for that package. | +| Tests or test fixtures | The [testing policy](policy/testing.md), owning package, and focused internal document listed by the component overview. | +| Maintained example | The [example](../examples/go-library/prepare/main.go), [consumer guide](consumers/pkg-promptkit.md), [framework format reference](formats.md), and [documentation policy](policy/documentation.md). | +| Documentation | The [documentation policy](policy/documentation.md) and canonical owner of every affected contract. | | Release preparation or publication | The [release procedure](release.md). | For cross-cutting changes, follow every applicable row. Do not create @@ -50,8 +53,10 @@ validation from the Promptkit repository root: ```sh go test ./... +go test -race ./... go vet ./... go build ./... +go run ./examples/go-library/prepare ``` Check formatting across every tracked Go file: @@ -75,7 +80,7 @@ behavior checked by the module. ## Focused Validation Use focused checks while iterating, then run the complete validation sequence -before accepting the change. The root package currently supports: +before accepting the change. The root package supports: ```sh go test . @@ -83,17 +88,16 @@ go vet . go build . ``` -Filter tests by name without assuming a future package layout: +Filter tests by name without assuming a fixed internal package layout: ```sh go test ./... -run 'TestName' ``` -Replace `TestName` with a useful regular expression. When internal packages are -introduced, target only paths that actually exist, such as -`go test ./path/to/package`, and consult the internal component overview for -their owning documentation. A filtered or package-specific run does not replace -the complete repository validation. +Replace `TestName` with a useful regular expression. Target only paths that +exist, and consult the internal component overview for their owning +documentation. A filtered or package-specific run does not replace the +complete repository validation. ## Coordinated Work With Scriptorium diff --git a/docs/formats.md b/docs/formats.md new file mode 100644 index 0000000..5ae6c1e --- /dev/null +++ b/docs/formats.md @@ -0,0 +1,276 @@ +# Framework Format Reference + +## Purpose + +This document is the canonical contract for Promptkit prompt-definition, +profile, and schema files. The [Go package consumer guide](consumers/pkg-promptkit.md) +explains how to select these sources and invoke the engine. The +[OpenAI-compatible integration contract](integrations/openai-compatible-chat.md) +owns the resulting outbound wire behavior. + +Prompt and profile sources recursively discover files ending in `.yaml` or +`.yml`. YAML decoding is strict: unknown fields are errors for the selected +definition. Definitions are selected by their YAML `id`, not their file name +or directory. + +## Prompt Definitions + +A prompt definition describes inputs, Go-template messages, an optional +default profile, and an output contract. + +```yaml +id: meeting.summary +version: "1.0.0" +default_profile: local-summary +description: Summarize a synthetic meeting note. +session_id: '{{.session}}' +inputs: + - name: note + required: true + content_type: text/plain + description: Meeting note to summarize. +messages: + - role: system + content: Return a concise summary. + cache_control: + type: ephemeral + ttl: 1h + - role: user + content_file: ./summary.user.md +output: + format: markdown + validation_mode: basic + repair_attempts: 0 +``` + +| Field | Required | Meaning | +| --- | --- | --- | +| `id` | yes | Non-empty prompt identifier used by `RunRequest.PromptID`. | +| `version` | yes | Non-empty version selected by an optional `RunRequest.PromptVersion`. | +| `default_profile` | no | Non-empty profile ID used when the request omits `ProfileID`. | +| `description` | no | Human-readable description. | +| `session_id` | no | Go template rendered from request variables and input helpers. | +| `inputs` | no | Declared input metadata. | +| `messages` | yes | One or more chat-message templates. | +| `output` | yes | Output format and validation settings. | + +When a request omits a version, the selected prompt ID must identify exactly +one definition. When it supplies a version, the ID and version pair must be +unique. + +### Inputs + +Each `inputs` item has these fields: + +| Field | Required | Meaning | +| --- | --- | --- | +| `name` | yes | Non-empty name used by the request input map and `input` template helper. Names must be unique. | +| `required` | no | When true, preparation fails if the request omits the input. The default is false. | +| `content_type` | no | Expected media-type metadata. | +| `description` | no | Human-readable input description. | + +Requests supply inputs as inline or file-backed `ArtifactRef` values. Declared +required inputs must be present. A template reference also requires the named +input to exist, whether or not it was declared. Extra request inputs are +allowed. + +### Messages And Templates + +Each message has a non-empty `role` and exactly one of: + +- `content`, containing an inline Go template; or +- `content_file`, naming a file whose contents are the Go template. + +For directory and `fs.FS` prompt sources, `content_file` resolves relative to +the prompt file and remains within the source root. `WithPromptFile` also +resolves it relative to that file. + +Request variables are the template data, so a variable named `audience` is +referenced as `{{.audience}}`. The `{{input "note"}}` helper renders the body +of a named input. Missing variables and input references are errors. + +The optional `session_id` uses the same template data and input helper. Its +rendered value is trimmed, omitted when empty, and limited to 256 Unicode code +points. + +### Cache Control + +`cache_control` is optional and has these fields: + +| Field | Required | Values | +| --- | --- | --- | +| `type` | yes | `ephemeral` | +| `ttl` | no | Empty or `1h` | + +Promptkit preserves cache-control metadata on the rendered message. The +outbound integration determines its wire representation. + +### Output Contract + +| Field | Required | Values or behavior | +| --- | --- | --- | +| `format` | yes | `text`, `markdown`, or `json`. | +| `validation_mode` | yes | `none`, `basic`, `json`, or `json_schema`. | +| `schema_path` | for `json_schema` | Path to a schema in the configured schema source. | +| `repair_attempts` | no | Integer zero or greater; omitted means zero. | + +The validation modes behave as follows: + +- `none` skips content validation; +- `basic` requires non-empty generated content; +- `json` requires valid JSON; and +- `json_schema` requires valid JSON that satisfies the selected schema. + +`format` controls output artifact metadata. JSON Schema mode also supplies the +schema to compatible model clients as structured-output metadata. The public +engine does not install an output repairer, so its validation is single-pass +even when a positive `repair_attempts` value is present. + +A request-level `OutputContract` replaces the complete prompt output contract. +It does not merge individual fields. If its format is empty, Promptkit uses +`text`. + +## Profile Definitions + +A profile supplies model execution settings: + +```yaml +id: local-summary +endpoint: http://localhost:8000/v1 +model: example-model +temperature: 0.2 +max_tokens: 500 +top_p: 0.95 +timeout_seconds: 90 +service_tier: flex +reasoning_effort: medium +api_key_env: EXAMPLE_API_KEY +extra_params: + provider_option: enabled +``` + +| Field | Required | Meaning | +| --- | --- | --- | +| `id` | yes | Non-empty profile identifier. IDs must be unique within one source. | +| `endpoint` | yes | Non-empty OpenAI-compatible base URL, including an API version path when required. | +| `model` | yes | Non-empty provider model name. | +| `temperature` | no | Number from 0 through 2. | +| `max_tokens` | no | Integer zero or greater. | +| `top_p` | no | Number from 0 through 1. | +| `timeout_seconds` | no | Per-generation deadline in whole seconds; integer zero or greater. | +| `service_tier` | no | Provider-specific request tier. | +| `reasoning_effort` | no | Provider-specific reasoning setting. | +| `api_key_env` | no | Name of an environment variable containing the API key. | +| `extra_params` | no | JSON-compatible provider-specific outbound fields. | + +Raw `api_key` is prohibited in profile YAML. Store only an environment +variable name in `api_key_env`. + +`extra_params` accepts null, booleans, finite numbers, strings, arrays, and +objects with string keys. Keys must be non-empty. With the built-in client, +they also cannot collide with the standard fields listed in the +[outbound request contract](integrations/openai-compatible-chat.md#request-body). + +### Defaults And Overrides + +Execution settings resolve in this order: + +1. framework defaults; +2. the selected profile; and +3. request `ExecutionTargetOverride` values. + +The framework defaults are: + +| Setting | Default | +| --- | --- | +| `temperature` | `0` | +| `max_tokens` | `0` | +| `top_p` | `1` | +| `timeout_seconds` | `600` | + +Numeric zero in a file or in-memory profile means that the profile does not +replace the framework default. Numeric request overrides use pointers, so an +explicit zero is preserved. In particular, an explicit request +`timeout_seconds` of zero disables the per-generation deadline while leaving +the caller context and transport timeout intact. + +Non-empty request strings replace profile strings. A non-empty request +`ExtraParams` map replaces the profile map rather than merging keys. +The [outbound integration contract](integrations/openai-compatible-chat.md) +defines how the effective settings are serialized. + +### Source And Profile Precedence + +An explicit request profile ID takes precedence over the prompt's +`default_profile`. If neither is present, preparation fails. + +Profile sources resolve matching IDs in this order: + +1. in-memory profiles supplied with `WithProfiles`; +2. a profile file, `fs.FS`, or configured profile directory; and +3. embedded built-in profiles. + +A higher-precedence source falls back only when the profile is absent. An +invalid matching profile is an error and does not fall back. In-memory +`Profile` values follow the same ranges as YAML profiles. They use +`APIKeyRequired` for request-scoped credentials instead of `api_key_env`. + +## Built-In Profile Catalog + +Built-ins use the OpenRouter-compatible endpoint and +`OPENROUTER_API_KEY`. A custom or in-memory profile with the same ID takes +precedence. + +| Provider | ID | Model | +| --- | --- | --- | +| aion-labs | `aion-2` | `aion-labs/aion-2.0` | +| anthropic | `claude-fable-latest` | `~anthropic/claude-fable-latest` | +| anthropic | `claude-haiku-latest` | `~anthropic/claude-haiku-latest` | +| anthropic | `claude-opus-latest` | `~anthropic/claude-opus-latest` | +| anthropic | `claude-sonnet-latest` | `~anthropic/claude-sonnet-latest` | +| deepseek | `deepseek-3-2` | `deepseek/deepseek-v3.2` | +| deepseek | `deepseek-4-flash` | `deepseek/deepseek-v4-flash` | +| deepseek | `deepseek-4-pro` | `deepseek/deepseek-v4-pro` | +| google | `gemini-2-flash` | `google/gemini-2.5-flash` | +| google | `gemini-2-flash-lite` | `google/gemini-2.5-flash-lite` | +| google | `gemini-2-pro` | `google/gemini-2.5-pro` | +| google | `gemini-3-flash-lite` | `google/gemini-3.1-flash-lite` | +| google | `gemini-flash-latest` | `~google/gemini-flash-latest` | +| google | `gemini-pro-latest` | `~google/gemini-pro-latest` | +| google | `gemma-4-31b` | `google/gemma-4-31b-it:exacto` | +| minimax | `minimax-m2` | `minimax/minimax-m2.5` | +| minimax | `minimax-m3` | `minimax/minimax-m3` | +| mistral | `mistral-large-2512` | `mistralai/mistral-large-2512` | +| mistral | `mistral-medium-3-5` | `mistralai/mistral-medium-3-5` | +| mistral | `mistral-small-3` | `mistralai/mistral-small-3.2-24b-instruct` | +| mistral | `mistral-small-4` | `mistralai/mistral-small-2603` | +| nvidia | `nemotron-3-ultra` | `nvidia/nemotron-3-ultra-550b-a55b` | +| openai | `gpt-5-mini` | `openai/gpt-5.4-mini` | +| openai | `gpt-5-nano` | `openai/gpt-5.4-nano` | + +## Schemas + +Schemas are JSON documents selected by a prompt or request +`schema_path`. For a directory or `fs.FS` source, paths resolve within the +configured source root. Referenced nested schemas resolve relative to the +owning schema document. `WithSchemaFile` exposes one schema, addressed by its +base name. + +An unreadable, invalid, or unresolvable schema produces an operational +validation error. Generated content that is valid JSON but does not satisfy the +schema produces a failed validation result. + +## Credentials + +Credential values belong at the request or environment boundary, never in +prompt, profile, schema, or example files: + +- a file profile names an environment variable with `api_key_env`; +- an in-memory profile may set `APIKeyRequired`; +- a request can provide a direct `APIKey` or override `APIKeyEnv`; and +- a direct request key takes precedence over environment lookup. + +Promptkit validates required credential availability during preparation. +Direct keys are excluded from JSON results and redacted by public string +formatters. Environment-variable names may appear in prepared metadata, but +their values do not. diff --git a/docs/integrations/openai-compatible-chat.md b/docs/integrations/openai-compatible-chat.md index 039f69f..92f50d4 100644 --- a/docs/integrations/openai-compatible-chat.md +++ b/docs/integrations/openai-compatible-chat.md @@ -6,7 +6,9 @@ This document defines the outbound HTTP behavior implemented by Promptkit's internal OpenAI-compatible model client. The [internal model-client document](../internal/llm.md) owns implementation flow, errors, and test ownership. The root Promptkit engine uses this client by -default unless a consumer injects another implementation. +default unless a consumer injects another implementation. The +[framework format reference](../formats.md) owns the profile and prompt values +that produce these outbound settings. ## Endpoint And Method diff --git a/docs/internal/llm.md b/docs/internal/llm.md index dd6899f..fa9f72d 100644 --- a/docs/internal/llm.md +++ b/docs/internal/llm.md @@ -6,7 +6,9 @@ This document describes Promptkit's internal model-client implementation. The [architecture policy](../policy/architecture.md) owns the library boundary, and the [OpenAI-compatible chat integration](../integrations/openai-compatible-chat.md) -owns the observable outbound HTTP contract. +owns the observable outbound HTTP contract. The +[framework format reference](../formats.md) owns the profile and prompt +settings consumed by the client. The concrete client remains under `internal/llm`. The root engine assembles it as the default implementation behind Promptkit's public client boundary. diff --git a/docs/internal/overview.md b/docs/internal/overview.md index 2f349de..8e03e72 100644 --- a/docs/internal/overview.md +++ b/docs/internal/overview.md @@ -12,15 +12,16 @@ contributor workflow and validation. | Component | Implemented responsibility | References | | --- | --- | --- | | Root `promptkit` package | Provides the supported engine facade, source and injection options, public request and result values, built-in profile construction, extension interfaces, value conversion, redacted formatting, and public error mapping. | [Package GoDoc](../../doc.go), [engine assembly](../../engine.go) | +| `examples/go-library/prepare` | Demonstrates an offline downstream consumer using a prompt file, in-memory profile, inline input, and `Prepare`. It is not a public library package. | [Example program](../../examples/go-library/prepare/main.go) | | `internal/domain` | Defines internal framework values for requests, artifacts, prompt definitions, profiles, execution targets, rendering, generation, and validation. | [Domain declarations](../../internal/domain/domain.go) | | `internal/defaults` | Defines application-neutral framework constants and constructs the default execution target. It contains no CLI, server, or inbound HTTP limits. | [Framework defaults](../../internal/defaults/defaults.go) | | `internal/filecatalog` | Provides deterministic YAML discovery and path helpers for operating-system filesystems and `fs.FS` sources. | [File catalog](../../internal/filecatalog/catalog.go) | -| `internal/promptdef` | Loads strictly decoded, validated prompt definitions from filesystem and `fs.FS` sources, including version selection and contained file-backed message content. | [Prompt-definition repository](../../internal/promptdef/filesystem_repository.go) | -| `internal/profile` | Loads strictly decoded, validated execution profiles from filesystem and `fs.FS` sources and composes repositories with error-preserving fallback. | [Profile repositories](../../internal/profile/filesystem_repository.go) | -| `internal/profile/builtin` | Embeds the built-in execution profile catalog and combines it with an optional primary repository. | [Built-in profile repository](../../internal/profile/builtin/repository.go) | +| `internal/promptdef` | Loads strictly decoded, validated prompt definitions from filesystem and `fs.FS` sources, including version selection and contained file-backed message content. | [Framework formats](../formats.md), [prompt-definition repository](../../internal/promptdef/filesystem_repository.go) | +| `internal/profile` | Loads strictly decoded, validated execution profiles from filesystem and `fs.FS` sources and composes repositories with error-preserving fallback. | [Framework formats](../formats.md), [profile repositories](../../internal/profile/filesystem_repository.go) | +| `internal/profile/builtin` | Embeds the built-in execution profile catalog and combines it with an optional primary repository. | [Built-in catalog](../formats.md#built-in-profile-catalog), [repository](../../internal/profile/builtin/repository.go) | | `internal/prompt` | Renders prompt messages from Go templates with artifact, variable, session, and cache-control data. | [Go-template renderer](../../internal/prompt/go_renderer.go) | | `internal/artifact` | Resolves ordinary inline and unrestricted caller-selected file references into copied artifacts with metadata and hashes. | [Internal sources and validation](sources.md) | -| `internal/validate` | Validates basic, JSON, and JSON Schema output using operating-system filesystem or `fs.FS` schema sources. | [Internal sources and validation](sources.md) | +| `internal/validate` | Validates basic, JSON, and JSON Schema output using operating-system filesystem or `fs.FS` schema sources. | [Framework formats](../formats.md#schemas), [internal sources and validation](sources.md) | | `internal/llm` | Defines the internal generation boundary and implements outbound OpenAI-compatible chat requests, response decoding, authentication, and deadline handling. | [Internal model client](llm.md) | | `internal/usecase` | Coordinates preparation and execution across internal sources, rendering, artifact loading, generation, validation, and optional repair. | [Internal runner](runner.md) | @@ -29,7 +30,7 @@ representations. Consumers depend only on the root facade. ## Maintenance -Update this inventory as framework extraction introduces packages or changes -component responsibilities. List only implemented components; proposed package +Update this inventory as implementation adds packages or changes component +responsibilities. List only implemented components; proposed package boundaries belong in temporary planning documents until their implementation lands. diff --git a/docs/internal/runner.md b/docs/internal/runner.md index 6096d5a..8bfdb34 100644 --- a/docs/internal/runner.md +++ b/docs/internal/runner.md @@ -11,6 +11,8 @@ categories. The runner remains under `internal/usecase` and is assembled by the root Promptkit engine. Its concrete type is not part of the public API. +The [framework format reference](../formats.md) owns prompt, profile, schema, +and override semantics consumed by the runner. ## Collaborators diff --git a/docs/internal/sources.md b/docs/internal/sources.md index afe03db..6f5fda6 100644 --- a/docs/internal/sources.md +++ b/docs/internal/sources.md @@ -7,13 +7,14 @@ rendering, and output-validation behavior. The [architecture policy](../policy/architecture.md) owns the library boundary and dependency rules. None of these internal packages is a supported consumer API, and the root engine assembles them behind its public source options and values. +The [framework format reference](../formats.md) owns the exact file fields, +validation modes, built-in catalog, and source precedence. ## Prompt Definitions -`internal/promptdef` loads prompt definitions from an operating-system -filesystem or an `fs.FS`. It discovers YAML deterministically, decodes fields -strictly, validates definitions, selects an ID and optional version, and -resolves file-backed message content within the selected source. +`internal/promptdef` discovers YAML deterministically, decodes and validates +definitions, selects an ID and optional version, and resolves file-backed +message content within the selected operating-system or `fs.FS` source. Its package tests own prompt selection, strict decoding, definition validation, duplicate detection, and source containment: @@ -21,10 +22,9 @@ duplicate detection, and source containment: ## Profiles And Built-Ins -`internal/profile` loads strictly decoded execution profiles from an -operating-system filesystem or an `fs.FS`. It validates required profile data, -rejects raw API keys, and supports a primary repository with fallback only -when the primary reports that a profile is absent. +`internal/profile` loads and validates execution profiles from an +operating-system filesystem or an `fs.FS`. It supports a primary repository +with fallback only when the primary reports that a profile is absent. `internal/profile/builtin` embeds the maintained built-in profile catalog and can place a caller-selected repository ahead of that catalog. Profile behavior @@ -57,10 +57,9 @@ control into the rendered prompt. The ## Schemas And Output Validation `internal/validate` provides validators backed by an operating-system -filesystem or an `fs.FS`. Validation can be skipped, require non-empty output, -require JSON, or apply a JSON Schema loaded with the source's path semantics. -Invalid generated content is returned as a validation result; inability to -load, register, or compile a schema is an operational error. +filesystem or an `fs.FS`. Invalid generated content is returned as a validation +result; inability to load, register, or compile a schema is an operational +error. The [validator tests](../../internal/validate/standard_validator_test.go) own basic, JSON, JSON Schema, source resolution, schema loading, compilation, and diff --git a/docs/policy/architecture.md b/docs/policy/architecture.md index f322c2e..7f9bd5f 100644 --- a/docs/policy/architecture.md +++ b/docs/policy/architecture.md @@ -9,8 +9,9 @@ implemented packages without redefining these rules. ## System Shape -Promptkit is an importable Go library. It does not provide a runnable command, -an HTTP service, or another application process. +Promptkit is an importable Go library. It does not ship a command, an HTTP +service, or another application process. Repository examples demonstrate +library use but are not Promptkit applications or release artifacts. The module root contains package `promptkit`, which is the public facade. It provides the supported engine, configuration and source options, requests, @@ -40,6 +41,10 @@ The implemented internal components consist of: - `internal/usecase`, which coordinates preparation and execution across the internal framework components. +The `examples/go-library/prepare` package is a maintained downstream consumer +of the root facade. It does not expose a library package or participate in +internal assembly. + The root facade assembles the internal repositories, renderer, validator, outbound client, and use-case runner while translating public values and errors at the library boundary. The defaults and renderer depend on the domain diff --git a/docs/policy/documentation.md b/docs/policy/documentation.md index 3bd59da..b0575a9 100644 --- a/docs/policy/documentation.md +++ b/docs/policy/documentation.md @@ -75,13 +75,14 @@ mechanisms, not secret values. | Testing policy | `docs/policy/testing.md` | Test philosophy, risk-based sufficiency, test boundaries, doubles, coverage guidance, regression policy, and test maintenance. | Subsystem behavior, exact public contracts, subsystem-specific test inventories, and implementation plans. | | Release procedure | `docs/release.md`, when present | Required release validation, version and tag procedure, release ordering, and post-publication checks. | General contributor workflow, public API semantics, and decision history. | | Exact exported Go API | Go declarations and GoDoc, as APIs are implemented | Exported names, signatures, types, values, errors, and exact behavioral contracts. | Task-oriented consumer walkthroughs, implementation details, and future API proposals. | +| Framework file formats | `docs/formats.md` | Prompt-definition and profile YAML fields, schema references, defaults, validation modes, built-in profiles, credentials, and file-to-request precedence. | Exported Go declarations, outbound wire behavior, internal parsing mechanics, and application configuration. | | Consumer guidance | `docs/consumers/`, when consumer workflows require dedicated guidance | Task-oriented use of implemented public APIs, minimal examples, and consumer responsibilities. | Exact exported declarations and internal mechanics. | | Durable integration contracts | `docs/integrations/`, when integrations exist | External formats and protocols, compatibility behavior, and upstream or downstream responsibilities. | Internal transformations and public Go declarations. | | Implemented component inventory | `docs/internal/overview.md` | Current packages and components, their implemented responsibilities, and links to focused internal documents. | Normative architecture, contributor workflow, external contracts, and proposed components. | | Internal subsystem behavior | Other files under `docs/internal/`, when a subsystem needs durable detail | Implementation flow, internal collaborators and state transitions, package-local guarantees and failures, and relevant tests. | Global architecture invariants, public API definitions, and future package plans. | | Architectural decision history | `docs/adr/`, when repository-local decisions require records | Significant decisions, context, alternatives, rationale, consequences, and supersession history. | Current behavior reference, implementation status, and task sequencing. | | Temporary feature roadmaps | `docs/roadmap/`, while planned work needs coordination | Proposed or accepted scope, sequencing, gates, and implementation status. | Implemented behavior reference and durable decision rationale. | -| Complete copyable artifacts | `examples/`, when maintained examples exist | Valid inputs, Go programs, and other files intended to be copied or run. | Field-by-field reference, exact API declarations, and prose explanation. | +| Complete copyable artifacts | `examples/` | Valid inputs, Go programs, and other files intended to be copied or run. | Field-by-field reference, exact API declarations, and prose explanation. | Conditional owners do not require placeholder files or directories. Create a consumer, integration, subsystem, ADR, roadmap, or example document only when @@ -105,6 +106,9 @@ for a task. Internal documents explain how Promptkit implements them. Internal documentation may identify a public type or external format as a dependency, but must link to its canonical definition rather than restate it. +The [framework format reference](../formats.md) owns exact prompt, profile, and +schema-file contracts. Integration documents own external wire formats. + ### Security Topics This policy owns what documentation and examples may contain. Architecture owns diff --git a/docs/policy/testing.md b/docs/policy/testing.md index 6207064..bd7cf8d 100644 --- a/docs/policy/testing.md +++ b/docs/policy/testing.md @@ -55,6 +55,15 @@ documentation-link, and repository-hygiene checks before accepting changes. Introducing hosted CI later would supplement, not silently redefine, this documented validation model. +The complete test sequence includes ordinary and race-enabled package tests. +The maintained offline consumer workflow is also run from the repository root: + +```sh +go test ./... +go test -race ./... +go run ./examples/go-library/prepare +``` + Tests in the default suite must be deterministic, offline, and independent of real credentials. They must not invoke paid APIs, use live network dependencies, or depend on mutable external services. Tests that require live @@ -77,6 +86,10 @@ Use each test type where it protects a distinct risk: relied upon by consumers. - Integration tests use real collaborators when correctness depends on their interaction, while replacing live or nondeterministic external boundaries. +- External-package root tests exercise the public facade as a Go consumer, + while internal package tests own focused implementation behavior. +- The maintained offline preparation example protects one representative + assembled consumer workflow without contacting a model provider. - Fixtures should be minimal, synthetic, versioned with the behavior they exercise, and free of credentials or private data. - Golden files are appropriate only when the complete output is intentionally @@ -154,7 +167,8 @@ A test failing is not the same as a test needing to be edited. Many tests may co Configurable thresholds and defaults must not be duplicated throughout the test suite. -For example, do not encode an internal concurrency limit indirectly: +The following fragments are illustrative rather than standalone Go programs. +Do not encode an internal concurrency limit indirectly: ```go // Production policy: diff --git a/docs/release.md b/docs/release.md index 1a9a83e..34154b7 100644 --- a/docs/release.md +++ b/docs/release.md @@ -47,8 +47,10 @@ Run the same default Go validation required by the ```sh go test ./... +go test -race ./... go vet ./... go build ./... +go run ./examples/go-library/prepare ``` Check every tracked Go file and repository whitespace: diff --git a/examples/go-library/prepare/main.go b/examples/go-library/prepare/main.go new file mode 100644 index 0000000..4466880 --- /dev/null +++ b/examples/go-library/prepare/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "gitea.maximumdirect.net/eric/promptkit" +) + +type summary struct { + PromptID string `json:"prompt_id"` + PromptVersion string `json:"prompt_version"` + SelectedProfile string `json:"selected_profile"` + Model string `json:"model"` + MessageCount int `json:"message_count"` +} + +func main() { + engine, err := promptkit.NewEngine( + promptkit.Config{}, + promptkit.WithPromptFile("examples/go-library/prepare/prompt.yaml"), + promptkit.WithProfiles(promptkit.Profile{ + ID: "offline-example", + Endpoint: "https://example.invalid/v1", + Model: "offline-model", + }), + ) + if err != nil { + exit(err) + } + + prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{ + PromptID: "example.prepare", + Inputs: map[string]promptkit.ArtifactRef{ + "note": promptkit.Inline("Ada finished the migration review."), + }, + }) + if err != nil { + exit(err) + } + + encoder := json.NewEncoder(os.Stdout) + encoder.SetIndent("", " ") + if err := encoder.Encode(summary{ + PromptID: prepared.PromptID, + PromptVersion: prepared.PromptVersion, + SelectedProfile: prepared.SelectedProfileID, + Model: prepared.EffectiveModelParams.Model, + MessageCount: len(prepared.Messages), + }); err != nil { + exit(err) + } +} + +func exit(err error) { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} diff --git a/examples/go-library/prepare/prompt.yaml b/examples/go-library/prepare/prompt.yaml new file mode 100644 index 0000000..11fc080 --- /dev/null +++ b/examples/go-library/prepare/prompt.yaml @@ -0,0 +1,16 @@ +id: example.prepare +version: "1.0.0" +default_profile: offline-example +description: Prepare a prompt without contacting a model provider. +inputs: + - name: note + required: true + content_type: text/plain +messages: + - role: system + content: Summarize the note in one sentence. + - role: user + content: '{{input "note"}}' +output: + format: text + validation_mode: basic