Omit unset optional request parameters

This commit is contained in:
2026-08-01 02:41:44 +00:00
parent bd6cffc9d0
commit a248433d0f
9 changed files with 678 additions and 77 deletions

View File

@@ -188,25 +188,27 @@ they also cannot collide with the standard fields listed in the
Execution settings resolve in this order:
1. framework defaults;
1. the framework timeout baseline;
2. the selected backend, when the profile names one;
3. the selected profile; and
4. request `ExecutionTargetOverride` values.
The framework defaults are:
The framework baseline is:
| Setting | Default |
| --- | --- |
| `temperature` | `0` |
| `max_tokens` | `0` |
| `top_p` | `1` |
| `temperature` | Unspecified and omitted from compatible provider requests unless a backend, profile, or runtime override selects it. |
| `max_tokens` | Unspecified and omitted from compatible provider requests unless a backend, profile, or runtime override selects it. |
| `top_p` | Unspecified and omitted from compatible provider requests unless a backend, profile, or runtime override selects it. |
| `timeout_seconds` | `600` |
Numeric zero in a file or in-memory profile means that the profile does not
replace the framework default. Numeric request overrides use pointers, so an
explicit zero is preserved. In particular, an explicit request
`timeout_seconds` of zero disables the per-generation deadline while leaving
the caller context and transport timeout intact.
replace a lower-precedence value. With no lower value, `temperature`,
`max_tokens`, and `top_p` remain unspecified. Numeric request overrides use
pointers, so an explicit zero is retained and sent to compatible providers.
In particular, an explicit request `timeout_seconds` of zero disables the
per-generation deadline while leaving the caller context and transport timeout
intact.
Non-empty profile strings replace backend defaults, and non-empty request
strings replace both. Request reasoning is the exception: a nil

View File

@@ -53,8 +53,9 @@ never also sent as a session header.
The client conditionally includes:
- `temperature`, `max_tokens`, and `top_p` when non-zero or explicitly
present;
- `temperature`, `max_tokens`, and `top_p` only when selected by a backend,
profile, or runtime override, including an explicit runtime zero; they are
absent when unspecified;
- non-empty `service_tier` and effective `reasoning_effort`; an explicitly
disabled reasoning setting is empty and therefore omitted; and
- `response_format` for JSON Schema structured output, including its name,

View File

