Remove superseded configuration loaders and consolidate runtime tests
This commit is contained in:
@@ -13,7 +13,7 @@ func TestRunnerPrepareExecutionRejectsInvalidExecutionSettings(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
&fakeLLM{forbid: true},
|
||||
@@ -37,7 +37,7 @@ func TestRunnerPrepareExecutionValidatesAndNormalizesRequestEndpoints(t *testing
|
||||
return NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
&fakeLLM{forbid: true},
|
||||
|
||||
@@ -27,7 +27,7 @@ func newOutputContractTestRunner() (*Runner, outputContractTestCollaborators) {
|
||||
return NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
collaborators.artifacts,
|
||||
collaborators.renderer,
|
||||
collaborators.llm,
|
||||
|
||||
@@ -104,13 +104,13 @@ func TestRunnerPrepareExecutionCompletesWithoutAdmissionOrGeneration(t *testing.
|
||||
}
|
||||
admitter := &fakeRunAdmitter{}
|
||||
profile := defaultExecutionProfile()
|
||||
profile.ExtraParams = map[string]any{
|
||||
profile.ResolvedTarget.ExtraParams = map[string]any{
|
||||
"metadata": map[string]any{"source": "original"},
|
||||
}
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: def},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": profile}},
|
||||
nil,
|
||||
|
||||
reader,
|
||||
renderer,
|
||||
llmClient,
|
||||
@@ -210,7 +210,7 @@ func TestRunnerPreparationRejectsExcessivelyDeepPreparedSchema(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: def},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
llmClient,
|
||||
@@ -265,9 +265,9 @@ func TestRunnerRunPreparedCredentialAvailabilityBeforeAdmission(t *testing.T) {
|
||||
t.Setenv(environmentName, "available-during-preparation")
|
||||
|
||||
profile := defaultExecutionProfile()
|
||||
profile.APIKeyRequired = tc.apiKeyRequired
|
||||
profile.ResolvedTarget.APIKeyRequired = tc.apiKeyRequired
|
||||
if tc.profileEnv {
|
||||
profile.APIKeyEnv = environmentName
|
||||
profile.ResolvedTarget.APIKeyEnv = environmentName
|
||||
}
|
||||
validator := &recordingValidationPreparer{plan: &recordingPreparedValidation{}}
|
||||
admitter := &fakeRunAdmitter{}
|
||||
@@ -275,7 +275,7 @@ func TestRunnerRunPreparedCredentialAvailabilityBeforeAdmission(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": profile}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
llmClient,
|
||||
@@ -319,12 +319,12 @@ func TestRunnerRunPreparedKeepsDirectCredentialOutOfMetadata(t *testing.T) {
|
||||
const directKey = "direct-prepared-test-key"
|
||||
|
||||
profile := defaultExecutionProfile()
|
||||
profile.APIKeyRequired = true
|
||||
profile.ResolvedTarget.APIKeyRequired = true
|
||||
client := &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": profile}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
client,
|
||||
@@ -397,7 +397,7 @@ func TestRunnerRunPreparedUsesFrozenValidationForInitialAndRepairOutputs(t *test
|
||||
runner := NewRunnerWithRepairer(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatJSON, domain.ValidationJSON, 1)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
reader,
|
||||
renderer,
|
||||
client,
|
||||
@@ -542,7 +542,7 @@ func TestRunnerRunPreparedReleasesAdmissionAcrossExecutionErrors(t *testing.T) {
|
||||
runner := NewRunnerWithRepairer(
|
||||
&fakePromptRepo{def: def},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
&fakeLLM{
|
||||
@@ -585,7 +585,7 @@ func TestRunnerPreparedExecutionOwnershipUseAndDiscard(t *testing.T) {
|
||||
return NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
&fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}},
|
||||
@@ -635,7 +635,7 @@ func TestRunnerPreparedExecutionWithoutValidatorSkipsValidation(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatJSON, domain.ValidationJSON, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
&fakeLLM{resp: &domain.GenerateResponse{Content: `{}`}},
|
||||
@@ -665,7 +665,7 @@ func TestRunnerPrepareExecutionRequiresValidationPreparer(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationBasic, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
reader,
|
||||
defaultRenderer(),
|
||||
&fakeLLM{forbid: true},
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/jsonvalue"
|
||||
)
|
||||
|
||||
type resolvedProfileSelection struct {
|
||||
id string
|
||||
profile *domain.ExecutionProfile
|
||||
backend *domain.Backend
|
||||
}
|
||||
|
||||
func (r *Runner) resolveProfileSelection(
|
||||
@@ -26,10 +26,6 @@ func (r *Runner) resolveProfileSelection(
|
||||
if r == nil || r.profiles == nil {
|
||||
return nil, fmt.Errorf("%w: profile repository is not configured", ErrProfileLoad)
|
||||
}
|
||||
if exact, ok := r.profiles.(interface{ ExactIDs() bool }); !ok || !exact.ExactIDs() {
|
||||
lookupID = strings.TrimSpace(lookupID)
|
||||
}
|
||||
|
||||
selectedProfile, err := r.profiles.GetProfile(ctx, lookupID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrProfileLoad, err)
|
||||
@@ -39,27 +35,13 @@ func (r *Runner) resolveProfileSelection(
|
||||
}
|
||||
|
||||
profileValue := *selectedProfile
|
||||
if profileValue.ResolvedTarget != nil {
|
||||
return &resolvedProfileSelection{id: lookupID, profile: &profileValue}, nil
|
||||
}
|
||||
profileValue.BackendID = strings.TrimSpace(profileValue.BackendID)
|
||||
|
||||
var selectedBackend *domain.Backend
|
||||
if profileValue.BackendID != "" {
|
||||
if r.backends == nil {
|
||||
return nil, fmt.Errorf("%w: backend %q cannot be resolved", ErrProfileLoad, profileValue.BackendID)
|
||||
}
|
||||
backendValue, err := r.backends.GetBackend(profileValue.BackendID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: backend %q: %w", ErrProfileLoad, profileValue.BackendID, err)
|
||||
}
|
||||
selectedBackend = &backendValue
|
||||
if profileValue.ResolvedTarget == nil {
|
||||
return nil, fmt.Errorf("%w: profile %q has no resolved target", ErrProfileLoad, lookupID)
|
||||
}
|
||||
|
||||
return &resolvedProfileSelection{
|
||||
id: lookupID,
|
||||
profile: &profileValue,
|
||||
backend: selectedBackend,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -96,14 +78,13 @@ func (r *Runner) InspectProfile(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target, _ := resolveExecutionTarget(selection.backend, selection.profile, nil)
|
||||
if selection.profile.ResolvedTarget == nil {
|
||||
target, err = normalizeResolvedExecutionTarget(target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrProfileLoad, err)
|
||||
}
|
||||
}
|
||||
target, _ := resolveExecutionTarget(selection.profile, nil)
|
||||
target.APIKey = ""
|
||||
params, err := jsonvalue.CopyMap(target.ExtraParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: profile extra parameters: %w", ErrProfileLoad, err)
|
||||
}
|
||||
target.ExtraParams = params
|
||||
|
||||
return &domain.ProfileInspection{
|
||||
ProfileID: selection.id,
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/defaults"
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/profile"
|
||||
)
|
||||
@@ -18,10 +17,7 @@ type inspectionProfileRepository struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func (r *inspectionProfileRepository) GetProfile(
|
||||
_ context.Context,
|
||||
id string,
|
||||
) (*domain.ExecutionProfile, error) {
|
||||
func (r *inspectionProfileRepository) GetProfile(_ context.Context, id string) (*domain.ExecutionProfile, error) {
|
||||
r.calls++
|
||||
r.id = id
|
||||
if r.err != nil {
|
||||
@@ -30,184 +26,65 @@ func (r *inspectionProfileRepository) GetProfile(
|
||||
return r.profile, nil
|
||||
}
|
||||
|
||||
type inspectionBackendResolver struct {
|
||||
backend domain.Backend
|
||||
err error
|
||||
calls int
|
||||
id string
|
||||
}
|
||||
|
||||
func (r *inspectionBackendResolver) GetBackend(id string) (domain.Backend, error) {
|
||||
r.calls++
|
||||
r.id = id
|
||||
if r.err != nil {
|
||||
return domain.Backend{}, r.err
|
||||
func TestRunnerInspectProfileUsesExactSnapshotAndCopiesTarget(t *testing.T) {
|
||||
configured := domain.ExecutionTarget{
|
||||
BackendID: "backend", Endpoint: "https://backend.example/v1", Model: "model",
|
||||
Temperature: 0, APIKeyEnv: "OPTIONAL_KEY", ExtraParams: map[string]any{"nested": map[string]any{"value": "original"}},
|
||||
}
|
||||
return r.backend, nil
|
||||
}
|
||||
|
||||
func TestRunnerInspectProfileResolvesProfileAndBackendOnce(t *testing.T) {
|
||||
profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{
|
||||
ID: "profile",
|
||||
BackendID: " backend ",
|
||||
Model: "profile-model",
|
||||
Temperature: 0.4,
|
||||
MaxTokens: 32,
|
||||
TimeoutSeconds: 45,
|
||||
ServiceTier: "priority",
|
||||
ReasoningEffort: "high",
|
||||
ExtraParams: map[string]any{
|
||||
"profile": "value",
|
||||
},
|
||||
ID: " literal ", ResolvedTarget: &configured,
|
||||
TargetPresence: domain.ExecutionTargetPresence{Temperature: true},
|
||||
}}
|
||||
backends := &inspectionBackendResolver{backend: domain.Backend{
|
||||
ID: "backend",
|
||||
Endpoint: "https://backend.example/v1",
|
||||
APIKeyEnv: "BACKEND_KEY",
|
||||
ExtraParams: map[string]any{
|
||||
"backend": "value",
|
||||
},
|
||||
}}
|
||||
runner := &Runner{profiles: profiles, backends: backends}
|
||||
|
||||
inspection, err := runner.InspectProfile(context.Background(), " profile ")
|
||||
runner := &Runner{profiles: profiles}
|
||||
first, err := runner.InspectProfile(context.Background(), " literal ")
|
||||
if err != nil {
|
||||
t.Fatalf("inspect profile: %v", err)
|
||||
t.Fatalf("inspect exact ID: %v", err)
|
||||
}
|
||||
if profiles.calls != 1 || profiles.id != "profile" {
|
||||
t.Fatalf("profile lookup=(calls=%d id=%q), want one exact lookup", profiles.calls, profiles.id)
|
||||
if profiles.calls != 1 || profiles.id != " literal " || first.ProfileID != profiles.id || !reflect.DeepEqual(first.EffectiveModelParams, configured) {
|
||||
t.Fatalf("inspection=%+v lookup=(%d,%q), want exact snapshot", first, profiles.calls, profiles.id)
|
||||
}
|
||||
if backends.calls != 1 || backends.id != "backend" {
|
||||
t.Fatalf("backend lookup=(calls=%d id=%q), want one exact lookup", backends.calls, backends.id)
|
||||
}
|
||||
if profiles.profile.BackendID != " backend " {
|
||||
t.Fatalf("inspection mutated repository profile backend: %q", profiles.profile.BackendID)
|
||||
}
|
||||
|
||||
wantTarget := domain.ExecutionTarget{
|
||||
BackendID: "backend",
|
||||
Endpoint: "https://backend.example/v1",
|
||||
Model: "profile-model",
|
||||
Temperature: 0.4,
|
||||
MaxTokens: 32,
|
||||
TopP: defaults.ExecutionTargetDefault().TopP,
|
||||
TimeoutSeconds: 45,
|
||||
ServiceTier: "priority",
|
||||
ReasoningEffort: "high",
|
||||
APIKeyEnv: "BACKEND_KEY",
|
||||
ExtraParams: map[string]any{
|
||||
"profile": "value",
|
||||
},
|
||||
}
|
||||
if inspection.ProfileID != "profile" || inspection.APIKeyRequired ||
|
||||
!reflect.DeepEqual(inspection.EffectiveModelParams, wantTarget) {
|
||||
t.Fatalf("inspection=%#v, want profile=%q target=%#v", inspection, "profile", wantTarget)
|
||||
first.EffectiveModelParams.ExtraParams["nested"].(map[string]any)["value"] = "changed"
|
||||
second, err := runner.InspectProfile(context.Background(), " literal ")
|
||||
if err != nil || second.EffectiveModelParams.ExtraParams["nested"].(map[string]any)["value"] != "original" {
|
||||
t.Fatalf("inspection mutated snapshot: (%+v, %v)", second, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerInspectProfileDoesNotNeedExecutionCollaboratorsOrCredentials(t *testing.T) {
|
||||
t.Setenv("PROMPTKIT_INSPECTION_TEST_KEY", "")
|
||||
profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{
|
||||
ID: "endpoint-only",
|
||||
Endpoint: "https://profile.example/v1",
|
||||
Model: "profile-model",
|
||||
APIKeyEnv: "PROMPTKIT_INSPECTION_TEST_KEY",
|
||||
ID: "endpoint-only", ResolvedTarget: &domain.ExecutionTarget{
|
||||
Endpoint: "https://profile.example/v1", Model: "profile-model", APIKeyEnv: "PROMPTKIT_INSPECTION_TEST_KEY",
|
||||
},
|
||||
}}
|
||||
inspection, err := (&Runner{profiles: profiles}).InspectProfile(context.Background(), "endpoint-only")
|
||||
if err != nil || inspection.EffectiveModelParams.APIKeyEnv != "PROMPTKIT_INSPECTION_TEST_KEY" || inspection.APIKeyRequired {
|
||||
t.Fatalf("unexpected endpoint-only inspection: (%+v, %v)", inspection, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerInspectProfileClassifiesLookupFailures(t *testing.T) {
|
||||
profiles := &inspectionProfileRepository{}
|
||||
runner := &Runner{profiles: profiles}
|
||||
|
||||
inspection, err := runner.InspectProfile(context.Background(), "endpoint-only")
|
||||
if err != nil {
|
||||
t.Fatalf("inspect endpoint-only profile: %v", err)
|
||||
if _, err := runner.InspectProfile(context.Background(), " \t "); !errors.Is(err, ErrInvalidRequest) || profiles.calls != 0 {
|
||||
t.Fatalf("blank inspection=(%v, calls=%d)", err, profiles.calls)
|
||||
}
|
||||
if inspection.EffectiveModelParams.BackendID != "" ||
|
||||
inspection.EffectiveModelParams.APIKeyEnv != "PROMPTKIT_INSPECTION_TEST_KEY" ||
|
||||
inspection.APIKeyRequired {
|
||||
t.Fatalf("unexpected endpoint-only inspection: %#v", inspection)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
if _, err := runner.InspectProfile(ctx, "profile"); !errors.Is(err, ErrProfileLoad) || !errors.Is(err, context.Canceled) || profiles.calls != 0 {
|
||||
t.Fatalf("canceled inspection=(%v, calls=%d)", err, profiles.calls)
|
||||
}
|
||||
profiles.err = profile.ErrProfileNotFound
|
||||
if _, err := runner.InspectProfile(context.Background(), "missing"); !errors.Is(err, ErrProfileLoad) || !errors.Is(err, profile.ErrProfileNotFound) {
|
||||
t.Fatalf("missing profile error=%v", err)
|
||||
}
|
||||
for _, invalid := range []*Runner{
|
||||
{},
|
||||
{profiles: &inspectionProfileRepository{}},
|
||||
{profiles: &inspectionProfileRepository{profile: &domain.ExecutionProfile{ID: "profile"}}},
|
||||
} {
|
||||
if _, err := invalid.InspectProfile(context.Background(), "profile"); !errors.Is(err, ErrProfileLoad) {
|
||||
t.Fatalf("invalid dependency error=%v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerInspectProfileDirectCredentialRequirementClearsBackendEnvironment(t *testing.T) {
|
||||
profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{
|
||||
ID: "direct-key",
|
||||
BackendID: "backend",
|
||||
Model: "profile-model",
|
||||
APIKeyRequired: true,
|
||||
}}
|
||||
backends := &inspectionBackendResolver{backend: domain.Backend{
|
||||
ID: "backend",
|
||||
Endpoint: "https://backend.example/v1",
|
||||
APIKeyEnv: "BACKEND_KEY",
|
||||
}}
|
||||
|
||||
inspection, err := (&Runner{profiles: profiles, backends: backends}).InspectProfile(
|
||||
context.Background(),
|
||||
"direct-key",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("inspect direct-key profile: %v", err)
|
||||
}
|
||||
if !inspection.APIKeyRequired || inspection.EffectiveModelParams.APIKeyEnv != "" {
|
||||
t.Fatalf("credential requirement was not resolved exclusively: %#v", inspection)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerInspectProfileClassifiesFailuresWithoutRepositoryWorkAfterCancellation(t *testing.T) {
|
||||
t.Run("blank ID", func(t *testing.T) {
|
||||
profiles := &inspectionProfileRepository{}
|
||||
_, err := (&Runner{profiles: profiles}).InspectProfile(context.Background(), " \t ")
|
||||
if !errors.Is(err, ErrInvalidRequest) || profiles.calls != 0 {
|
||||
t.Fatalf("blank inspection=(%v, calls=%d), want invalid request without lookup", err, profiles.calls)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("canceled context", func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
profiles := &inspectionProfileRepository{}
|
||||
_, err := (&Runner{profiles: profiles}).InspectProfile(ctx, "profile")
|
||||
if !errors.Is(err, ErrProfileLoad) || !errors.Is(err, context.Canceled) || profiles.calls != 0 {
|
||||
t.Fatalf("canceled inspection=(%v, calls=%d), want profile load and context identities without lookup", err, profiles.calls)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing profile", func(t *testing.T) {
|
||||
profiles := &inspectionProfileRepository{err: profile.ErrProfileNotFound}
|
||||
_, err := (&Runner{profiles: profiles}).InspectProfile(context.Background(), "missing")
|
||||
if !errors.Is(err, ErrProfileLoad) || !errors.Is(err, profile.ErrProfileNotFound) {
|
||||
t.Fatalf("missing profile error=%v, want profile load and not-found identities", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unknown backend", func(t *testing.T) {
|
||||
backendErr := errors.New("unknown backend")
|
||||
profiles := &inspectionProfileRepository{profile: &domain.ExecutionProfile{
|
||||
ID: "profile", BackendID: "backend", Model: "profile-model",
|
||||
}}
|
||||
backends := &inspectionBackendResolver{err: backendErr}
|
||||
_, err := (&Runner{profiles: profiles, backends: backends}).InspectProfile(context.Background(), "profile")
|
||||
if !errors.Is(err, ErrProfileLoad) || !errors.Is(err, backendErr) {
|
||||
t.Fatalf("unknown backend error=%v, want profile load and backend identities", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("defensive invalid dependencies", func(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
runner *Runner
|
||||
}{
|
||||
{name: "nil repository", runner: &Runner{}},
|
||||
{name: "nil profile", runner: &Runner{profiles: &inspectionProfileRepository{}}},
|
||||
{name: "invalid target", runner: &Runner{profiles: &inspectionProfileRepository{
|
||||
profile: &domain.ExecutionProfile{ID: "profile", Endpoint: "https://profile.example/v1"},
|
||||
}}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := tc.runner.InspectProfile(context.Background(), "profile")
|
||||
if !errors.Is(err, ErrProfileLoad) {
|
||||
t.Fatalf("inspection error=%v, want profile load", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ func TestRunnerPrepareUsesThePromptInspectionSelectionAndHash(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
repository,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
&fakeArtifactReader{},
|
||||
&fakeRenderer{rendered: &domain.RenderedPrompt{Messages: []domain.RenderedMessage{{Role: "user", Content: "hello"}}}},
|
||||
nil,
|
||||
|
||||
@@ -44,7 +44,6 @@ var (
|
||||
type Runner struct {
|
||||
promptDefs promptdef.Repository
|
||||
profiles profile.Repository
|
||||
backends BackendResolver
|
||||
artifacts artifact.Reader
|
||||
renderer prompt.Renderer
|
||||
llm llm.Client
|
||||
@@ -54,11 +53,6 @@ type Runner struct {
|
||||
defaultTransientRetries uint64
|
||||
}
|
||||
|
||||
// BackendResolver resolves one normalized backend ID.
|
||||
type BackendResolver interface {
|
||||
GetBackend(string) (domain.Backend, error)
|
||||
}
|
||||
|
||||
// RunAdmitter reserves capacity for one resolved backend run.
|
||||
type RunAdmitter interface {
|
||||
Admit(context.Context, string) (func(), error)
|
||||
@@ -84,7 +78,6 @@ type preparedOperation struct {
|
||||
func NewRunner(
|
||||
promptDefs promptdef.Repository,
|
||||
profiles profile.Repository,
|
||||
backends BackendResolver,
|
||||
artifacts artifact.Reader,
|
||||
renderer prompt.Renderer,
|
||||
llmClient llm.Client,
|
||||
@@ -94,7 +87,6 @@ func NewRunner(
|
||||
return NewRunnerWithRepairer(
|
||||
promptDefs,
|
||||
profiles,
|
||||
backends,
|
||||
artifacts,
|
||||
renderer,
|
||||
llmClient,
|
||||
@@ -107,7 +99,6 @@ func NewRunner(
|
||||
func NewRunnerWithRepairer(
|
||||
promptDefs promptdef.Repository,
|
||||
profiles profile.Repository,
|
||||
backends BackendResolver,
|
||||
artifacts artifact.Reader,
|
||||
renderer prompt.Renderer,
|
||||
llmClient llm.Client,
|
||||
@@ -123,7 +114,6 @@ func NewRunnerWithRepairer(
|
||||
return &Runner{
|
||||
promptDefs: promptDefs,
|
||||
profiles: profiles,
|
||||
backends: backends,
|
||||
artifacts: artifacts,
|
||||
renderer: renderer,
|
||||
llm: llmClient,
|
||||
@@ -355,7 +345,7 @@ func (r *Runner) resolvePreparation(
|
||||
recorder.UpdateSelection(observation.Selection{PromptID: def.ID, PromptVersion: def.Version, ProfileID: selection.id})
|
||||
}
|
||||
|
||||
effectiveModel, targetPresence := resolveExecutionTarget(selection.backend, selection.profile, req.Execution)
|
||||
effectiveModel, targetPresence := resolveExecutionTarget(selection.profile, req.Execution)
|
||||
effectiveModel.APIKey = req.APIKey
|
||||
effectiveModel, err = normalizeResolvedExecutionTarget(effectiveModel)
|
||||
if err != nil {
|
||||
@@ -578,48 +568,6 @@ func (r *Runner) shouldAttemptRepair(contract domain.OutputContract, validationR
|
||||
}
|
||||
}
|
||||
|
||||
func mergeExecutionTarget(base domain.ExecutionTarget, override domain.ExecutionTarget) domain.ExecutionTarget {
|
||||
out := base
|
||||
if strings.TrimSpace(override.BackendID) != "" {
|
||||
out.BackendID = override.BackendID
|
||||
}
|
||||
if strings.TrimSpace(override.Endpoint) != "" {
|
||||
out.Endpoint = override.Endpoint
|
||||
}
|
||||
if override.Model != "" {
|
||||
out.Model = override.Model
|
||||
}
|
||||
if override.Temperature != 0 {
|
||||
out.Temperature = override.Temperature
|
||||
}
|
||||
if override.MaxTokens != 0 {
|
||||
out.MaxTokens = override.MaxTokens
|
||||
}
|
||||
if override.TopP != 0 {
|
||||
out.TopP = override.TopP
|
||||
}
|
||||
if override.TimeoutSeconds != 0 {
|
||||
out.TimeoutSeconds = override.TimeoutSeconds
|
||||
}
|
||||
if strings.TrimSpace(override.ServiceTier) != "" {
|
||||
out.ServiceTier = override.ServiceTier
|
||||
}
|
||||
if strings.TrimSpace(override.ReasoningEffort) != "" {
|
||||
out.ReasoningEffort = override.ReasoningEffort
|
||||
}
|
||||
if strings.TrimSpace(override.APIKeyEnv) != "" {
|
||||
out.APIKeyEnv = override.APIKeyEnv
|
||||
}
|
||||
if override.APIKeyRequired {
|
||||
out.APIKeyRequired = true
|
||||
out.APIKeyEnv = ""
|
||||
}
|
||||
if len(override.ExtraParams) > 0 {
|
||||
out.ExtraParams = copyExtraParams(override.ExtraParams)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func mergeExecutionTargetOverride(base domain.ExecutionTarget, presence domain.ExecutionTargetPresence, override domain.ExecutionTargetOverride) (domain.ExecutionTarget, domain.ExecutionTargetPresence) {
|
||||
out := base
|
||||
if override.Endpoint != "" {
|
||||
@@ -659,22 +607,12 @@ func mergeExecutionTargetOverride(base domain.ExecutionTarget, presence domain.E
|
||||
return out, presence
|
||||
}
|
||||
|
||||
func resolveExecutionTarget(backendValue *domain.Backend, profileValue *domain.ExecutionProfile, override *domain.ExecutionTargetOverride) (domain.ExecutionTarget, domain.ExecutionTargetPresence) {
|
||||
if profileValue != nil && profileValue.ResolvedTarget != nil {
|
||||
out := *profileValue.ResolvedTarget
|
||||
out.ExtraParams = copyExtraParams(out.ExtraParams)
|
||||
presence := profileValue.TargetPresence
|
||||
if override != nil {
|
||||
return mergeExecutionTargetOverride(out, presence, *override)
|
||||
}
|
||||
return out, presence
|
||||
}
|
||||
out := defaults.ExecutionTargetDefault()
|
||||
out = mergeExecutionTarget(out, backendToTarget(backendValue))
|
||||
out = mergeExecutionTarget(out, executionProfileToTarget(profileValue))
|
||||
var presence domain.ExecutionTargetPresence
|
||||
func resolveExecutionTarget(profileValue *domain.ExecutionProfile, override *domain.ExecutionTargetOverride) (domain.ExecutionTarget, domain.ExecutionTargetPresence) {
|
||||
out := *profileValue.ResolvedTarget
|
||||
out.ExtraParams = copyExtraParams(out.ExtraParams)
|
||||
presence := profileValue.TargetPresence
|
||||
if override != nil {
|
||||
out, presence = mergeExecutionTargetOverride(out, presence, *override)
|
||||
return mergeExecutionTargetOverride(out, presence, *override)
|
||||
}
|
||||
return out, presence
|
||||
}
|
||||
@@ -696,42 +634,6 @@ func validateAPIKey(apiKeyEnv string, apiKey string, apiKeyRequired bool) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func executionProfileToTarget(p *domain.ExecutionProfile) domain.ExecutionTarget {
|
||||
if p == nil {
|
||||
return domain.ExecutionTarget{}
|
||||
}
|
||||
endpoint := p.Endpoint
|
||||
if strings.TrimSpace(endpoint) == "" {
|
||||
endpoint = ""
|
||||
}
|
||||
return domain.ExecutionTarget{
|
||||
BackendID: p.BackendID,
|
||||
Endpoint: endpoint,
|
||||
Model: p.Model,
|
||||
Temperature: p.Temperature,
|
||||
MaxTokens: p.MaxTokens,
|
||||
TopP: p.TopP,
|
||||
TimeoutSeconds: p.TimeoutSeconds,
|
||||
ServiceTier: p.ServiceTier,
|
||||
ReasoningEffort: p.ReasoningEffort,
|
||||
APIKeyEnv: p.APIKeyEnv,
|
||||
APIKeyRequired: p.APIKeyRequired,
|
||||
ExtraParams: copyExtraParams(p.ExtraParams),
|
||||
}
|
||||
}
|
||||
|
||||
func backendToTarget(value *domain.Backend) domain.ExecutionTarget {
|
||||
if value == nil {
|
||||
return domain.ExecutionTarget{}
|
||||
}
|
||||
return domain.ExecutionTarget{
|
||||
BackendID: value.ID,
|
||||
Endpoint: value.Endpoint,
|
||||
APIKeyEnv: value.APIKeyEnv,
|
||||
ExtraParams: copyExtraParams(value.ExtraParams),
|
||||
}
|
||||
}
|
||||
|
||||
func copyExtraParams(src map[string]any) map[string]any {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -20,9 +19,7 @@ import (
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/defaults"
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/llm"
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/profile"
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/prompt"
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/promptdef"
|
||||
"gitea.maximumdirect.net/eric/promptkit/internal/validate"
|
||||
)
|
||||
|
||||
@@ -39,10 +36,6 @@ type fakeExecutionProfileRepo struct {
|
||||
lastID string
|
||||
}
|
||||
|
||||
type fakeBackendResolver struct {
|
||||
backends map[string]domain.Backend
|
||||
}
|
||||
|
||||
type staticPromptRepo struct {
|
||||
def *domain.PromptDefinition
|
||||
}
|
||||
@@ -89,14 +82,6 @@ func (staticRenderer) Render(
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f fakeBackendResolver) GetBackend(id string) (domain.Backend, error) {
|
||||
value, ok := f.backends[id]
|
||||
if !ok {
|
||||
return domain.Backend{}, errors.New("backend not found")
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (f *fakeExecutionProfileRepo) GetProfile(ctx context.Context, id string) (*domain.ExecutionProfile, error) {
|
||||
f.lastID = id
|
||||
if f.err != nil {
|
||||
@@ -371,7 +356,7 @@ func TestRunnerPrepareWithExplicitProfileSelection(t *testing.T) {
|
||||
renderer := &fakeRenderer{rendered: &domain.RenderedPrompt{SessionID: "session-123", Messages: []domain.RenderedMessage{{Role: "system", Content: "sys"}, {Role: "user", Content: "usr"}}}}
|
||||
llmClient := &fakeLLM{forbid: true}
|
||||
|
||||
runner := NewRunner(promptRepo, execRepo, nil, reader, renderer, llmClient, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, reader, renderer, llmClient, nil, nil)
|
||||
prepared, err := runner.Prepare(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
PromptVersion: "1",
|
||||
@@ -422,7 +407,7 @@ func TestRunnerDirectSessionResolution(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
promptRepo,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
prompt.NewGoRenderer(),
|
||||
&fakeLLM{forbid: true},
|
||||
@@ -469,7 +454,7 @@ func TestRunnerDirectSessionResolution(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: def},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
prompt.NewGoRenderer(),
|
||||
&fakeLLM{forbid: true},
|
||||
@@ -499,7 +484,7 @@ func TestRunnerDirectSessionResolution(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: def},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
prompt.NewGoRenderer(),
|
||||
&fakeLLM{forbid: true},
|
||||
@@ -526,7 +511,7 @@ func TestRunnerDirectSessionResolution(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
promptRepo,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
llmClient,
|
||||
@@ -555,7 +540,7 @@ func TestRunnerDirectSessionResolution(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
promptRepo,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
llmClient,
|
||||
@@ -583,7 +568,7 @@ func TestRunnerPrepareUsesPromptDefaultProfileWhenNoExplicitProfileID(t *testing
|
||||
promptRepo := &fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)}
|
||||
promptRepo.def.DefaultProfile = "from-prompt"
|
||||
execRepo := &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"from-prompt": {ID: "from-prompt", Endpoint: "http://llm/v1", Model: "m"},
|
||||
"from-prompt": {ID: "from-prompt", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m", TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
|
||||
runner := newMinimalRunner(promptRepo, execRepo)
|
||||
@@ -626,7 +611,7 @@ func TestRunnerPrepareSelectedProfileDoesNotExistFails(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRunnerPreparePromptLoadFailure(t *testing.T) {
|
||||
runner := NewRunner(&fakePromptRepo{err: errors.New("boom")}, &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil, defaultArtifactReader(), defaultRenderer(), &fakeLLM{}, nil, nil)
|
||||
runner := NewRunner(&fakePromptRepo{err: errors.New("boom")}, &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, defaultArtifactReader(), defaultRenderer(), &fakeLLM{}, nil, nil)
|
||||
_, err := runner.Prepare(context.Background(), domain.RunRequest{PromptID: "p"})
|
||||
if !errors.Is(err, ErrPromptLoad) {
|
||||
t.Fatalf("expected ErrPromptLoad, got %v", err)
|
||||
@@ -640,17 +625,16 @@ func TestRunnerPrepareRuntimeOverrideBeatsSelectedProfileValue(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",
|
||||
Temperature: 0.2,
|
||||
MaxTokens: 500,
|
||||
TopP: 0.9,
|
||||
TimeoutSeconds: 120,
|
||||
ServiceTier: "priority",
|
||||
ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1",
|
||||
Model: "profile-model",
|
||||
Temperature: 0.2,
|
||||
MaxTokens: 500,
|
||||
TopP: 0.9,
|
||||
TimeoutSeconds: 120,
|
||||
ServiceTier: "priority"},
|
||||
},
|
||||
}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
|
||||
prepared, err := runner.Prepare(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
@@ -740,15 +724,14 @@ func TestRunnerPrepareRequestNumericOverridePresence(t *testing.T) {
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"exec": {
|
||||
ID: "exec",
|
||||
Endpoint: "http://profile/v1",
|
||||
Model: "profile-model",
|
||||
Temperature: 0.7,
|
||||
MaxTokens: 321,
|
||||
TopP: 0.8,
|
||||
TimeoutSeconds: 45,
|
||||
ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1",
|
||||
Model: "profile-model",
|
||||
Temperature: 0.7,
|
||||
MaxTokens: 321,
|
||||
TopP: 0.8,
|
||||
TimeoutSeconds: 45},
|
||||
},
|
||||
}}, nil,
|
||||
}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
@@ -802,7 +785,7 @@ func TestRunnerPrepareInvalidRequestNumericOverridesFail(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
@@ -826,15 +809,14 @@ func TestRunnerPrepareSelectedProfileBeatsBuiltInDefault(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",
|
||||
TopP: 0.8,
|
||||
TimeoutSeconds: 90,
|
||||
ServiceTier: "priority",
|
||||
ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1",
|
||||
Model: "profile-model",
|
||||
TopP: 0.8,
|
||||
TimeoutSeconds: 90,
|
||||
ServiceTier: "priority"},
|
||||
},
|
||||
}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
|
||||
prepared, err := runner.Prepare(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
@@ -855,54 +837,13 @@ func TestRunnerPrepareSelectedProfileBeatsBuiltInDefault(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerPrepareFileBackedPromptBodiesRenderCorrectly(t *testing.T) {
|
||||
promptDir := filepath.Join("..", "promptdef", "testdata")
|
||||
profileDir := filepath.Join("..", "profile", "testdata")
|
||||
|
||||
reader := &fakeArtifactReader{
|
||||
artifactsByURI: map[string]*domain.Artifact{
|
||||
"a://transcript": {
|
||||
Name: "transcript",
|
||||
Body: []byte("Session transcript body."),
|
||||
Hash: hashString("Session transcript body."),
|
||||
},
|
||||
},
|
||||
}
|
||||
llmClient := &fakeLLM{forbid: true}
|
||||
runner := NewRunner(
|
||||
promptdef.NewFilesystemRepository(promptDir),
|
||||
profile.NewFilesystemRepository(profileDir), nil,
|
||||
|
||||
reader,
|
||||
prompt.NewGoRenderer(),
|
||||
llmClient,
|
||||
nil, nil)
|
||||
|
||||
prepared, err := runner.Prepare(context.Background(), domain.RunRequest{
|
||||
PromptID: "valid-file-backed",
|
||||
ProfileID: "local-default",
|
||||
Inputs: map[string]domain.ArtifactRef{
|
||||
"transcript": {Type: domain.ArtifactRefFile, URI: "a://transcript"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if len(prepared.Messages) != 2 {
|
||||
t.Fatalf("expected two rendered messages, got %d", len(prepared.Messages))
|
||||
}
|
||||
if !strings.Contains(prepared.Messages[1].Content, "Session transcript body.") {
|
||||
t.Fatalf("expected file-backed template content to render input, got %q", prepared.Messages[1].Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerPrepareRequiredInputMissingFails(t *testing.T) {
|
||||
def := promptDef(domain.FormatText, domain.ValidationNone, 0)
|
||||
def.Templates = []domain.PromptMessageTemplate{{Role: "user", Content: `{{input "transcript"}}`}}
|
||||
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: def},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
prompt.NewGoRenderer(),
|
||||
@@ -929,7 +870,7 @@ func TestRunnerPrepareUnknownTemplateInputReferenceFails(t *testing.T) {
|
||||
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: def},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
prompt.NewGoRenderer(),
|
||||
@@ -955,9 +896,9 @@ func TestRunnerPrepareAPIKeyEnvNameIncludedButNotResolvedValue(t *testing.T) {
|
||||
t.Setenv(envName, secret)
|
||||
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", APIKeyEnv: envName},
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1", Model: "profile-model", APIKeyEnv: envName, TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
|
||||
prepared, err := runner.Prepare(context.Background(), domain.RunRequest{PromptID: "p", ProfileID: "exec", Inputs: singleInputRef()})
|
||||
if err != nil {
|
||||
@@ -985,7 +926,7 @@ func TestRunnerPrepareJSONSchemaBuildsStructuredOutputSpec(t *testing.T) {
|
||||
}
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: def},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
@@ -1029,7 +970,7 @@ func TestRunnerPrepareJSONSchemaSchemaLoadFailureReturnsValidationError(t *testi
|
||||
validator := &fakeValidator{schemaErr: errors.New("schema unavailable")}
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: def},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
@@ -1059,7 +1000,7 @@ func TestRunnerRunJSONSchemaSchemaLoadFailureFailsBeforeLLM(t *testing.T) {
|
||||
validator := &fakeValidator{schemaErr: errors.New("schema unavailable")}
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: def},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
@@ -1243,7 +1184,7 @@ func TestRunnerComposesAppendedMessagesIntoEveryRenderedPrompt(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
renderer,
|
||||
client,
|
||||
@@ -1310,7 +1251,7 @@ func TestRunnerRunSuccessful(t *testing.T) {
|
||||
renderer := &fakeRenderer{rendered: &domain.RenderedPrompt{SessionID: "session-123", Messages: []domain.RenderedMessage{{Role: "system", Content: "sys"}, {Role: "user", Content: "usr"}}}}
|
||||
llmClient := &fakeLLM{resp: &domain.GenerateResponse{Content: "# recap", Usage: domain.TokenUsage{TotalTokens: 7}}}
|
||||
|
||||
runner := NewRunner(promptRepo, execRepo, nil, reader, renderer, llmClient, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, reader, renderer, llmClient, nil, nil)
|
||||
res, err := runner.Run(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
PromptVersion: "1",
|
||||
@@ -1385,14 +1326,13 @@ func TestRunnerRunPassesExtraParamsToGenerateRequestTarget(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",
|
||||
ExtraParams: extraParams,
|
||||
ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1",
|
||||
Model: "profile-model",
|
||||
ExtraParams: extraParams, TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds},
|
||||
},
|
||||
}}
|
||||
llmClient := &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
|
||||
res, err := runner.Run(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
@@ -1418,7 +1358,7 @@ func TestRunnerRunAndPrepareResolveSameProfileAndEffectiveSettings(t *testing.T)
|
||||
}}
|
||||
renderer := &fakeRenderer{rendered: &domain.RenderedPrompt{Messages: []domain.RenderedMessage{{Role: "system", Content: "sys"}, {Role: "user", Content: "usr"}}}}
|
||||
llmClient := &fakeLLM{resp: &domain.GenerateResponse{Content: "# recap"}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, reader, renderer, llmClient, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, reader, renderer, llmClient, nil, nil)
|
||||
|
||||
req := domain.RunRequest{
|
||||
PromptID: "p",
|
||||
@@ -1463,14 +1403,11 @@ func TestRunnerAdmissionUsesResolvedBackendIdentity(t *testing.T) {
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"exec": {
|
||||
ID: "exec",
|
||||
BackendID: "custom",
|
||||
Model: "model",
|
||||
ID: "exec", ResolvedTarget: &domain.ExecutionTarget{BackendID: "custom",
|
||||
Model: "model", TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds},
|
||||
},
|
||||
}},
|
||||
fakeBackendResolver{backends: map[string]domain.Backend{
|
||||
"custom": {ID: "custom", Endpoint: "http://backend.example/v1"},
|
||||
}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
&fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}},
|
||||
@@ -1505,7 +1442,7 @@ func TestRunnerAdmissionUsesResolvedBackendIdentity(t *testing.T) {
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"exec": defaultExecutionProfile(),
|
||||
}},
|
||||
nil,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
&fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}},
|
||||
@@ -1565,11 +1502,9 @@ func TestRunnerAdmissionFailureSkipsCompletionCollaborators(t *testing.T) {
|
||||
runner := NewRunnerWithRepairer(
|
||||
&fakePromptRepo{def: def},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"exec": {ID: "exec", BackendID: "custom", Model: "model"},
|
||||
}},
|
||||
fakeBackendResolver{backends: map[string]domain.Backend{
|
||||
"custom": {ID: "custom", Endpoint: "http://backend.example/v1"},
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{BackendID: "custom", Endpoint: "http://backend.example/v1", Model: "model"}},
|
||||
}},
|
||||
|
||||
reader,
|
||||
renderer,
|
||||
llmClient,
|
||||
@@ -1681,7 +1616,7 @@ func TestRunnerReleasesAdmissionAcrossRunOutcomes(t *testing.T) {
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"exec": defaultExecutionProfile(),
|
||||
}},
|
||||
nil,
|
||||
|
||||
reader,
|
||||
defaultRenderer(),
|
||||
llmClient,
|
||||
@@ -1715,8 +1650,8 @@ func TestRunnerRunExplicitProfileIDIsUsed(t *testing.T) {
|
||||
promptRepo := &fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)}
|
||||
promptRepo.def.DefaultProfile = "default-prof"
|
||||
execRepo := &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"explicit-prof": {ID: "explicit-prof", Endpoint: "http://explicit/v1", Model: "explicit"},
|
||||
"default-prof": {ID: "default-prof", Endpoint: "http://default/v1", Model: "default"},
|
||||
"explicit-prof": {ID: "explicit-prof", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://explicit/v1", Model: "explicit", TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
"default-prof": {ID: "default-prof", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://default/v1", Model: "default", TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
|
||||
runner := newMinimalRunner(promptRepo, execRepo)
|
||||
@@ -1740,7 +1675,7 @@ func TestRunnerRunPromptDefaultProfileIsUsedWhenNoExplicitProfileID(t *testing.T
|
||||
promptRepo := &fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)}
|
||||
promptRepo.def.DefaultProfile = "from-prompt"
|
||||
execRepo := &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"from-prompt": {ID: "from-prompt", Endpoint: "http://llm/v1", Model: "m"},
|
||||
"from-prompt": {ID: "from-prompt", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://llm/v1", Model: "m", TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
|
||||
runner := newMinimalRunner(promptRepo, execRepo)
|
||||
@@ -1792,18 +1727,17 @@ func TestRunnerRunExplicitRuntimeOverrideBeatsSelectedProfileValue(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",
|
||||
Temperature: 0.2,
|
||||
MaxTokens: 500,
|
||||
TopP: 0.9,
|
||||
TimeoutSeconds: 120,
|
||||
ServiceTier: "priority",
|
||||
ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1",
|
||||
Model: "profile-model",
|
||||
Temperature: 0.2,
|
||||
MaxTokens: 500,
|
||||
TopP: 0.9,
|
||||
TimeoutSeconds: 120,
|
||||
ServiceTier: "priority"},
|
||||
},
|
||||
}}
|
||||
llmClient := &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
|
||||
res, err := runner.Run(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
@@ -1838,16 +1772,15 @@ func TestRunnerRunSelectedProfileBeatsBuiltInDefault(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",
|
||||
TopP: 0.8,
|
||||
TimeoutSeconds: 90,
|
||||
ServiceTier: "priority",
|
||||
ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1",
|
||||
Model: "profile-model",
|
||||
TopP: 0.8,
|
||||
TimeoutSeconds: 90,
|
||||
ServiceTier: "priority"},
|
||||
},
|
||||
}}
|
||||
llmClient := &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
|
||||
res, err := runner.Run(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
@@ -1872,9 +1805,9 @@ func TestRunnerRunAPIKeyEnvResolvesFromEnvironment(t *testing.T) {
|
||||
t.Setenv("PROMPTKIT_TEST_API_KEY", "secret")
|
||||
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", APIKeyEnv: "PROMPTKIT_TEST_API_KEY"},
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1", Model: "profile-model", APIKeyEnv: "PROMPTKIT_TEST_API_KEY", TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}, nil, nil)
|
||||
|
||||
res, err := runner.Run(context.Background(), domain.RunRequest{PromptID: "p", ProfileID: "exec", Inputs: singleInputRef()})
|
||||
if err != nil {
|
||||
@@ -1891,10 +1824,10 @@ func TestRunnerRunOptionalAPIKeyEnvMissingEnvironmentValueReachesLLM(t *testing.
|
||||
|
||||
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", APIKeyEnv: environmentName},
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1", Model: "profile-model", APIKeyEnv: environmentName, TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
llmClient := &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
|
||||
result, err := runner.Run(context.Background(), domain.RunRequest{PromptID: "p", ProfileID: "exec", Inputs: singleInputRef()})
|
||||
if err != nil || result == nil {
|
||||
@@ -1912,10 +1845,10 @@ func TestRunnerRunDirectAPIKeyBypassesMissingEnvAndReachesLLM(t *testing.T) {
|
||||
const directKey = "direct-runner-key"
|
||||
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", APIKeyEnv: "PROMPTKIT_MISSING_KEY"},
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1", Model: "profile-model", APIKeyEnv: "PROMPTKIT_MISSING_KEY", TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
llmClient := &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
|
||||
result, err := runner.Run(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
@@ -1940,9 +1873,9 @@ func TestRunnerRunDirectAPIKeyBypassesMissingEnvAndReachesLLM(t *testing.T) {
|
||||
func TestRunnerPrepareAPIKeyRequiredFailsWithoutDirectKey(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", APIKeyRequired: true},
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1", Model: "profile-model", APIKeyRequired: true, TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}, nil, nil)
|
||||
|
||||
_, err := runner.Prepare(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
@@ -1958,10 +1891,10 @@ func TestRunnerRunAPIKeyRequiredSucceedsWithDirectKey(t *testing.T) {
|
||||
const directKey = "direct-required-key"
|
||||
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", APIKeyRequired: true},
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1", Model: "profile-model", APIKeyRequired: true, TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
llmClient := &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), llmClient, nil, nil)
|
||||
|
||||
_, err := runner.Run(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
@@ -1986,9 +1919,9 @@ func TestRunnerRunRuntimeAPIKeyEnvOverrideWorks(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"},
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1", Model: "profile-model", TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}, nil, nil)
|
||||
|
||||
res, err := runner.Run(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
@@ -2011,9 +1944,9 @@ func TestRunnerRunRuntimeAPIKeyEnvOverrideBeatsProfile(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", APIKeyEnv: profileEnv},
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1", Model: "profile-model", APIKeyEnv: profileEnv, TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}, nil, nil)
|
||||
|
||||
res, err := runner.Run(context.Background(), domain.RunRequest{
|
||||
PromptID: "p",
|
||||
@@ -2035,9 +1968,9 @@ func TestRunnerRunAPIKeyValueNotPresentInMetadata(t *testing.T) {
|
||||
t.Setenv(envName, secret)
|
||||
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", APIKeyEnv: envName},
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://profile/v1", Model: "profile-model", APIKeyEnv: envName, TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds}},
|
||||
}}
|
||||
runner := NewRunner(promptRepo, execRepo, nil, defaultArtifactReader(), defaultRenderer(), &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}, nil, nil)
|
||||
runner := NewRunner(promptRepo, execRepo, defaultArtifactReader(), defaultRenderer(), &fakeLLM{resp: &domain.GenerateResponse{Content: "ok"}}, nil, nil)
|
||||
|
||||
res, err := runner.Run(context.Background(), domain.RunRequest{PromptID: "p", ProfileID: "exec", Inputs: singleInputRef()})
|
||||
if err != nil {
|
||||
@@ -2053,7 +1986,7 @@ func TestRunnerRunAPIKeyValueNotPresentInMetadata(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRunnerRunPromptLoadFailure(t *testing.T) {
|
||||
runner := NewRunner(&fakePromptRepo{err: errors.New("boom")}, &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil, defaultArtifactReader(), defaultRenderer(), &fakeLLM{}, nil, nil)
|
||||
runner := NewRunner(&fakePromptRepo{err: errors.New("boom")}, &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, defaultArtifactReader(), defaultRenderer(), &fakeLLM{}, nil, nil)
|
||||
_, err := runner.Run(context.Background(), domain.RunRequest{PromptID: "p"})
|
||||
if !errors.Is(err, ErrPromptLoad) {
|
||||
t.Fatalf("expected ErrPromptLoad, got %v", err)
|
||||
@@ -2066,7 +1999,7 @@ func TestRunnerRunPromptLoadFailure(t *testing.T) {
|
||||
func TestRunnerRunArtifactLoadFailure(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
&fakeArtifactReader{errByURI: map[string]error{"a://bad": errors.New("read failed")}},
|
||||
&fakeRenderer{rendered: &domain.RenderedPrompt{}},
|
||||
@@ -2086,7 +2019,7 @@ func TestRunnerRunArtifactLoadFailure(t *testing.T) {
|
||||
func TestRunnerRunPromptRenderFailure(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
&fakeRenderer{err: errors.New("render failed")},
|
||||
@@ -2106,7 +2039,7 @@ func TestRunnerRunPromptRenderFailure(t *testing.T) {
|
||||
func TestRunnerRunLLMFailure(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
@@ -2126,7 +2059,7 @@ func TestRunnerRunLLMFailure(t *testing.T) {
|
||||
func TestRunnerRunCancellationPreservesGenerationCategory(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
@@ -2152,7 +2085,7 @@ func TestRunnerRunCancellationPreservesGenerationCategory(t *testing.T) {
|
||||
func TestRunnerRunLLMInvalidRequestMapsToUsecaseInvalidRequest(t *testing.T) {
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
@@ -2176,7 +2109,7 @@ func TestRunnerRunValidationStillWorks(t *testing.T) {
|
||||
validator := &fakeValidator{result: domain.ValidationResult{Status: domain.ValidationFailed, Mode: domain.ValidationBasic, Errors: []string{"bad"}, IsValid: false}}
|
||||
runner := NewRunner(
|
||||
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationBasic, 0)},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}}, nil,
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{"exec": defaultExecutionProfile()}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
@@ -2343,11 +2276,9 @@ func TestRunnerRepairStateMachine(t *testing.T) {
|
||||
runner := NewRunnerWithRepairer(
|
||||
&fakePromptRepo{def: definition},
|
||||
&fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"exec": {ID: "exec", BackendID: "custom", Model: "profile-model"},
|
||||
}},
|
||||
fakeBackendResolver{backends: map[string]domain.Backend{
|
||||
"custom": {ID: "custom", Endpoint: "http://backend.example/v1"},
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{BackendID: "custom", Endpoint: "http://backend.example/v1", Model: "profile-model"}},
|
||||
}},
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
client,
|
||||
@@ -2483,11 +2414,9 @@ func TestRunnerSchedulesInitialAndRepairGenerationThroughOneBackendPool(t *testi
|
||||
runner := NewRunnerWithRepairer(
|
||||
staticPromptRepo{def: promptDef(domain.FormatJSON, domain.ValidationJSON, 1)},
|
||||
staticExecutionProfileRepo{profile: &domain.ExecutionProfile{
|
||||
ID: "exec", BackendID: "custom", Model: "model",
|
||||
}},
|
||||
fakeBackendResolver{backends: map[string]domain.Backend{
|
||||
"custom": {ID: "custom", Endpoint: "http://backend.example/v1"},
|
||||
ID: "exec", ResolvedTarget: &domain.ExecutionTarget{BackendID: "custom", Endpoint: "http://backend.example/v1", Model: "model"},
|
||||
}},
|
||||
|
||||
staticArtifactReader{},
|
||||
staticRenderer{},
|
||||
scheduledClient,
|
||||
@@ -2556,220 +2485,6 @@ func TestRunnerSchedulesInitialAndRepairGenerationThroughOneBackendPool(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutionProfileToTargetPopulatesAllFieldsAndCopiesExtraParams(t *testing.T) {
|
||||
src := &domain.ExecutionProfile{
|
||||
ID: "exec",
|
||||
Endpoint: "http://profile/v1",
|
||||
Model: "profile-model",
|
||||
Temperature: 0.2,
|
||||
MaxTokens: 123,
|
||||
TopP: 0.75,
|
||||
TimeoutSeconds: 90,
|
||||
ServiceTier: "priority",
|
||||
ReasoningEffort: "medium",
|
||||
APIKeyEnv: "PROMPTKIT_API_KEY",
|
||||
APIKeyRequired: true,
|
||||
ExtraParams: map[string]any{
|
||||
"provider_option": "on",
|
||||
},
|
||||
}
|
||||
|
||||
target := executionProfileToTarget(src)
|
||||
if target.Endpoint != src.Endpoint ||
|
||||
target.Model != src.Model ||
|
||||
target.Temperature != src.Temperature ||
|
||||
target.MaxTokens != src.MaxTokens ||
|
||||
target.TopP != src.TopP ||
|
||||
target.TimeoutSeconds != src.TimeoutSeconds ||
|
||||
target.ServiceTier != src.ServiceTier ||
|
||||
target.ReasoningEffort != src.ReasoningEffort ||
|
||||
target.APIKeyEnv != src.APIKeyEnv ||
|
||||
target.APIKeyRequired != src.APIKeyRequired {
|
||||
t.Fatalf("expected all profile fields to populate target, got %+v", target)
|
||||
}
|
||||
if !reflect.DeepEqual(target.ExtraParams, src.ExtraParams) {
|
||||
t.Fatalf("expected extra_params to match, got %#v", target.ExtraParams)
|
||||
}
|
||||
|
||||
src.ExtraParams["provider_option"] = "changed"
|
||||
if target.ExtraParams["provider_option"] != "on" {
|
||||
t.Fatalf("expected extra_params copy to be independent, got %#v", target.ExtraParams)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveExecutionTargetProfileValuesPopulateAllSupportedFields(t *testing.T) {
|
||||
profileValue := &domain.ExecutionProfile{
|
||||
ID: "exec",
|
||||
Endpoint: "http://profile/v1",
|
||||
Model: "profile-model",
|
||||
Temperature: 0.3,
|
||||
MaxTokens: 222,
|
||||
TopP: 0.6,
|
||||
TimeoutSeconds: 77,
|
||||
ServiceTier: "priority",
|
||||
ReasoningEffort: "low",
|
||||
APIKeyEnv: "PROFILE_KEY",
|
||||
ExtraParams: map[string]any{
|
||||
"profile_option": "enabled",
|
||||
},
|
||||
}
|
||||
|
||||
target, presence := resolveExecutionTarget(nil, profileValue, nil)
|
||||
if presence != (domain.ExecutionTargetPresence{}) {
|
||||
t.Fatalf("expected no request override presence, got %+v", presence)
|
||||
}
|
||||
if target.Endpoint != profileValue.Endpoint ||
|
||||
target.Model != profileValue.Model ||
|
||||
target.Temperature != profileValue.Temperature ||
|
||||
target.MaxTokens != profileValue.MaxTokens ||
|
||||
target.TopP != profileValue.TopP ||
|
||||
target.TimeoutSeconds != profileValue.TimeoutSeconds ||
|
||||
target.ServiceTier != profileValue.ServiceTier ||
|
||||
target.ReasoningEffort != profileValue.ReasoningEffort ||
|
||||
target.APIKeyEnv != profileValue.APIKeyEnv ||
|
||||
target.APIKeyRequired != profileValue.APIKeyRequired {
|
||||
t.Fatalf("expected profile values to populate target, got %+v", target)
|
||||
}
|
||||
if !reflect.DeepEqual(target.ExtraParams, profileValue.ExtraParams) {
|
||||
t.Fatalf("expected profile extra_params in target, got %#v", target.ExtraParams)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveExecutionTargetRuntimeOverridesBeatProfileForAllOverrideableFields(t *testing.T) {
|
||||
profileValue := &domain.ExecutionProfile{
|
||||
ID: "exec",
|
||||
Endpoint: "http://profile/v1",
|
||||
Model: "profile-model",
|
||||
Temperature: 0.2,
|
||||
MaxTokens: 200,
|
||||
TopP: 0.8,
|
||||
TimeoutSeconds: 90,
|
||||
ServiceTier: "priority",
|
||||
ReasoningEffort: "medium",
|
||||
APIKeyEnv: "PROFILE_KEY",
|
||||
ExtraParams: map[string]any{
|
||||
"profile_only": "yes",
|
||||
},
|
||||
}
|
||||
override := &domain.ExecutionTargetOverride{
|
||||
Endpoint: "http://override/v1",
|
||||
Model: "override-model",
|
||||
Temperature: float64Ptr(0.9),
|
||||
MaxTokens: intPtr(111),
|
||||
TopP: float64Ptr(0.5),
|
||||
TimeoutSeconds: intPtr(30),
|
||||
ServiceTier: "flex",
|
||||
ReasoningEffort: stringPtr("high"),
|
||||
APIKeyEnv: "RUNTIME_KEY",
|
||||
ExtraParams: map[string]any{
|
||||
"runtime_only": "yes",
|
||||
},
|
||||
}
|
||||
|
||||
target, presence := resolveExecutionTarget(nil, profileValue, override)
|
||||
if presence != (domain.ExecutionTargetPresence{Temperature: true, MaxTokens: true, TopP: true, TimeoutSeconds: true}) {
|
||||
t.Fatalf("unexpected override presence: %+v", presence)
|
||||
}
|
||||
if target.Endpoint != override.Endpoint ||
|
||||
target.Model != override.Model ||
|
||||
target.Temperature != *override.Temperature ||
|
||||
target.MaxTokens != *override.MaxTokens ||
|
||||
target.TopP != *override.TopP ||
|
||||
target.TimeoutSeconds != *override.TimeoutSeconds ||
|
||||
target.ServiceTier != override.ServiceTier ||
|
||||
target.ReasoningEffort != *override.ReasoningEffort ||
|
||||
target.APIKeyEnv != override.APIKeyEnv {
|
||||
t.Fatalf("expected runtime overrides to win for all fields, got %+v", target)
|
||||
}
|
||||
if !reflect.DeepEqual(target.ExtraParams, override.ExtraParams) {
|
||||
t.Fatalf("expected runtime extra_params to replace profile extra_params, got %#v", target.ExtraParams)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveExecutionTargetReasoningOverrideStates(t *testing.T) {
|
||||
profileValue := &domain.ExecutionProfile{
|
||||
ReasoningEffort: "medium",
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
override *string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "nil inherits profile value",
|
||||
want: "medium",
|
||||
},
|
||||
{
|
||||
name: "nonblank replaces and trims profile value",
|
||||
override: stringPtr(" high "),
|
||||
want: "high",
|
||||
},
|
||||
{
|
||||
name: "blank clears profile value",
|
||||
override: stringPtr(" \t "),
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
target, _ := resolveExecutionTarget(nil, profileValue, &domain.ExecutionTargetOverride{
|
||||
ReasoningEffort: tt.override,
|
||||
})
|
||||
if target.ReasoningEffort != tt.want {
|
||||
t.Fatalf("reasoning effort = %q, want %q", target.ReasoningEffort, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeExecutionTargetEmptyStringOverridesDoNotErase(t *testing.T) {
|
||||
base := domain.ExecutionTarget{
|
||||
Endpoint: "http://base/v1",
|
||||
Model: "base-model",
|
||||
ServiceTier: "priority",
|
||||
ReasoningEffort: "medium",
|
||||
APIKeyEnv: "BASE_KEY",
|
||||
}
|
||||
override := domain.ExecutionTarget{
|
||||
Endpoint: "http://override/v1",
|
||||
Model: "override-model",
|
||||
ServiceTier: " ",
|
||||
ReasoningEffort: " ",
|
||||
APIKeyEnv: "",
|
||||
}
|
||||
|
||||
merged := mergeExecutionTarget(base, override)
|
||||
if merged.Endpoint != "http://override/v1" || merged.Model != "override-model" {
|
||||
t.Fatalf("expected endpoint/model to override, got %+v", merged)
|
||||
}
|
||||
if merged.ServiceTier != "priority" {
|
||||
t.Fatalf("expected empty service_tier override to be ignored, got %q", merged.ServiceTier)
|
||||
}
|
||||
if merged.ReasoningEffort != "medium" {
|
||||
t.Fatalf("expected empty reasoning_effort override to be ignored, got %q", merged.ReasoningEffort)
|
||||
}
|
||||
if merged.APIKeyEnv != "BASE_KEY" {
|
||||
t.Fatalf("expected empty api_key_env override to be ignored, got %q", merged.APIKeyEnv)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeExecutionTargetEmptyExtraParamsDoesNotErase(t *testing.T) {
|
||||
base := domain.ExecutionTarget{
|
||||
ExtraParams: map[string]any{
|
||||
"keep": "value",
|
||||
},
|
||||
}
|
||||
override := domain.ExecutionTarget{
|
||||
ExtraParams: map[string]any{},
|
||||
}
|
||||
|
||||
merged := mergeExecutionTarget(base, override)
|
||||
if !reflect.DeepEqual(merged.ExtraParams, base.ExtraParams) {
|
||||
t.Fatalf("expected empty extra_params override not to erase base values, got %#v", merged.ExtraParams)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOutputArtifactDefaults(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -2817,87 +2532,19 @@ func hashString(s string) string {
|
||||
|
||||
func defaultExecutionProfile() *domain.ExecutionProfile {
|
||||
return &domain.ExecutionProfile{
|
||||
ID: "exec",
|
||||
Endpoint: "http://llm/v1",
|
||||
Model: "model-from-profile",
|
||||
ID: "exec", ResolvedTarget: &domain.ExecutionTarget{Endpoint: "http://llm/v1",
|
||||
Model: "model-from-profile", TimeoutSeconds: defaults.ExecutionDefaultTimeoutSeconds},
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveExecutionTargetUsesBackendProfileAndRequestPrecedence(t *testing.T) {
|
||||
backendValue := &domain.Backend{
|
||||
ID: "custom",
|
||||
Endpoint: "http://backend/v1",
|
||||
APIKeyEnv: "BACKEND_KEY",
|
||||
ExtraParams: map[string]any{"backend": true},
|
||||
}
|
||||
profileValue := &domain.ExecutionProfile{
|
||||
ID: "exec",
|
||||
BackendID: "custom",
|
||||
Endpoint: "http://profile/v1",
|
||||
Model: "profile-model",
|
||||
APIKeyEnv: "PROFILE_KEY",
|
||||
ExtraParams: map[string]any{"profile": true},
|
||||
}
|
||||
override := &domain.ExecutionTargetOverride{
|
||||
Endpoint: "http://request/v1",
|
||||
APIKeyEnv: "REQUEST_KEY",
|
||||
ExtraParams: map[string]any{"request": true},
|
||||
}
|
||||
|
||||
target, _ := resolveExecutionTarget(backendValue, profileValue, override)
|
||||
if target.BackendID != "custom" {
|
||||
t.Fatalf("endpoint override changed backend identity: %+v", target)
|
||||
}
|
||||
if target.Endpoint != "http://request/v1" || target.APIKeyEnv != "REQUEST_KEY" {
|
||||
t.Fatalf("request values did not win: %+v", target)
|
||||
}
|
||||
if !reflect.DeepEqual(target.ExtraParams, map[string]any{"request": true}) {
|
||||
t.Fatalf("expected whole-map request replacement, got %#v", target.ExtraParams)
|
||||
}
|
||||
|
||||
target, _ = resolveExecutionTarget(backendValue, &domain.ExecutionProfile{
|
||||
ID: "exec", BackendID: "custom", Model: "profile-model",
|
||||
}, nil)
|
||||
if target.Endpoint != backendValue.Endpoint ||
|
||||
target.APIKeyEnv != backendValue.APIKeyEnv ||
|
||||
!reflect.DeepEqual(target.ExtraParams, backendValue.ExtraParams) {
|
||||
t.Fatalf("backend defaults were not inherited: %+v", target)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerPrepareBackendResolutionAndCredentialPrecedence(t *testing.T) {
|
||||
resolver := fakeBackendResolver{backends: map[string]domain.Backend{
|
||||
"custom": {ID: "custom", Endpoint: "http://backend/v1", APIKeyEnv: "BACKEND_KEY"},
|
||||
}}
|
||||
func TestRunnerPrepareCredentialPrecedence(t *testing.T) {
|
||||
promptRepo := &fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationNone, 0)}
|
||||
|
||||
t.Run("unknown backend is a profile load failure", func(t *testing.T) {
|
||||
runner := NewRunner(promptRepo, &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"exec": {ID: "exec", BackendID: "unknown", Model: "model"},
|
||||
}}, resolver, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
|
||||
_, err := runner.Prepare(context.Background(), domain.RunRequest{PromptID: "p", ProfileID: "exec", Inputs: singleInputRef()})
|
||||
if !errors.Is(err, ErrProfileLoad) {
|
||||
t.Fatalf("expected ErrProfileLoad, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nil resolver is a profile load failure", func(t *testing.T) {
|
||||
runner := NewRunner(promptRepo, &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"exec": {ID: "exec", BackendID: "custom", Model: "model"},
|
||||
}}, nil, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
|
||||
_, err := runner.Prepare(context.Background(), domain.RunRequest{PromptID: "p", ProfileID: "exec", Inputs: singleInputRef()})
|
||||
if !errors.Is(err, ErrProfileLoad) {
|
||||
t.Fatalf("expected ErrProfileLoad, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("request environment wins", func(t *testing.T) {
|
||||
t.Setenv("REQUEST_KEY", "request-secret")
|
||||
runner := NewRunner(promptRepo, &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"exec": {ID: "exec", BackendID: "custom", Model: "model", APIKeyEnv: "PROFILE_KEY"},
|
||||
}}, resolver, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{BackendID: "custom", Endpoint: "http://backend/v1", Model: "model", APIKeyEnv: "PROFILE_KEY"}},
|
||||
}}, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
|
||||
prepared, err := runner.Prepare(context.Background(), domain.RunRequest{
|
||||
PromptID: "p", ProfileID: "exec", Inputs: singleInputRef(),
|
||||
@@ -2914,8 +2561,8 @@ func TestRunnerPrepareBackendResolutionAndCredentialPrecedence(t *testing.T) {
|
||||
t.Run("required direct key clears inherited environment", func(t *testing.T) {
|
||||
t.Setenv("BACKEND_KEY", "backend-secret")
|
||||
runner := NewRunner(promptRepo, &fakeExecutionProfileRepo{profiles: map[string]*domain.ExecutionProfile{
|
||||
"exec": {ID: "exec", BackendID: "custom", Model: "model", APIKeyRequired: true},
|
||||
}}, resolver, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
"exec": {ID: "exec", ResolvedTarget: &domain.ExecutionTarget{BackendID: "custom", Endpoint: "http://backend/v1", Model: "model", APIKeyRequired: true}},
|
||||
}}, defaultArtifactReader(), defaultRenderer(), &fakeLLM{forbid: true}, nil, nil)
|
||||
|
||||
_, err := runner.Prepare(context.Background(), domain.RunRequest{PromptID: "p", ProfileID: "exec", Inputs: singleInputRef()})
|
||||
if !errors.Is(err, ErrAPIKeyRequired) {
|
||||
@@ -2962,7 +2609,7 @@ func intPtr(v int) *int {
|
||||
func newMinimalRunner(promptRepo *fakePromptRepo, execRepo *fakeExecutionProfileRepo) *Runner {
|
||||
return NewRunner(
|
||||
promptRepo,
|
||||
execRepo, nil,
|
||||
execRepo,
|
||||
|
||||
defaultArtifactReader(),
|
||||
defaultRenderer(),
|
||||
|
||||
Reference in New Issue
Block a user