mudler/LocalAI · error

router/score: unknown score_normalization %q (want %q or %q)

Error message

router/score: unknown score_normalization %q (want %q or %q)

What it means

Construction-time panic in NewScoreClassifier: opts.Normalization is neither empty/raw nor ScoreNormalizationMean. Only two normalization modes exist for turning per-label logprob scores into comparable numbers — raw (no transform) and mean (average over label tokens) — anything else (e.g. 'sum', 'softmax', 'average') is rejected.

Source

Thrown at core/services/routing/router/score.go:199

	for _, p := range policies {
		labels = append(labels, p.Label)
	}
	if opts.ActivationThreshold <= 0 {
		opts.ActivationThreshold = defaultActivationThreshold
	}
	if opts.StopToken == "" {
		opts.StopToken = defaultStopToken
	}
	if opts.PromptRenderer == nil {
		opts.PromptRenderer = chatMLRenderer
	}
	switch opts.Normalization {
	case "", ScoreNormalizationRaw:
		opts.Normalization = ScoreNormalizationRaw
	case ScoreNormalizationMean:
		// ok
	default:
		panic(fmt.Sprintf("router/score: unknown score_normalization %q (want %q or %q)",
			opts.Normalization, ScoreNormalizationRaw, ScoreNormalizationMean))
	}
	candidates := make([]string, len(labels))
	for i, l := range labels {
		candidates[i] = buildCandidate(l, opts.StopToken)
	}
	systemPrompt, err := renderSystemPrompt(opts.SystemPromptTemplate, policies)
	if err != nil {
		// Parse-time error here means the operator-supplied template
		// is malformed. Config-load validation (ModelConfig.Validate)
		// should have caught it earlier; reaching this is either a
		// direct programmatic construction or a validation gap.
		panic(fmt.Sprintf("router/score: system_prompt_template: %v", err))
	}
	return &ScoreClassifier{
		scorer:              scorer,
		activationThreshold: opts.ActivationThreshold,
		normalization:       opts.Normalization,

View on GitHub (pinned to 44413a9d06)

Solutions

  1. Use 'raw' or 'mean' (the exact ScoreNormalizationRaw / ScoreNormalizationMean values)
  2. Leave score_normalization unset to get the raw default
  3. If a new mode is genuinely needed, add it as a ScoreNormalization* constant in score.go rather than passing arbitrary strings

Example fix

# before (yaml)
classifier:
  type: score
  score_normalization: average
# after
classifier:
  type: score
  score_normalization: mean
Defensive patterns

Strategy: type-guard

Validate before calling

switch cfg.Normalization {
case "", string(router.ScoreNormalizationRaw), string(router.ScoreNormalizationMean):
default:
    return fmt.Errorf("score_normalization must be raw or mean, got %q", cfg.Normalization)
}

Type guard

func validScoreNormalization(s string) bool { return s == "" || s == string(ScoreNormalizationRaw) || s == string(ScoreNormalizationMean) }

Prevention

When it happens

Trigger: Setting score_normalization: 'sum' or 'average' in the router classifier options; passing a ScoreClassifierOptions with Normalization set from user input without whitelisting; case/format variants like 'Mean' not matching the constants.

Common situations: Operators inventing normalization names from intuition; docs/examples referencing a mode removed in a refactor; config values copied from a different classifier implementation.

Related errors


AI-assisted analysis of mudler/LocalAI@44413a9d06 (2026-08-15). Data as JSON: /api/errors/4a315aab4fdc206a. Report an issue: GitHub.