@@ -0,0 +1,152 @@
# Application Fallback Profiles
Status: Selected for implementation.
## Purpose
Promptkit will allow a consuming application to supply an embedded fallback
profile source below operator-configured profiles and above Promptkit's
built-in profile catalog.
This gives consumers stable, application-owned profile IDs with useful
packaged defaults while preserving the existing ability for an operator to
replace those definitions. Promptkit will own the reusable source layer and
lookup semantics without owning downstream profile names, model assignments,
or application configuration policy.
## Consumer Outcome
A consumer such as Weatherreporter can embed profiles named for application
workloads or execution tiers, such as `weather-light`, `weather-balanced`, and
`weather-deep`. Prompts can select those logical IDs without coupling the
application to a particular provider or model.
An operator can define the same profile ID in the application's ordinary
configured profile source to replace the packaged default. If no operator
definition exists, Promptkit resolves the application's embedded definition.
If neither source contains the ID, Promptkit retains access to its own built-in
profile catalog.
## Scope
Promptkit will add one optional `fs.FS`-backed application fallback profile
source to engine construction. The intended public surface is:
```go
promptkit.WithFallbackProfileFS(profileFS, ".")
```
The source will use the existing profile YAML format, discovery behavior,
strict decoding, validation rules, and credential restrictions. The option
will accept a non-nil filesystem and nonblank root. Repeating the option will
follow Promptkit's same-category convention: the last valid value replaces the
earlier fallback source, while an invalid option still fails construction when
it is applied.
No programmatic fallback-profile option is included. Consumers that need the
new precedence relationship can embed YAML assets, while `WithProfiles`
continues to serve the distinct highest-precedence in-memory use case.
## Profile Selection And Source Precedence
Profile ID selection remains separate from profile definition lookup. An
explicit request profile ID continues to take precedence over a prompt's
`default_profile`. After an ID has been selected, matching definitions resolve
in this order:
1. in-memory profiles supplied through `WithProfiles`;
2. the ordinary configured profile source selected through `WithProfileFile`,
`WithProfileFS`, or `Config.ProfileDir`;
3. the application fallback profile source; and
4. Promptkit's embedded built-in profiles.
A higher-precedence source falls through only when the requested profile ID is
absent. An unreadable, malformed, duplicate, ambiguous, or otherwise invalid
matching definition is an error and does not permit lookup in a lower layer.
Profiles are selected as complete values; sources do not merge fields or
inherit from one another.
The fallback source remains lazily read and validated when a requested ID
reaches that layer. This feature does not introduce engine-wide eager source
validation, and an unrelated malformed asset does not acquire stronger
validation guarantees than it has in an ordinary profile source.
## Consistent Engine Behavior
The engine will assemble one profile repository with the complete precedence
chain. Exact profile inspection, ordinary preparation, prepared execution, and
ordinary execution will all use that same repository and therefore observe the
same definition for a given profile ID.
Exact profile inspection remains side-effect free and does not contact a model
provider. Existing public error identities remain applicable to cancellation,
profile-not-found and profile-load failures, invalid fallback definitions,
credential resolution, and unknown backends. The feature does not add a new
fallback-specific public error category.
Internally, the root facade will own composition of all profile-source layers.
The application fallback is another use of the profile repository's existing
error-preserving overlay semantics; it is not a separate profile-loading or
validation implementation.
## Compatibility
The feature is additive. An engine that does not configure an application
fallback source retains its current behavior and profile precedence. Existing
uses of `Config.ProfileDir`, `WithProfileFile`, `WithProfileFS`, and
`WithProfiles` keep their meanings.
The application fallback is deliberately below every existing
consumer-configured profile source. An ordinary configured profile therefore
continues to override any packaged definition with the same ID. A missing or
invalid configured source also retains its current behavior; the new layer
does not turn configuration failures into silent fallthrough.
## Ownership Boundaries
Promptkit owns:
- the additional source layer and its construction option;
- deterministic lookup and fallthrough semantics;
- use of the existing profile format, validation, and public error mapping;
- consistent repository use across inspection, preparation, and execution;
and
- canonical public, format, consumer, and contributor documentation for the
implemented capability.
The consuming application owns:
- whether to supply a fallback source;
- application-specific profile IDs and their domain meaning;
- embedded profile contents, backend selections, and model choices;
- application configuration discovery and operator override policy;
- assignment of profiles to reports or other workloads; and
- credential policy and operator-facing error presentation beyond Promptkit's
public contract.
## Non-Goals
This scope does not add:
- downstream-specific profiles to Promptkit's built-in catalog;
- profile inheritance, aliases, or field-level merging;
- provider failover after a selected profile or generation attempt fails;
- automatic endpoint discovery, probing, benchmarking, or tier selection;
- application configuration discovery;
- eager validation of every profile in every source;
- source-provenance fields in profile inspection or preparation results; or
- an in-memory companion to `WithFallbackProfileFS`.
## Target End State
The feature is complete when a consumer can embed an application fallback
profile source through the root facade and rely on the documented four-layer
precedence everywhere profiles are resolved. Operator definitions override
application defaults, application defaults override Promptkit built-ins,
invalid matching definitions never silently fall through, and engines that do
not use the new option remain behaviorally compatible.
The implemented public option and precedence contract will be owned by GoDoc
and the framework format reference. Consumer guidance will show the embedded
application-default workflow without making this temporary roadmap a second
current-state reference.

View File

