Configure PromptKit transient retries

This commit is contained in:
2026-09-25 21:56:29 +00:00
parent d29a5cecd3
commit 10ac29d149
19 changed files with 214 additions and 37 deletions

View File

@@ -14,9 +14,10 @@ type Config struct {
}
type PromptKitConfig struct {
ProfileDir string `json:"profile_dir,omitempty"`
ProfileFile string `json:"profile_file,omitempty"`
LocalBackend *PromptKitLocalBackendConfig `json:"local_backend,omitempty"`
ProfileDir string `json:"profile_dir,omitempty"`
ProfileFile string `json:"profile_file,omitempty"`
TransientRetries int `json:"transient_retries"`
LocalBackend *PromptKitLocalBackendConfig `json:"local_backend,omitempty"`
}
type PromptKitLocalBackendConfig struct {
@@ -29,7 +30,10 @@ type ConcurrencyConfig struct {
StageWorkers map[string]int `json:"stage_workers"`
}
const defaultLLMConcurrency = 16
const (
defaultLLMConcurrency = 16
defaultTransientRetries = 2
)
type OutputConfig struct {
Directory string `json:"directory"`
@@ -55,6 +59,7 @@ type DebugConfig struct {
func Default() Config {
return Config{
PromptKit: PromptKitConfig{TransientRetries: defaultTransientRetries},
Pipelines: map[string]pipeline.PipelineProfile{},
Concurrency: ConcurrencyConfig{
TotalLLM: defaultLLMConcurrency,

View File

@@ -16,9 +16,10 @@ type Document struct {
}
type PromptKitDocument struct {
ProfileDir *string `yaml:"profile_dir,omitempty"`
ProfileFile *string `yaml:"profile_file,omitempty"`
LocalBackend *PromptKitLocalBackendDocument `yaml:"local_backend,omitempty"`
ProfileDir *string `yaml:"profile_dir,omitempty"`
ProfileFile *string `yaml:"profile_file,omitempty"`
TransientRetries *int `yaml:"transient_retries,omitempty"`
LocalBackend *PromptKitLocalBackendDocument `yaml:"local_backend,omitempty"`
}
type PromptKitLocalBackendDocument struct {

View File

@@ -63,6 +63,9 @@ func documentDefinition(state *documentLoadState) configkit.Definition[Document]
func documentDefaults() Document {
return Document{
PromptKit: &PromptKitDocument{
TransientRetries: documentValue(defaultTransientRetries),
},
Pipelines: map[string]PipelineDocument{},
Concurrency: &ConcurrencyDocument{
TotalLLM: documentValue(defaultLLMConcurrency),

View File

@@ -94,6 +94,9 @@ func documentToConfig(document Document) (Config, error) {
if document.PromptKit.ProfileFile != nil {
config.PromptKit.ProfileFile = *document.PromptKit.ProfileFile
}
if document.PromptKit.TransientRetries != nil {
config.PromptKit.TransientRetries = *document.PromptKit.TransientRetries
}
if document.PromptKit.LocalBackend != nil {
backend := PromptKitLocalBackendConfig{}
if document.PromptKit.LocalBackend.Endpoint != nil {

View File

@@ -2,6 +2,7 @@ package config
import (
"bytes"
"strconv"
"strings"
"testing"
@@ -58,6 +59,44 @@ pipelines:
}
}
func TestLoadedConfigResolvesPromptKitTransientRetriesAndRetainsInspection(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
document string
want int
}{
{name: "omitted", document: "version: 5\n", want: defaultTransientRetries},
{name: "disabled", document: "version: 5\npromptkit:\n transient_retries: 0\n", want: 0},
{name: "configured", document: "version: 5\npromptkit:\n transient_retries: 5\n", want: 5},
} {
t.Run(test.name, func(t *testing.T) {
loaded, err := loadConfigSource(configkit.Bytes("document.yml", []byte(test.document)), LoadOptions{})
if err != nil {
t.Fatal(err)
}
if got := loaded.Config.PromptKit.TransientRetries; got != test.want {
t.Fatalf("transient retries = %d, want %d", got, test.want)
}
var effective bytes.Buffer
if err := loaded.WriteEffective(&effective); err != nil {
t.Fatal(err)
}
if !strings.Contains(effective.String(), "transient_retries: "+strconv.Itoa(test.want)) {
t.Fatalf("effective configuration = %q", effective.String())
}
var explanation bytes.Buffer
if err := loaded.WriteExplanation(&explanation, "/promptkit/transient_retries"); err != nil {
t.Fatal(err)
}
if !strings.Contains(explanation.String(), strconv.Itoa(test.want)) {
t.Fatalf("explanation = %q", explanation.String())
}
})
}
}
func TestLoadedConfigOwnsRuntimeValuesAndKeepsFrozenInspection(t *testing.T) {
t.Parallel()

View File

@@ -167,6 +167,11 @@ func validateDocumentPromptKit(promptKit *PromptKitDocument, validation *configk
if err := validatePromptKitProfileSources(profileDir, profileFile); err != nil {
validation.Error(configkit.Keys("promptkit"), err.Error())
}
if promptKit.TransientRetries == nil {
validation.Error(configkit.Keys("promptkit", "transient_retries"), "must be an integer")
} else if err := validatePromptKitTransientRetries(*promptKit.TransientRetries); err != nil {
validation.Error(configkit.Keys("promptkit", "transient_retries"), err.Error())
}
if promptKit.LocalBackend == nil {
return
}
@@ -283,6 +288,13 @@ func validatePromptKitLocalBackendConcurrencyLimit(concurrencyLimit int) error {
return nil
}
func validatePromptKitTransientRetries(transientRetries int) error {
if transientRetries < 0 {
return fmt.Errorf("promptkit.transient_retries must not be negative")
}
return nil
}
func validateRequiredPath(name, value string) error {
if strings.TrimSpace(value) == "" {
return fmt.Errorf("%s must not be empty", name)

View File

@@ -223,6 +223,13 @@ cache:
assertDocumentDiagnostic(t, err, configkit.CodeValidateError, "/promptkit/local_backend/endpoint")
}
func TestDocumentLoadRejectsNegativePromptKitTransientRetries(t *testing.T) {
t.Parallel()
_, err := loadDocumentSource(configkit.Bytes("promptkit.yml", []byte("version: 5\npromptkit:\n transient_retries: -1\n")), LoadOptions{})
assertDocumentDiagnostic(t, err, configkit.CodeValidateError, "/promptkit/transient_retries")
}
func assertDocumentDiagnostic(t *testing.T, err error, code configkit.Code, path string) {
t.Helper()
var loadError *configkit.LoadError

View File

@@ -46,6 +46,9 @@ func validatePromptKit(cfg PromptKitConfig) error {
if err := validatePromptKitProfileSources(cfg.ProfileDir, cfg.ProfileFile); err != nil {
return err
}
if err := validatePromptKitTransientRetries(cfg.TransientRetries); err != nil {
return err
}
if cfg.LocalBackend == nil {
return nil
}

View File

@@ -89,6 +89,22 @@ func TestValidatePromptKitSourcesAreMutuallyExclusive(t *testing.T) {
assertValidationContains(t, cfg, "promptkit profile_dir and profile_file are mutually exclusive")
}
func TestValidatePromptKitTransientRetries(t *testing.T) {
if got := Default().PromptKit.TransientRetries; got != defaultTransientRetries {
t.Fatalf("default transient retries = %d, want %d", got, defaultTransientRetries)
}
for _, retries := range []int{0, 4} {
cfg := Default()
cfg.PromptKit.TransientRetries = retries
if err := cfg.Validate(); err != nil {
t.Fatalf("Validate() with transient retries %d error = %v", retries, err)
}
}
negative := Default()
negative.PromptKit.TransientRetries = -1
assertValidationContains(t, negative, "promptkit.transient_retries must not be negative")
}
func TestValidatePromptKitLocalBackendEndpoints(t *testing.T) {
tests := []struct {
name string