Centralize remaining runtime constants and add precedence/default fallback coverage
This commit is contained in:
@@ -206,7 +206,7 @@ func serveCommand(args []string, stderr io.Writer) int {
|
||||
srv := &http.Server{
|
||||
Addr: cfg.addr,
|
||||
Handler: h,
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
ReadHeaderTimeout: defaults.HTTPReadHeaderTimeoutDefault,
|
||||
}
|
||||
|
||||
fmt.Fprintf(stderr, "serving on %s\n", cfg.addr)
|
||||
@@ -397,5 +397,5 @@ 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 --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]")
|
||||
fmt.Fprintf(w, " serve: scriptorium serve --addr %s --prompt-dir DIR --profile-dir DIR [--llm-base-url URL] [--schema-dir DIR] [--model NAME] [--timeout 10m]\n", defaults.HTTPAddrDefault)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/defaults"
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/domain"
|
||||
"mime"
|
||||
"os"
|
||||
@@ -67,7 +68,7 @@ func (r *inlineReader) Read(ctx context.Context, ref domain.ArtifactRef) (*domai
|
||||
|
||||
body := []byte(ref.Body)
|
||||
return &domain.Artifact{
|
||||
ContentType: "text/plain",
|
||||
ContentType: defaults.ContentTypeTextPlain,
|
||||
Body: body,
|
||||
Size: int64(len(body)),
|
||||
Hash: fmt.Sprintf("%x", sha256.Sum256(body)),
|
||||
@@ -95,7 +96,7 @@ func (r *fileReader) Read(ctx context.Context, ref domain.ArtifactRef) (*domain.
|
||||
|
||||
contentType := mime.TypeByExtension(filepath.Ext(ref.URI))
|
||||
if contentType == "" {
|
||||
contentType = "text/plain" // Default
|
||||
contentType = defaults.ContentTypeTextPlain
|
||||
}
|
||||
|
||||
return &domain.Artifact{
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
ContentTypeTextPlain = "text/plain"
|
||||
ContentTypeTextMarkdown = "text/markdown"
|
||||
ContentTypeApplicationJSON = "application/json"
|
||||
OpenAIChatCompletionsPath = "/chat/completions"
|
||||
|
||||
ExecutionDefaultTemperature = 0.0
|
||||
ExecutionDefaultMaxTokens = 0
|
||||
@@ -21,7 +22,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
LLMRequestTimeoutDefault = 10 * time.Minute
|
||||
LLMRequestTimeoutDefault = 10 * time.Minute
|
||||
HTTPReadHeaderTimeoutDefault = 10 * time.Second
|
||||
)
|
||||
|
||||
func ExecutionTargetDefault() domain.ExecutionTarget {
|
||||
|
||||
@@ -90,7 +90,7 @@ func (c *OpenAICompatibleClient) Generate(ctx context.Context, req domain.Genera
|
||||
if endpoint == "" {
|
||||
return nil, fmt.Errorf("%w: endpoint is required", ErrInvalidRequest)
|
||||
}
|
||||
endpoint = strings.TrimRight(endpoint, "/") + "/chat/completions"
|
||||
endpoint = strings.TrimRight(endpoint, "/") + defaults.OpenAIChatCompletionsPath
|
||||
|
||||
wireReq := openAIChatRequest{
|
||||
Model: model,
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/defaults"
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/domain"
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/validate"
|
||||
)
|
||||
@@ -164,6 +165,12 @@ func TestRunnerRunSuccessful(t *testing.T) {
|
||||
if res.EffectiveModelParams.Model != "m" || res.Endpoint != "http://override/v1" {
|
||||
t.Fatalf("unexpected model params: %+v", res.EffectiveModelParams)
|
||||
}
|
||||
if res.Artifact.Name != defaults.OutputArtifactName {
|
||||
t.Fatalf("expected default output artifact name %q, got %q", defaults.OutputArtifactName, res.Artifact.Name)
|
||||
}
|
||||
if res.Artifact.ContentType != defaults.ContentTypeTextMarkdown {
|
||||
t.Fatalf("expected markdown content type %q, got %q", defaults.ContentTypeTextMarkdown, res.Artifact.ContentType)
|
||||
}
|
||||
if res.RawOutput != "# recap" {
|
||||
t.Fatalf("expected raw output, got %q", res.RawOutput)
|
||||
}
|
||||
@@ -323,6 +330,36 @@ func TestRunnerRunSelectedProfileBeatsBuiltInDefault(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerRunBuiltInDefaultsUsedWhenProfileOmitsOptionalFields(t *testing.T) {
|
||||
promptRepo := &fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)}
|
||||
execRepo := &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"exec": {ID: "exec", Endpoint: "http://profile/v1", Model: "profile-model"},
|
||||
}}
|
||||
llmClient := &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), llmClient, nil)
|
||||
|
||||
res, err := runner.Run(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
ProfileID: "exec",
|
||||
Inputs: singleInputRef(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if res.EffectiveModelParams.Temperature != defaults.ExecutionDefaultTemperature {
|
||||
t.Fatalf("expected default temperature %v, got %v", defaults.ExecutionDefaultTemperature, res.EffectiveModelParams.Temperature)
|
||||
}
|
||||
if res.EffectiveModelParams.TopP != defaults.ExecutionDefaultTopP {
|
||||
t.Fatalf("expected default top_p %v, got %v", defaults.ExecutionDefaultTopP, res.EffectiveModelParams.TopP)
|
||||
}
|
||||
if res.EffectiveModelParams.MaxTokens != defaults.ExecutionDefaultMaxTokens {
|
||||
t.Fatalf("expected default max_tokens %d, got %d", defaults.ExecutionDefaultMaxTokens, res.EffectiveModelParams.MaxTokens)
|
||||
}
|
||||
if res.EffectiveModelParams.TimeoutSeconds != defaults.ExecutionDefaultTimeoutSeconds {
|
||||
t.Fatalf("expected default timeout_seconds %d, got %d", defaults.ExecutionDefaultTimeoutSeconds, res.EffectiveModelParams.TimeoutSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerRunAPIKeyEnvResolvesFromEnvironment(t *testing.T) {
|
||||
t.Setenv("SCRIPTORIUM_TEST_API_KEY", "secret")
|
||||
promptRepo := &fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)}
|
||||
@@ -506,6 +543,30 @@ func TestRunnerRunStructuredRepairRemainsBoundedAndUsesEffectiveModelSettings(t
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOutputArtifactDefaults(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
format domain.OutputFormat
|
||||
contentType string
|
||||
}{
|
||||
{name: "text", format: domain.FormatText, contentType: defaults.ContentTypeTextPlain},
|
||||
{name: "markdown", format: domain.FormatMarkdown, contentType: defaults.ContentTypeTextMarkdown},
|
||||
{name: "json", format: domain.FormatJSON, contentType: defaults.ContentTypeApplicationJSON},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
art := buildOutputArtifact("body", tc.format)
|
||||
if art.Name != defaults.OutputArtifactName {
|
||||
t.Fatalf("expected artifact name %q, got %q", defaults.OutputArtifactName, art.Name)
|
||||
}
|
||||
if art.ContentType != tc.contentType {
|
||||
t.Fatalf("expected content type %q, got %q", tc.contentType, art.ContentType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func promptDef(format domain.OutputFormat, mode domain.ValidationMode, attempts int) *domain.PromptDefinition {
|
||||
return &domain.PromptDefinition{
|
||||
ID: "p",
|
||||
|
||||
Reference in New Issue
Block a user