@@ -0,0 +1,378 @@
# Optional Request-Parameter Omission Implementation Plan
**Status:** Ready for implementation.
## Purpose
This document is the decision-complete implementation plan for
[omitting unset optional request parameters](optional-request-parameters.md).
It is written for a `gpt-5.6-terra` coding agent that will implement each stage
in order.
The feature roadmap owns the motivation, policy choices, compatibility
boundary, non-goals, and target end state. This document owns the concrete
design, file-level work, test ownership, documentation updates, validation,
and completion gates.
The separate [application fallback profiles](fallback-profiles.md) roadmap is
not part of this implementation plan. Preserve it unchanged for its own later
planning and implementation cycle.
## Implementation Rules
- Complete the stages in order. Stage 1 must leave code, tests, GoDoc, and
current-state documentation mutually accurate; Stage 2 performs the final
audit and repository-wide acceptance.
- Preserve unrelated working-tree changes. In particular, do not edit,
implement, retire, or reclassify `fallback-profiles.md`.
- Follow every policy under `docs/policy/`, the task-specific reading guide in
`docs/development.md`, and the accepted behavior in
`optional-request-parameters.md`.
- Keep the existing package boundaries. Framework defaults remain in
`internal/defaults`, resolution remains in `internal/usecase`, and outbound
OpenAI-compatible serialization remains in `internal/llm`.
- Do not add an exported type, field, option, method, error, or public package.
This feature changes default and wire semantics within existing contracts.
- Do not replace numeric profile fields with pointers or add profile presence
tracking. File and in-memory profile zero values retain their existing
inheritance semantics; runtime pointer overrides remain the only supported
way to select an explicit numeric zero.
- Preserve required request fields, session IDs, structured output,
credentials, extra-parameter validation, reasoning clearing, deadlines,
capacity management, and all existing precedence rules.
- Do not query a provider for defaults or capabilities and do not add
backend- or model-specific serialization branches.
- Keep tests lean and behavioral. Use the existing root precedence test to own
resolved public/injected-client metadata and the existing model-client tests
to own wire inclusion and omission. Do not duplicate those matrices in a
new end-to-end fixture.
- Update exact exported semantics in GoDoc, profile/default semantics in
`docs/formats.md`, and provider request-body semantics in
`docs/integrations/openai-compatible-chat.md` in the same stage as the code.
- Do not add release notes, change a module version, commit, tag, push, or
publish a release as part of this plan.
## Fixed Design
### Framework Defaults
In `internal/defaults/defaults.go`, remove these constants:
```go
ExecutionDefaultTemperature
ExecutionDefaultMaxTokens
ExecutionDefaultTopP
```
They currently encode zero for `temperature` and `max_tokens` and one for
`top_p`. Optional provider controls are no longer framework defaults, so
retaining zero-valued constants under default-oriented names would obscure the
new contract.
Keep `ExecutionDefaultTimeoutSeconds` at its current positive value. Timeout is
a Promptkit-owned generation deadline and is not an OpenAI-compatible request
body field.
Keep `ExecutionTargetDefault` as the common resolution baseline, but have it
initialize only `TimeoutSeconds`. The zero Go values for `Temperature`,
`MaxTokens`, and `TopP` then represent unspecified provider controls. Do not
rename this internal function or add a second defaults constructor.
### Resolution And Public Metadata
Do not change the merge functions or precedence in
`internal/usecase/runner.go`:
1. the baseline target supplies only the Promptkit timeout;
2. nonzero profile numeric fields replace the baseline;
3. non-nil runtime numeric overrides replace profile values; and
4. `ExecutionTargetPresence` records runtime overrides, including explicit
zero values.
Consequently, when a profile omits the optional provider controls, resolved
`ExecutionTarget` values contain zero for `Temperature`, `MaxTokens`, and
`TopP`. That zero is stable metadata for “unspecified” unless the accompanying
`GenerateRequest.TargetPresence` bit reports an explicit runtime zero.
Do not expose target presence in `PreparedRun`, `RunResult`, or
`ProfileInspection`, and do not change their stable JSON shapes. As already
true for `max_tokens`, those metadata values report the resolved numeric value
rather than provenance. A prepared result containing `top_p: 0` therefore does
not distinguish an unspecified value from an explicit runtime zero; injected
clients receive the separate presence value when the distinction affects
execution.
Update root GoDoc in `types.go` so it no longer calls an unspecified optional
provider control an effective provider value:
- `ExecutionTarget.Temperature`, `MaxTokens`, and `TopP` must each state that
zero leaves the field unspecified to compatible providers unless the
corresponding `ExecutionTargetPresence` bit is true;
- `ExecutionTarget.TimeoutSeconds` retains its existing deadline semantics;
- `Profile` and `ExecutionTargetOverride` documentation must describe zero or
nil as inheriting a lower-precedence value and otherwise leaving the provider
control unspecified, rather than implying that every field receives a
concrete framework value; and
- `PreparedRun`, `ProfileInspection`, and other effective-target summaries may
continue to describe precedence, but must not imply that Promptkit knows a
provider's omitted default.
Do not change field types, field order, JSON tags, conversion functions, string
formatting, or copying behavior.
### Outbound Request Semantics
The current built-in client already has the required mechanism:
`openAIChatRequestFromGenerateRequest` includes `temperature`, `max_tokens`, or
`top_p` when the resolved value is nonzero or the corresponding target-presence
bit is true, and `openAIChatRequestPayload` omits nil fields. Preserve that
logic.
No production change should be needed in
`internal/llm/openai_compatible_client.go`. Change it only if a focused failing
test demonstrates that the existing implementation does not meet this plan;
do not special-case `top_p`, inspect profile provenance, or move framework
default policy into the transport.
The resulting behavior is:
- an omitted profile `top_p` resolves to zero and is absent from the body;
- a nonzero profile or runtime `top_p` is included;
- an explicit runtime `top_p` of zero is included because presence is true;
- the same rules continue to apply to `temperature` and `max_tokens`;
- empty `service_tier` and effective `reasoning_effort` remain absent;
- configured `extra_params` remain present after validation; and
- `model`, `messages`, conditional `session_id`, and conditional
`response_format` remain unchanged.
### Profile Formats And Built-In Profiles
Do not change YAML or public `Profile` field shapes. Numeric zero in a file or
in-memory profile continues to mean “do not replace the lower layer.” With no
lower provider value, zero therefore resolves to unspecified. An explicit
profile-level numeric zero remains unsupported; consumers use a runtime
pointer override when zero itself must be sent.
Do not edit files under `internal/profile/builtin/assets/`. Values declared in
those files are explicit profile policy and remain effective. Existing
nonzero-profile tests are sufficient to protect explicit inclusion; do not add
one test per built-in asset or parameter.
### Test Ownership
Use these existing boundaries:
- In `engine_test.go`, update the “framework defaults” row of
`TestEngineExecutionSettingPrecedence` so the zero-valued profile expects
`TopP: 0` while retaining `Temperature: 0`, `MaxTokens: 0`, and the positive
timeout. Rename that row to describe unspecified provider controls plus the
framework timeout. Keep the rows proving nonzero profile precedence and
explicit runtime-zero presence unchanged.
- Remove
`TestRunnerRunBuiltInDefaultsUsedWhenProfileOmitsOptionalFields` from
`internal/usecase/runner_test.go`. Its literal-default assertions duplicate
the stronger assembled root precedence test and depend on the internal
constants being removed. Do not replace it with another internal
default-value test.
- Keep
`TestOpenAICompatibleClientOmitsImplicitZeroNumericFields` and
`TestOpenAICompatibleClientSerializesExplicitZeroNumericOverrides` in
`internal/llm/openai_compatible_client_test.go`. Together they own the wire
distinction and should pass without weakening their assertions.
- Keep the existing nonzero request serialization and profile-precedence tests
passing. They prove that explicitly configured values continue to be sent
and selected.
Do not add snapshots, golden files, provider calls, or a broad duplicate
integration test. Add a new test only if the implementation exposes a distinct
contract risk not covered by the tests above, and record that reason in the
test name or nearby test structure rather than in a new planning document.
### Canonical Documentation
Update current-state documentation in Stage 1:
- In `docs/formats.md`, replace the optional provider-control entries in the
framework-default table with clear unspecified/omitted semantics, while
retaining the positive `timeout_seconds` framework default. Explain that
profile numeric zero inherits a lower layer and otherwise remains
unspecified; an explicit runtime pointer zero is retained.
- In `docs/integrations/openai-compatible-chat.md`, state that
`temperature`, `max_tokens`, and `top_p` are included only when selected by a
profile or runtime override, including explicit runtime zero, and are absent
when unspecified. Keep the existing ownership of required fields,
`session_id`, structured output, extra parameters, and timeout behavior.
- In `types.go`, apply the GoDoc changes described above; these declarations
own the exact public value semantics.
Do not add a README or release-document note. The consumer guide already
routes exact field behavior to GoDoc and profile/default behavior to the format
reference, so do not duplicate the new contract there. The internal LLM
document describes flow rather than exact field omission and does not require
a change unless its current text is found to contradict the implementation.
## Stage 1: Implement Omission Semantics And Canonical Contracts
### Objective
Remove optional provider controls from the framework baseline, preserve
explicit profile and runtime values, update the canonical contracts, and prove
the behavior at the existing resolution and wire boundaries.
### Implementation Prompt
Implement only Stage 1 of `docs/roadmap/implementation.md`. Read the complete
feature roadmap, implementation rules, and fixed design above before editing.
1. In `internal/defaults/defaults.go`, remove the three provider-control
constants and make `ExecutionTargetDefault` initialize only
`TimeoutSeconds`.
2. In `engine_test.go`, update and rename the default-precedence table row
exactly as described under Test Ownership.
3. Remove the redundant literal-default test from
`internal/usecase/runner_test.go`; do not weaken other precedence,
profile-value, or runtime-zero tests.
4. Update the affected exported GoDoc in `types.go` without changing any
declaration, JSON tag, or serialization shape.
5. Update `docs/formats.md` and
`docs/integrations/openai-compatible-chat.md` according to Canonical
Documentation.
6. Run the focused validation below. Repair regressions in scope, but do not
broaden the feature or change the established serializer merely to make a
mistaken expectation pass.
Do not edit built-in profile assets, fallback-profile work, backend
registration, profile parsing, target merge logic, public value shapes,
prepared-execution lifecycle, capacity management, or release material.
### Focused Validation
Run from the repository root:
```sh
gofmt -w internal/defaults/defaults.go types.go engine_test.go \
internal/usecase/runner_test.go
go test . -run 'TestEngineExecutionSettingPrecedence'
go test ./internal/llm -run \
'TestOpenAICompatibleClient(GenerateSuccess|OmitsImplicitZeroNumericFields|SerializesExplicitZeroNumericOverrides)'
go test ./internal/usecase -run \
'Test(ResolveExecutionTarget|RunnerPrepareRequestNumericOverridePresence|RunnerPrepareSelectedProfileBeatsBuiltInDefault|RunnerRunSelectedProfileBeatsBuiltInDefault)'
go test . ./internal/defaults ./internal/usecase ./internal/llm
go vet . ./internal/defaults ./internal/usecase ./internal/llm
git diff --check
```
If a focused regular expression does not match an existing test name, inspect
the current names and run the narrowest equivalent set; do not silently skip
the intended resolution, explicit-profile, explicit-zero, and wire-omission
coverage.
### Completion Gate
Stage 1 is complete only when:
- the resolution baseline contains no provider tuning value and retains the
Promptkit timeout;
- an omitted `top_p` resolves to zero and the built-in client omits it;
- nonzero profile and runtime values remain effective and serialized;
- explicit runtime zero values remain distinguishable and serialized through
`ExecutionTargetPresence`;
- no public type or stable JSON shape changed;
- required fields, structured output, session IDs, extra parameters,
reasoning, credentials, and deadlines retain their existing behavior;
- GoDoc, format documentation, and the integration contract describe the
implemented behavior without conflicting ownership; and
- all focused tests, vet, formatting, and whitespace checks pass.
## Stage 2: Audit Compatibility And Validate The Repository
### Objective
Confirm that the narrow semantic change is complete across all public,
injected-client, built-in-profile, documentation, and repository surfaces,
then mark the temporary planning documents complete.
### Implementation Prompt
Implement only Stage 2 of `docs/roadmap/implementation.md` after Stage 1
satisfies its completion gate.
1. Search tracked Go and Markdown files for the removed constant names,
framework `top_p` defaults, claims that all effective provider controls have
concrete framework values, and request-body inclusion rules. Correct only
stale statements or tests owned by this feature.
2. Confirm that `internal/profile/builtin/assets/` has no feature-related diff
and that its explicit nonzero optional controls still pass ordinary profile
validation and resolution tests.
3. Confirm that `internal/llm/openai_compatible_client.go` either has no diff or
contains only a change required by a focused failing contract test. The
default policy must remain outside the transport.
4. Follow every changed Markdown link and confirm its target exists. Verify
that current-state documents describe implemented behavior and that exact
contracts remain with GoDoc, the format reference, and the integration
contract.
5. Run the complete validation sequence below and repair only in-scope
failures.
6. After all checks pass, change the status of
`optional-request-parameters.md` and this document to `Complete`. Do not
change the status or contents of `fallback-profiles.md`.
7. Re-run `git diff --check` and inspect the final working tree and diff.
Do not delete temporary roadmaps in this stage; retirement is a separate
maintainer action. Do not add release notes, change versions, or create a
commit, tag, push, or release.
### Full Validation
Run from the repository root:
```sh
gofmt -w internal/defaults/defaults.go types.go engine_test.go \
internal/usecase/runner_test.go
gofmt -l $(git ls-files '*.go')
go test ./...
go test -race ./...
go vet ./...
go build ./...
go run ./examples/go-library/prepare
git diff --check
git status --short
```
The `gofmt -l` command must print no paths. The maintained example must remain
offline and require no real credential or provider.
Inspect the final diff and confirm:
- only this feature's files and pre-existing user changes are present;
- no built-in profile asset, public declaration shape, stable JSON tag,
credential rule, workspace file, local module replacement, generated binary,
or unrelated formatting changed;
- the removed provider-default constants have no remaining references;
- the provider omission policy is implemented by resolution plus the existing
generic serializer, not by a `top_p` transport special case;
- the optional-parameter roadmap and this plan are complete while the fallback
roadmap remains selected; and
- no commit, tag, push, or release was created.
### Completion Gate
The implementation is complete only when:
- every Stage 1 gate remains satisfied;
- the ordinary and race-enabled suites pass;
- vet, build, formatting, the maintained offline example, Markdown links, and
whitespace checks pass;
- public metadata, injected-client presence, profile inheritance, and outbound
omission semantics are mutually consistent;
- explicit built-in and consumer profile values retain their behavior;
- both feature-specific roadmap statuses are `Complete`;
- `fallback-profiles.md` remains unchanged and selected for later work; and
- the repository is ready for maintainer review without a commit or release
having been created by this plan.
## Open Questions
None. The feature roadmap and fixed design above fully specify the behavior,
compatibility boundary, implementation, documentation ownership, and test
strategy.

