Refactor: separate prompt/profile run resolution with explicit precedence, centralized defaults, and api_key_env validation
This commit is contained in:
@@ -8,10 +8,12 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/artifact"
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/defaults"
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/domain"
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/llm"
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/profile"
|
||||
@@ -102,13 +104,16 @@ func (r *Runner) Run(ctx context.Context, req domain.RunRequest) (*domain.RunRes
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrProfileLoad, err)
|
||||
}
|
||||
effectiveModel := mergeExecutionTarget(executionProfileToTarget(execProfile), req.Execution)
|
||||
effectiveModel := resolveExecutionTarget(execProfile, req.Execution)
|
||||
if strings.TrimSpace(effectiveModel.Endpoint) == "" {
|
||||
return nil, fmt.Errorf("%w: execution endpoint is required", ErrInvalidRequest)
|
||||
}
|
||||
if strings.TrimSpace(effectiveModel.Model) == "" {
|
||||
return nil, fmt.Errorf("%w: execution model is required", ErrInvalidRequest)
|
||||
}
|
||||
if err := validateAPIKeyEnv(effectiveModel.APIKeyEnv); err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err)
|
||||
}
|
||||
effectiveContract := resolveOutputContract(def, req.Validation)
|
||||
|
||||
resolvedInputs := make(map[string]*domain.Artifact, len(req.Inputs))
|
||||
@@ -231,11 +236,7 @@ func (r *Runner) shouldAttemptRepair(contract domain.OutputContract, validationR
|
||||
return contract.ValidationMode == domain.ValidationJSON || contract.ValidationMode == domain.ValidationJSONSchema
|
||||
}
|
||||
|
||||
func mergeExecutionTarget(base domain.ExecutionTarget, override *domain.ExecutionTarget) domain.ExecutionTarget {
|
||||
if override == nil {
|
||||
return base
|
||||
}
|
||||
|
||||
func mergeExecutionTarget(base domain.ExecutionTarget, override domain.ExecutionTarget) domain.ExecutionTarget {
|
||||
out := base
|
||||
if override.Endpoint != "" {
|
||||
out.Endpoint = override.Endpoint
|
||||
@@ -271,6 +272,26 @@ func mergeExecutionTarget(base domain.ExecutionTarget, override *domain.Executio
|
||||
return out
|
||||
}
|
||||
|
||||
func resolveExecutionTarget(profileValue *domain.ExecutionProfile, override *domain.ExecutionTarget) domain.ExecutionTarget {
|
||||
out := defaults.ExecutionTargetDefault()
|
||||
out = mergeExecutionTarget(out, executionProfileToTarget(profileValue))
|
||||
if override != nil {
|
||||
out = mergeExecutionTarget(out, *override)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func validateAPIKeyEnv(apiKeyEnv string) error {
|
||||
envName := strings.TrimSpace(apiKeyEnv)
|
||||
if envName == "" {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(os.Getenv(envName)) == "" {
|
||||
return fmt.Errorf("api key environment variable %q is not set", envName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func executionProfileToTarget(p *domain.ExecutionProfile) domain.ExecutionTarget {
|
||||
if p == nil {
|
||||
return domain.ExecutionTarget{}
|
||||
@@ -325,16 +346,16 @@ func buildOutputArtifact(content string, format domain.OutputFormat) domain.Arti
|
||||
body := []byte(content)
|
||||
hash := sha256.Sum256(body)
|
||||
|
||||
contentType := "text/plain"
|
||||
contentType := defaults.ContentTypeTextPlain
|
||||
switch format {
|
||||
case domain.FormatMarkdown:
|
||||
contentType = "text/markdown"
|
||||
contentType = defaults.ContentTypeTextMarkdown
|
||||
case domain.FormatJSON:
|
||||
contentType = "application/json"
|
||||
contentType = defaults.ContentTypeApplicationJSON
|
||||
}
|
||||
|
||||
return domain.Artifact{
|
||||
Name: "output",
|
||||
Name: defaults.OutputArtifactName,
|
||||
ContentType: contentType,
|
||||
Body: body,
|
||||
Size: int64(len(body)),
|
||||
|
||||
Reference in New Issue
Block a user