Document appended request messages

This commit is contained in:
2026-08-26 02:19:44 +00:00
parent b0999112cc
commit 174516cb39
11 changed files with 80 additions and 24 deletions

View File

@@ -196,6 +196,26 @@ validity is not evidence of factual or domain correctness. See the
[`OutputContract` GoDoc](../../types.go) for the exact budget and eligibility
rules.
### Append Already-Rendered Messages
An application can include an earlier assistant response and its own corrective
instruction in a fresh request without changing the configured prompt:
```go
request.AppendedMessages = []promptkit.RenderedMessage{
{Role: promptkit.RoleAssistant, Content: previousResponse},
{Role: promptkit.RoleUser, Content: correction},
}
result, err := engine.Run(ctx, request)
```
These messages are already rendered: Promptkit does not template or resolve
files in them, and they can contain sensitive model output or application
feedback. Promptkit remains stateless; every `Run` call re-resolves its current
sources and the application owns any semantic retry budget. When a
pre-execution equality check is required, use `PrepareExecution` and compare
its opaque rendered-prompt hash before invoking `RunPrepared`.
## Inputs, Profiles, And Overrides
Use `File`, `Inline`, or `InlineWithURI` to construct request inputs. A request

View File

@@ -82,7 +82,13 @@ allowed.
### Messages And Templates
Each message has a non-empty `role` and exactly one of:
Each message has a `role` that Promptkit trims and lowercases. It must then be
exactly one of `developer`, `system`, `user`, or `assistant`; blank, custom,
`tool`, and `function` roles are invalid. This intentionally tightens the
previous nonblank-string rule. Consumers migrating to the next minor release
must update any nonstandard prompt-definition roles before upgrading.
Each message also has exactly one of:
- `content`, containing an inline Go template; or
- `content_file`, naming a file whose contents are the Go template.

View File

@@ -55,6 +55,12 @@ Each ordinary message contains its `role` and string `content`. A
cache-controlled message instead uses a text content block containing `type`,
`text`, and `cache_control`; an empty cache-control TTL is omitted.
Promptkit sends only `developer`, `system`, `user`, and `assistant` roles and
does so without provider-specific translation. Tool and deprecated function
payloads are outside this text-message contract. A backend or model that
rejects an otherwise supported role or context returns its ordinary provider
error, which follows the normal generation-error path.
The effective direct or prompt-rendered session ID is trimmed, limited to 256
Unicode code points, and sent when nonempty as top-level `session_id`. It is
never also sent as a session header.

View File

@@ -21,8 +21,9 @@ uses internal domain values for rendered prompts, execution targets,
structured output, responses, and token usage.
The runner supplies a fully resolved target after applying backend, profile,
and request precedence. The client uses its endpoint, credential metadata,
generation fields, and extra parameters. `BackendID` remains routing metadata
and request precedence, plus canonical provider-bound text messages. The
client uses its endpoint, credential metadata, generation fields, and extra
parameters. `BackendID` remains routing metadata
for the generation boundary and is not mapped into the provider payload.
Construction trims and validates a nonempty configured base URL and clones any

View File

@@ -84,7 +84,8 @@ profile, or backend:
schema metadata from it when required;
2. load and hash input artifacts;
3. render messages and the prompt-defined session;
4. apply any direct session ID;
4. apply any direct session ID, then append already-normalized request messages
after the rendered definition messages;
5. hash the effective rendered prompt; and
6. construct the prepared value and preparation timing.
@@ -113,7 +114,10 @@ the prompt session template, and is applied after ordinary message rendering.
A blank direct value retains prompt-template behavior. The runner clears the
template only on a value copy of the definition, so the definition hash always
describes the original source while the rendered-prompt hash includes the
effective direct or rendered session.
effective direct or rendered session and the complete effective message
sequence. The rendered-prompt hash uses a versioned, length-framed SHA-256
encoding of that session, every role and content value, and cache-control
presence and values; its hexadecimal value is opaque.
The registry is read-only after engine construction. Concurrent `Prepare` and
`Run` calls resolve independent defensive backend values and keep all
@@ -146,9 +150,10 @@ each actual generation call.
After a failed `basic`, JSON, or JSON Schema validation with a positive frozen
budget, the installed repairer can make a bounded corrective call. Each request
starts with a fresh copy of the complete original rendered messages, includes
only the latest nonempty candidate as an assistant message, and appends one
corrective user message. Empty candidates omit that assistant message. The
starts with a fresh copy of the complete effective message sequence (the
configured prefix followed by the request suffix), includes only the latest
nonempty candidate as an assistant message, and appends one corrective user
message. Empty candidates omit that assistant message. The
correction carries validation diagnostics as JSON data bounded to 64 KiB; the
full diagnostics remain in the validation result.

View File

