Make GoDoc the public API contract

This commit is contained in:
2026-07-29 14:06:54 +00:00
parent c1cecb1ee8
commit 086cf0fc86
10 changed files with 798 additions and 359 deletions

39
doc.go
View File

@@ -1,8 +1,39 @@
// Package promptkit provides an embeddable engine for preparing and executing // Package promptkit provides an embeddable engine for preparing and executing
// prompt-defined LLM workflows. // prompt-defined LLM workflows.
// //
// Applications construct an Engine with NewEngine, select filesystem or // Applications construct an [Engine] with [NewEngine], select filesystem or
// in-memory definition sources with options, and use Prepare or Run to execute // in-memory sources with options, and call [Engine.Prepare] or [Engine.Run].
// requests. Concrete repositories, validators, and outbound clients remain // Concrete repositories, validators, and the built-in OpenAI-compatible client
// internal implementation details. // remain internal implementation details.
//
// # Concurrency and ownership
//
// An Engine supports concurrent Prepare and Run calls. An injected [LLMClient]
// or [ArtifactReader] can therefore receive concurrent calls and must be safe
// for that use.
//
// NewEngine copies in-memory profiles. Prepare and Run copy request maps,
// slices, pointer values, and JSON-compatible extra parameters before using
// them. Returned values and values passed to extension interfaces are likewise
// isolated from engine state. Callers own those copies and may mutate them
// after the call that supplied or returned them.
//
// # JSON
//
// Stable JSON representations are provided for [PreparedRun], [RunResult],
// [Artifact], [ExecutionTarget], [OutputContract], [ValidationResult],
// [TokenUsage], [RenderedPrompt], [RenderedMessage], [CacheControl],
// [StructuredOutputSpec], [StructuredOutputJSONSpec], [GenerateRequest],
// [GenerateResponse], [ExecutionTargetPresence], and the string value types
// used by those values.
//
// Construction values, including [Config], [RunRequest], [ArtifactRef],
// [ExecutionTargetOverride], [Profile], and
// [OpenAICompatibleProfileConfig], do not have stable JSON representations.
// Direct API keys are nevertheless excluded from JSON for every public value.
//
// JSON timestamps use time.Time's RFC 3339 encoding and are omitted when zero.
// PreparedRun and RunResult durations are encoded as integer milliseconds in
// duration_ms and omitted when zero. Run IDs and all exposed hashes are opaque:
// their spelling, length, character set, and algorithm are not API contracts.
package promptkit package promptkit

View File