View File

@@ -0,0 +1,83 @@
# Omit Unset Optional Request Parameters
Status: Selected for implementation.
## Purpose
Promptkit will stop turning an unset optional provider control into an explicit
outbound value. Required protocol fields and settings needed to enforce
Promptkit's own contracts will retain defined behavior, while provider tuning
choices will be sent only when a supported configuration layer selects them.
This keeps profiles intentional, avoids overriding model- or backend-specific
defaults, and improves compatibility across OpenAI-compatible endpoints with
different supported parameter sets.
## Scope
The framework-level `top_p` default of `1` will become unspecified. When no
profile or runtime override supplies `top_p`, the built-in OpenAI-compatible
client will omit it from the request body rather than serialize `1`.
This establishes the general outbound policy for currently supported optional
controls:
- `temperature`, `max_tokens`, and `top_p` are omitted when unspecified and
included when selected by a profile or runtime override;
- an explicit numeric zero supplied through a runtime override remains present
on the wire through the existing numeric-presence contract;
- `service_tier` and `reasoning_effort` remain omitted when their resolved
values are empty;
- backend, profile, or runtime `extra_params` remain explicit configuration and
are sent when present; and
- `session_id` remains conditional on a supplied nonempty value, while
`response_format` remains conditional on the effective output contract.
The request body will continue to require `model` and `messages`. Endpoint and
credential resolution remain transport concerns rather than body defaults.
The positive framework `timeout_seconds` default also remains in place because
it enforces a Promptkit-owned generation deadline and is not serialized as a
provider request field.
## Effective Settings And Compatibility
An unspecified numeric provider control continues to use its zero Go value in
resolved public metadata. For a `GenerateRequest` delivered to an injected
client, the existing `ExecutionTargetPresence` value distinguishes an explicit
runtime zero from an inherited unspecified zero. Prepared and inspection
metadata will continue to report the resolved numeric value without adding
source-provenance fields.
The behavior change is intentional and belongs in a minor release. Consumers
that require stable sampling behavior should declare the desired values in
their profiles or runtime overrides instead of relying on Promptkit to repeat a
provider's conventional default.
Built-in profile parameters remain explicit profile policy and will continue
to be sent. An explicit built-in setting will not be removed merely because
the framework default becomes unspecified.
## Non-Goals
This change does not add:
- pointer-valued numeric fields to file or in-memory profiles;
- a general provenance model for effective settings;
- automatic discovery of provider defaults or supported parameters;
- backend- or model-specific request-shape negotiation;
- changes to required request fields, structured-output behavior, credentials,
or timeout enforcement; or
- removal of deliberately configured settings from consumer or built-in
profiles.
## Target End State
An otherwise unset optional provider tuning parameter is absent from the
OpenAI-compatible request body. Explicit profile values and runtime overrides,
including explicit runtime zero values, retain their current precedence and
wire effect. Promptkit continues to supply only the required protocol fields
and the settings necessary to honor its own execution and output contracts.
GoDoc, the framework format reference, and the OpenAI-compatible integration
contract will own the implemented omission and metadata semantics once the
feature lands.

