Refactor: split prompt definition from execution settings and migrate run contracts to prompt_* + execution_target
This commit is contained in:
@@ -30,12 +30,13 @@ const (
|
||||
|
||||
type runConfig struct {
|
||||
profileDir string
|
||||
promptID string
|
||||
profileID string
|
||||
inputRaw listFlag
|
||||
varRaw listFlag
|
||||
outputPath string
|
||||
llmBaseURL string
|
||||
llmAPIKey string
|
||||
apiKeyEnv string
|
||||
model string
|
||||
temperature float64
|
||||
maxTokens int
|
||||
@@ -43,6 +44,7 @@ type runConfig struct {
|
||||
timeout time.Duration
|
||||
|
||||
llmBaseURLSet bool
|
||||
apiKeyEnvSet bool
|
||||
modelSet bool
|
||||
temperatureSet bool
|
||||
maxTokensSet bool
|
||||
@@ -53,7 +55,6 @@ type serveConfig struct {
|
||||
profileDir string
|
||||
schemaDir string
|
||||
llmBaseURL string
|
||||
llmAPIKey string
|
||||
model string
|
||||
timeout time.Duration
|
||||
}
|
||||
@@ -115,7 +116,6 @@ func runCommand(args []string, stdout, stderr io.Writer) int {
|
||||
|
||||
llmClient, err := llm.NewOpenAICompatibleClient(llm.OpenAICompatibleConfig{
|
||||
BaseURL: cfg.llmBaseURL,
|
||||
APIKey: cfg.llmAPIKey,
|
||||
Model: cfg.model,
|
||||
Timeout: cfg.timeout,
|
||||
})
|
||||
@@ -132,21 +132,24 @@ func runCommand(args []string, stdout, stderr io.Writer) int {
|
||||
validate.NewStandardValidator(cfg.schemaDir),
|
||||
)
|
||||
|
||||
var modelOverride *domain.ModelTarget
|
||||
if cfg.llmBaseURLSet || cfg.modelSet || cfg.temperatureSet || cfg.maxTokensSet {
|
||||
modelOverride = &domain.ModelTarget{
|
||||
Endpoint: cfg.llmBaseURL,
|
||||
Model: cfg.model,
|
||||
Temperature: cfg.temperature,
|
||||
MaxTokens: cfg.maxTokens,
|
||||
var modelOverride *domain.ExecutionTarget
|
||||
if cfg.llmBaseURLSet || cfg.modelSet || cfg.temperatureSet || cfg.maxTokensSet || cfg.apiKeyEnvSet {
|
||||
modelOverride = &domain.ExecutionTarget{
|
||||
Endpoint: cfg.llmBaseURL,
|
||||
Model: cfg.model,
|
||||
Temperature: cfg.temperature,
|
||||
MaxTokens: cfg.maxTokens,
|
||||
TimeoutSeconds: int(cfg.timeout.Seconds()),
|
||||
APIKeyEnv: cfg.apiKeyEnv,
|
||||
}
|
||||
}
|
||||
|
||||
res, runErr := runner.Run(context.Background(), domain.RunRequest{
|
||||
PromptID: cfg.promptID,
|
||||
ProfileID: cfg.profileID,
|
||||
Inputs: inputs,
|
||||
Vars: varMappings,
|
||||
Model: modelOverride,
|
||||
Execution: modelOverride,
|
||||
})
|
||||
if runErr != nil {
|
||||
fmt.Fprintf(stderr, "run error: %v\n", runErr)
|
||||
@@ -171,7 +174,6 @@ func serveCommand(args []string, stderr io.Writer) int {
|
||||
|
||||
llmClient, err := llm.NewOpenAICompatibleClient(llm.OpenAICompatibleConfig{
|
||||
BaseURL: cfg.llmBaseURL,
|
||||
APIKey: cfg.llmAPIKey,
|
||||
Model: cfg.model,
|
||||
Timeout: cfg.timeout,
|
||||
})
|
||||
@@ -208,13 +210,14 @@ 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 profile YAML files")
|
||||
fs.StringVar(&cfg.profileID, "profile-id", "", "profile ID to run")
|
||||
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.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")
|
||||
fs.StringVar(&cfg.llmBaseURL, "llm-base-url", "", "OpenAI-compatible base URL including /v1")
|
||||
fs.StringVar(&cfg.llmAPIKey, "llm-api-key", "", "optional API key")
|
||||
fs.StringVar(&cfg.apiKeyEnv, "api-key-env", "", "environment variable name containing API key")
|
||||
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")
|
||||
@@ -231,8 +234,8 @@ func parseRunArgs(args []string) (*runConfig, error) {
|
||||
if strings.TrimSpace(cfg.profileDir) == "" {
|
||||
return nil, errors.New("--profile-dir is required")
|
||||
}
|
||||
if strings.TrimSpace(cfg.profileID) == "" {
|
||||
return nil, errors.New("--profile-id is required")
|
||||
if strings.TrimSpace(cfg.promptID) == "" {
|
||||
return nil, errors.New("--prompt-id is required")
|
||||
}
|
||||
if len(cfg.inputRaw) == 0 {
|
||||
return nil, errors.New("at least one --input is required")
|
||||
@@ -243,6 +246,7 @@ func parseRunArgs(args []string) (*runConfig, error) {
|
||||
cfg.outputPath = filepath.Clean(cfg.outputPath)
|
||||
}
|
||||
cfg.llmBaseURLSet = flagWasSet(fs, "llm-base-url")
|
||||
cfg.apiKeyEnvSet = flagWasSet(fs, "api-key-env")
|
||||
cfg.modelSet = flagWasSet(fs, "model")
|
||||
cfg.temperatureSet = flagWasSet(fs, "temperature")
|
||||
cfg.maxTokensSet = flagWasSet(fs, "max-tokens")
|
||||
@@ -256,10 +260,9 @@ func parseServeArgs(args []string) (*serveConfig, error) {
|
||||
fs.SetOutput(io.Discard)
|
||||
|
||||
fs.StringVar(&cfg.addr, "addr", ":8080", "HTTP listen address")
|
||||
fs.StringVar(&cfg.profileDir, "profile-dir", "", "directory containing prompt profile YAML files")
|
||||
fs.StringVar(&cfg.profileDir, "profile-dir", "", "directory containing prompt definition YAML files")
|
||||
fs.StringVar(&cfg.schemaDir, "schema-dir", ".", "base directory for validation schemas")
|
||||
fs.StringVar(&cfg.llmBaseURL, "llm-base-url", "", "OpenAI-compatible base URL including /v1")
|
||||
fs.StringVar(&cfg.llmAPIKey, "llm-api-key", "", "optional API key")
|
||||
fs.StringVar(&cfg.model, "model", "", "optional default model")
|
||||
fs.DurationVar(&cfg.timeout, "timeout", 10*time.Minute, "LLM request timeout")
|
||||
|
||||
@@ -351,14 +354,15 @@ func printSummary(stderr io.Writer, res *domain.RunResult) {
|
||||
if res == nil {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(stderr, "profile=%s@%s model=%s validation=%s mode=%s validation_errors=%d prompt_hash=%s inputs=%d usage=%d/%d/%d\n",
|
||||
res.ProfileID,
|
||||
res.ProfileVersion,
|
||||
fmt.Fprintf(stderr, "prompt=%s@%s selected_profile=%s model=%s validation=%s mode=%s validation_errors=%d prompt_hash=%s inputs=%d usage=%d/%d/%d\n",
|
||||
res.PromptID,
|
||||
res.PromptVersion,
|
||||
res.SelectedProfileID,
|
||||
res.ModelName,
|
||||
res.Validation.Status,
|
||||
res.Validation.Mode,
|
||||
len(res.Validation.Errors),
|
||||
res.PromptHash,
|
||||
res.RenderedPromptHash,
|
||||
len(res.InputHashes),
|
||||
res.Usage.PromptTokens,
|
||||
res.Usage.CompletionTokens,
|
||||
@@ -368,6 +372,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 --profile-id ID --input name=path [--input ...] [--llm-base-url URL] [--model NAME] [--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] [--llm-api-key KEY] [--model NAME] [--timeout 10m]")
|
||||
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]")
|
||||
}
|
||||
|
||||
@@ -51,17 +51,17 @@ func TestParseMappingsMalformed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseRunArgsRequiredFlags(t *testing.T) {
|
||||
_, err := parseRunArgs([]string{"--profile-id", "p", "--input", "a=b", "--llm-base-url", "http://x/v1", "--model", "m"})
|
||||
_, err := parseRunArgs([]string{"--prompt-id", "p", "--input", "a=b", "--llm-base-url", "http://x/v1", "--model", "m"})
|
||||
if err == nil {
|
||||
t.Fatal("expected missing --profile-dir error")
|
||||
}
|
||||
|
||||
_, err = parseRunArgs([]string{"--profile-dir", "./profiles", "--input", "a=b", "--llm-base-url", "http://x/v1", "--model", "m"})
|
||||
if err == nil {
|
||||
t.Fatal("expected missing --profile-id error")
|
||||
t.Fatal("expected missing --prompt-id error")
|
||||
}
|
||||
|
||||
_, err = parseRunArgs([]string{"--profile-dir", "./profiles", "--profile-id", "p", "--llm-base-url", "http://x/v1", "--model", "m"})
|
||||
_, err = parseRunArgs([]string{"--profile-dir", "./profiles", "--prompt-id", "p", "--llm-base-url", "http://x/v1", "--model", "m"})
|
||||
if err == nil {
|
||||
t.Fatal("expected missing --input error")
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func TestParseRunArgsRequiredFlags(t *testing.T) {
|
||||
func TestParseRunArgsAllowsOmittedModelAndBaseURL(t *testing.T) {
|
||||
cfg, err := parseRunArgs([]string{
|
||||
"--profile-dir", "./profiles",
|
||||
"--profile-id", "p",
|
||||
"--prompt-id", "p",
|
||||
"--input", "a=b",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -107,7 +107,7 @@ func TestParseServeArgsRequiredFlags(t *testing.T) {
|
||||
func TestParseRunArgsTimeout(t *testing.T) {
|
||||
cfg, err := parseRunArgs([]string{
|
||||
"--profile-dir", "./profiles",
|
||||
"--profile-id", "p",
|
||||
"--prompt-id", "p",
|
||||
"--input", "a=b",
|
||||
"--llm-base-url", "http://x/v1",
|
||||
"--model", "m",
|
||||
@@ -121,7 +121,7 @@ func TestParseRunArgsTimeout(t *testing.T) {
|
||||
|
||||
cfg, err = parseRunArgs([]string{
|
||||
"--profile-dir", "./profiles",
|
||||
"--profile-id", "p",
|
||||
"--prompt-id", "p",
|
||||
"--input", "a=b",
|
||||
"--llm-base-url", "http://x/v1",
|
||||
"--model", "m",
|
||||
@@ -156,7 +156,7 @@ func TestRunCommandVarsOptional(t *testing.T) {
|
||||
|
||||
code := runCommand([]string{
|
||||
"--profile-dir", "./profiles",
|
||||
"--profile-id", "p",
|
||||
"--prompt-id", "p",
|
||||
"--input", "transcript=./t.md",
|
||||
"--llm-base-url", "://bad-url",
|
||||
"--model", "m",
|
||||
@@ -184,18 +184,19 @@ func TestWriteOutputAndSummaryUseSeparateWriters(t *testing.T) {
|
||||
t.Fatalf("unexpected writeOutput error: %v", err)
|
||||
}
|
||||
printSummary(&stderr, &domain.RunResult{
|
||||
ProfileID: "p",
|
||||
ProfileVersion: "1",
|
||||
ModelName: "m",
|
||||
Validation: domain.ValidationResult{Status: domain.ValidationPassed, Mode: domain.ValidationBasic},
|
||||
PromptHash: "h",
|
||||
InputHashes: map[string]string{"in": "x"},
|
||||
PromptID: "p",
|
||||
PromptVersion: "1",
|
||||
SelectedProfileID: "exec",
|
||||
ModelName: "m",
|
||||
Validation: domain.ValidationResult{Status: domain.ValidationPassed, Mode: domain.ValidationBasic},
|
||||
RenderedPromptHash: "h",
|
||||
InputHashes: map[string]string{"in": "x"},
|
||||
})
|
||||
|
||||
if stdout.String() != "artifact-body" {
|
||||
t.Fatalf("expected artifact output on stdout, got %q", stdout.String())
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "profile=p@1") {
|
||||
if !strings.Contains(stderr.String(), "prompt=p@1") {
|
||||
t.Fatalf("expected summary on stderr, got %q", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
)
|
||||
|
||||
type runRequestDTO struct {
|
||||
ProfileID string `json:"profile_id"`
|
||||
ProfileVersion string `json:"profile_version,omitempty"`
|
||||
Inputs map[string]inputRefDTO `json:"inputs"`
|
||||
Vars map[string]string `json:"vars,omitempty"`
|
||||
Model *modelOverrideRequestDTO `json:"model,omitempty"`
|
||||
PromptID string `json:"prompt_id"`
|
||||
PromptVersion string `json:"prompt_version,omitempty"`
|
||||
ProfileID string `json:"profile_id,omitempty"`
|
||||
Inputs map[string]inputRefDTO `json:"inputs"`
|
||||
Vars map[string]string `json:"vars,omitempty"`
|
||||
Model *modelOverrideRequestDTO `json:"model,omitempty"`
|
||||
}
|
||||
|
||||
type inputRefDTO struct {
|
||||
@@ -19,12 +20,15 @@ type inputRefDTO struct {
|
||||
}
|
||||
|
||||
type modelOverrideRequestDTO struct {
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
TopP float64 `json:"top_p,omitempty"`
|
||||
TimeoutSeconds int `json:"timeout_seconds,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
TopP float64 `json:"top_p,omitempty"`
|
||||
TimeoutSeconds int `json:"timeout_seconds,omitempty"`
|
||||
ReasoningEffort string `json:"reasoning_effort,omitempty"`
|
||||
APIKeyEnv string `json:"api_key_env,omitempty"`
|
||||
ExtraParams map[string]string `json:"extra_params,omitempty"`
|
||||
}
|
||||
|
||||
type runResponseDTO struct {
|
||||
@@ -45,14 +49,15 @@ type artifactDTO struct {
|
||||
|
||||
type metadataDTO struct {
|
||||
RunID string `json:"run_id"`
|
||||
ProfileID string `json:"profile_id"`
|
||||
ProfileVersion string `json:"profile_version"`
|
||||
ProfileHash string `json:"profile_hash"`
|
||||
PromptID string `json:"prompt_id"`
|
||||
PromptVersion string `json:"prompt_version"`
|
||||
PromptHash string `json:"prompt_hash"`
|
||||
RenderedPromptHash string `json:"rendered_prompt_hash"`
|
||||
SelectedProfileID string `json:"selected_profile_id"`
|
||||
ModelName string `json:"model_name"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
ModelParams modelParamsDTO `json:"model_params"`
|
||||
InputHashes map[string]string `json:"input_hashes"`
|
||||
PromptHash string `json:"prompt_hash"`
|
||||
Usage tokenUsageDTO `json:"usage"`
|
||||
StartTime time.Time `json:"start_time"`
|
||||
EndTime time.Time `json:"end_time"`
|
||||
@@ -63,12 +68,15 @@ type metadataDTO struct {
|
||||
}
|
||||
|
||||
type modelParamsDTO struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Model string `json:"model"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
TopP float64 `json:"top_p"`
|
||||
TimeoutSeconds int `json:"timeout_seconds"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
Model string `json:"model"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
TopP float64 `json:"top_p"`
|
||||
TimeoutSeconds int `json:"timeout_seconds"`
|
||||
ReasoningEffort string `json:"reasoning_effort,omitempty"`
|
||||
APIKeyEnv string `json:"api_key_env,omitempty"`
|
||||
ExtraParams map[string]string `json:"extra_params,omitempty"`
|
||||
}
|
||||
|
||||
type tokenUsageDTO struct {
|
||||
|
||||
@@ -40,8 +40,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.TrimSpace(req.ProfileID) == "" {
|
||||
writeError(w, http.StatusBadRequest, "invalid_request", "profile_id is required")
|
||||
if strings.TrimSpace(req.PromptID) == "" {
|
||||
writeError(w, http.StatusBadRequest, "invalid_request", "prompt_id is required")
|
||||
return
|
||||
}
|
||||
if len(req.Inputs) == 0 {
|
||||
@@ -58,24 +58,28 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
var model *domain.ModelTarget
|
||||
var model *domain.ExecutionTarget
|
||||
if req.Model != nil {
|
||||
model = &domain.ModelTarget{
|
||||
Endpoint: req.Model.Endpoint,
|
||||
Model: req.Model.Model,
|
||||
Temperature: req.Model.Temperature,
|
||||
MaxTokens: req.Model.MaxTokens,
|
||||
TopP: req.Model.TopP,
|
||||
TimeoutSeconds: req.Model.TimeoutSeconds,
|
||||
model = &domain.ExecutionTarget{
|
||||
Endpoint: req.Model.Endpoint,
|
||||
Model: req.Model.Model,
|
||||
Temperature: req.Model.Temperature,
|
||||
MaxTokens: req.Model.MaxTokens,
|
||||
TopP: req.Model.TopP,
|
||||
TimeoutSeconds: req.Model.TimeoutSeconds,
|
||||
ReasoningEffort: req.Model.ReasoningEffort,
|
||||
APIKeyEnv: req.Model.APIKeyEnv,
|
||||
ExtraParams: req.Model.ExtraParams,
|
||||
}
|
||||
}
|
||||
|
||||
res, err := h.runner.Run(r.Context(), domain.RunRequest{
|
||||
ProfileID: req.ProfileID,
|
||||
ProfileVersion: req.ProfileVersion,
|
||||
Inputs: mappedInputs,
|
||||
Vars: req.Vars,
|
||||
Model: model,
|
||||
PromptID: req.PromptID,
|
||||
PromptVersion: req.PromptVersion,
|
||||
ProfileID: req.ProfileID,
|
||||
Inputs: mappedInputs,
|
||||
Vars: req.Vars,
|
||||
Execution: model,
|
||||
})
|
||||
if err != nil {
|
||||
status, code, message := mapRunError(err)
|
||||
@@ -94,22 +98,26 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
},
|
||||
Validation: mapValidation(res.Validation),
|
||||
Metadata: metadataDTO{
|
||||
RunID: res.RunID,
|
||||
ProfileID: res.ProfileID,
|
||||
ProfileVersion: res.ProfileVersion,
|
||||
ProfileHash: res.ProfileHash,
|
||||
ModelName: res.ModelName,
|
||||
Endpoint: res.Endpoint,
|
||||
RunID: res.RunID,
|
||||
PromptID: res.PromptID,
|
||||
PromptVersion: res.PromptVersion,
|
||||
PromptHash: res.PromptHash,
|
||||
RenderedPromptHash: res.RenderedPromptHash,
|
||||
SelectedProfileID: res.SelectedProfileID,
|
||||
ModelName: res.ModelName,
|
||||
Endpoint: res.Endpoint,
|
||||
ModelParams: modelParamsDTO{
|
||||
Endpoint: res.ModelParams.Endpoint,
|
||||
Model: res.ModelParams.Model,
|
||||
Temperature: res.ModelParams.Temperature,
|
||||
MaxTokens: res.ModelParams.MaxTokens,
|
||||
TopP: res.ModelParams.TopP,
|
||||
TimeoutSeconds: res.ModelParams.TimeoutSeconds,
|
||||
Endpoint: res.EffectiveModelParams.Endpoint,
|
||||
Model: res.EffectiveModelParams.Model,
|
||||
Temperature: res.EffectiveModelParams.Temperature,
|
||||
MaxTokens: res.EffectiveModelParams.MaxTokens,
|
||||
TopP: res.EffectiveModelParams.TopP,
|
||||
TimeoutSeconds: res.EffectiveModelParams.TimeoutSeconds,
|
||||
ReasoningEffort: res.EffectiveModelParams.ReasoningEffort,
|
||||
APIKeyEnv: res.EffectiveModelParams.APIKeyEnv,
|
||||
ExtraParams: res.EffectiveModelParams.ExtraParams,
|
||||
},
|
||||
InputHashes: res.InputHashes,
|
||||
PromptHash: res.PromptHash,
|
||||
Usage: tokenUsageDTO{
|
||||
PromptTokens: res.Usage.PromptTokens,
|
||||
CompletionTokens: res.Usage.CompletionTokens,
|
||||
@@ -140,11 +148,11 @@ func mapValidation(v domain.ValidationResult) validationDTO {
|
||||
func mapRunError(err error) (int, string, string) {
|
||||
switch {
|
||||
case errors.Is(err, profile.ErrProfileNotFound):
|
||||
return http.StatusNotFound, "profile_not_found", "profile not found"
|
||||
return http.StatusNotFound, "prompt_not_found", "prompt definition not found"
|
||||
case errors.Is(err, usecase.ErrInvalidRequest):
|
||||
return http.StatusBadRequest, "invalid_request", "invalid run request"
|
||||
case errors.Is(err, usecase.ErrProfileLoad):
|
||||
return http.StatusBadRequest, "profile_load_failed", "failed to load profile"
|
||||
return http.StatusBadRequest, "prompt_load_failed", "failed to load prompt definition"
|
||||
case errors.Is(err, usecase.ErrArtifactLoad):
|
||||
return http.StatusBadRequest, "artifact_read_failed", "failed to read input artifact"
|
||||
case errors.Is(err, usecase.ErrPromptRender):
|
||||
|
||||
@@ -42,13 +42,15 @@ func TestHandlerPostRunsSuccess(t *testing.T) {
|
||||
Size: 5,
|
||||
Hash: "abc",
|
||||
},
|
||||
Validation: domain.ValidationResult{Status: domain.ValidationPassed, Mode: domain.ValidationBasic, IsValid: true},
|
||||
ProfileID: "p1",
|
||||
ProfileVersion: "1.0.0",
|
||||
ProfileHash: "phash",
|
||||
ModelName: "m1",
|
||||
Endpoint: "http://llm/v1",
|
||||
ModelParams: domain.ModelTarget{
|
||||
Validation: domain.ValidationResult{Status: domain.ValidationPassed, Mode: domain.ValidationBasic, IsValid: true},
|
||||
PromptID: "prompt-1",
|
||||
PromptVersion: "1.0.0",
|
||||
PromptHash: "phash",
|
||||
RenderedPromptHash: "rhash",
|
||||
SelectedProfileID: "exec-default",
|
||||
ModelName: "m1",
|
||||
Endpoint: "http://llm/v1",
|
||||
EffectiveModelParams: domain.ExecutionTarget{
|
||||
Endpoint: "http://llm/v1",
|
||||
Model: "m1",
|
||||
Temperature: 0.2,
|
||||
@@ -57,7 +59,6 @@ func TestHandlerPostRunsSuccess(t *testing.T) {
|
||||
TimeoutSeconds: 120,
|
||||
},
|
||||
InputHashes: map[string]string{"transcript": "h1"},
|
||||
PromptHash: "ph",
|
||||
Usage: domain.TokenUsage{PromptTokens: 1, CompletionTokens: 2, TotalTokens: 3},
|
||||
StartTime: start,
|
||||
EndTime: end,
|
||||
@@ -68,7 +69,8 @@ func TestHandlerPostRunsSuccess(t *testing.T) {
|
||||
h := NewHandler(r)
|
||||
|
||||
body := []byte(`{
|
||||
"profile_id": "p1",
|
||||
"prompt_id": "prompt-1",
|
||||
"profile_id": "exec-default",
|
||||
"inputs": {
|
||||
"transcript": {"type": "file", "uri": "./t.md"}
|
||||
},
|
||||
@@ -101,8 +103,8 @@ func TestHandlerPostRunsSuccess(t *testing.T) {
|
||||
if metadata["run_id"] != "11111111-1111-4111-8111-111111111111" {
|
||||
t.Fatalf("unexpected metadata.run_id: %#v", metadata["run_id"])
|
||||
}
|
||||
if metadata["profile_hash"] != "phash" {
|
||||
t.Fatalf("unexpected metadata.profile_hash: %#v", metadata["profile_hash"])
|
||||
if metadata["prompt_hash"] != "phash" {
|
||||
t.Fatalf("unexpected metadata.prompt_hash: %#v", metadata["prompt_hash"])
|
||||
}
|
||||
usage := metadata["usage"].(map[string]any)
|
||||
if usage["total_tokens"] != float64(3) {
|
||||
@@ -122,14 +124,17 @@ func TestHandlerPostRunsSuccess(t *testing.T) {
|
||||
t.Fatalf("expected raw model output hello, got %#v", resp["raw_model_output"])
|
||||
}
|
||||
|
||||
if r.last.ProfileID != "p1" {
|
||||
t.Fatalf("expected request profile_id p1, got %q", r.last.ProfileID)
|
||||
if r.last.PromptID != "prompt-1" {
|
||||
t.Fatalf("expected request prompt_id prompt-1, got %q", r.last.PromptID)
|
||||
}
|
||||
if r.last.Model == nil || r.last.Model.Model != "gpt-x" {
|
||||
t.Fatalf("expected model override, got %#v", r.last.Model)
|
||||
if r.last.ProfileID != "exec-default" {
|
||||
t.Fatalf("expected request profile_id exec-default, got %q", r.last.ProfileID)
|
||||
}
|
||||
if r.last.Model.TimeoutSeconds != 120 {
|
||||
t.Fatalf("expected timeout_seconds override 120, got %#v", r.last.Model)
|
||||
if r.last.Execution == nil || r.last.Execution.Model != "gpt-x" {
|
||||
t.Fatalf("expected model override, got %#v", r.last.Execution)
|
||||
}
|
||||
if r.last.Execution.TimeoutSeconds != 120 {
|
||||
t.Fatalf("expected timeout_seconds override 120, got %#v", r.last.Execution)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +150,7 @@ func TestHandlerInvalidJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerMissingProfileID(t *testing.T) {
|
||||
func TestHandlerMissingPromptID(t *testing.T) {
|
||||
h := NewHandler(&fakeRunner{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/runs", bytes.NewBufferString(`{"inputs":{"x":{"type":"file","uri":"a"}}}`))
|
||||
w := httptest.NewRecorder()
|
||||
@@ -173,7 +178,7 @@ func TestHandlerUsecaseErrorMapping(t *testing.T) {
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
h := NewHandler(&fakeRunner{err: tc.err})
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/runs", bytes.NewBufferString(`{"profile_id":"p","inputs":{"x":{"type":"file","uri":"a"}}}`))
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/runs", bytes.NewBufferString(`{"prompt_id":"p","inputs":{"x":{"type":"file","uri":"a"}}}`))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
h.ServeHTTP(w, req)
|
||||
@@ -210,7 +215,7 @@ func TestHandlerValidationFailureStillSuccess(t *testing.T) {
|
||||
},
|
||||
}})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/runs", bytes.NewBufferString(`{"profile_id":"p","inputs":{"x":{"type":"file","uri":"a"}}}`))
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/runs", bytes.NewBufferString(`{"prompt_id":"p","inputs":{"x":{"type":"file","uri":"a"}}}`))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
h.ServeHTTP(w, req)
|
||||
|
||||
Reference in New Issue
Block a user