@@ -1,151 +1,122 @@
# Package `promptkit` # Package `promptkit`
Import path: ## Purpose
This guide helps Go consumers assemble Promptkit and choose the main
preparation or execution workflow. The declarations and GoDoc in the
[root package](../../doc.go) own exact field, option, serialization,
concurrency, ownership, failure, and cancellation semantics. The
[framework format reference](../formats.md) owns prompt, profile, and schema
file contracts.
Import the package as:
```go ```go
import "gitea.maximumdirect.net/eric/promptkit" import "gitea.maximumdirect.net/eric/promptkit"
``` ```
Package `promptkit` is the supported Go contract for in-process prompt The following Go fragments are illustrative and omit surrounding package,
preparation and execution. The declarations and their GoDoc in the import, and error-handling code. Use the maintained example for a complete
[root package](../../doc.go) own the exact API; this guide explains how the program.
pieces are used together. The [framework format reference](../formats.md) owns
prompt, profile, and schema file contracts.
## Engine Construction And Sources ## Construct An Engine
Construct an engine with [`NewEngine`, `Config`, and Create an engine with
`Option`](../../engine.go). `PromptDir` is required unless a prompt source [`NewEngine`](../../engine.go). A directory-backed setup supplies a prompt
option is supplied. `ProfileDir` optionally overlays built-in profiles, and an directory and may supply profile and schema directories:
empty `SchemaDir` uses the current directory. `Timeout` is the transport-wide
safety cap for the built-in OpenAI-compatible client. An optional `HTTPClient`
is cloned; its positive timeout takes precedence.
Nil options are ignored. Invalid construction, including a nil injected client ```go
or artifact reader, returns an error matching `ErrInvalidConfig`. engine, err := promptkit.NewEngine(promptkit.Config{
PromptDir: "prompts",
ProfileDir: "profiles",
SchemaDir: "schemas",
})
```
The [source options](../../engine.go) replace their matching directory source: Options support single-file or `fs.FS` sources, in-memory profiles, and
injected artifact or model clients. Consult the
- `WithPromptFS` and `WithPromptFile` select prompt definitions; [constructor and option GoDoc](../../engine.go) for composition, precedence,
- `WithProfileFS` and `WithProfileFile` overlay built-in profiles; validation, and default transport behavior. Source discovery, format
- `WithProfiles` adds in-memory profiles ahead of file and built-in profiles; validation, and profile precedence are defined by the
- `WithSchemaFS` and `WithSchemaFile` select JSON Schema documents;
- `WithLLMClient` replaces the built-in model client; and
- `WithArtifactReader` replaces the default reader for every input.
Source selection, path resolution, strict decoding, profile overlays, and
file-to-request precedence are defined in the
[framework format reference](../formats.md). [framework format reference](../formats.md).
Per-generation timeout values from profiles or requests are independent of ## Prepare Without Model Execution
the transport cap and caller context. An explicit request value of zero
disables only the per-generation deadline. The
[outbound integration contract](../integrations/openai-compatible-chat.md#timeout-and-cancellation)
defines the complete timeout layering.
## Preparation And Execution [`Engine.Prepare`](../../engine.go) resolves the selected prompt and profile,
loads inputs and any structured-output schema, and renders messages without
calling a model client:
[`Engine.Prepare` and `Engine.Run`](../../engine.go) accept the public ```go
[`RunRequest`](../../types.go). `Prepare` resolves the prompt, profile, input prepared, err := engine.Prepare(ctx, promptkit.RunRequest{
artifacts, validation contract, and rendered messages without calling an LLM. PromptID: "meeting.summary",
`Run` performs the same preparation, calls the configured client, and validates Inputs: map[string]promptkit.ArtifactRef{
the generated content. The maintained "note": promptkit.Inline("Synthetic meeting notes"),
},
})
```
The maintained
[offline preparation example](../../examples/go-library/prepare/main.go) [offline preparation example](../../examples/go-library/prepare/main.go)
provides a complete runnable workflow using a prompt file, in-memory profile, shows a complete runnable setup with a prompt file, in-memory profile, and
and inline input. inline input. Exact request requirements and prepared-result fields belong to
the [`RunRequest` and `PreparedRun` GoDoc](../../types.go).
[`PreparedRun` and `RunResult`](../../types.go) expose copied public values. ## Execute And Validate
Preparation returns effective settings, hashes, rendered messages, selected
profile, structured-output information, and timing without resolved secrets or
model output. Execution adds the generated artifact and raw output, validation
state, model metadata, usage, run ID, and duration.
A generated-content validation failure returns a result with [`Engine.Run`](../../engine.go) performs the same preparation, invokes the
`Validation.Status == ValidationFailed`. An inability to perform validation configured model client, classifies the generated artifact, and validates the
returns an error matching `ErrValidation`. content. A completed content check may return `ValidationFailed` in the result;
an operational inability to validate returns an error.
## Requests, Inputs, And Overrides Use the [`RunResult` and `ValidationResult` GoDoc](../../types.go) for the
returned data and the `Engine.Run` GoDoc for failure and cancellation
semantics. The
[OpenAI-compatible integration contract](../integrations/openai-compatible-chat.md)
owns the built-in client's outbound HTTP behavior.
The [request and value declarations](../../types.go) own the available fields, ## Inputs, Profiles, And Overrides
serialized constants, and result shapes. Use `File`, `Inline`, or
`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 Use `File`, `Inline`, or `InlineWithURI` to construct request inputs. A request
zero remains distinct from no override. `ExtraParams` accepts JSON-compatible can select a profile explicitly or use the prompt's default profile, and can
strings, booleans, finite numbers, string-keyed objects, arrays or slices, and replace execution settings or the complete output contract.
nil. Unsupported values, non-string map keys, non-finite numbers, and cycles
match `ErrInvalidConfig` in profiles or `ErrInvalidRequest` in request
overrides.
Returned requests, profiles, prepared values, results, artifacts, maps, and The [public value GoDoc](../../types.go) defines nil, empty, zero, replacement,
slices are isolated from internal engine state. Consumers and injected copy, and credential behavior. The
extensions should not retain or mutate values owned by another caller. [framework format reference](../formats.md) defines how those request values
interact with prompt definitions, file-backed profiles, built-ins, schemas,
and framework defaults.
## Profiles And Credentials For programmatic profiles,
[`OpenAICompatibleProfile`](../../profiles.go) converts ordinary
OpenAI-compatible settings into a value accepted by `WithProfiles`.
[`OpenAICompatibleProfile`](../../profiles.go) constructs an ordinary ## Credentials
in-memory profile for an OpenAI-compatible chat-completions endpoint.
`WithProfiles` rejects duplicate IDs in one call and gives in-memory profiles
precedence over explicit file sources and built-ins.
Raw API keys do not belong in profiles. File-backed profiles may name an File-backed profiles name an environment variable; in-memory profiles can
environment variable, while an in-memory profile can require a request key. require a direct request key. Direct keys are request-scoped and are excluded
A direct `RunRequest.APIKey` is request-scoped and takes precedence over an from supported JSON values and the package's `String` and `GoString`
environment lookup for the built-in client. Profile fields, ranges, built-ins, summaries. The exact precedence and redaction guarantees belong to
precedence, and credential rules are owned by the [`RunRequest`, `GenerateRequest`, and the profile GoDoc](../../types.go).
[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.
Avoid reflection-based dumps of request structs, which can bypass that
redaction.
## Extension Interfaces ## Extension Interfaces
The [`LLMClient`, `GenerateRequest`, and Inject an [`LLMClient` or `ArtifactReader`](../../types.go) when the built-in
`GenerateResponse`](../../types.go) boundary lets a consumer replace model behavior does not fit the application. Their GoDoc defines concurrent use,
generation. Injected clients receive copied rendered messages, effective context handling, ownership of copied values, nil responses, and preservation
settings, explicit numeric-setting presence, structured-output constraints, of collaborator errors.
and the request-scoped key. They return generated content and token usage.
The [`ArtifactReader`](../../types.go) boundary replaces the default inline and ## Handle Errors
file reader for every input. Readers provide artifact content and metadata; the
engine fills an empty artifact name from the input-map key. A reader error
matches `ErrArtifactLoad` while preserving the original identity for
`errors.Is`. A nil artifact with a nil error is also an artifact-load failure.
Extensions should honor context cancellation and avoid logging raw prompts, Use `errors.Is` with the
artifacts, or credentials. [public error sentinels and operation GoDoc](../../engine.go). The declarations
distinguish invalid construction, invalid requests, absent sources,
source-loading failures, collaborator failures, and operational validation
failures. Specific request conditions may also match the broader
`ErrInvalidRequest`, and injected collaborator identities are preserved where
documented.
## Errors ## Application Boundary
The [public error declarations](../../engine.go) and
[mapping](../../errors.go) preserve these sentinel checks through `errors.Is`:
- `ErrInvalidConfig`
- `ErrInvalidRequest`
- `ErrPromptNotFound`
- `ErrProfileNotFound`
- `ErrProfileRequired`
- `ErrPromptLoad`
- `ErrProfileLoad`
- `ErrAPIKeyEnvMissing`
- `ErrArtifactLoad`
- `ErrPromptRender`
- `ErrLLMGenerate`
- `ErrValidation`
`ErrProfileRequired` and `ErrAPIKeyEnvMissing` also match
`ErrInvalidRequest`, allowing either broad request handling or a specific
condition. Wrapped collaborator errors retain their identity where the public
contract promises it.
## Consumer Boundary
Promptkit is an importable library. It does not own a command, inbound HTTP Promptkit is an importable library. It does not own a command, inbound HTTP
API, process configuration, or deployment policy. Scriptorium is one API, process configuration, or deployment policy. Applications map the root
downstream application that maps this root package contract into those package's results and errors into those concerns.
application concerns.

View File

@@ -57,47 +57,15 @@ that is currently ambiguous.
- [x] Confirm the supported JSON Schema dialect and reference boundaries, - [x] Confirm the supported JSON Schema dialect and reference boundaries,
including whether remote references are allowed. including whether remote references are allowed.
The resolved contract is: The selected exported API contracts are implemented and tested. Their durable
definitions now belong to the root package declarations and GoDoc.
- `RunRequest.Metadata` had no observable purpose and has been removed from the One format-level decision remains here until Stage 5 moves it to the framework
public and internal request values. format reference: JSON Schema uses Draft 2020-12, with that dialect selected
- One engine supports overlapping `Prepare` and `Run` calls. Built-in when `$schema` is omitted. Same-document fragments and relative references
collaborators satisfy that contract; injected collaborators may be called contained by a directory or `fs.FS` schema root are supported. A single-file
concurrently and therefore share responsibility for concurrency safety. source supports only references contained in that document. Absolute,
- Options are applied in order. Within each prompt-source, profile-source, escaping, and remote references are rejected.
in-memory-profile, schema-source, model-client, or artifact-reader category,
the last non-nil valid option replaces the earlier value for that category.
An invalid earlier option still makes construction fail.
- The supported JSON values are `PreparedRun`, `RunResult`, `Artifact`,
`ExecutionTarget`, `OutputContract`, `ValidationResult`, `TokenUsage`,
`RenderedPrompt`, `RenderedMessage`, `CacheControl`,
`StructuredOutputSpec`, `StructuredOutputJSONSpec`, `GenerateRequest`,
`GenerateResponse`, `ExecutionTargetPresence`, and the public string value
types used by them. Construction inputs such as `Config`, `RunRequest`,
`ArtifactRef`, `ExecutionTargetOverride`, `Profile`, and
`OpenAICompatibleProfileConfig` do not have stable JSON representations.
Request-scoped API keys remain excluded from JSON as a security guarantee,
including on otherwise unsupported construction values.
- JSON timestamps use `time.Time`'s RFC 3339 representation and are omitted
when zero. Both prepared and completed run durations use integer
milliseconds in `duration_ms` and are omitted when zero. A prepared duration
measures preparation only; a run-result duration measures the complete run,
including its preparation, generation, and validation.
- Run IDs, prompt hashes, rendered-prompt hashes, input hashes, and artifact
hashes are non-empty correlation or equality values where produced. Their
spelling, length, character set, and algorithm are opaque and not stable
formats.
- The built-in transport timeout defaults to 10 minutes. A zero or negative
`Config.Timeout` selects that default. A supplied HTTP client's positive
timeout takes precedence; its zero or negative timeout inherits the positive
configured timeout or the default. These transport semantics are independent
of caller cancellation and per-generation timeout settings.
- JSON Schema uses Draft 2020-12; omission of `$schema` selects that dialect
and an explicit different dialect is rejected. Same-document fragment
references are supported. Relative references may load other schema
documents only within a configured directory or `fs.FS` schema root.
A single-file schema source supports only references contained in that
document. Absolute, escaping, and remote references are not allowed.
**Gate:** Each question has an explicit answer backed by existing behavior or **Gate:** Each question has an explicit answer backed by existing behavior or
by an accepted implementation change and proportionate tests. No later stage by an accepted implementation change and proportionate tests. No later stage
@@ -108,21 +76,21 @@ should invent a contract merely to fill a documentation gap.
Strengthen the root package declarations so `go doc` is sufficient to Strengthen the root package declarations so `go doc` is sufficient to
understand exact public behavior without relying on internal documents. understand exact public behavior without relying on internal documents.
- [ ] Add useful field-level GoDoc to configuration, request, profile, - [x] Add useful field-level GoDoc to configuration, request, profile,
execution-target, result, artifact, validation, structured-output, and model execution-target, result, artifact, validation, structured-output, and model
client values. client values.
- [ ] Document required fields and nil, empty, and zero-value semantics. - [x] Document required fields and nil, empty, and zero-value semantics.
- [ ] Document override, replacement, profile-precedence, and copy-ownership - [x] Document override, replacement, profile-precedence, and copy-ownership
behavior where it belongs to the exported API. behavior where it belongs to the exported API.
- [ ] Document credential inputs, redaction, and the values intentionally - [x] Document credential inputs, redaction, and the values intentionally
excluded from serialization. excluded from serialization.
- [ ] Give each public error sentinel an accurate comment and document the - [x] Give each public error sentinel an accurate comment and document the
supported `errors.Is` relationships. supported `errors.Is` relationships.
- [ ] Document engine concurrency and option-composition behavior selected in - [x] Document engine concurrency and option-composition behavior selected in
Stage 1. Stage 1.
- [ ] Document serialization, time, run-ID, and hash semantics selected in - [x] Document serialization, time, run-ID, and hash semantics selected in
Stage 1. Stage 1.
- [ ] Review constructor, option, extension-interface, `Prepare`, and `Run` - [x] Review constructor, option, extension-interface, `Prepare`, and `Run`
GoDoc for complete failure and cancellation expectations. GoDoc for complete failure and cancellation expectations.
Update the [consumer guide](../consumers/pkg-promptkit.md) to summarize and link Update the [consumer guide](../consumers/pkg-promptkit.md) to summarize and link

141
engine.go
View File

@@ -22,42 +22,93 @@ import (
"gitea.maximumdirect.net/eric/promptkit/internal/validate" "gitea.maximumdirect.net/eric/promptkit/internal/validate"
) )
// ErrInvalidConfig indicates invalid public engine configuration. // ErrInvalidConfig identifies invalid engine construction, including missing
// required configuration, invalid options, and a nil Engine receiver.
var ErrInvalidConfig = errors.New("invalid engine configuration") var ErrInvalidConfig = errors.New("invalid engine configuration")
var ( var (
// ErrInvalidRequest identifies a request whose required values, overrides,
// credentials, or effective settings are invalid.
ErrInvalidRequest = errors.New("invalid run request") ErrInvalidRequest = errors.New("invalid run request")
// ErrPromptNotFound identifies a requested prompt ID or version that is not
// present in the selected prompt source. It does not also match
// ErrPromptLoad.
ErrPromptNotFound = errors.New("prompt not found") ErrPromptNotFound = errors.New("prompt not found")
// ErrProfileNotFound identifies a selected profile ID that is absent from
// every configured profile source. It does not also match ErrProfileLoad.
ErrProfileNotFound = errors.New("profile not found") ErrProfileNotFound = errors.New("profile not found")
// ErrProfileRequired identifies a request for which neither RunRequest.ProfileID
// nor the selected prompt's default profile is present. Such an error also
// matches ErrInvalidRequest.
ErrProfileRequired = errors.New("profile selection is required") ErrProfileRequired = errors.New("profile selection is required")
// ErrPromptLoad identifies a failure to read, decode, validate, select, or
// hash a prompt definition, except for the not-found case represented by
// ErrPromptNotFound.
ErrPromptLoad = errors.New("failed to load prompt definition") ErrPromptLoad = errors.New("failed to load prompt definition")
// ErrProfileLoad identifies a failure to read, decode, validate, or select
// an execution profile, except for the not-found case represented by
// ErrProfileNotFound.
ErrProfileLoad = errors.New("failed to load execution profile") ErrProfileLoad = errors.New("failed to load execution profile")
// ErrAPIKeyEnvMissing identifies an APIKeyEnv whose environment variable is
// unset or empty when no direct RunRequest.APIKey takes precedence. Such an
// error also matches ErrInvalidRequest.
ErrAPIKeyEnvMissing = errors.New("api_key_env points to an unset environment variable") ErrAPIKeyEnvMissing = errors.New("api_key_env points to an unset environment variable")
// ErrArtifactLoad identifies a failure to resolve an input artifact. Errors
// returned by an injected ArtifactReader remain available through errors.Is.
ErrArtifactLoad = errors.New("failed to load artifact") ErrArtifactLoad = errors.New("failed to load artifact")
// ErrPromptRender identifies a failure to render prompt messages or the
// session ID from the resolved inputs and variables.
ErrPromptRender = errors.New("failed to render prompt") ErrPromptRender = errors.New("failed to render prompt")
// ErrLLMGenerate identifies a model-client failure or a nil successful
// response. Errors returned by an injected LLMClient remain available
// through errors.Is.
ErrLLMGenerate = errors.New("failed to generate output") ErrLLMGenerate = errors.New("failed to generate output")
// ErrValidation identifies an operational failure to load or compile a
// schema or validate output. A completed validation whose Status is
// ValidationFailed is returned in RunResult without this error.
ErrValidation = errors.New("failed to validate output") ErrValidation = errors.New("failed to validate output")
) )
// Engine prepares and runs Promptkit prompt requests. // Engine prepares and runs Promptkit prompt requests.
//
// An Engine is safe for concurrent calls to [Engine.Prepare] and [Engine.Run].
// Injected collaborators may consequently be invoked concurrently.
type Engine struct { type Engine struct {
runner *usecase.Runner runner *usecase.Runner
} }
// Config configures a public Promptkit engine. // Config selects the directory-backed sources and built-in model-client
// transport used by [NewEngine]. Config has no stable JSON representation.
type Config struct { type Config struct {
// PromptDir is the directory searched recursively for prompt definitions.
// It is required unless a WithPromptFS or WithPromptFile option supplies the
// prompt source.
PromptDir string PromptDir string
// ProfileDir is an optional directory whose profiles take precedence over
// embedded built-in profiles. An empty value selects only built-ins unless
// profile options are also supplied.
ProfileDir string ProfileDir string
// SchemaDir is the root for JSON Schema files. An empty value uses the
// current directory. WithSchemaFS or WithSchemaFile replaces this source.
SchemaDir string SchemaDir string
// Timeout is the transport-wide safety cap for the built-in LLM client // Timeout is the transport-wide safety cap for the built-in LLM client
// when HTTPClient is absent or has a non-positive timeout. // when HTTPClient is absent or has a non-positive timeout. A zero or negative
// value selects the 10-minute default.
Timeout time.Duration Timeout time.Duration
// HTTPClient is cloned for the built-in LLM client. Its positive Timeout // HTTPClient is cloned for the built-in LLM client. Its positive Timeout
// takes precedence over Config.Timeout as the transport-wide safety cap. // takes precedence over Timeout. A zero or negative client Timeout inherits
// Timeout or the 10-minute default. The supplied client is not mutated. This
// field is ignored when WithLLMClient is used.
HTTPClient *http.Client HTTPClient *http.Client
} }
// Option customizes engine construction. // Option customizes engine construction.
//
// NewEngine applies options in argument order and ignores nil options. Within
// each prompt-source, profile-source, in-memory-profile, schema-source,
// model-client, and artifact-reader category, the last non-nil valid option
// replaces earlier options in that category. An invalid option fails
// construction even if a later option would replace it.
type Option interface { type Option interface {
apply(*engineOptions) error apply(*engineOptions) error
} }
@@ -82,7 +133,10 @@ type engineOptions struct {
artifactSource bool artifactSource bool
} }
// WithLLMClient injects a custom LLM client for execution. // WithLLMClient replaces the built-in model client used by [Engine.Run].
//
// A nil client makes NewEngine fail with ErrInvalidConfig. The client may be
// called concurrently and is not used by [Engine.Prepare].
func WithLLMClient(client LLMClient) Option { func WithLLMClient(client LLMClient) Option {
return optionFunc(func(options *engineOptions) error { return optionFunc(func(options *engineOptions) error {
if client == nil { if client == nil {
@@ -93,7 +147,11 @@ func WithLLMClient(client LLMClient) Option {
}) })
} }
// WithArtifactReader injects a reader for every input artifact reference. // WithArtifactReader replaces the default reader for every input artifact
// reference, regardless of its ArtifactRef.Type.
//
// A nil reader makes NewEngine fail with ErrInvalidConfig. The reader may be
// called concurrently.
func WithArtifactReader(reader ArtifactReader) Option { func WithArtifactReader(reader ArtifactReader) Option {
return optionFunc(func(options *engineOptions) error { return optionFunc(func(options *engineOptions) error {
if reader == nil { if reader == nil {
@@ -109,6 +167,9 @@ func WithArtifactReader(reader ArtifactReader) Option {
// //
// The source uses the same strict prompt YAML rules as configured prompt // The source uses the same strict prompt YAML rules as configured prompt
// directories, and prompt content_file paths resolve within this source. // directories, and prompt content_file paths resolve within this source.
// fsys must be non-nil and root must be non-empty; otherwise NewEngine fails
// with ErrInvalidConfig. This option replaces Config.PromptDir and earlier
// prompt-source options.
func WithPromptFS(fsys fs.FS, root string) Option { func WithPromptFS(fsys fs.FS, root string) Option {
return optionFunc(func(options *engineOptions) error { return optionFunc(func(options *engineOptions) error {
if fsys == nil { if fsys == nil {
@@ -125,7 +186,9 @@ func WithPromptFS(fsys fs.FS, root string) Option {
// WithPromptFile loads prompt definitions from the single prompt file at path. // WithPromptFile loads prompt definitions from the single prompt file at path.
// //
// Relative prompt content_file paths resolve from the file's directory. // Relative prompt content_file paths resolve from the file's directory. path
// must name an existing non-directory file when NewEngine applies the option.
// This option replaces Config.PromptDir and earlier prompt-source options.
func WithPromptFile(path string) Option { func WithPromptFile(path string) Option {
return optionFunc(func(options *engineOptions) error { return optionFunc(func(options *engineOptions) error {
fsys, root, err := fileSource(path) fsys, root, err := fileSource(path)
@@ -142,6 +205,10 @@ func WithPromptFile(path string) Option {
// //
// Profiles from this source overlay built-in profiles. Profile YAML must use // Profiles from this source overlay built-in profiles. Profile YAML must use
// api_key_env for environment-based credentials; raw API keys are rejected. // api_key_env for environment-based credentials; raw API keys are rejected.
// fsys must be non-nil and root must be non-empty; otherwise NewEngine fails
// with ErrInvalidConfig. This option replaces Config.ProfileDir and earlier
// file or FS profile-source options, but remains below WithProfiles in
// precedence.
func WithProfileFS(fsys fs.FS, root string) Option { func WithProfileFS(fsys fs.FS, root string) Option {
return optionFunc(func(options *engineOptions) error { return optionFunc(func(options *engineOptions) error {
if fsys == nil { if fsys == nil {
@@ -159,7 +226,10 @@ func WithProfileFS(fsys fs.FS, root string) Option {
// WithProfileFile loads execution profiles from the single profile file at path. // WithProfileFile loads execution profiles from the single profile file at path.
// //
// The profile overlays built-in profiles. Profile YAML must use api_key_env for // The profile overlays built-in profiles. Profile YAML must use api_key_env for
// environment-based credentials; raw API keys are rejected. // environment-based credentials; raw API keys are rejected. path must name an
// existing non-directory file when NewEngine applies the option. This option
// replaces Config.ProfileDir and earlier file or FS profile-source options,
// but remains below WithProfiles in precedence.
func WithProfileFile(path string) Option { func WithProfileFile(path string) Option {
return optionFunc(func(options *engineOptions) error { return optionFunc(func(options *engineOptions) error {
fsys, root, err := fileSource(path) fsys, root, err := fileSource(path)
@@ -174,6 +244,11 @@ func WithProfileFile(path string) Option {
// WithProfiles configures in-memory profiles that take precedence over // WithProfiles configures in-memory profiles that take precedence over
// configured profile files and built-in profiles. // configured profile files and built-in profiles.
//
// NewEngine validates and copies every profile. IDs must be unique within one
// call. An invalid profile, duplicate ID, or unsupported ExtraParams value
// makes construction fail with ErrInvalidConfig. Repeating WithProfiles
// replaces the complete earlier in-memory set rather than merging it.
func WithProfiles(profiles ...Profile) Option { func WithProfiles(profiles ...Profile) Option {
return optionFunc(func(options *engineOptions) error { return optionFunc(func(options *engineOptions) error {
repo, err := newMemoryProfileRepository(profiles) repo, err := newMemoryProfileRepository(profiles)
@@ -189,7 +264,9 @@ func WithProfiles(profiles ...Profile) Option {
// WithSchemaFS loads JSON Schema documents from fsys under root. // WithSchemaFS loads JSON Schema documents from fsys under root.
// //
// Prompt schema_path values resolve within this source when schema validation // Prompt schema_path values resolve within this source when schema validation
// or structured output is requested. // or structured output is requested. fsys must be non-nil and root must be
// non-empty; otherwise NewEngine fails with ErrInvalidConfig. This option
// replaces Config.SchemaDir and earlier schema-source options.
func WithSchemaFS(fsys fs.FS, root string) Option { func WithSchemaFS(fsys fs.FS, root string) Option {
return optionFunc(func(options *engineOptions) error { return optionFunc(func(options *engineOptions) error {
if fsys == nil { if fsys == nil {
@@ -206,7 +283,9 @@ func WithSchemaFS(fsys fs.FS, root string) Option {
// WithSchemaFile loads JSON Schema documents from the single schema file at path. // WithSchemaFile loads JSON Schema documents from the single schema file at path.
// //
// Prompt schema_path values refer to the file's base name. // Prompt schema_path values refer to the file's base name. path must name an
// existing non-directory file when NewEngine applies the option. This option
// replaces Config.SchemaDir and earlier schema-source options.
func WithSchemaFile(path string) Option { func WithSchemaFile(path string) Option {
return optionFunc(func(options *engineOptions) error { return optionFunc(func(options *engineOptions) error {
fsys, root, err := fileSource(path) fsys, root, err := fileSource(path)
@@ -220,6 +299,15 @@ func WithSchemaFile(path string) Option {
} }
// NewEngine constructs an Engine from configuration and options. // NewEngine constructs an Engine from configuration and options.
//
// Options are applied in order according to [Option]. PromptDir is required
// unless a prompt-source option is present. Construction validates option
// arguments and in-memory profiles but defers reading and validating prompt,
// file-backed profile, and schema contents until Prepare or Run needs them.
//
// NewEngine returns an error matching ErrInvalidConfig for invalid
// configuration or options. It does not perform model requests or require
// credentials.
func NewEngine(cfg Config, opts ...Option) (*Engine, error) { func NewEngine(cfg Config, opts ...Option) (*Engine, error) {
var options engineOptions var options engineOptions
for _, opt := range opts { for _, opt := range opts {
@@ -305,7 +393,22 @@ func fileSource(name string) (fs.FS, string, error) {
return os.DirFS(dir), filepath.ToSlash(base), nil return os.DirFS(dir), filepath.ToSlash(base), nil
} }
// Prepare resolves a prompt request without calling an LLM. // Prepare resolves and renders a prompt request without calling an LLM.
//
// Prepare selects the prompt and profile, resolves effective execution
// settings and the output contract, loads and hashes inputs, loads structured
// output schema metadata when required, and renders the session ID and
// messages. The returned PreparedRun is owned by the caller and never contains
// a resolved API-key value, model output, or validation result.
//
// A nil Engine returns an error matching ErrInvalidConfig. Request and
// preparation failures may match ErrInvalidRequest, ErrPromptNotFound,
// ErrPromptLoad, ErrProfileNotFound, ErrProfileLoad, ErrProfileRequired,
// ErrAPIKeyEnvMissing, ErrArtifactLoad, ErrPromptRender, or ErrValidation as
// applicable. Cancellation is passed to the active collaborator and is
// reported in the applicable operation category; no general errors.Is
// relationship to ctx.Err is promised. Prepare returns no partial result on
// error.
func (e *Engine) Prepare(ctx context.Context, req RunRequest) (*PreparedRun, error) { func (e *Engine) Prepare(ctx context.Context, req RunRequest) (*PreparedRun, error) {
if e == nil || e.runner == nil { if e == nil || e.runner == nil {
return nil, fmt.Errorf("%w: engine is nil", ErrInvalidConfig) return nil, fmt.Errorf("%w: engine is nil", ErrInvalidConfig)
@@ -323,7 +426,21 @@ func (e *Engine) Prepare(ctx context.Context, req RunRequest) (*PreparedRun, err
return fromDomainPreparedRun(prepared), nil return fromDomainPreparedRun(prepared), nil
} }
// Run executes a prompt request and returns the generated artifact and metadata. // Run prepares a request, invokes the configured LLMClient, and validates the
// generated output.
//
// A content-validation failure is a successful run whose
// RunResult.Validation has Status ValidationFailed. An inability to perform
// validation returns an error matching ErrValidation and no partial result.
// The public Engine does not perform output repair, so validation is
// single-pass even when OutputContract.RepairAttempts is positive.
//
// Run can return every error category documented by [Engine.Prepare], plus
// ErrLLMGenerate. Errors from injected clients remain available through
// errors.Is. Cancellation is passed through the active collaborator and is
// reported in the applicable operation category; no general errors.Is
// relationship to ctx.Err is promised. A nil Engine returns ErrInvalidConfig.
// Run returns no partial result on error.
func (e *Engine) Run(ctx context.Context, req RunRequest) (*RunResult, error) { func (e *Engine) Run(ctx context.Context, req RunRequest) (*RunResult, error) {
if e == nil || e.runner == nil { if e == nil || e.runner == nil {
return nil, fmt.Errorf("%w: engine is nil", ErrInvalidConfig) return nil, fmt.Errorf("%w: engine is nil", ErrInvalidConfig)

View File

@@ -1020,6 +1020,7 @@ func TestPublicErrorsSupportErrorsIs(t *testing.T) {
client promptkit.LLMClient client promptkit.LLMClient
schemaDir string schemaDir string
want error want error
notWant error
}{ }{
{ {
name: "invalid request", name: "invalid request",
@@ -1032,6 +1033,7 @@ func TestPublicErrorsSupportErrorsIs(t *testing.T) {
req: promptkit.RunRequest{PromptID: "missing.prompt"}, req: promptkit.RunRequest{PromptID: "missing.prompt"},
client: &fakeLLMClient{response: &promptkit.GenerateResponse{Content: "ok"}}, client: &fakeLLMClient{response: &promptkit.GenerateResponse{Content: "ok"}},
want: promptkit.ErrPromptNotFound, want: promptkit.ErrPromptNotFound,
notWant: promptkit.ErrPromptLoad,
}, },
{ {
name: "profile not found", name: "profile not found",
@@ -1044,6 +1046,7 @@ func TestPublicErrorsSupportErrorsIs(t *testing.T) {
}, },
client: &fakeLLMClient{response: &promptkit.GenerateResponse{Content: "ok"}}, client: &fakeLLMClient{response: &promptkit.GenerateResponse{Content: "ok"}},
want: promptkit.ErrProfileNotFound, want: promptkit.ErrProfileNotFound,
notWant: promptkit.ErrProfileLoad,
}, },
{ {
name: "artifact load", name: "artifact load",
@@ -1114,6 +1117,9 @@ func TestPublicErrorsSupportErrorsIs(t *testing.T) {
if !errors.Is(err, tc.want) { if !errors.Is(err, tc.want) {
t.Fatalf("expected errors.Is(%v), got %v", tc.want, err) t.Fatalf("expected errors.Is(%v), got %v", tc.want, err)
} }
if tc.notWant != nil && errors.Is(err, tc.notWant) {
t.Fatalf("did not expect errors.Is(%v), got %v", tc.notWant, err)
}
}) })
} }
} }
@@ -1699,6 +1705,16 @@ func TestEngineRunLayersTransportAndGenerationTimeouts(t *testing.T) {
configTimeout: 5 * time.Second, configTimeout: 5 * time.Second,
wantRemainingAtRequest: 5 * time.Second, wantRemainingAtRequest: 5 * time.Second,
}, },
{
name: "zero configuration uses ten minute transport default",
wantRemainingAtRequest: 10 * time.Minute,
},
{
name: "negative configuration uses ten minute transport default",
configTimeout: -2 * time.Second,
suppliedClientTimeout: -3 * time.Second,
wantRemainingAtRequest: 10 * time.Minute,
},
{ {
name: "profile deadline is shorter than transport cap", name: "profile deadline is shorter than transport cap",
suppliedClientTimeout: 6 * time.Second, suppliedClientTimeout: 6 * time.Second,

View File

@@ -2,12 +2,16 @@ package promptkit
import "fmt" import "fmt"
// String returns a concise request summary without exposing direct API keys. // String returns a concise request summary without exposing the direct API key
// or input and variable contents. Reflection-based formatting does not carry
// this guarantee.
func (r RunRequest) String() string { func (r RunRequest) String() string {
return r.redactedString() return r.redactedString()
} }
// GoString returns a concise request summary without exposing direct API keys. // GoString returns a concise request summary without exposing the direct API
// key or input and variable contents. Reflection-based formatting does not
// carry this guarantee.
func (r RunRequest) GoString() string { func (r RunRequest) GoString() string {
return r.redactedString() return r.redactedString()
} }
@@ -27,13 +31,15 @@ func (r RunRequest) redactedString() string {
} }
// String returns a concise request summary without exposing direct API keys or // String returns a concise request summary without exposing direct API keys or
// rendered prompt content. // rendered prompt content. Reflection-based formatting does not carry this
// guarantee.
func (r GenerateRequest) String() string { func (r GenerateRequest) String() string {
return r.redactedString() return r.redactedString()
} }
// GoString returns a concise request summary without exposing direct API keys or // GoString returns a concise request summary without exposing direct API keys or
// rendered prompt content. // rendered prompt content. Reflection-based formatting does not carry this
// guarantee.
func (r GenerateRequest) GoString() string { func (r GenerateRequest) GoString() string {
return r.redactedString() return r.redactedString()
} }

View File

@@ -5,7 +5,8 @@ import (
"time" "time"
) )
// MarshalJSON emits prepared-run timestamps only when they are non-zero. // MarshalJSON implements json.Marshaler for PreparedRun. It uses RFC 3339
// timestamps, integer duration_ms, and omits zero timing values.
func (r PreparedRun) MarshalJSON() ([]byte, error) { func (r PreparedRun) MarshalJSON() ([]byte, error) {
var startTime, endTime *time.Time var startTime, endTime *time.Time
if !r.StartTime.IsZero() { if !r.StartTime.IsZero() {
@@ -53,7 +54,8 @@ func (r PreparedRun) MarshalJSON() ([]byte, error) {
}) })
} }
// MarshalJSON emits run duration in milliseconds and omits zero timing values. // MarshalJSON implements json.Marshaler for RunResult. It encodes Duration as
// integer milliseconds in duration_ms and omits zero timing values.
func (r RunResult) MarshalJSON() ([]byte, error) { func (r RunResult) MarshalJSON() ([]byte, error) {
var startTime, endTime *time.Time var startTime, endTime *time.Time
if !r.StartTime.IsZero() { if !r.StartTime.IsZero() {
@@ -90,7 +92,8 @@ func (r RunResult) MarshalJSON() ([]byte, error) {
}) })
} }
// UnmarshalJSON decodes the supported run-result representation. // UnmarshalJSON implements json.Unmarshaler for RunResult. It decodes
// duration_ms into Duration with millisecond precision.
func (r *RunResult) UnmarshalJSON(data []byte) error { func (r *RunResult) UnmarshalJSON(data []byte) error {
var wire runResultJSON var wire runResultJSON
if err := json.Unmarshal(data, &wire); err != nil { if err := json.Unmarshal(data, &wire); err != nil {

View File

@@ -16,6 +16,10 @@ import (
// It does not register global state, maintain a model catalog, or resolve // It does not register global state, maintain a model catalog, or resolve
// credentials. If APIKeyRequired is true, callers satisfy it with // credentials. If APIKeyRequired is true, callers satisfy it with
// RunRequest.APIKey. Raw API keys do not belong in profiles. // RunRequest.APIKey. Raw API keys do not belong in profiles.
//
// The function copies the ExtraParams map itself but does not recursively copy
// nested values. Validation and a deep copy occur when NewEngine applies a
// WithProfiles option containing the returned Profile.
func OpenAICompatibleProfile(cfg OpenAICompatibleProfileConfig) Profile { func OpenAICompatibleProfile(cfg OpenAICompatibleProfileConfig) Profile {
return Profile{ return Profile{
ID: cfg.ID, ID: cfg.ID,

View File

@@ -26,6 +26,30 @@ func TestPreparedRunJSONOmitsZeroTimingValues(t *testing.T) {
} }
} }
func TestPreparedRunJSONTimingRoundTrips(t *testing.T) {
start := time.Date(2026, time.July, 29, 12, 0, 0, 0, time.UTC)
prepared := promptkit.PreparedRun{
PromptID: "prompt",
StartTime: start,
EndTime: start.Add(1250 * time.Millisecond),
DurationMS: 1250,
}
payload, err := json.Marshal(prepared)
if err != nil {
t.Fatalf("marshal prepared run: %v", err)
}
var decoded promptkit.PreparedRun
if err := json.Unmarshal(payload, &decoded); err != nil {
t.Fatalf("unmarshal prepared run: %v", err)
}
if decoded.DurationMS != prepared.DurationMS ||
!decoded.StartTime.Equal(prepared.StartTime) ||
!decoded.EndTime.Equal(prepared.EndTime) {
t.Fatalf("timing values did not round trip: got %#v, want %#v", decoded, prepared)
}
}
func TestRunResultJSONUsesMillisecondsAndRoundTrips(t *testing.T) { func TestRunResultJSONUsesMillisecondsAndRoundTrips(t *testing.T) {
start := time.Date(2026, time.July, 29, 12, 0, 0, 0, time.UTC) start := time.Date(2026, time.July, 29, 12, 0, 0, 0, time.UTC)
result := promptkit.RunResult{ result := promptkit.RunResult{
@@ -74,6 +98,36 @@ func TestRunResultJSONUsesMillisecondsAndRoundTrips(t *testing.T) {
} }
} }
func TestEngineValidationIsSinglePass(t *testing.T) {
client := &fakeLLMClient{
response: &promptkit.GenerateResponse{Content: "not-json"},
}
engine := newContractEngineWithOptions(t, frameworkSchemaDir, promptkit.WithLLMClient(client))
result, err := engine.Run(context.Background(), promptkit.RunRequest{
PromptID: frameworkMarkdownSummaryPromptID,
Inputs: map[string]promptkit.ArtifactRef{
"transcript": promptkit.Inline("Rin opens the gate."),
"glossary": promptkit.Inline("gate: A guarded passage."),
},
Validation: &promptkit.OutputContract{
Format: promptkit.FormatJSON,
ValidationMode: promptkit.ValidationJSON,
RepairAttempts: 3,
},
})
if err != nil {
t.Fatalf("run with failed content validation: %v", err)
}
if result.Validation.Status != promptkit.ValidationFailed ||
result.Validation.RepairAttempts != 0 {
t.Fatalf("expected failed single-pass validation, got %#v", result.Validation)
}
if len(client.requests) != 1 {
t.Fatalf("expected one model generation, got %d", len(client.requests))
}
}
func TestRepeatedOptionsUseLastValueInEachCategory(t *testing.T) { func TestRepeatedOptionsUseLastValueInEachCategory(t *testing.T) {
profile := promptkit.Profile{ID: "profile", Endpoint: "http://example.test/v1", Model: "model"} profile := promptkit.Profile{ID: "profile", Endpoint: "http://example.test/v1", Model: "model"}

329
types.go
View File

@@ -5,159 +5,313 @@ import (
"time" "time"
) )
// ArtifactRefType defines how an artifact is referenced. // ArtifactRefType identifies how an [ArtifactRef] supplies content.
type ArtifactRefType string type ArtifactRefType string
const ( const (
// ArtifactRefInline selects ArtifactRef.Body as the content.
ArtifactRefInline ArtifactRefType = "inline" ArtifactRefInline ArtifactRefType = "inline"
// ArtifactRefFile selects the filesystem path in ArtifactRef.URI.
ArtifactRefFile ArtifactRefType = "file" ArtifactRefFile ArtifactRefType = "file"
) )
// OutputFormat defines the desired output format. // OutputFormat identifies the media format of generated output.
// OutputFormat has a stable JSON string representation.
type OutputFormat string type OutputFormat string
const ( const (
// FormatText identifies plain-text output.
FormatText OutputFormat = "text" FormatText OutputFormat = "text"
// FormatMarkdown identifies Markdown output.
FormatMarkdown OutputFormat = "markdown" FormatMarkdown OutputFormat = "markdown"
// FormatJSON identifies JSON output.
FormatJSON OutputFormat = "json" FormatJSON OutputFormat = "json"
) )
// ValidationMode defines the output validation strategy. // ValidationMode identifies how generated output is checked.
// ValidationMode has a stable JSON string representation.
type ValidationMode string type ValidationMode string
const ( const (
// ValidationNone skips content validation.
ValidationNone ValidationMode = "none" ValidationNone ValidationMode = "none"
// ValidationBasic requires non-empty output.
ValidationBasic ValidationMode = "basic" ValidationBasic ValidationMode = "basic"
// ValidationJSON requires syntactically valid JSON.
ValidationJSON ValidationMode = "json" ValidationJSON ValidationMode = "json"
// ValidationJSONSchema requires JSON that satisfies OutputContract.SchemaPath.
ValidationJSONSchema ValidationMode = "json_schema" ValidationJSONSchema ValidationMode = "json_schema"
) )
// ValidationStatus defines the result of a validation check. // ValidationStatus identifies the completed state of an output check.
// ValidationStatus has a stable JSON string representation.
type ValidationStatus string type ValidationStatus string
const ( const (
// ValidationPassed means the generated output satisfied its contract.
ValidationPassed ValidationStatus = "passed" ValidationPassed ValidationStatus = "passed"
// ValidationFailed means validation completed and rejected the generated
// output. Engine.Run returns this status in a result, not as an error.
ValidationFailed ValidationStatus = "failed" ValidationFailed ValidationStatus = "failed"
// ValidationSkipped means ValidationNone selected no content check.
ValidationSkipped ValidationStatus = "skipped" ValidationSkipped ValidationStatus = "skipped"
) )
// CacheControlType defines provider cache behavior for prompt content. // CacheControlType identifies provider cache behavior for prompt content.
// CacheControlType has a stable JSON string representation.
type CacheControlType string type CacheControlType string
const ( const (
// CacheControlEphemeral requests provider-defined ephemeral caching.
CacheControlEphemeral CacheControlType = "ephemeral" CacheControlEphemeral CacheControlType = "ephemeral"
) )
// StructuredOutputType identifies provider-level structured output modes. // StructuredOutputType identifies provider-level structured output modes.
// StructuredOutputType has a stable JSON string representation.
type StructuredOutputType string type StructuredOutputType string
const ( const (
// StructuredOutputJSONSchema supplies JSON Schema response constraints.
StructuredOutputJSONSchema StructuredOutputType = "json_schema" StructuredOutputJSONSchema StructuredOutputType = "json_schema"
) )
// RunRequest represents a request to prepare or run a single prompt. // RunRequest selects one prompt execution. It has no stable JSON
// representation.
//
// Prepare and Run copy the request's maps, pointers, and nested
// JSON-compatible values before using them. The caller may mutate the request
// after either method returns.
type RunRequest struct { type RunRequest struct {
// PromptID is the required non-empty prompt identifier.
PromptID string PromptID string
// PromptVersion optionally selects one version of PromptID. When empty, the
// prompt source must contain exactly one matching version.
PromptVersion string PromptVersion string
// ProfileID selects an execution profile. When empty, the prompt's default
// profile is used; if both are empty, the error matches ErrProfileRequired
// and ErrInvalidRequest.
ProfileID string ProfileID string
// APIKey is a request-scoped direct credential. It takes precedence over
// APIKeyEnv, is passed to the selected LLMClient, and is never included in
// prepared values, results, hashes, JSON, String, or GoString output.
APIKey string `json:"-"` APIKey string `json:"-"`
// Inputs maps prompt input names to references. A nil or empty map is valid
// only when the selected prompt and its templates require no inputs.
Inputs map[string]ArtifactRef Inputs map[string]ArtifactRef
// Vars supplies Go-template data for messages and the session ID. Nil and
// empty maps are equivalent.
Vars map[string]string Vars map[string]string
// Execution optionally overrides individual profile execution settings.
// Nil uses the selected profile over framework defaults.
Execution *ExecutionTargetOverride Execution *ExecutionTargetOverride
// Validation optionally replaces the prompt's complete output contract. It
// does not merge individual fields. Nil uses the prompt contract.
Validation *OutputContract Validation *OutputContract
} }
// PreparedRun contains prepared prompt execution state. It does not include // PreparedRun contains prepared prompt execution state. It does not include
// resolved API key values, model output, validation results, or internal target // resolved API key values, model output, validation results, or internal target
// presence metadata. // presence metadata. PreparedRun has a stable JSON representation.
//
// All maps, slices, pointers, and schema values are caller-owned copies. JSON
// timestamps use RFC 3339 and zero timing values are omitted. Hash formats are
// opaque.
type PreparedRun struct { type PreparedRun struct {
// PromptID is the selected prompt identifier.
PromptID string `json:"prompt_id"` PromptID string `json:"prompt_id"`
// PromptVersion is the selected prompt version.
PromptVersion string `json:"prompt_version,omitempty"` PromptVersion string `json:"prompt_version,omitempty"`
// PromptHash is an opaque equality value for the selected definition.
PromptHash string `json:"prompt_hash,omitempty"` PromptHash string `json:"prompt_hash,omitempty"`
// SelectedProfileID is the explicit request profile or prompt default that
// supplied execution settings.
SelectedProfileID string `json:"selected_profile_id"` SelectedProfileID string `json:"selected_profile_id"`
// EffectiveModelParams contains framework defaults overlaid by the selected
// profile and then request overrides. It excludes resolved API-key values.
EffectiveModelParams ExecutionTarget `json:"effective_model_params"` EffectiveModelParams ExecutionTarget `json:"effective_model_params"`
// OutputContract is the complete effective output contract.
OutputContract OutputContract `json:"output_contract"` OutputContract OutputContract `json:"output_contract"`
// StructuredOutput is non-nil for JSON Schema validation and contains the
// provider-facing response constraint passed to an LLM client.
StructuredOutput *StructuredOutputSpec `json:"structured_output,omitempty"` StructuredOutput *StructuredOutputSpec `json:"structured_output,omitempty"`
// InputHashes maps every supplied input name to its opaque artifact hash.
InputHashes map[string]string `json:"input_hashes,omitempty"` InputHashes map[string]string `json:"input_hashes,omitempty"`
// SessionID is the trimmed rendered session identifier, if any.
SessionID string `json:"session_id,omitempty"` SessionID string `json:"session_id,omitempty"`
// RenderedPromptHash is an opaque equality value for SessionID and Messages.
RenderedPromptHash string `json:"rendered_prompt_hash"` RenderedPromptHash string `json:"rendered_prompt_hash"`
// Messages are the rendered messages that Run passes to the LLM client.
Messages []RenderedMessage `json:"messages"` Messages []RenderedMessage `json:"messages"`
// StartTime is the UTC time at which preparation began.
StartTime time.Time `json:"start_time,omitempty"` StartTime time.Time `json:"start_time,omitempty"`
// EndTime is the UTC time at which preparation completed.
EndTime time.Time `json:"end_time,omitempty"` EndTime time.Time `json:"end_time,omitempty"`
// DurationMS is preparation elapsed time in integer milliseconds. JSON uses
// duration_ms and omits a zero value.
DurationMS int64 `json:"duration_ms,omitempty"` DurationMS int64 `json:"duration_ms,omitempty"`
} }
// RunResult contains generated output, validation state, and run metadata. // RunResult contains generated output, validation state, and run metadata.
// RunResult has a stable JSON representation and round-trips its Duration
// through the duration_ms JSON field.
//
// All maps, slices, and nested values are caller-owned copies. JSON timestamps
// use RFC 3339 and zero timing values are omitted. Run IDs and hash formats are
// opaque.
type RunResult struct { type RunResult struct {
// RunID is an opaque identifier for this invocation.
RunID string `json:"run_id"` RunID string `json:"run_id"`
// Artifact contains the generated output and derived metadata.
Artifact Artifact `json:"artifact"` Artifact Artifact `json:"artifact"`
// RawOutput is the exact generated content before artifact classification
// and validation.
RawOutput string `json:"raw_output"` RawOutput string `json:"raw_output"`
// Validation records the completed content check.
Validation ValidationResult `json:"validation"` Validation ValidationResult `json:"validation"`
// PromptID is the selected prompt identifier.
PromptID string `json:"prompt_id"` PromptID string `json:"prompt_id"`
// PromptVersion is the selected prompt version.
PromptVersion string `json:"prompt_version,omitempty"` PromptVersion string `json:"prompt_version,omitempty"`
// PromptHash is the same opaque definition equality value exposed by
// PreparedRun.
PromptHash string `json:"prompt_hash,omitempty"` PromptHash string `json:"prompt_hash,omitempty"`
// RenderedPromptHash is the same opaque rendered-prompt equality value
// computed during preparation.
RenderedPromptHash string `json:"rendered_prompt_hash"` RenderedPromptHash string `json:"rendered_prompt_hash"`
// SelectedProfileID identifies the profile used for execution.
SelectedProfileID string `json:"selected_profile_id"` SelectedProfileID string `json:"selected_profile_id"`
// ModelName is the effective model name and equals
// EffectiveModelParams.Model.
ModelName string `json:"model_name"` ModelName string `json:"model_name"`
// Endpoint is the effective base endpoint and equals
// EffectiveModelParams.Endpoint.
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
// EffectiveModelParams contains the settings supplied to the LLM client,
// excluding resolved API-key values.
EffectiveModelParams ExecutionTarget `json:"effective_model_params"` EffectiveModelParams ExecutionTarget `json:"effective_model_params"`
// InputHashes are the opaque input equality values computed during
// preparation.
InputHashes map[string]string `json:"input_hashes,omitempty"` InputHashes map[string]string `json:"input_hashes,omitempty"`
// Usage is the token accounting reported by the LLM client.
Usage TokenUsage `json:"usage"` Usage TokenUsage `json:"usage"`
// StartTime is the UTC time immediately before preparation begins.
StartTime time.Time `json:"start_time,omitempty"` StartTime time.Time `json:"start_time,omitempty"`
// EndTime is the UTC time after generation and validation complete.
EndTime time.Time `json:"end_time,omitempty"` EndTime time.Time `json:"end_time,omitempty"`
// Duration covers preparation, generation, and validation. JSON represents
// it as integer milliseconds in duration_ms and omits a zero value.
Duration time.Duration `json:"-"` Duration time.Duration `json:"-"`
} }
// ArtifactRef represents a reference to prompt input content. // ArtifactRef identifies prompt input content. It has no stable JSON
// representation. Prefer [File], [Inline], or [InlineWithURI] to construct one.
type ArtifactRef struct { type ArtifactRef struct {
// Type must be ArtifactRefInline or ArtifactRefFile.
Type ArtifactRefType Type ArtifactRefType
// URI is the file path for ArtifactRefFile and optional provenance metadata
// for ArtifactRefInline.
URI string URI string
// Body is the content for ArtifactRefInline and is ignored for
// ArtifactRefFile.
Body string Body string
} }
// Artifact represents loaded artifact content. // Artifact represents loaded or generated content and has a stable JSON
// representation. Body uses encoding/json's base64 representation for []byte.
type Artifact struct { type Artifact struct {
// Name is artifact metadata. During input preparation the engine fills an
// empty reader-supplied name with the request input-map key.
Name string `json:"name"` Name string `json:"name"`
// ContentType is the media type reported by the reader or derived for
// generated output.
ContentType string `json:"content_type"` ContentType string `json:"content_type"`
// Body is the artifact content. Engine boundaries copy this slice.
Body []byte `json:"body"` Body []byte `json:"body"`
// URI is optional source or result provenance metadata.
URI string `json:"uri"` URI string `json:"uri"`
// Size is content-size metadata in bytes.
Size int64 `json:"size"` Size int64 `json:"size"`
// Hash is an opaque content equality value when the producing reader
// supplies one. Its format and algorithm are not API contracts.
Hash string `json:"hash"` Hash string `json:"hash"`
} }
// ArtifactReader resolves a prompt input reference into its content. // ArtifactReader resolves a prompt input reference into its content.
// //
// Readers are responsible for supplying artifact metadata. The engine assigns // Read may be called concurrently. It must honor ctx cancellation to make
// an input-map name only when the returned artifact name is empty. // Prepare and Run responsive to cancellation. The engine passes a copied ref
// and immediately copies the returned Artifact.Body; it does not retain either
// value. Readers supply artifact metadata, and the engine assigns an input-map
// name only when the returned artifact name is empty.
//
// Returning a non-nil error makes the engine return an error matching
// ErrArtifactLoad while preserving the reader error through errors.Is.
// Returning a nil artifact with a nil error also produces ErrArtifactLoad.
type ArtifactReader interface { type ArtifactReader interface {
Read(context.Context, ArtifactRef) (*Artifact, error) Read(context.Context, ArtifactRef) (*Artifact, error)
} }
// ExecutionTarget represents effective model runtime settings. // ExecutionTarget represents effective model runtime settings and has a stable
// JSON representation. It never exposes a resolved API-key value.
type ExecutionTarget struct { type ExecutionTarget struct {
// Endpoint is the model-provider base URL.
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
// Model is the provider model identifier.
Model string `json:"model"` Model string `json:"model"`
// Temperature is the effective sampling temperature from 0 through 2.
Temperature float64 `json:"temperature"` Temperature float64 `json:"temperature"`
// MaxTokens is the non-negative effective output-token limit. Zero leaves
// the limit unspecified to compatible providers unless it was an explicit
// request override.
MaxTokens int `json:"max_tokens"` MaxTokens int `json:"max_tokens"`
// TopP is the effective nucleus-sampling value from 0 through 1.
TopP float64 `json:"top_p"` TopP float64 `json:"top_p"`
// TimeoutSeconds is the non-negative per-generation deadline. Zero disables
// this deadline without disabling caller cancellation or the transport cap.
TimeoutSeconds int `json:"timeout_seconds"` TimeoutSeconds int `json:"timeout_seconds"`
// ServiceTier is an optional provider-specific request tier.
ServiceTier string `json:"service_tier"` ServiceTier string `json:"service_tier"`
// ReasoningEffort is an optional provider-specific reasoning setting.
ReasoningEffort string `json:"reasoning_effort"` ReasoningEffort string `json:"reasoning_effort"`
// APIKeyEnv is an environment-variable name, not its credential value.
APIKeyEnv string `json:"api_key_env"` APIKeyEnv string `json:"api_key_env"`
// ExtraParams contains copied JSON-compatible provider parameters.
ExtraParams map[string]any `json:"extra_params"` ExtraParams map[string]any `json:"extra_params"`
} }
// ExecutionTargetOverride represents per-request runtime setting overrides. // ExecutionTargetOverride represents per-request runtime setting overrides and
// has no stable JSON representation.
//
// Non-empty string fields replace profile values. Non-nil numeric pointers
// replace profile values and preserve explicit zero. A non-empty ExtraParams
// map replaces the complete profile map rather than merging keys. Empty string
// fields, nil pointers, and a nil or empty ExtraParams map inherit the selected
// profile over framework defaults.
type ExecutionTargetOverride struct { type ExecutionTargetOverride struct {
// Endpoint replaces the profile endpoint when non-empty.
Endpoint string Endpoint string
// Model replaces the profile model when non-empty.
Model string Model string
// Temperature, when non-nil, must point to a value from 0 through 2.
Temperature *float64 Temperature *float64
// MaxTokens, when non-nil, must point to a non-negative value.
MaxTokens *int MaxTokens *int
// TopP, when non-nil, must point to a value from 0 through 1.
TopP *float64 TopP *float64
// TimeoutSeconds, when non-nil, must point to a non-negative value. A
// pointed-to zero disables the per-generation deadline.
TimeoutSeconds *int TimeoutSeconds *int
// ServiceTier replaces the profile value when non-blank.
ServiceTier string ServiceTier string
// ReasoningEffort replaces the profile value when non-blank. An empty value
// cannot clear a profile setting.
ReasoningEffort string ReasoningEffort string
// APIKeyEnv replaces the profile environment-variable name when non-blank.
// A direct RunRequest.APIKey still takes precedence over environment lookup.
APIKeyEnv string APIKeyEnv string
// ExtraParams, when non-empty, replaces the profile map. Values must be
// JSON-compatible: nil, booleans, finite numbers, strings, arrays or slices,
// and maps with non-empty string keys. Cycles are invalid.
ExtraParams map[string]any ExtraParams map[string]any
} }
@@ -166,18 +320,38 @@ type ExecutionTargetOverride struct {
// It is equivalent to a loaded profile file after validation. Raw API keys do // It is equivalent to a loaded profile file after validation. Raw API keys do
// not belong in profiles; use APIKeyRequired to require callers to provide // not belong in profiles; use APIKeyRequired to require callers to provide
// RunRequest.APIKey for each request, or use profile YAML api_key_env with file // RunRequest.APIKey for each request, or use profile YAML api_key_env with file
// and FS profile sources. // and FS profile sources. Profile has no stable JSON representation.
//
// WithProfiles validates and copies Profile values during NewEngine. Numeric
// zero, blank strings, and an empty ExtraParams map inherit framework defaults;
// use ExecutionTargetOverride pointer fields to request explicit numeric zero.
type Profile struct { type Profile struct {
// ID is the required non-blank profile identifier. WithProfiles trims it.
ID string ID string
// Endpoint is the required non-blank model-provider base URL.
Endpoint string Endpoint string
// Model is the required non-blank provider model identifier.
Model string Model string
// Temperature is from 0 through 2. Zero inherits the framework default.
Temperature float64 Temperature float64
// MaxTokens is non-negative. Zero inherits the framework default.
MaxTokens int MaxTokens int
// TopP is from 0 through 1. Zero inherits the framework default rather than
// selecting an explicit zero.
TopP float64 TopP float64
// TimeoutSeconds is non-negative. Zero inherits the framework default.
TimeoutSeconds int TimeoutSeconds int
// ServiceTier is optional; a blank value inherits the framework default.
ServiceTier string ServiceTier string
// ReasoningEffort is optional; a blank value inherits the framework
// default.
ReasoningEffort string ReasoningEffort string
// APIKeyRequired requires a non-blank RunRequest.APIKey. It does not store a
// credential or enable environment lookup.
APIKeyRequired bool APIKeyRequired bool
// ExtraParams contains provider-specific JSON-compatible values. An empty
// map inherits framework defaults. WithProfiles validates and deeply copies
// it during NewEngine.
ExtraParams map[string]any ExtraParams map[string]any
} }
@@ -186,120 +360,215 @@ type Profile struct {
// //
// It contains ordinary profile fields for OpenAI-compatible chat-completions // It contains ordinary profile fields for OpenAI-compatible chat-completions
// endpoints. APIKeyRequired is satisfied by RunRequest.APIKey. Raw API keys do // endpoints. APIKeyRequired is satisfied by RunRequest.APIKey. Raw API keys do
// not belong in this config. // not belong in this config. OpenAICompatibleProfileConfig has no stable JSON
// representation and is not validated until its resulting Profile is supplied
// through WithProfiles to NewEngine.
type OpenAICompatibleProfileConfig struct { type OpenAICompatibleProfileConfig struct {
// ID becomes Profile.ID.
ID string ID string
// Endpoint becomes Profile.Endpoint.
Endpoint string Endpoint string
// Model becomes Profile.Model.
Model string Model string
// APIKeyRequired becomes Profile.APIKeyRequired.
APIKeyRequired bool APIKeyRequired bool
// Temperature becomes Profile.Temperature.
Temperature float64 Temperature float64
// MaxTokens becomes Profile.MaxTokens.
MaxTokens int MaxTokens int
// TopP becomes Profile.TopP.
TopP float64 TopP float64
// TimeoutSeconds becomes Profile.TimeoutSeconds.
TimeoutSeconds int TimeoutSeconds int
// ServiceTier becomes Profile.ServiceTier.
ServiceTier string ServiceTier string
// ReasoningEffort becomes Profile.ReasoningEffort.
ReasoningEffort string ReasoningEffort string
// ExtraParams becomes a shallow-copied Profile.ExtraParams map. NewEngine
// performs validation and a deep copy when WithProfiles applies the result.
ExtraParams map[string]any ExtraParams map[string]any
} }
// ExecutionTargetPresence tracks which numeric runtime settings were explicit // ExecutionTargetPresence tracks which numeric runtime settings were explicit
// request overrides. // request overrides, including explicit zero values. It has a stable JSON
// representation and is supplied to injected LLM clients so they can preserve
// omission semantics.
type ExecutionTargetPresence struct { type ExecutionTargetPresence struct {
// Temperature reports a non-nil ExecutionTargetOverride.Temperature.
Temperature bool `json:"temperature"` Temperature bool `json:"temperature"`
// MaxTokens reports a non-nil ExecutionTargetOverride.MaxTokens.
MaxTokens bool `json:"max_tokens"` MaxTokens bool `json:"max_tokens"`
// TopP reports a non-nil ExecutionTargetOverride.TopP.
TopP bool `json:"top_p"` TopP bool `json:"top_p"`
// TimeoutSeconds reports a non-nil ExecutionTargetOverride.TimeoutSeconds.
TimeoutSeconds bool `json:"timeout_seconds"` TimeoutSeconds bool `json:"timeout_seconds"`
} }
// OutputContract defines output and validation requirements. // OutputContract defines output and validation requirements and has a stable
// JSON representation.
//
// A non-nil RunRequest.Validation replaces the complete prompt contract. It
// does not merge fields. The public Engine validates generated output once and
// does not install an output repairer.
type OutputContract struct { type OutputContract struct {
// Format selects generated artifact metadata. An empty effective value
// defaults to FormatText.
Format OutputFormat `json:"format"` Format OutputFormat `json:"format"`
// ValidationMode selects the content check. Use one of the declared
// ValidationMode constants.
ValidationMode ValidationMode `json:"validation_mode"` ValidationMode ValidationMode `json:"validation_mode"`
// SchemaPath is required when ValidationMode is ValidationJSONSchema and is
// ignored by other modes.
SchemaPath string `json:"schema_path"` SchemaPath string `json:"schema_path"`
// RepairAttempts is a requested repair limit. A non-positive value requests
// no repairs. The public Engine performs no repairs even when this value is
// positive, so its runs report zero attempts used.
RepairAttempts int `json:"repair_attempts"` RepairAttempts int `json:"repair_attempts"`
} }
// ValidationResult represents output validation state. // ValidationResult represents a completed output check and has a stable JSON
// representation. An operational inability to perform validation is returned
// as ErrValidation instead of a ValidationResult.
type ValidationResult struct { type ValidationResult struct {
// Status is Passed, Failed, or Skipped.
Status ValidationStatus `json:"status"` Status ValidationStatus `json:"status"`
// Mode is the effective validation mode.
Mode ValidationMode `json:"mode"` Mode ValidationMode `json:"mode"`
// Errors contains validation diagnostics when Status is ValidationFailed.
Errors []string `json:"errors,omitempty"` Errors []string `json:"errors,omitempty"`
// SchemaPath is the effective schema path for JSON Schema validation.
SchemaPath string `json:"schema_path,omitempty"` SchemaPath string `json:"schema_path,omitempty"`
// RepairAttempts is the number of repairs actually attempted. It is always
// zero for the public Engine.
RepairAttempts int `json:"repair_attempts"` RepairAttempts int `json:"repair_attempts"`
// IsValid is true for ValidationPassed and ValidationSkipped and false for
// ValidationFailed.
IsValid bool `json:"is_valid"` IsValid bool `json:"is_valid"`
} }
// TokenUsage tracks token consumption. // TokenUsage contains model-client token accounting and has a stable JSON
// representation. Promptkit preserves values reported by the client and does
// not derive or reconcile them.
type TokenUsage struct { type TokenUsage struct {
// PromptTokens is the reported input-token count.
PromptTokens int `json:"prompt_tokens"` PromptTokens int `json:"prompt_tokens"`
// CompletionTokens is the reported generated-token count.
CompletionTokens int `json:"completion_tokens"` CompletionTokens int `json:"completion_tokens"`
// TotalTokens is the reported total-token count.
TotalTokens int `json:"total_tokens"` TotalTokens int `json:"total_tokens"`
// CachedTokens is the reported cached-input-token count.
CachedTokens int `json:"cached_tokens"` CachedTokens int `json:"cached_tokens"`
// CacheWriteTokens is the reported cache-write-token count.
CacheWriteTokens int `json:"cache_write_tokens"` CacheWriteTokens int `json:"cache_write_tokens"`
} }
// RenderedPrompt is the fully rendered prompt passed to an LLM client. // RenderedPrompt is the fully rendered prompt passed to an LLM client and has
// a stable JSON representation.
type RenderedPrompt struct { type RenderedPrompt struct {
// SessionID is the optional trimmed session identifier rendered from the
// prompt definition.
SessionID string `json:"session_id,omitempty"` SessionID string `json:"session_id,omitempty"`
// Messages contains rendered messages in definition order.
Messages []RenderedMessage `json:"messages"` Messages []RenderedMessage `json:"messages"`
} }
// RenderedMessage is a rendered chat message. // RenderedMessage is a rendered chat message and has a stable JSON
// representation.
type RenderedMessage struct { type RenderedMessage struct {
// Role is the definition-supplied chat role.
Role string `json:"role"` Role string `json:"role"`
// Content is the rendered message text.
Content string `json:"content"` Content string `json:"content"`
// CacheControl is optional provider cache metadata.
CacheControl *CacheControl `json:"cache_control,omitempty"` CacheControl *CacheControl `json:"cache_control,omitempty"`
} }
// CacheControl describes provider cache metadata attached to prompt content. // CacheControl describes provider cache metadata attached to prompt content
// and has a stable JSON representation.
type CacheControl struct { type CacheControl struct {
// Type identifies the cache behavior.
Type CacheControlType `json:"type"` Type CacheControlType `json:"type"`
// TTL is an optional provider cache lifetime.
TTL string `json:"ttl,omitempty"` TTL string `json:"ttl,omitempty"`
} }
// StructuredOutputSpec describes provider-level structured output. // StructuredOutputSpec describes provider-level structured output and has a
// stable JSON representation.
type StructuredOutputSpec struct { type StructuredOutputSpec struct {
// Type identifies the structured-output mechanism.
Type StructuredOutputType `json:"type"` Type StructuredOutputType `json:"type"`
// JSONSchema contains constraints when Type is StructuredOutputJSONSchema.
JSONSchema *StructuredOutputJSONSpec `json:"json_schema,omitempty"` JSONSchema *StructuredOutputJSONSpec `json:"json_schema,omitempty"`
} }
// StructuredOutputJSONSpec contains JSON Schema output constraints. // StructuredOutputJSONSpec contains provider-facing JSON Schema output
// constraints and has a stable JSON representation.
type StructuredOutputJSONSpec struct { type StructuredOutputJSONSpec struct {
// Name is the provider-facing schema name.
Name string `json:"name"` Name string `json:"name"`
// Strict requests strict provider enforcement of Schema.
Strict bool `json:"strict"` Strict bool `json:"strict"`
// Schema is a caller-owned copy of the loaded JSON Schema document.
Schema any `json:"schema"` Schema any `json:"schema"`
} }
// LLMClient executes rendered prompts for Engine.Run. // LLMClient executes rendered prompts for [Engine.Run].
//
// Generate may be called concurrently. It must honor context cancellation to
// make Run responsive to cancellation. The request and all nested maps,
// slices, and pointers are client-owned copies and may be mutated or retained
// without affecting engine state.
//
// A returned error makes Run return ErrLLMGenerate while preserving the client
// error through errors.Is. A nil response with a nil error also produces
// ErrLLMGenerate. Promptkit copies the non-nil response before returning from
// Run.
type LLMClient interface { type LLMClient interface {
Generate(context.Context, GenerateRequest) (*GenerateResponse, error) Generate(context.Context, GenerateRequest) (*GenerateResponse, error)
} }
// GenerateRequest is passed to an injected LLM client. // GenerateRequest is passed to an injected LLM client and has a stable JSON
// representation. Its String and GoString methods omit rendered content and
// direct credentials.
type GenerateRequest struct { type GenerateRequest struct {
// Prompt contains the rendered session ID and messages.
Prompt RenderedPrompt `json:"prompt"` Prompt RenderedPrompt `json:"prompt"`
// Target contains effective model settings without the direct API key.
Target ExecutionTarget `json:"target"` Target ExecutionTarget `json:"target"`
// TargetPresence distinguishes inherited numeric zeros from explicit
// request overrides.
TargetPresence ExecutionTargetPresence `json:"target_presence"` TargetPresence ExecutionTargetPresence `json:"target_presence"`
// StructuredOutput contains provider response constraints when requested.
StructuredOutput *StructuredOutputSpec `json:"structured_output,omitempty"` StructuredOutput *StructuredOutputSpec `json:"structured_output,omitempty"`
// APIKey is the direct request-scoped credential, if any. It is excluded
// from JSON, String, and GoString output.
APIKey string `json:"-"` APIKey string `json:"-"`
} }
// GenerateResponse is returned by an injected LLM client. // GenerateResponse is returned by an injected LLM client and has a stable JSON
// representation.
type GenerateResponse struct { type GenerateResponse struct {
// Content is the generated output. It must be non-empty when using the
// built-in client; injected clients may return empty content for Promptkit
// validation to classify.
Content string `json:"content"` Content string `json:"content"`
// Usage is the client's token accounting.
Usage TokenUsage `json:"usage"` Usage TokenUsage `json:"usage"`
} }
// File returns a file-backed artifact reference. // File returns a file-backed artifact reference whose URI is path.
func File(path string) ArtifactRef { func File(path string) ArtifactRef {
return ArtifactRef{Type: ArtifactRefFile, URI: path} return ArtifactRef{Type: ArtifactRefFile, URI: path}
} }
// Inline returns an inline artifact reference. // Inline returns an inline artifact reference whose Body is body and whose URI
// is empty.
func Inline(body string) ArtifactRef { func Inline(body string) ArtifactRef {
return ArtifactRef{Type: ArtifactRefInline, Body: body} return ArtifactRef{Type: ArtifactRefInline, Body: body}
} }
// InlineWithURI returns an inline artifact reference with URI metadata. // InlineWithURI returns an inline artifact reference with body content and uri
// provenance metadata.
func InlineWithURI(uri string, body string) ArtifactRef { func InlineWithURI(uri string, body string) ArtifactRef {
return ArtifactRef{Type: ArtifactRefInline, URI: uri, Body: body} return ArtifactRef{Type: ArtifactRefInline, URI: uri, Body: body}
} }