View File

@@ -360,14 +360,14 @@ func TestEngineExecutionSettingPrecedence(t *testing.T) {
wantPresence promptkit.ExecutionTargetPresence
}{
{
name: "framework defaults fill zero-valued profile settings",
name: "unspecified provider controls retain framework timeout",
profile: defaultsProfile,
want: promptkit.ExecutionTarget{
Endpoint: defaultsProfile.endpoint,
Model: defaultsProfile.model,
Temperature: 0,
MaxTokens: 0,
TopP: 1,
TopP: 0,
TimeoutSeconds: 600,
ServiceTier: defaultsProfile.serviceTier,
ReasoningEffort: defaultsProfile.reasoningEffort,

View File

@@ -14,9 +14,6 @@ const (
ContentTypeApplicationJSON = "application/json"
OpenAIChatCompletionsPath = "/chat/completions"
ExecutionDefaultTemperature = 0.0
ExecutionDefaultMaxTokens = 0
ExecutionDefaultTopP = 1.0
ExecutionDefaultTimeoutSeconds = 600
)
@@ -26,9 +23,6 @@ var (
func ExecutionTargetDefault() domain.ExecutionTarget {
return domain.ExecutionTarget{
Temperature: ExecutionDefaultTemperature,
MaxTokens: ExecutionDefaultMaxTokens,
TopP: ExecutionDefaultTopP,
TimeoutSeconds: ExecutionDefaultTimeoutSeconds,
}
}

View File

@@ -1690,36 +1690,6 @@ func TestRunnerRunSelectedProfileBeatsBuiltInDefault(t *testing.T) {
}
}
func TestRunnerRunBuiltInDefaultsUsedWhenProfileOmitsOptionalFields(t *testing.T) {
promptRepo := &fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)}
execRepo := &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
"exec": {ID: "exec", Endpoint: "http://profile/v1", Model: "profile-model"},
}}
llmClient := &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
res, err := runner.Run(context.Background(), domain.RunRequest{
PromptID: "p",
ProfileID: "exec",
Inputs: singleInputRef(),
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if res.EffectiveModelParams.Temperature != defaults.ExecutionDefaultTemperature {
t.Fatalf("expected default temperature %v, got %v", defaults.ExecutionDefaultTemperature, res.EffectiveModelParams.Temperature)
}
if res.EffectiveModelParams.TopP != defaults.ExecutionDefaultTopP {
t.Fatalf("expected default top_p %v, got %v", defaults.ExecutionDefaultTopP, res.EffectiveModelParams.TopP)
}
if res.EffectiveModelParams.MaxTokens != defaults.ExecutionDefaultMaxTokens {
t.Fatalf("expected default max_tokens %d, got %d", defaults.ExecutionDefaultMaxTokens, res.EffectiveModelParams.MaxTokens)
}
if res.EffectiveModelParams.TimeoutSeconds != defaults.ExecutionDefaultTimeoutSeconds {
t.Fatalf("expected default timeout_seconds %d, got %d", defaults.ExecutionDefaultTimeoutSeconds, res.EffectiveModelParams.TimeoutSeconds)
}
}
func TestRunnerRunAPIKeyEnvResolvesFromEnvironment(t *testing.T) {
t.Setenv("PROMPTKIT_TEST_API_KEY", "secret")
promptRepo := &fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)}