@@ -21,6 +21,12 @@ paths, content opening, and root containment. Each lookup remains a
point-in-time scan: definitions and catalogs are not cached, and file-backed
message content is opened only for the exact selected candidate.
Message roles are normalized through the shared domain owner by trimming
Unicode whitespace and lowercasing. Only `developer`, `system`, `user`, and
`assistant` are published; invalid roles remain selected prompt-definition
failures rather than becoming request errors. Cache-control metadata uses the
same shared domain normalization and defensive-copy rule.
Operating-system sources enforce containment against canonical roots and
targets so symlinks cannot escape. Injected `fs.FS` sources enforce containment
in their clean relative path namespace. A single-file source uses the selected
@@ -122,8 +128,9 @@ Session and message parsing and execution remain synchronous. The renderer
checks cancellation before and after each parse and execution boundary,
between artifact conversion chunks, around each message, and before publishing
the complete prompt. It cannot interrupt template work already in progress and
never publishes a partial prompt after observing cancellation. It carries
message roles, session IDs, and cache control into the rendered prompt. The
never publishes a partial prompt after observing cancellation. It validates and
canonicalizes message roles before carrying roles, session IDs, and cache
control into the rendered prompt. The
[renderer tests](../../internal/prompt/renderer_test.go) own rendering behavior.
## Schemas And Output Validation

View File

@@ -347,6 +347,8 @@ structural repair, and opaque rendered-prompt identity.
## Stage 4: Publish Canonical Documentation And Complete Validation
**Status:** Complete
### Objective
Bring every canonical documentation owner into line with the implemented API,

View File

@@ -571,6 +571,8 @@ func (e *Engine) InspectProfile(ctx context.Context, profileID string) (*Profile
}
// Prepare resolves and renders a prompt request without calling an LLM.
// It appends any validated RunRequest.AppendedMessages after rendered
// definition messages in the returned caller-owned snapshot.
//
// Prepare selects the prompt and profile, resolves any selected backend and
// effective execution settings, resolves the output contract, loads and hashes
@@ -605,7 +607,8 @@ func (e *Engine) Prepare(ctx context.Context, req RunRequest) (*PreparedRun, err
}
// PrepareExecution completely prepares a prompt request without calling the
// configured LLMClient or reserving backend admission capacity.
// configured LLMClient or reserving backend admission capacity. Validated
// RunRequest.AppendedMessages are included in the frozen effective messages.
//
// The returned opaque handle is bound to this Engine and permits one
// [Engine.RunPrepared] invocation. Preparation freezes the selected sources,
@@ -637,7 +640,8 @@ func (e *Engine) PrepareExecution(ctx context.Context, req RunRequest) (*Prepare
}
// Run prepares a request, invokes the configured LLMClient, and validates the
// generated output.
// generated output. Each call resolves current sources and composes a fresh,
// stateless effective prompt with any validated RunRequest.AppendedMessages.
//
// A content-validation failure is a successful run whose
// RunResult.Validation has Status ValidationFailed. When its output contract

View File

@@ -3,12 +3,12 @@ package promptkit
import "gitea.maximumdirect.net/eric/promptkit/internal/domain"
const (
// RoleDeveloper identifies a developer instruction message.
// RoleDeveloper identifies a provider-bound developer instruction message.
RoleDeveloper = domain.RoleDeveloper
// RoleSystem identifies a system instruction message.
// RoleSystem identifies a provider-bound system instruction message.
RoleSystem = domain.RoleSystem
// RoleUser identifies a user message.
// RoleUser identifies a provider-bound user message.
RoleUser = domain.RoleUser
// RoleAssistant identifies an assistant message.
// RoleAssistant identifies a provider-bound assistant message.
RoleAssistant = domain.RoleAssistant
)

View File

@@ -17,7 +17,9 @@ type PreparedExecution struct {
// Details returns a fresh caller-owned, credential-redacted copy of the
// prepared request details. Mutating the result cannot affect execution or a
// later Details call. Details remains available after execution or discard.
// later Details call. Its complete effective message content, including any
// appended request messages, remains subject to the caller's data-handling
// policy. Details remains available after execution or discard.
//
// A nil receiver or zero-value PreparedExecution returns a zero [PreparedRun].
func (p *PreparedExecution) Details() PreparedRun {

View File

@@ -125,10 +125,11 @@ type RunRequest struct {
Validation *OutputContract
// AppendedMessages are already-rendered messages appended after every prompt
// definition message. Promptkit neither templates nor resolves files in
// them, and preserves valid content exactly. Nil and empty slices are
// equivalent. Prepare, PrepareExecution, and Run validate and copy the
// messages before source or model work; malformed values return an error
// matching ErrInvalidRequest.
// them, and preserves valid content exactly. Roles are trimmed and
// lowercased, then must be [RoleDeveloper], [RoleSystem], [RoleUser], or
// [RoleAssistant]. Nil and empty slices are equivalent. Prepare,
// PrepareExecution, and Run validate and copy the messages before source or
// model work; malformed values return an error matching ErrInvalidRequest.
AppendedMessages []RenderedMessage
}
@@ -632,13 +633,15 @@ type RenderedPrompt struct {
// SessionID is the optional effective direct or rendered session
// identifier supplied to the model client.
SessionID string `json:"session_id,omitempty"`
// Messages contains rendered messages in definition order.
// Messages contains the frozen effective messages: definition messages in
// their rendered order followed by any RunRequest.AppendedMessages.
Messages []RenderedMessage `json:"messages"`
}
// RenderedMessage is a prepared, provider-bound text chat message and has a
// stable JSON representation. Its role must be one of [RoleDeveloper],
// [RoleSystem], [RoleUser], or [RoleAssistant].
// stable JSON representation. At a request boundary its role is trimmed and
// lowercased, then must be one of [RoleDeveloper], [RoleSystem], [RoleUser],
// or [RoleAssistant].
type RenderedMessage struct {
// Role is the provider-bound chat role.
Role string `json:"role"`