Add utilization diagnostics and correction ledger
This commit is contained in:
354
internal/framework/runner/observability.go
Normal file
354
internal/framework/runner/observability.go
Normal file
@@ -0,0 +1,354 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
)
|
||||
|
||||
type UtilizationDiagnostics struct {
|
||||
EffectiveConcurrency EffectiveConcurrencyLimits `json:"effective_concurrency"`
|
||||
RunTiming RunTimingSummary `json:"run_timing"`
|
||||
LLMCalls LLMCallSummary `json:"llm_calls"`
|
||||
Modules []ModuleTimingSummary `json:"modules,omitempty"`
|
||||
Validators []ValidatorTimingSummary `json:"validators,omitempty"`
|
||||
}
|
||||
|
||||
type EffectiveConcurrencyLimits struct {
|
||||
TotalLLM int `json:"total_llm"`
|
||||
ProposalLLM int `json:"proposal_llm"`
|
||||
ValidationLLM int `json:"validation_llm"`
|
||||
}
|
||||
|
||||
type RunTimingSummary struct {
|
||||
RunWallTimeMS int64 `json:"run_wall_time_ms"`
|
||||
SchedulerQueueWaitMS int64 `json:"scheduler_queue_wait_ms"`
|
||||
LLMExecutionTimeMS int64 `json:"llm_execution_time_ms"`
|
||||
DeterministicValidationMS int64 `json:"deterministic_validation_time_ms"`
|
||||
MaxInFlightLLMCalls int `json:"max_in_flight_llm_calls"`
|
||||
AverageInFlightLLMCalls int `json:"average_in_flight_llm_calls"`
|
||||
}
|
||||
|
||||
type LLMCallSummary struct {
|
||||
TotalProposalCalls int `json:"total_proposal_calls"`
|
||||
TotalValidationCalls int `json:"total_validation_calls"`
|
||||
}
|
||||
|
||||
type ModuleTimingSummary struct {
|
||||
ModuleKey string `json:"module_key"`
|
||||
ModuleInstance string `json:"module_instance"`
|
||||
ProposalLLMCalls int `json:"proposal_llm_calls"`
|
||||
ValidationLLMCalls int `json:"validation_llm_calls"`
|
||||
SchedulerQueueWaitMS int64 `json:"scheduler_queue_wait_ms"`
|
||||
LLMExecutionTimeMS int64 `json:"llm_execution_time_ms"`
|
||||
DeterministicValidationMS int64 `json:"deterministic_validation_time_ms"`
|
||||
ModuleWallTimeMS int64 `json:"module_wall_time_ms"`
|
||||
}
|
||||
|
||||
type ValidatorTimingSummary struct {
|
||||
ModuleKey string `json:"module_key"`
|
||||
ModuleInstance string `json:"module_instance"`
|
||||
ValidatorKey string `json:"validator_key"`
|
||||
LLMBacked bool `json:"llm_backed"`
|
||||
Calls int `json:"calls"`
|
||||
ElapsedMS int64 `json:"elapsed_ms"`
|
||||
}
|
||||
|
||||
type runInstrumentation struct {
|
||||
startedAt time.Time
|
||||
|
||||
mu sync.Mutex
|
||||
totalProposalCalls int
|
||||
totalValidationCalls int
|
||||
schedulerQueueWait time.Duration
|
||||
llmExecutionTime time.Duration
|
||||
deterministicValidation time.Duration
|
||||
perModuleProposalCalls map[string]int
|
||||
perModuleValidationCalls map[string]int
|
||||
perModuleSchedulerQueueWait map[string]time.Duration
|
||||
perModuleLLMExecutionTime map[string]time.Duration
|
||||
perModuleDeterministicValidation map[string]time.Duration
|
||||
validatorSummaries map[string]*ValidatorTimingSummary
|
||||
|
||||
inflight int
|
||||
maxInflight int
|
||||
inflightArea float64
|
||||
lastInflightChange time.Time
|
||||
averageInflightComputed bool
|
||||
}
|
||||
|
||||
func newRunInstrumentation(startedAt time.Time) *runInstrumentation {
|
||||
return &runInstrumentation{
|
||||
startedAt: startedAt,
|
||||
perModuleProposalCalls: make(map[string]int),
|
||||
perModuleValidationCalls: make(map[string]int),
|
||||
perModuleSchedulerQueueWait: make(map[string]time.Duration),
|
||||
perModuleLLMExecutionTime: make(map[string]time.Duration),
|
||||
perModuleDeterministicValidation: make(map[string]time.Duration),
|
||||
validatorSummaries: make(map[string]*ValidatorTimingSummary),
|
||||
lastInflightChange: startedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *runInstrumentation) wrapProposalClient(client contracts.StructuredLLMClient) contracts.StructuredLLMClient {
|
||||
if client == nil {
|
||||
return nil
|
||||
}
|
||||
return measuredClient{
|
||||
inner: client,
|
||||
collector: i,
|
||||
isProposal: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *runInstrumentation) wrapValidationClient(client contracts.StructuredLLMClient) contracts.StructuredLLMClient {
|
||||
if client == nil {
|
||||
return nil
|
||||
}
|
||||
return measuredClient{
|
||||
inner: client,
|
||||
collector: i,
|
||||
isProposal: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *runInstrumentation) wrapScheduler(s contracts.LLMScheduler, isProposal bool) contracts.LLMScheduler {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return measuredScheduler{
|
||||
inner: s,
|
||||
collector: i,
|
||||
isProposal: isProposal,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *runInstrumentation) recordValidatorTiming(moduleKey, moduleInstance, validatorKey string, llmBacked bool, elapsed time.Duration) {
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
if !llmBacked {
|
||||
i.deterministicValidation += elapsed
|
||||
i.perModuleDeterministicValidation[moduleInstance] += elapsed
|
||||
}
|
||||
k := moduleInstance + "::" + validatorKey
|
||||
summary, ok := i.validatorSummaries[k]
|
||||
if !ok {
|
||||
summary = &ValidatorTimingSummary{
|
||||
ModuleKey: moduleKey,
|
||||
ModuleInstance: moduleInstance,
|
||||
ValidatorKey: validatorKey,
|
||||
LLMBacked: llmBacked,
|
||||
}
|
||||
i.validatorSummaries[k] = summary
|
||||
}
|
||||
summary.Calls++
|
||||
summary.ElapsedMS += elapsed.Milliseconds()
|
||||
}
|
||||
|
||||
func (i *runInstrumentation) recordModuleWallTime(moduleInstance string, elapsed time.Duration) {
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
// Derived later from started/completed, but we keep a slot for sanity.
|
||||
_ = moduleInstance
|
||||
_ = elapsed
|
||||
}
|
||||
|
||||
func (i *runInstrumentation) beginInFlight() {
|
||||
now := time.Now().UTC()
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
i.inflightArea += float64(i.inflight) * now.Sub(i.lastInflightChange).Seconds()
|
||||
i.lastInflightChange = now
|
||||
i.inflight++
|
||||
if i.inflight > i.maxInflight {
|
||||
i.maxInflight = i.inflight
|
||||
}
|
||||
}
|
||||
|
||||
func (i *runInstrumentation) endInFlight() {
|
||||
now := time.Now().UTC()
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
i.inflightArea += float64(i.inflight) * now.Sub(i.lastInflightChange).Seconds()
|
||||
i.lastInflightChange = now
|
||||
if i.inflight > 0 {
|
||||
i.inflight--
|
||||
}
|
||||
}
|
||||
|
||||
func (i *runInstrumentation) finalize(runOutput *RunOutput, completedAt time.Time, limits EffectiveConcurrencyLimits) *UtilizationDiagnostics {
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
|
||||
if !i.averageInflightComputed {
|
||||
i.inflightArea += float64(i.inflight) * completedAt.Sub(i.lastInflightChange).Seconds()
|
||||
i.lastInflightChange = completedAt
|
||||
i.averageInflightComputed = true
|
||||
}
|
||||
|
||||
runSeconds := completedAt.Sub(i.startedAt).Seconds()
|
||||
avgInflight := 0
|
||||
if runSeconds > 0 {
|
||||
avgInflight = int(i.inflightArea / runSeconds)
|
||||
}
|
||||
|
||||
moduleSummaries := make([]ModuleTimingSummary, 0)
|
||||
if runOutput != nil {
|
||||
moduleSummaries = make([]ModuleTimingSummary, 0, len(runOutput.ModuleResults))
|
||||
for _, moduleResult := range runOutput.ModuleResults {
|
||||
moduleSummaries = append(moduleSummaries, ModuleTimingSummary{
|
||||
ModuleKey: moduleResult.ModuleKey,
|
||||
ModuleInstance: moduleResult.ModuleInstance,
|
||||
ProposalLLMCalls: i.perModuleProposalCalls[moduleResult.ModuleInstance],
|
||||
ValidationLLMCalls: i.perModuleValidationCalls[moduleResult.ModuleInstance],
|
||||
SchedulerQueueWaitMS: i.perModuleSchedulerQueueWait[moduleResult.ModuleInstance].Milliseconds(),
|
||||
LLMExecutionTimeMS: i.perModuleLLMExecutionTime[moduleResult.ModuleInstance].Milliseconds(),
|
||||
DeterministicValidationMS: i.perModuleDeterministicValidation[moduleResult.ModuleInstance].Milliseconds(),
|
||||
ModuleWallTimeMS: moduleResult.CompletedAt.Sub(moduleResult.StartedAt).Milliseconds(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
validatorSummaries := make([]ValidatorTimingSummary, 0, len(i.validatorSummaries))
|
||||
for _, summary := range i.validatorSummaries {
|
||||
validatorSummaries = append(validatorSummaries, *summary)
|
||||
}
|
||||
sortValidatorSummaries(validatorSummaries)
|
||||
|
||||
return &UtilizationDiagnostics{
|
||||
EffectiveConcurrency: limits,
|
||||
RunTiming: RunTimingSummary{
|
||||
RunWallTimeMS: completedAt.Sub(i.startedAt).Milliseconds(),
|
||||
SchedulerQueueWaitMS: i.schedulerQueueWait.Milliseconds(),
|
||||
LLMExecutionTimeMS: i.llmExecutionTime.Milliseconds(),
|
||||
DeterministicValidationMS: i.deterministicValidation.Milliseconds(),
|
||||
MaxInFlightLLMCalls: i.maxInflight,
|
||||
AverageInFlightLLMCalls: avgInflight,
|
||||
},
|
||||
LLMCalls: LLMCallSummary{
|
||||
TotalProposalCalls: i.totalProposalCalls,
|
||||
TotalValidationCalls: i.totalValidationCalls,
|
||||
},
|
||||
Modules: moduleSummaries,
|
||||
Validators: validatorSummaries,
|
||||
}
|
||||
}
|
||||
|
||||
type measuredClient struct {
|
||||
inner contracts.StructuredLLMClient
|
||||
collector *runInstrumentation
|
||||
isProposal bool
|
||||
}
|
||||
|
||||
func (c measuredClient) CompleteStructured(ctx context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||
if c.collector != nil {
|
||||
c.collector.beginInFlight()
|
||||
}
|
||||
started := time.Now().UTC()
|
||||
resp, err := c.inner.CompleteStructured(ctx, req, out)
|
||||
elapsed := time.Since(started)
|
||||
if c.collector != nil {
|
||||
c.collector.endInFlight()
|
||||
c.collector.mu.Lock()
|
||||
moduleInstance := moduleInstanceFromStage(req.StageName)
|
||||
c.collector.llmExecutionTime += elapsed
|
||||
c.collector.perModuleLLMExecutionTime[moduleInstance] += elapsed
|
||||
if c.isProposal {
|
||||
c.collector.totalProposalCalls++
|
||||
c.collector.perModuleProposalCalls[moduleInstance]++
|
||||
} else {
|
||||
c.collector.totalValidationCalls++
|
||||
c.collector.perModuleValidationCalls[moduleInstance]++
|
||||
}
|
||||
c.collector.mu.Unlock()
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
type measuredScheduler struct {
|
||||
inner contracts.LLMScheduler
|
||||
collector *runInstrumentation
|
||||
isProposal bool
|
||||
}
|
||||
|
||||
func (s measuredScheduler) Run(ctx context.Context, fn func(context.Context) error) error {
|
||||
started := time.Now().UTC()
|
||||
var execDuration time.Duration
|
||||
err := s.inner.Run(ctx, func(runCtx context.Context) error {
|
||||
execStart := time.Now().UTC()
|
||||
callErr := fn(runCtx)
|
||||
execDuration += time.Since(execStart)
|
||||
return callErr
|
||||
})
|
||||
totalDuration := time.Since(started)
|
||||
waitDuration := totalDuration - execDuration
|
||||
if waitDuration < 0 {
|
||||
waitDuration = 0
|
||||
}
|
||||
if s.collector != nil {
|
||||
stageModule := moduleInstanceFromContext(ctx)
|
||||
s.collector.mu.Lock()
|
||||
s.collector.schedulerQueueWait += waitDuration
|
||||
if stageModule != "" {
|
||||
s.collector.perModuleSchedulerQueueWait[stageModule] += waitDuration
|
||||
}
|
||||
s.collector.mu.Unlock()
|
||||
}
|
||||
_ = s.isProposal
|
||||
return err
|
||||
}
|
||||
|
||||
func moduleInstanceFromStage(stage string) string {
|
||||
stage = strings.TrimSpace(stage)
|
||||
if stage == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Split(stage, ":")
|
||||
if len(parts) == 0 {
|
||||
return stage
|
||||
}
|
||||
return strings.TrimSpace(parts[0])
|
||||
}
|
||||
|
||||
func moduleInstanceFromContext(ctx context.Context) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
if v := ctx.Value(moduleInstanceContextKey{}); v != nil {
|
||||
if s, ok := v.(string); ok {
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type moduleInstanceContextKey struct{}
|
||||
|
||||
func withModuleInstanceContext(ctx context.Context, moduleInstance string) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, moduleInstanceContextKey{}, strings.TrimSpace(moduleInstance))
|
||||
}
|
||||
|
||||
func isLLMBackedValidator(v contracts.Validator) bool {
|
||||
if v == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := v.(*frameworkvalidators.LLMBackedValidator)
|
||||
return ok
|
||||
}
|
||||
|
||||
func sortValidatorSummaries(in []ValidatorTimingSummary) {
|
||||
sort.SliceStable(in, func(i, j int) bool {
|
||||
if in[i].ModuleInstance != in[j].ModuleInstance {
|
||||
return in[i].ModuleInstance < in[j].ModuleInstance
|
||||
}
|
||||
return in[i].ValidatorKey < in[j].ValidatorKey
|
||||
})
|
||||
}
|
||||
@@ -77,6 +77,7 @@ type RunInput struct {
|
||||
Transcript *schema.Transcript
|
||||
Glossary *schema.Glossary
|
||||
ModuleSpecs []contracts.ModuleRunSpec
|
||||
EffectiveConcurrency EffectiveConcurrencyLimits
|
||||
ProposalLLMClient contracts.StructuredLLMClient
|
||||
ProposalLLMScheduler contracts.LLMScheduler
|
||||
ProposalDiagnosticsDir string
|
||||
@@ -89,6 +90,7 @@ type RunInput struct {
|
||||
type RunOutput struct {
|
||||
FinalTranscript *schema.Transcript `json:"-"`
|
||||
ModuleResults []ModuleResult `json:"module_results"`
|
||||
Utilization *UtilizationDiagnostics
|
||||
}
|
||||
|
||||
func New(factory ModuleFactory) *Runner {
|
||||
@@ -100,6 +102,14 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) {
|
||||
return RunOutput{}, fmt.Errorf("runner module factory is required")
|
||||
}
|
||||
|
||||
startedAt := time.Now().UTC()
|
||||
instrumentation := newRunInstrumentation(startedAt)
|
||||
|
||||
input.ProposalLLMClient = instrumentation.wrapProposalClient(input.ProposalLLMClient)
|
||||
input.ValidationLLMClient = instrumentation.wrapValidationClient(input.ValidationLLMClient)
|
||||
input.ProposalLLMScheduler = instrumentation.wrapScheduler(input.ProposalLLMScheduler, true)
|
||||
input.ValidationLLMScheduler = instrumentation.wrapScheduler(input.ValidationLLMScheduler, false)
|
||||
|
||||
working := cloneTranscript(input.Transcript)
|
||||
results := make([]ModuleResult, 0, len(input.ModuleSpecs))
|
||||
|
||||
@@ -116,7 +126,11 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) {
|
||||
CompletedAt: time.Now().UTC(),
|
||||
}
|
||||
results = append(results, failed)
|
||||
return RunOutput{FinalTranscript: working, ModuleResults: results}, fmt.Errorf("module %q setup failed: %w", spec.InstanceName, err)
|
||||
return RunOutput{
|
||||
FinalTranscript: working,
|
||||
ModuleResults: results,
|
||||
Utilization: instrumentation.finalize(&RunOutput{ModuleResults: results}, time.Now().UTC(), input.EffectiveConcurrency),
|
||||
}, fmt.Errorf("module %q setup failed: %w", spec.InstanceName, err)
|
||||
}
|
||||
|
||||
policy := module.ReplacementPolicy()
|
||||
@@ -132,7 +146,11 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) {
|
||||
CompletedAt: time.Now().UTC(),
|
||||
}
|
||||
results = append(results, failed)
|
||||
return RunOutput{FinalTranscript: working, ModuleResults: results}, fmt.Errorf("module %q chunking failed: %w", spec.InstanceName, err)
|
||||
return RunOutput{
|
||||
FinalTranscript: working,
|
||||
ModuleResults: results,
|
||||
Utilization: instrumentation.finalize(&RunOutput{ModuleResults: results}, time.Now().UTC(), input.EffectiveConcurrency),
|
||||
}, fmt.Errorf("module %q chunking failed: %w", spec.InstanceName, err)
|
||||
}
|
||||
|
||||
pipelineResult, pipelineErr := runModulePipeline(ctx, collectSectionProposalsInput{
|
||||
@@ -148,6 +166,7 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) {
|
||||
ValidationClient: input.ValidationLLMClient,
|
||||
ValidationScheduler: input.ValidationLLMScheduler,
|
||||
ValidationDiagnosticsDir: input.ValidationDiagnosticsDir,
|
||||
Instrumentation: instrumentation,
|
||||
})
|
||||
if pipelineErr != nil {
|
||||
failed := ModuleResult{
|
||||
@@ -163,7 +182,11 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) {
|
||||
CompletedAt: time.Now().UTC(),
|
||||
}
|
||||
results = append(results, failed)
|
||||
return RunOutput{FinalTranscript: working, ModuleResults: results}, fmt.Errorf("module %q failed: %w", spec.InstanceName, pipelineErr)
|
||||
return RunOutput{
|
||||
FinalTranscript: working,
|
||||
ModuleResults: results,
|
||||
Utilization: instrumentation.finalize(&RunOutput{ModuleResults: results}, time.Now().UTC(), input.EffectiveConcurrency),
|
||||
}, fmt.Errorf("module %q failed: %w", spec.InstanceName, pipelineErr)
|
||||
}
|
||||
|
||||
applyResult := proposals.ApplyProposals(working, pipelineResult.Approved, policy)
|
||||
@@ -184,7 +207,9 @@ func (r *Runner) Run(ctx context.Context, input RunInput) (RunOutput, error) {
|
||||
})
|
||||
}
|
||||
|
||||
return RunOutput{FinalTranscript: working, ModuleResults: results}, nil
|
||||
output := RunOutput{FinalTranscript: working, ModuleResults: results}
|
||||
output.Utilization = instrumentation.finalize(&output, time.Now().UTC(), input.EffectiveConcurrency)
|
||||
return output, nil
|
||||
}
|
||||
|
||||
type collectSectionProposalsInput struct {
|
||||
@@ -200,6 +225,7 @@ type collectSectionProposalsInput struct {
|
||||
ValidationClient contracts.StructuredLLMClient
|
||||
ValidationScheduler ValidationScheduler
|
||||
ValidationDiagnosticsDir string
|
||||
Instrumentation *runInstrumentation
|
||||
}
|
||||
|
||||
type sectionProposals struct {
|
||||
@@ -244,7 +270,7 @@ func collectSectionProposals(ctx context.Context, input collectSectionProposalsI
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
meta := contracts.SectionMetadataFromSection(section)
|
||||
corrected, err := input.Module.Propose(runCtx, contracts.ProposalRequest{
|
||||
corrected, err := input.Module.Propose(withModuleInstanceContext(runCtx, input.Spec.InstanceName), contracts.ProposalRequest{
|
||||
ExecutionContext: contracts.ExecutionContext{
|
||||
Config: input.Config,
|
||||
WorkingTranscript: transcriptFromSection(section),
|
||||
@@ -366,6 +392,7 @@ func runModulePipeline(ctx context.Context, input collectSectionProposalsInput)
|
||||
ValidationLLMClient: input.ValidationClient,
|
||||
ValidationScheduler: input.ValidationScheduler,
|
||||
DiagnosticsDir: input.ValidationDiagnosticsDir,
|
||||
Instrumentation: input.Instrumentation,
|
||||
})
|
||||
validationResults <- sectionValidationResult{
|
||||
sectionPos: sectionPos,
|
||||
@@ -432,6 +459,7 @@ type validateSectionCandidatesInput struct {
|
||||
ValidationLLMClient contracts.StructuredLLMClient
|
||||
ValidationScheduler ValidationScheduler
|
||||
DiagnosticsDir string
|
||||
Instrumentation *runInstrumentation
|
||||
}
|
||||
|
||||
type validateSectionCandidatesResult struct {
|
||||
@@ -456,7 +484,8 @@ func validateSectionCandidates(ctx context.Context, input validateSectionCandida
|
||||
}
|
||||
}
|
||||
|
||||
vResult, err := validator.Validate(ctx, contracts.ValidationRequest{
|
||||
validatorStartedAt := time.Now().UTC()
|
||||
vResult, err := validator.Validate(withModuleInstanceContext(ctx, input.Spec.InstanceName), contracts.ValidationRequest{
|
||||
WorkingTranscript: input.WorkingTranscript,
|
||||
CandidateProposal: eligible,
|
||||
ModuleKey: input.Spec.ModuleKey,
|
||||
@@ -468,6 +497,15 @@ func validateSectionCandidates(ctx context.Context, input validateSectionCandida
|
||||
Scheduler: input.ValidationScheduler,
|
||||
DiagnosticsWriter: diagnosticsWriter,
|
||||
})
|
||||
if input.Instrumentation != nil {
|
||||
input.Instrumentation.recordValidatorTiming(
|
||||
input.Spec.ModuleKey,
|
||||
input.Spec.InstanceName,
|
||||
validator.Name(),
|
||||
isLLMBackedValidator(validator),
|
||||
time.Since(validatorStartedAt),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return validateSectionCandidatesResult{
|
||||
approved: eligible,
|
||||
|
||||
Reference in New Issue
Block a user