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{
|
srv := &http.Server{
|
||||||
Addr: cfg.addr,
|
Addr: cfg.addr,
|
||||||
Handler: h,
|
Handler: h,
|
||||||
ReadHeaderTimeout: 10 * time.Second,
|
ReadHeaderTimeout: defaults.HTTPReadHeaderTimeoutDefault,
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(stderr, "serving on %s\n", cfg.addr)
|
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) {
|
func printUsage(w io.Writer) {
|
||||||
fmt.Fprintln(w, "usage: scriptorium <run|serve> ...")
|
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, " 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"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"gitea.maximumdirect.net/eric/scriptorium/internal/defaults"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/domain"
|
"gitea.maximumdirect.net/eric/scriptorium/internal/domain"
|
||||||
"mime"
|
"mime"
|
||||||
"os"
|
"os"
|
||||||
@@ -67,7 +68,7 @@ func (r *inlineReader) Read(ctx context.Context, ref domain.ArtifactRef) (*domai
|
|||||||
|
|
||||||
body := []byte(ref.Body)
|
body := []byte(ref.Body)
|
||||||
return &domain.Artifact{
|
return &domain.Artifact{
|
||||||
ContentType: "text/plain",
|
ContentType: defaults.ContentTypeTextPlain,
|
||||||
Body: body,
|
Body: body,
|
||||||
Size: int64(len(body)),
|
Size: int64(len(body)),
|
||||||
Hash: fmt.Sprintf("%x", sha256.Sum256(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))
|
contentType := mime.TypeByExtension(filepath.Ext(ref.URI))
|
||||||
if contentType == "" {
|
if contentType == "" {
|
||||||
contentType = "text/plain" // Default
|
contentType = defaults.ContentTypeTextPlain
|
||||||
}
|
}
|
||||||
|
|
||||||
return &domain.Artifact{
|
return &domain.Artifact{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const (
|
|||||||
ContentTypeTextPlain = "text/plain"
|
ContentTypeTextPlain = "text/plain"
|
||||||
ContentTypeTextMarkdown = "text/markdown"
|
ContentTypeTextMarkdown = "text/markdown"
|
||||||
ContentTypeApplicationJSON = "application/json"
|
ContentTypeApplicationJSON = "application/json"
|
||||||
|
OpenAIChatCompletionsPath = "/chat/completions"
|
||||||
|
|
||||||
ExecutionDefaultTemperature = 0.0
|
ExecutionDefaultTemperature = 0.0
|
||||||
ExecutionDefaultMaxTokens = 0
|
ExecutionDefaultMaxTokens = 0
|
||||||
@@ -21,7 +22,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
LLMRequestTimeoutDefault = 10 * time.Minute
|
LLMRequestTimeoutDefault = 10 * time.Minute
|
||||||
|
HTTPReadHeaderTimeoutDefault = 10 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExecutionTargetDefault() domain.ExecutionTarget {
|
func ExecutionTargetDefault() domain.ExecutionTarget {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func (c *OpenAICompatibleClient) Generate(ctx context.Context, req domain.Genera
|
|||||||
if endpoint == "" {
|
if endpoint == "" {
|
||||||
return nil, fmt.Errorf("%w: endpoint is required", ErrInvalidRequest)
|
return nil, fmt.Errorf("%w: endpoint is required", ErrInvalidRequest)
|
||||||
}
|
}
|
||||||
endpoint = strings.TrimRight(endpoint, "/") + "/chat/completions"
|
endpoint = strings.TrimRight(endpoint, "/") + defaults.OpenAIChatCompletionsPath
|
||||||
|
|
||||||
wireReq := openAIChatRequest{
|
wireReq := openAIChatRequest{
|
||||||
Model: model,
|
Model: model,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/scriptorium/internal/defaults"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/domain"
|
"gitea.maximumdirect.net/eric/scriptorium/internal/domain"
|
||||||
"gitea.maximumdirect.net/eric/scriptorium/internal/validate"
|
"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" {
|
if res.EffectiveModelParams.Model != "m" || res.Endpoint != "http://override/v1" {
|
||||||
t.Fatalf("unexpected model params: %+v", res.EffectiveModelParams)
|
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" {
|
if res.RawOutput != "# recap" {
|
||||||
t.Fatalf("expected raw output, got %q", res.RawOutput)
|
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) {
|
func TestRunnerRunAPIKeyEnvResolvesFromEnvironment(t *testing.T) {
|
||||||
t.Setenv("SCRIPTORIUM_TEST_API_KEY", "secret")
|
t.Setenv("SCRIPTORIUM_TEST_API_KEY", "secret")
|
||||||
promptRepo := &fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)}
|
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 {
|
func promptDef(format domain.OutputFormat, mode domain.ValidationMode, attempts int) *domain.PromptDefinition {
|
||||||
return &domain.PromptDefinition{
|
return &domain.PromptDefinition{
|
||||||
ID: "p",
|
ID: "p",
|
||||||
|
|||||||
Reference in New Issue
Block a user