View File

@@ -116,7 +116,8 @@ type RunRequest struct {
// empty maps are equivalent.
Vars map[string]string
// Execution optionally overrides individual execution settings. Nil uses
// the selected profile over its backend, when any, and framework defaults.
// the selected profile over its backend, when any, and the framework
// baseline.
Execution *ExecutionTargetOverride
// Validation optionally replaces the prompt's complete output contract. It
// does not merge individual fields. Nil uses the prompt contract.
@@ -144,9 +145,10 @@ type PreparedRun struct {
// SelectedBackendID equals EffectiveModelParams.BackendID. It is empty for
// an endpoint-only profile.
SelectedBackendID string `json:"selected_backend_id,omitempty"`
// EffectiveModelParams contains framework defaults overlaid by the selected
// backend, profile, and then request overrides. It excludes resolved API-key
// values.
// EffectiveModelParams contains settings resolved from the framework timeout
// baseline, selected backend, profile, and then request overrides. Unset
// optional provider controls remain zero rather than reporting a provider
// default. It excludes resolved API-key values.
EffectiveModelParams ExecutionTarget `json:"effective_model_params"`
// OutputContract is the complete effective output contract.
OutputContract OutputContract `json:"output_contract"`
@@ -298,13 +300,17 @@ type ExecutionTarget struct {
Endpoint string `json:"endpoint"`
// Model is the provider model identifier.
Model string `json:"model"`
// Temperature is the effective sampling temperature from 0 through 2.
// Temperature is the resolved sampling temperature from 0 through 2. Zero
// leaves the field unspecified to compatible providers unless the
// corresponding ExecutionTargetPresence bit is true.
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 is the non-negative resolved output-token limit. Zero leaves
// the limit unspecified to compatible providers unless the corresponding
// ExecutionTargetPresence bit is true.
MaxTokens int `json:"max_tokens"`
// TopP is the effective nucleus-sampling value from 0 through 1.
// TopP is the resolved nucleus-sampling value from 0 through 1. Zero leaves
// the field unspecified to compatible providers unless the corresponding
// ExecutionTargetPresence bit is true.
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.
@@ -329,9 +335,11 @@ type ExecutionTarget struct {
type ProfileInspection struct {
// ProfileID is the trimmed, exact profile ID inspected by the engine.
ProfileID string
// EffectiveModelParams contains framework defaults overlaid by the selected
// backend and then the profile, without a request override. APIKeyEnv is an
// environment-variable name, never its credential value.
// EffectiveModelParams contains settings resolved from the framework timeout
// baseline, selected backend, and then profile, without a request override.
// Unset optional provider controls remain zero rather than reporting a
// provider default. APIKeyEnv is an environment-variable name, never its
// credential value.
EffectiveModelParams ExecutionTarget
// APIKeyRequired reports that a later execution must supply a direct API
// key or an explicit request environment override. It is mutually exclusive
@@ -382,19 +390,26 @@ type PromptInspection struct {
// fields replace profile values and preserve explicit zero or empty values. A
// non-empty ExtraParams map replaces the complete profile or backend map
// rather than merging keys. Empty string fields, nil pointers, and a nil or
// empty ExtraParams map inherit the selected profile over its backend, when
// any, and framework defaults.
// empty ExtraParams map inherit lower-precedence values. An optional provider
// control that remains zero is unspecified; TimeoutSeconds retains its
// framework deadline when no higher-precedence value is present.
type ExecutionTargetOverride struct {
// Endpoint replaces the profile or backend endpoint when non-empty without
// changing the effective BackendID.
Endpoint string
// Model replaces the profile model when non-empty.
Model string
// Temperature, when non-nil, must point to a value from 0 through 2.
// Temperature, when non-nil, must point to a value from 0 through 2. A
// pointed-to zero is explicitly present; nil inherits a lower-precedence
// value and otherwise leaves the provider control unspecified.
Temperature *float64
// MaxTokens, when non-nil, must point to a non-negative value.
// MaxTokens, when non-nil, must point to a non-negative value. A pointed-to
// zero is explicitly present; nil inherits a lower-precedence value and
// otherwise leaves the provider control unspecified.
MaxTokens *int
// TopP, when non-nil, must point to a value from 0 through 1.
// TopP, when non-nil, must point to a value from 0 through 1. A pointed-to
// zero is explicitly present; nil inherits a lower-precedence value and
// otherwise leaves the provider control unspecified.
TopP *float64
// TimeoutSeconds, when non-nil, must point to a non-negative value. A
// pointed-to zero disables the per-generation deadline.
@@ -425,9 +440,11 @@ type ExecutionTargetOverride struct {
// use profile YAML api_key_env with file 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.
// WithProfiles validates and copies Profile values during NewEngine. Optional
// numeric zero, blank strings, and an empty ExtraParams map inherit
// lower-precedence values. An optional provider control that remains zero is
// unspecified; use ExecutionTargetOverride pointer fields to request an
// explicit numeric zero.
type Profile struct {
// ID is the required non-blank profile identifier. WithProfiles trims it.
ID string
@@ -441,19 +458,23 @@ type Profile struct {
Endpoint string
// Model is the required non-blank provider model identifier.
Model string
// Temperature is from 0 through 2. Zero inherits the framework default.
// Temperature is from 0 through 2. Zero inherits a lower-precedence value
// and otherwise leaves the provider control unspecified.
Temperature float64
// MaxTokens is non-negative. Zero inherits the framework default.
// MaxTokens is non-negative. Zero inherits a lower-precedence value and
// otherwise leaves the provider control unspecified.
MaxTokens int
// TopP is from 0 through 1. Zero inherits the framework default rather than
// selecting an explicit zero.
// TopP is from 0 through 1. Zero inherits a lower-precedence value and
// otherwise leaves the provider control unspecified rather than selecting an
// explicit zero.
TopP float64
// TimeoutSeconds is non-negative. Zero inherits the framework default.
// TimeoutSeconds is non-negative. Zero inherits a lower-precedence value and
// otherwise the framework deadline.
TimeoutSeconds int
// ServiceTier is optional; a blank value inherits the framework default.
// ServiceTier is optional; a blank value inherits a lower-precedence value.
ServiceTier string
// ReasoningEffort is optional; a blank value inherits the framework
// default.
// ReasoningEffort is optional; a blank value inherits a lower-precedence
// value.
ReasoningEffort string
// APIKeyRequired clears a backend's inherited API-key environment name and
// requires a non-blank RunRequest.APIKey unless the request explicitly