Configure built-in retries across execution paths

This commit is contained in:
2026-09-23 16:56:20 +00:00
parent ae4540822e
commit 07a131bd33
21 changed files with 441 additions and 90 deletions

View File

@@ -70,6 +70,7 @@ type RunRequest struct {
Execution *ExecutionTargetOverride
Validation *OutputContract
AppendedMessages []RenderedMessage
TransientRetries *int
}
// RunResult represents the complete result of a prompt execution run.

View File

@@ -0,0 +1,15 @@
package domain
import "fmt"
// ResolveTransientRetries selects a nonnegative retry budget. A nil selection
// inherits the supplied default.
func ResolveTransientRetries(defaultBudget uint64, selection *int) (uint64, error) {
if selection == nil {
return defaultBudget, nil
}
if *selection < 0 {
return 0, fmt.Errorf("transient retries must not be negative")
}
return uint64(*selection), nil
}

View File

@@ -19,6 +19,7 @@ import (
"gitea.maximumdirect.net/eric/promptkit/internal/catalog"
"gitea.maximumdirect.net/eric/promptkit/internal/defaults"
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
"gitea.maximumdirect.net/eric/promptkit/internal/generation"
"gitea.maximumdirect.net/eric/promptkit/internal/llm"
"gitea.maximumdirect.net/eric/promptkit/internal/profile"
"gitea.maximumdirect.net/eric/promptkit/internal/prompt"
@@ -29,11 +30,12 @@ import (
// Config carries construction settings selected by the public facade.
type Config struct {
PromptDir string
ProfileDir string
SchemaDir string
Timeout time.Duration
HTTPClient *http.Client
PromptDir string
ProfileDir string
SchemaDir string
Timeout time.Duration
HTTPClient *http.Client
TransientRetries *int
}
// Options collects construction choices from the public facade.
@@ -157,6 +159,10 @@ type Engine struct {
// New assembles an engine from facade-selected configuration and collaborators.
func New(cfg Config, options Options) (*Engine, error) {
defaultRetries, err := domain.ResolveTransientRetries(2, cfg.TransientRetries)
if err != nil {
return nil, err
}
promptDefinitions := options.promptDefinitions
if !options.promptSource {
if strings.TrimSpace(cfg.PromptDir) == "" {
@@ -196,20 +202,22 @@ func New(cfg Config, options Options) (*Engine, error) {
}
validator = validate.NewStandardValidator(schemaDir)
}
client := options.llmClient
if client == nil {
client, err = llm.NewOpenAICompatibleClient(llm.OpenAICompatibleConfig{Timeout: cfg.Timeout, HTTPClient: cfg.HTTPClient})
if err != nil {
return nil, err
var client llm.Client
if options.llmClient == nil {
builtIn, newErr := llm.NewOpenAICompatibleClient(llm.OpenAICompatibleConfig{Timeout: cfg.Timeout, HTTPClient: cfg.HTTPClient})
if newErr != nil {
return nil, newErr
}
client = generation.NewBuiltIn(manager, builtIn)
} else {
client = generation.NewInjected(manager, options.llmClient)
}
client = capacity.NewClient(manager, client)
reader := options.artifactReader
if !options.artifactSource {
reader = artifact.NewCompositeReader()
}
return &Engine{runner: usecase.NewRunnerWithRepairer(promptDefinitions, profiles, registry, reader, prompt.NewGoRenderer(), client, validator, usecase.NewDefaultOutputRepairer(client), manager)}, nil
return &Engine{runner: usecase.NewRunnerWithRepairer(promptDefinitions, profiles, registry, reader, prompt.NewGoRenderer(), client, validator, usecase.NewDefaultOutputRepairer(client), manager, defaultRetries)}, nil
}
func newProfileRepository(profileDir string, options Options, maintained profile.Repository) profile.Repository {

View File

@@ -8,6 +8,7 @@ func newGenerationRequest(
target domain.ExecutionTarget,
targetPresence domain.ExecutionTargetPresence,
structuredOutput *domain.StructuredOutputSpec,
transientRetries uint64,
) domain.GenerateRequest {
prompt.SessionID = sessionID
return domain.GenerateRequest{
@@ -15,5 +16,6 @@ func newGenerationRequest(
Target: target,
TargetPresence: targetPresence,
StructuredOutput: structuredOutput,
TransientRetries: transientRetries,
}
}

View File

@@ -29,9 +29,10 @@ type PreparedExecution struct {
}
type preparedExecutionPayload struct {
prepared *domain.PreparedRun
validation validate.PreparedValidation
directKey string
prepared *domain.PreparedRun
validation validate.PreparedValidation
directKey string
transientRetries uint64
}
// PrepareExecution completes preparation without generation or admission and
@@ -62,9 +63,10 @@ func (r *Runner) PrepareExecution(ctx context.Context, req domain.RunRequest) (*
state: preparedExecutionReady,
details: details,
payload: &preparedExecutionPayload{
prepared: executionSnapshot,
validation: operation.validation,
directKey: state.effectiveModel.APIKey,
prepared: executionSnapshot,
validation: operation.validation,
directKey: state.effectiveModel.APIKey,
transientRetries: state.transientRetries,
},
}, nil
}
@@ -129,7 +131,7 @@ func (r *Runner) RunPrepared(ctx context.Context, prepared *PreparedExecution) (
}
defer release()
return r.executePreparedRun(ctx, payload.prepared, payload.directKey, runID, start, func(
return r.executePreparedRun(ctx, payload.prepared, payload.directKey, payload.transientRetries, runID, start, func(
ctx context.Context,
artifact *domain.Artifact,
attemptsUsed int,
@@ -169,6 +171,7 @@ func (p *preparedExecutionPayload) clear() {
p.prepared = nil
p.validation = nil
p.directKey = ""
p.transientRetries = 0
}
type noOpPreparedValidation struct {

View File

@@ -31,6 +31,7 @@ type RepairRequest struct {
Target domain.ExecutionTarget
TargetPresence domain.ExecutionTargetPresence
StructuredOutput *domain.StructuredOutputSpec
TransientRetries uint64
Attempt int
MaxAttempts int
Mode domain.ValidationMode
@@ -91,6 +92,7 @@ func (r *defaultOutputRepairer) Repair(ctx context.Context, req RepairRequest) (
req.Target,
req.TargetPresence,
req.StructuredOutput,
req.TransientRetries,
))
if err != nil {
return nil, err

View File

@@ -43,15 +43,16 @@ var (
// Runner executes the Promptkit core use case.
type Runner struct {
promptDefs promptdef.Repository
profiles profile.Repository
backends BackendResolver
artifacts artifact.Reader
renderer prompt.Renderer
llm llm.Client
validator validate.Validator
repairer OutputRepairer
admitter RunAdmitter
promptDefs promptdef.Repository
profiles profile.Repository
backends BackendResolver
artifacts artifact.Reader
renderer prompt.Renderer
llm llm.Client
validator validate.Validator
repairer OutputRepairer
admitter RunAdmitter
defaultTransientRetries uint64
}
// BackendResolver resolves one normalized backend ID.
@@ -73,6 +74,7 @@ type preparationState struct {
targetPresence domain.ExecutionTargetPresence
effectiveContract domain.OutputContract
start time.Time
transientRetries uint64
}
type preparedOperation struct {
@@ -113,17 +115,23 @@ func NewRunnerWithRepairer(
validator validate.Validator,
repairer OutputRepairer,
admitter RunAdmitter,
defaultTransientRetries ...uint64,
) *Runner {
retries := uint64(0)
if len(defaultTransientRetries) > 0 {
retries = defaultTransientRetries[0]
}
return &Runner{
promptDefs: promptDefs,
profiles: profiles,
backends: backends,
artifacts: artifacts,
renderer: renderer,
llm: llmClient,
validator: validator,
repairer: repairer,
admitter: admitter,
promptDefs: promptDefs,
profiles: profiles,
backends: backends,
artifacts: artifacts,
renderer: renderer,
llm: llmClient,
validator: validator,
repairer: repairer,
admitter: admitter,
defaultTransientRetries: retries,
}
}
@@ -152,7 +160,7 @@ func (r *Runner) Run(ctx context.Context, req domain.RunRequest) (*domain.RunRes
}
directAPIKey := state.effectiveModel.APIKey
return r.executePreparedRun(ctx, operation.run, directAPIKey, runID, start, func(
return r.executePreparedRun(ctx, operation.run, directAPIKey, state.transientRetries, runID, start, func(
ctx context.Context,
artifact *domain.Artifact,
attemptsUsed int,
@@ -173,6 +181,7 @@ func (r *Runner) executePreparedRun(
ctx context.Context,
prepared *domain.PreparedRun,
directAPIKey string,
transientRetries uint64,
runID string,
start time.Time,
validateArtifact preparedValidationFunc,
@@ -185,6 +194,7 @@ func (r *Runner) executePreparedRun(
executionTarget,
prepared.TargetPresence,
prepared.StructuredOutput,
transientRetries,
))
if err != nil {
return nil, wrapGenerationError(err)
@@ -213,6 +223,7 @@ func (r *Runner) executePreparedRun(
Target: executionTarget,
TargetPresence: prepared.TargetPresence,
StructuredOutput: prepared.StructuredOutput,
TransientRetries: transientRetries,
Attempt: attemptsUsed,
MaxAttempts: prepared.OutputContract.RepairAttempts,
Mode: prepared.OutputContract.ValidationMode,
@@ -295,6 +306,10 @@ func (r *Runner) resolvePreparation(
req domain.RunRequest,
start time.Time,
) (*preparationState, error) {
transientRetries, err := domain.ResolveTransientRetries(r.defaultTransientRetries, req.TransientRetries)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err)
}
if strings.TrimSpace(req.PromptID) == "" {
return nil, fmt.Errorf("%w: prompt id is required", ErrInvalidRequest)
}
@@ -346,6 +361,7 @@ func (r *Runner) resolvePreparation(
targetPresence: targetPresence,
effectiveContract: effectiveContract,
start: start,
transientRetries: transientRetries,
}, nil
}