Add smoke diagnostic domain values
This commit is contained in:
@@ -242,6 +242,8 @@ Validate: `go test ./internal/domain ./internal/backend`. Add ownership tests
|
|||||||
only for mutable structures whose accidental sharing would affect calls.
|
only for mutable structures whose accidental sharing would affect calls.
|
||||||
No public `SmokeTest` method or temporary not-implemented API is introduced yet.
|
No public `SmokeTest` method or temporary not-implemented API is introduced yet.
|
||||||
|
|
||||||
|
**Completed: 2026-10-13.**
|
||||||
|
|
||||||
## Stage 2: Load Final Backend Diagnostic Metadata
|
## Stage 2: Load Final Backend Diagnostic Metadata
|
||||||
|
|
||||||
Scope: `internal/configuration`, `internal/backend`, and `backends.go`.
|
Scope: `internal/configuration`, `internal/backend`, and `backends.go`.
|
||||||
|
|||||||
@@ -180,12 +180,14 @@ type PromptMessageTemplate struct {
|
|||||||
CacheControl *CacheControl `yaml:"cache_control,omitempty" json:"cache_control,omitempty"`
|
CacheControl *CacheControl `yaml:"cache_control,omitempty" json:"cache_control,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backend describes reusable OpenAI-compatible connection defaults.
|
// Backend describes reusable OpenAI-compatible connection defaults and optional
|
||||||
|
// diagnostic metadata.
|
||||||
type Backend struct {
|
type Backend struct {
|
||||||
ID string
|
ID string
|
||||||
Endpoint string
|
Endpoint string
|
||||||
APIKeyEnv string
|
APIKeyEnv string
|
||||||
ExtraParams map[string]any
|
ExtraParams map[string]any
|
||||||
|
SmokeTests *BackendSmokeTestConfig
|
||||||
ConcurrencyLimit int
|
ConcurrencyLimit int
|
||||||
QueueCapacity int
|
QueueCapacity int
|
||||||
QueueCapacitySet bool
|
QueueCapacitySet bool
|
||||||
|
|||||||
263
internal/domain/smoke_diagnostics.go
Normal file
263
internal/domain/smoke_diagnostics.go
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/promptkit/internal/jsonvalue"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackendSmokeTestConfig describes optional backend-owned diagnostic defaults.
|
||||||
|
type BackendSmokeTestConfig struct {
|
||||||
|
DefaultProfile *string
|
||||||
|
Reachability *ReachabilityProbeConfig
|
||||||
|
Authentication *AuthenticationProbeConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReachabilityProbeConfig describes an optional endpoint for reachability tests.
|
||||||
|
type ReachabilityProbeConfig struct {
|
||||||
|
Endpoint *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthenticationProbeConfig describes an optional authentication diagnostic.
|
||||||
|
type AuthenticationProbeConfig struct {
|
||||||
|
Kind *string
|
||||||
|
Endpoint *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmokeTestKind identifies one built-in diagnostic operation.
|
||||||
|
type SmokeTestKind string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SmokeTestReachability SmokeTestKind = "reachability"
|
||||||
|
SmokeTestAuthentication SmokeTestKind = "authentication"
|
||||||
|
SmokeTestTextInference SmokeTestKind = "text_inference"
|
||||||
|
SmokeTestStructuredOutput SmokeTestKind = "structured_output"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SmokeTestRequest selects one diagnostic and its execution target.
|
||||||
|
type SmokeTestRequest struct {
|
||||||
|
Kind SmokeTestKind
|
||||||
|
BackendID string
|
||||||
|
PromptID string
|
||||||
|
PromptVersion string
|
||||||
|
ProfileID string
|
||||||
|
Execution *ExecutionTargetOverride
|
||||||
|
APIKey string `json:"-" yaml:"-"`
|
||||||
|
TransientRetries *int
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmokeTestReport contains factual diagnostic results, not generated content.
|
||||||
|
type SmokeTestReport struct {
|
||||||
|
Kind SmokeTestKind
|
||||||
|
Supported bool
|
||||||
|
Applicable *bool
|
||||||
|
Completed bool
|
||||||
|
Reason SmokeTestReason
|
||||||
|
Target SmokeTestTarget
|
||||||
|
Facts SmokeTestFacts
|
||||||
|
Timing SmokeTestTiming
|
||||||
|
Attempts []SmokeTestAttempt
|
||||||
|
Usage ObservedTokenUsage
|
||||||
|
Failure ExecutionFailureKind
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmokeTestTarget identifies the selected diagnostic target and its settings.
|
||||||
|
type SmokeTestTarget struct {
|
||||||
|
BackendID string
|
||||||
|
ProfileID string
|
||||||
|
PromptID string
|
||||||
|
PromptVersion string
|
||||||
|
DefaultSourceBackendID string
|
||||||
|
SelectionSource string
|
||||||
|
Endpoint string
|
||||||
|
EffectiveSettings ExecutionTarget
|
||||||
|
SettingsPresence ExecutionTargetPresence
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmokeTestFacts contains optional factual observations from a diagnostic.
|
||||||
|
type SmokeTestFacts struct {
|
||||||
|
CredentialsSupplied *bool
|
||||||
|
EndpointReachable *bool
|
||||||
|
CredentialsAccepted *bool
|
||||||
|
ResponseReceived *bool
|
||||||
|
GenerationCompleted *bool
|
||||||
|
TextReturned *bool
|
||||||
|
ExpectedTextMatched *bool
|
||||||
|
StructuredOutputRequested *bool
|
||||||
|
ValidJSON *bool
|
||||||
|
SchemaMatched *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmokeTestTiming records elapsed diagnostic and phase durations.
|
||||||
|
type SmokeTestTiming struct {
|
||||||
|
StartedAt time.Time
|
||||||
|
Duration time.Duration
|
||||||
|
CapacityWait *time.Duration
|
||||||
|
RequestDuration *time.Duration
|
||||||
|
RetryDelay *time.Duration
|
||||||
|
EvaluationDuration *time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmokeTestAttempt records one dispatched attempt or an interrupted acquisition.
|
||||||
|
type SmokeTestAttempt struct {
|
||||||
|
Dispatched bool
|
||||||
|
InitialEndpoint string
|
||||||
|
Endpoint string
|
||||||
|
HTTPStatus *int
|
||||||
|
Facts SmokeTestFacts
|
||||||
|
Timing SmokeTestTiming
|
||||||
|
Failure ExecutionFailureKind
|
||||||
|
Usage ObservedTokenUsage
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmokeTestReason identifies additional factual context for a diagnostic.
|
||||||
|
type SmokeTestReason string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SmokeTestReasonNone SmokeTestReason = ""
|
||||||
|
SmokeTestReasonAuthenticationNotApplicable SmokeTestReason = "authentication_not_applicable"
|
||||||
|
SmokeTestReasonProbeNotConfigured SmokeTestReason = "probe_not_configured"
|
||||||
|
SmokeTestReasonHTTPDiagnosticsUnavailable SmokeTestReason = "http_diagnostics_unavailable"
|
||||||
|
SmokeTestReasonRequiredCredentialMissing SmokeTestReason = "required_credential_missing"
|
||||||
|
SmokeTestReasonAuthenticationResponseUnrecognized SmokeTestReason = "authentication_response_unrecognized"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CloneBackend returns a copy of a backend, including its mutable settings.
|
||||||
|
func CloneBackend(backend Backend) (Backend, error) {
|
||||||
|
cloned := backend
|
||||||
|
var err error
|
||||||
|
cloned.ExtraParams, err = jsonvalue.CopyMap(backend.ExtraParams)
|
||||||
|
if err != nil {
|
||||||
|
return Backend{}, err
|
||||||
|
}
|
||||||
|
cloned.SmokeTests = CloneBackendSmokeTestConfig(backend.SmokeTests)
|
||||||
|
return cloned, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneBackendSmokeTestConfig returns an independent copy of optional metadata.
|
||||||
|
func CloneBackendSmokeTestConfig(config *BackendSmokeTestConfig) *BackendSmokeTestConfig {
|
||||||
|
if config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cloned := *config
|
||||||
|
cloned.DefaultProfile = cloneString(config.DefaultProfile)
|
||||||
|
if config.Reachability != nil {
|
||||||
|
reachability := *config.Reachability
|
||||||
|
reachability.Endpoint = cloneString(config.Reachability.Endpoint)
|
||||||
|
cloned.Reachability = &reachability
|
||||||
|
}
|
||||||
|
if config.Authentication != nil {
|
||||||
|
authentication := *config.Authentication
|
||||||
|
authentication.Kind = cloneString(config.Authentication.Kind)
|
||||||
|
authentication.Endpoint = cloneString(config.Authentication.Endpoint)
|
||||||
|
cloned.Authentication = &authentication
|
||||||
|
}
|
||||||
|
return &cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneSmokeTestRequest returns a copy that owns its optional settings.
|
||||||
|
func CloneSmokeTestRequest(request SmokeTestRequest) (SmokeTestRequest, error) {
|
||||||
|
cloned := request
|
||||||
|
cloned.TransientRetries = cloneInt(request.TransientRetries)
|
||||||
|
if request.Execution == nil {
|
||||||
|
return cloned, nil
|
||||||
|
}
|
||||||
|
execution := *request.Execution
|
||||||
|
execution.Temperature = cloneFloat64(request.Execution.Temperature)
|
||||||
|
execution.MaxTokens = cloneInt(request.Execution.MaxTokens)
|
||||||
|
execution.TopP = cloneFloat64(request.Execution.TopP)
|
||||||
|
execution.TimeoutSeconds = cloneInt(request.Execution.TimeoutSeconds)
|
||||||
|
execution.ReasoningEffort = cloneString(request.Execution.ReasoningEffort)
|
||||||
|
var err error
|
||||||
|
execution.ExtraParams, err = jsonvalue.CopyMap(request.Execution.ExtraParams)
|
||||||
|
if err != nil {
|
||||||
|
return SmokeTestRequest{}, err
|
||||||
|
}
|
||||||
|
cloned.Execution = &execution
|
||||||
|
return cloned, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneSmokeTestReport returns a copy with independent optional facts and maps.
|
||||||
|
func CloneSmokeTestReport(report SmokeTestReport) (SmokeTestReport, error) {
|
||||||
|
cloned := report
|
||||||
|
cloned.Applicable = cloneBool(report.Applicable)
|
||||||
|
cloned.Facts = cloneSmokeTestFacts(report.Facts)
|
||||||
|
cloned.Timing = cloneSmokeTestTiming(report.Timing)
|
||||||
|
var err error
|
||||||
|
cloned.Target.EffectiveSettings.ExtraParams, err = jsonvalue.CopyMap(report.Target.EffectiveSettings.ExtraParams)
|
||||||
|
if err != nil {
|
||||||
|
return SmokeTestReport{}, err
|
||||||
|
}
|
||||||
|
if report.Attempts != nil {
|
||||||
|
cloned.Attempts = make([]SmokeTestAttempt, len(report.Attempts))
|
||||||
|
for index, attempt := range report.Attempts {
|
||||||
|
cloned.Attempts[index] = attempt
|
||||||
|
cloned.Attempts[index].HTTPStatus = cloneInt(attempt.HTTPStatus)
|
||||||
|
cloned.Attempts[index].Facts = cloneSmokeTestFacts(attempt.Facts)
|
||||||
|
cloned.Attempts[index].Timing = cloneSmokeTestTiming(attempt.Timing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cloned, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneSmokeTestFacts(facts SmokeTestFacts) SmokeTestFacts {
|
||||||
|
facts.CredentialsSupplied = cloneBool(facts.CredentialsSupplied)
|
||||||
|
facts.EndpointReachable = cloneBool(facts.EndpointReachable)
|
||||||
|
facts.CredentialsAccepted = cloneBool(facts.CredentialsAccepted)
|
||||||
|
facts.ResponseReceived = cloneBool(facts.ResponseReceived)
|
||||||
|
facts.GenerationCompleted = cloneBool(facts.GenerationCompleted)
|
||||||
|
facts.TextReturned = cloneBool(facts.TextReturned)
|
||||||
|
facts.ExpectedTextMatched = cloneBool(facts.ExpectedTextMatched)
|
||||||
|
facts.StructuredOutputRequested = cloneBool(facts.StructuredOutputRequested)
|
||||||
|
facts.ValidJSON = cloneBool(facts.ValidJSON)
|
||||||
|
facts.SchemaMatched = cloneBool(facts.SchemaMatched)
|
||||||
|
return facts
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneSmokeTestTiming(timing SmokeTestTiming) SmokeTestTiming {
|
||||||
|
timing.CapacityWait = cloneDuration(timing.CapacityWait)
|
||||||
|
timing.RequestDuration = cloneDuration(timing.RequestDuration)
|
||||||
|
timing.RetryDelay = cloneDuration(timing.RetryDelay)
|
||||||
|
timing.EvaluationDuration = cloneDuration(timing.EvaluationDuration)
|
||||||
|
return timing
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneBool(value *bool) *bool {
|
||||||
|
if value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cloned := *value
|
||||||
|
return &cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneDuration(value *time.Duration) *time.Duration {
|
||||||
|
if value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cloned := *value
|
||||||
|
return &cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneFloat64(value *float64) *float64 {
|
||||||
|
if value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cloned := *value
|
||||||
|
return &cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneInt(value *int) *int {
|
||||||
|
if value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cloned := *value
|
||||||
|
return &cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneString(value *string) *string {
|
||||||
|
if value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cloned := *value
|
||||||
|
return &cloned
|
||||||
|
}
|
||||||
110
internal/domain/smoke_diagnostics_test.go
Normal file
110
internal/domain/smoke_diagnostics_test.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCloneBackendCopiesDiagnosticMetadataAndProviderValues(t *testing.T) {
|
||||||
|
profile := "profile"
|
||||||
|
reachabilityEndpoint := "https://probe.example/health"
|
||||||
|
authKind := "openrouter_key"
|
||||||
|
authEndpoint := "https://probe.example/key"
|
||||||
|
backend := Backend{
|
||||||
|
ExtraParams: map[string]any{"nested": map[string]any{"value": "original"}},
|
||||||
|
SmokeTests: &BackendSmokeTestConfig{
|
||||||
|
DefaultProfile: &profile,
|
||||||
|
Reachability: &ReachabilityProbeConfig{Endpoint: &reachabilityEndpoint},
|
||||||
|
Authentication: &AuthenticationProbeConfig{
|
||||||
|
Kind: &authKind,
|
||||||
|
Endpoint: &authEndpoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cloned, err := CloneBackend(backend)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
*cloned.SmokeTests.DefaultProfile = "changed"
|
||||||
|
*cloned.SmokeTests.Reachability.Endpoint = "changed"
|
||||||
|
*cloned.SmokeTests.Authentication.Kind = "changed"
|
||||||
|
*cloned.SmokeTests.Authentication.Endpoint = "changed"
|
||||||
|
cloned.ExtraParams["nested"].(map[string]any)["value"] = "changed"
|
||||||
|
|
||||||
|
if profile != "profile" || reachabilityEndpoint != "https://probe.example/health" ||
|
||||||
|
authKind != "openrouter_key" || authEndpoint != "https://probe.example/key" {
|
||||||
|
t.Fatal("cloned diagnostic metadata shared source pointers")
|
||||||
|
}
|
||||||
|
if got := backend.ExtraParams["nested"].(map[string]any)["value"]; got != "original" {
|
||||||
|
t.Fatalf("source provider parameter changed through clone: %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloneSmokeTestRequestOwnsOptionalSettings(t *testing.T) {
|
||||||
|
temperature := 0.25
|
||||||
|
retries := 2
|
||||||
|
reasoning := "low"
|
||||||
|
request := SmokeTestRequest{
|
||||||
|
APIKey: "secret",
|
||||||
|
TransientRetries: &retries,
|
||||||
|
Execution: &ExecutionTargetOverride{
|
||||||
|
Temperature: &temperature,
|
||||||
|
ReasoningEffort: &reasoning,
|
||||||
|
ExtraParams: map[string]any{"nested": map[string]any{"value": "original"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cloned, err := CloneSmokeTestRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
*cloned.TransientRetries = 3
|
||||||
|
*cloned.Execution.Temperature = 0.5
|
||||||
|
*cloned.Execution.ReasoningEffort = "high"
|
||||||
|
cloned.Execution.ExtraParams["nested"].(map[string]any)["value"] = "changed"
|
||||||
|
|
||||||
|
if retries != 2 || temperature != 0.25 || reasoning != "low" {
|
||||||
|
t.Fatal("cloned request shared optional scalar pointers")
|
||||||
|
}
|
||||||
|
if got := request.Execution.ExtraParams["nested"].(map[string]any)["value"]; got != "original" {
|
||||||
|
t.Fatalf("source provider parameter changed through clone: %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloneSmokeTestReportOwnsMutableValues(t *testing.T) {
|
||||||
|
applicable := true
|
||||||
|
reachable := true
|
||||||
|
status := 200
|
||||||
|
wait := 3 * time.Millisecond
|
||||||
|
report := SmokeTestReport{
|
||||||
|
Applicable: &applicable,
|
||||||
|
Target: SmokeTestTarget{EffectiveSettings: ExecutionTarget{
|
||||||
|
ExtraParams: map[string]any{"nested": map[string]any{"value": "original"}},
|
||||||
|
}},
|
||||||
|
Facts: SmokeTestFacts{EndpointReachable: &reachable},
|
||||||
|
Attempts: []SmokeTestAttempt{{
|
||||||
|
HTTPStatus: &status,
|
||||||
|
Facts: SmokeTestFacts{EndpointReachable: &reachable},
|
||||||
|
Timing: SmokeTestTiming{CapacityWait: &wait},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
cloned, err := CloneSmokeTestReport(report)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
*cloned.Applicable = false
|
||||||
|
*cloned.Facts.EndpointReachable = false
|
||||||
|
*cloned.Attempts[0].HTTPStatus = 503
|
||||||
|
*cloned.Attempts[0].Facts.EndpointReachable = false
|
||||||
|
*cloned.Attempts[0].Timing.CapacityWait = time.Second
|
||||||
|
cloned.Target.EffectiveSettings.ExtraParams["nested"].(map[string]any)["value"] = "changed"
|
||||||
|
|
||||||
|
if !applicable || !reachable || status != 200 || wait != 3*time.Millisecond {
|
||||||
|
t.Fatal("cloned report shared optional scalar pointers")
|
||||||
|
}
|
||||||
|
if got := report.Target.EffectiveSettings.ExtraParams["nested"].(map[string]any)["value"]; got != "original" {
|
||||||
|
t.Fatalf("source provider parameter changed through clone: %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user