153 lines
6.9 KiB
Go
153 lines
6.9 KiB
Go
package configuration
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/promptkit/internal/defaults"
|
|
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
|
"gitea.maximumdirect.net/eric/promptkit/internal/llm"
|
|
)
|
|
|
|
func TestLoadBuildsEffectiveTargetsAfterInheritance(t *testing.T) {
|
|
state, err := load(mapping("effective-targets", map[string]any{
|
|
"backends": map[string]any{
|
|
"first": map[string]any{"endpoint": "https://first.example/v1", "api_key_env": "FIRST_KEY",
|
|
"extra_params": map[string]any{"source": "first", "nested": map[string]any{"old": true}}},
|
|
"second": map[string]any{"endpoint": "https://second.example/v1", "api_key_env": "SECOND_KEY",
|
|
"concurrency_limit": 2, "queue_capacity": 0,
|
|
"extra_params": map[string]any{"source": "second", "nested": map[string]any{"backend": true}}},
|
|
},
|
|
"profiles": map[string]any{
|
|
"base": map[string]any{"backend": "first", "model": "model", "temperature": 0.8,
|
|
"top_p": 0.9, "max_tokens": 100, "timeout_seconds": 30, "api_key_required": true,
|
|
"extra_params": map[string]any{"nested": map[string]any{"profile": true}}},
|
|
"child": map[string]any{"extends": "base", "backend": "second", "temperature": 0.0,
|
|
"top_p": nil, "max_tokens": 0, "timeout_seconds": nil, "api_key_env": nil,
|
|
"api_key_required": false,
|
|
"extra_params": map[string]any{"nested": map[string]any{"child": nil}}},
|
|
"direct-key": map[string]any{"backend": "second", "api_key_required": true},
|
|
"named-key": map[string]any{"backend": "second", "api_key_required": true, "api_key_env": "CUSTOM_KEY"},
|
|
"no-provider-map": map[string]any{"backend": "second", "extra_params": nil},
|
|
"no-timeout": map[string]any{"backend": "second", "timeout_seconds": 0},
|
|
"partial": map[string]any{},
|
|
"endpoint-only": map[string]any{"endpoint": "https://standalone.example/v1"},
|
|
"cleared-endpoint": map[string]any{"extends": "base", "endpoint": nil},
|
|
},
|
|
}), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
selected, err := state.registry.GetBackend("second")
|
|
if err != nil || selected.QueueCapacity != 0 || !selected.QueueCapacitySet {
|
|
t.Fatalf("backend capacity was not preserved: %+v, %v", selected, err)
|
|
}
|
|
child, err := state.GetProfile(context.Background(), "child")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := state.GetProfile(context.Background(), " child "); err == nil {
|
|
t.Fatal("profile lookup silently changed a literal ID")
|
|
}
|
|
target := child.ResolvedTarget
|
|
if target.BackendID != "second" || target.Endpoint != "https://second.example/v1" || target.Model != "model" ||
|
|
target.Temperature != 0 || target.TopP != 0 || target.MaxTokens != 0 ||
|
|
target.TimeoutSeconds != defaults.ExecutionDefaultTimeoutSeconds || target.APIKeyEnv != "" || target.APIKeyRequired {
|
|
t.Fatalf("effective child target is wrong: %+v", target)
|
|
}
|
|
if !child.TargetPresence.Temperature || !child.TargetPresence.MaxTokens || child.TargetPresence.TopP || child.TargetPresence.TimeoutSeconds {
|
|
t.Fatalf("numeric presence was not retained: %+v", child.TargetPresence)
|
|
}
|
|
wantParams := map[string]any{"source": "second", "nested": map[string]any{
|
|
"backend": true, "profile": true, "child": nil,
|
|
}}
|
|
if !reflect.DeepEqual(target.ExtraParams, wantParams) {
|
|
t.Fatalf("backend/profile provider map overlay failed: %+v", target.ExtraParams)
|
|
}
|
|
if _, exists := target.ExtraParams["nested"].(map[string]any)["old"]; exists {
|
|
t.Fatal("previous backend derived parameter leaked into child")
|
|
}
|
|
for id, wantEnv := range map[string]string{"direct-key": "", "named-key": "CUSTOM_KEY"} {
|
|
profile, err := state.GetProfile(context.Background(), id)
|
|
if err != nil || profile.ResolvedTarget.APIKeyEnv != wantEnv {
|
|
t.Fatalf("credential source for %s: %+v, %v", id, profile, err)
|
|
}
|
|
}
|
|
noMap, _ := state.GetProfile(context.Background(), "no-provider-map")
|
|
if noMap.ResolvedTarget.ExtraParams != nil {
|
|
t.Fatalf("explicit null provider map did not suppress backend defaults: %+v", noMap.ResolvedTarget.ExtraParams)
|
|
}
|
|
noTimeout, _ := state.GetProfile(context.Background(), "no-timeout")
|
|
if noTimeout.ResolvedTarget.TimeoutSeconds != 0 || !noTimeout.TargetPresence.TimeoutSeconds {
|
|
t.Fatalf("explicit zero timeout was lost: %+v", noTimeout)
|
|
}
|
|
partial, _ := state.GetProfile(context.Background(), "partial")
|
|
if partial.ResolvedTarget.Endpoint != "" || partial.ResolvedTarget.Model != "" {
|
|
t.Fatalf("partial profile gained a runnable target: %+v", partial.ResolvedTarget)
|
|
}
|
|
endpointOnly, _ := state.GetProfile(context.Background(), "endpoint-only")
|
|
if endpointOnly.ResolvedTarget.Endpoint != "https://standalone.example/v1" ||
|
|
endpointOnly.ResolvedTarget.BackendID != "" || endpointOnly.ResolvedTarget.Model != "" {
|
|
t.Fatalf("endpoint-only profile was not retained: %+v", endpointOnly.ResolvedTarget)
|
|
}
|
|
clearedEndpoint, _ := state.GetProfile(context.Background(), "cleared-endpoint")
|
|
if clearedEndpoint.ResolvedTarget.Endpoint != "" || clearedEndpoint.ResolvedTarget.BackendID != "first" {
|
|
t.Fatalf("explicit null recovered backend endpoint: %+v", clearedEndpoint.ResolvedTarget)
|
|
}
|
|
child.ResolvedTarget.ExtraParams["source"] = "mutated"
|
|
again, _ := state.GetProfile(context.Background(), "child")
|
|
if again.ResolvedTarget.ExtraParams["source"] != "second" {
|
|
t.Fatal("profile lookup exposed snapshot ownership")
|
|
}
|
|
}
|
|
|
|
func TestLoadedZeroControlsReachBuiltInRequest(t *testing.T) {
|
|
payloads := make(chan map[string]any, 1)
|
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
var payload map[string]any
|
|
if err := json.NewDecoder(request.Body).Decode(&payload); err != nil {
|
|
writer.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
payloads <- payload
|
|
writer.Header().Set("Content-Type", "application/json")
|
|
_, _ = writer.Write([]byte(`{"choices":[{"message":{"content":"ok"}}]}`))
|
|
}))
|
|
defer server.Close()
|
|
state, err := load(mapping("wire-controls", map[string]any{
|
|
"backends": map[string]any{"local": map[string]any{"endpoint": server.URL + "/v1"}},
|
|
"profiles": map[string]any{"direct": map[string]any{
|
|
"backend": "local", "model": "example", "temperature": 0.0, "max_tokens": 0, "top_p": nil,
|
|
}},
|
|
}), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
profile, err := state.GetProfile(context.Background(), "direct")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client, err := llm.NewOpenAICompatibleClient(llm.OpenAICompatibleConfig{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = client.Generate(context.Background(), domain.GenerateRequest{
|
|
Prompt: domain.RenderedPrompt{Messages: []domain.RenderedMessage{{Role: "user", Content: "hello"}}},
|
|
Target: *profile.ResolvedTarget, TargetPresence: profile.TargetPresence,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
payload := <-payloads
|
|
if payload["temperature"] != float64(0) || payload["max_tokens"] != float64(0) {
|
|
t.Fatalf("explicit zeros were omitted from outbound request: %+v", payload)
|
|
}
|
|
if _, exists := payload["top_p"]; exists {
|
|
t.Fatalf("explicit null top_p was sent: %+v", payload)
|
|
}
|
|
}
|