Split prompt/profile dirs, adopt --prompt/--profile flags, and add override/selection integration tests

This commit is contained in:
2026-05-05 10:49:33 -05:00
parent 8818796b8b
commit e66763f2a7
2 changed files with 310 additions and 44 deletions

View File

@@ -31,6 +31,7 @@ const (
)
type runConfig struct {
promptDir string
profileDir string
promptID string
profileID string
@@ -42,6 +43,7 @@ type runConfig struct {
model string
temperature float64
maxTokens int
topP float64
schemaDir string
timeout time.Duration
@@ -50,10 +52,13 @@ type runConfig struct {
modelSet bool
temperatureSet bool
maxTokensSet bool
topPSet bool
timeoutSet bool
}
type serveConfig struct {
addr string
promptDir string
profileDir string
schemaDir string
llmBaseURL string
@@ -127,7 +132,7 @@ func runCommand(args []string, stdout, stderr io.Writer) int {
}
runner := usecase.NewRunner(
promptdef.NewFilesystemRepository(cfg.profileDir),
promptdef.NewFilesystemRepository(cfg.promptDir),
profile.NewFilesystemRepository(cfg.profileDir),
artifactadapter.NewCompositeReader(),
prompt.NewGoRenderer(),
@@ -136,14 +141,17 @@ func runCommand(args []string, stdout, stderr io.Writer) int {
)
var modelOverride *domain.ExecutionTarget
if cfg.llmBaseURLSet || cfg.modelSet || cfg.temperatureSet || cfg.maxTokensSet || cfg.apiKeyEnvSet {
if cfg.llmBaseURLSet || cfg.modelSet || cfg.temperatureSet || cfg.maxTokensSet || cfg.topPSet || cfg.apiKeyEnvSet || cfg.timeoutSet {
modelOverride = &domain.ExecutionTarget{
Endpoint: cfg.llmBaseURL,
Model: cfg.model,
Temperature: cfg.temperature,
MaxTokens: cfg.maxTokens,
TimeoutSeconds: int(cfg.timeout.Seconds()),
APIKeyEnv: cfg.apiKeyEnv,
Endpoint: cfg.llmBaseURL,
Model: cfg.model,
Temperature: cfg.temperature,
MaxTokens: cfg.maxTokens,
TopP: cfg.topP,
APIKeyEnv: cfg.apiKeyEnv,
}
if cfg.timeoutSet {
modelOverride.TimeoutSeconds = int(cfg.timeout.Seconds())
}
}
@@ -186,7 +194,7 @@ func serveCommand(args []string, stderr io.Writer) int {
}
runner := usecase.NewRunner(
promptdef.NewFilesystemRepository(cfg.profileDir),
promptdef.NewFilesystemRepository(cfg.promptDir),
profile.NewFilesystemRepository(cfg.profileDir),
artifactadapter.NewCompositeReader(),
prompt.NewGoRenderer(),
@@ -214,9 +222,10 @@ func parseRunArgs(args []string) (*runConfig, error) {
fs := flag.NewFlagSet("run", flag.ContinueOnError)
fs.SetOutput(io.Discard)
fs.StringVar(&cfg.profileDir, "profile-dir", "", "directory containing prompt definition YAML files")
fs.StringVar(&cfg.promptID, "prompt-id", "", "prompt ID to run")
fs.StringVar(&cfg.profileID, "profile-id", "", "optional execution profile ID; if omitted, prompt default_profile is used")
fs.StringVar(&cfg.promptDir, "prompt-dir", "", "directory containing prompt definition YAML files")
fs.StringVar(&cfg.profileDir, "profile-dir", "", "directory containing execution profile YAML files")
fs.StringVar(&cfg.promptID, "prompt", "", "prompt ID to run")
fs.StringVar(&cfg.profileID, "profile", "", "optional execution profile ID; if omitted, prompt default_profile is used")
fs.Var(&cfg.inputRaw, "input", "input mapping(s): name=path (repeatable, comma-separated)")
fs.Var(&cfg.varRaw, "var", "variable mapping(s): name=value (repeatable, comma-separated)")
fs.StringVar(&cfg.outputPath, "out", "", "optional output file path")
@@ -225,8 +234,11 @@ func parseRunArgs(args []string) (*runConfig, error) {
fs.StringVar(&cfg.model, "model", "", "model name")
fs.Float64Var(&cfg.temperature, "temperature", 0, "optional temperature override")
fs.IntVar(&cfg.maxTokens, "max-tokens", 0, "optional max tokens override")
fs.Float64Var(&cfg.topP, "top-p", 0, "optional top_p override")
fs.StringVar(&cfg.schemaDir, "schema-dir", defaults.SchemaDirDefault, "base directory for validation schemas")
fs.DurationVar(&cfg.timeout, "timeout", defaults.LLMRequestTimeoutDefault, "LLM request timeout")
fs.StringVar(&cfg.promptID, "prompt-id", "", "deprecated alias for --prompt")
fs.StringVar(&cfg.profileID, "profile-id", "", "deprecated alias for --profile")
if err := fs.Parse(args); err != nil {
return nil, err
@@ -235,15 +247,19 @@ func parseRunArgs(args []string) (*runConfig, error) {
return nil, fmt.Errorf("unexpected positional args: %v", fs.Args())
}
if strings.TrimSpace(cfg.promptDir) == "" {
return nil, errors.New("--prompt-dir is required")
}
if strings.TrimSpace(cfg.profileDir) == "" {
return nil, errors.New("--profile-dir is required")
}
if strings.TrimSpace(cfg.promptID) == "" {
return nil, errors.New("--prompt-id is required")
return nil, errors.New("--prompt is required")
}
if len(cfg.inputRaw) == 0 {
return nil, errors.New("at least one --input is required")
}
cfg.promptDir = filepath.Clean(cfg.promptDir)
cfg.profileDir = filepath.Clean(cfg.profileDir)
cfg.schemaDir = filepath.Clean(cfg.schemaDir)
if cfg.outputPath != "" {
@@ -254,6 +270,8 @@ func parseRunArgs(args []string) (*runConfig, error) {
cfg.modelSet = flagWasSet(fs, "model")
cfg.temperatureSet = flagWasSet(fs, "temperature")
cfg.maxTokensSet = flagWasSet(fs, "max-tokens")
cfg.topPSet = flagWasSet(fs, "top-p")
cfg.timeoutSet = flagWasSet(fs, "timeout")
return cfg, nil
}
@@ -264,7 +282,8 @@ func parseServeArgs(args []string) (*serveConfig, error) {
fs.SetOutput(io.Discard)
fs.StringVar(&cfg.addr, "addr", defaults.HTTPAddrDefault, "HTTP listen address")
fs.StringVar(&cfg.profileDir, "profile-dir", "", "directory containing prompt definition YAML files")
fs.StringVar(&cfg.promptDir, "prompt-dir", "", "directory containing prompt definition YAML files")
fs.StringVar(&cfg.profileDir, "profile-dir", "", "directory containing execution profile YAML files")
fs.StringVar(&cfg.schemaDir, "schema-dir", defaults.SchemaDirDefault, "base directory for validation schemas")
fs.StringVar(&cfg.llmBaseURL, "llm-base-url", "", "OpenAI-compatible base URL including /v1")
fs.StringVar(&cfg.model, "model", "", "optional default model")
@@ -277,13 +296,14 @@ func parseServeArgs(args []string) (*serveConfig, error) {
return nil, fmt.Errorf("unexpected positional args: %v", fs.Args())
}
if strings.TrimSpace(cfg.promptDir) == "" {
return nil, errors.New("--prompt-dir is required")
}
if strings.TrimSpace(cfg.profileDir) == "" {
return nil, errors.New("--profile-dir is required")
}
if strings.TrimSpace(cfg.llmBaseURL) == "" {
return nil, errors.New("--llm-base-url is required")
}
cfg.promptDir = filepath.Clean(cfg.promptDir)
cfg.profileDir = filepath.Clean(cfg.profileDir)
cfg.schemaDir = filepath.Clean(cfg.schemaDir)
return cfg, nil
@@ -376,6 +396,6 @@ func printSummary(stderr io.Writer, res *domain.RunResult) {
func printUsage(w io.Writer) {
fmt.Fprintln(w, "usage: scriptorium <run|serve> ...")
fmt.Fprintln(w, " run: scriptorium run --profile-dir DIR --prompt-id ID --input name=path [--input ...] [--llm-base-url URL] [--model NAME] [--api-key-env ENV] [--var k=v] [--out path] [--timeout 10m]")
fmt.Fprintln(w, " serve: scriptorium serve --addr :8080 --profile-dir DIR --llm-base-url URL [--schema-dir DIR] [--model NAME] [--timeout 10m]")
fmt.Fprintln(w, " run: scriptorium run --prompt-dir DIR --profile-dir DIR --prompt ID --input name=path [--input ...] [--profile ID] [--llm-base-url URL] [--model NAME] [--api-key-env ENV] [--temperature N] [--max-tokens N] [--top-p N] [--var k=v] [--out path] [--timeout 10m]")
fmt.Fprintln(w, " serve: scriptorium serve --addr :8080 --prompt-dir DIR --profile-dir DIR [--llm-base-url URL] [--schema-dir DIR] [--model NAME] [--timeout 10m]")
}