phase 2: matrix.downgrade reads WeakModels from config
migrate the strong-model auto-downgrade gate from a hardcoded weak list to cfg.Models.WeakModels. backward compatible: existing API preserved, callers that don't migrate keep using DefaultWeakModels. changes: - internal/matrix/downgrade.go: split IsWeakModel into rule-based base (`:free` suffix/infix) + literal-list lookup. New IsWeakModelInList(model, list) takes the config-supplied list. DowngradeInput grows a WeakModels field; nil falls back to DefaultWeakModels (preserves pre-phase-2 behavior). - internal/workflow/modes.go: add MatrixDowngradeWithWeakList(list) factory mirroring MatrixSearch's pattern. Plain MatrixDowngrade kept for backward compat. - cmd/matrixd/main.go: handlers struct holds weakModels populated from cfg.Models.WeakModels at startup; handleDowngrade threads it into every DowngradeInput. - cmd/observerd/main.go: registerBuiltinModes accepts weakModels and uses the factory variant. observerd reads cfg.Models.WeakModels in main(). end-to-end verified: downgrade + matrix + observer + workflow smokes all pass. Existing TestMaybeDowngrade_TruthTable + TestIsWeakModel unchanged (backward compat). Two new tests cover the config path: - TestIsWeakModelInList — covers rule + literal + empty + nil - TestMaybeDowngrade_WithConfigList — verifies cfg list overrides default Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,12 @@ const (
|
||||
// diagnostic runs ("LH_FORCE_FULL_ENRICHMENT=1" or "true").
|
||||
const EnvForceFullEnrichment = "LH_FORCE_FULL_ENRICHMENT"
|
||||
|
||||
// DefaultWeakModels is the fallback weak-model list used when no
|
||||
// config-driven list is provided. Matches the pre-config hardcoded
|
||||
// switch — preserved for backward compatibility with callers that
|
||||
// haven't migrated to the [models] tier config (Phase 1).
|
||||
var DefaultWeakModels = []string{"qwen3.5:latest", "qwen3:latest"}
|
||||
|
||||
// IsWeakModel returns true for models matrix-corpus composition
|
||||
// demonstrably helped during the 2026-04-26 pass5 bake-off. Strong
|
||||
// models (default) get matrix dropped to avoid the "composed lost
|
||||
@@ -45,17 +51,29 @@ const EnvForceFullEnrichment = "LH_FORCE_FULL_ENRICHMENT"
|
||||
// Weak signals:
|
||||
// - `:free` suffix (OpenRouter free tier, e.g. `gpt-oss-120b:free`)
|
||||
// - `:free/` infix (handles routing-prefixed names like `or:free/x`)
|
||||
// - `qwen3.5:latest` / `qwen3:latest` — local last-resort rung
|
||||
// - DefaultWeakModels list — local-hot-path eligible.
|
||||
//
|
||||
// Add new weak models by extending this function alongside variance
|
||||
// data that justifies it.
|
||||
// For config-driven lists, use IsWeakModelInList.
|
||||
func IsWeakModel(model string) bool {
|
||||
return IsWeakModelInList(model, DefaultWeakModels)
|
||||
}
|
||||
|
||||
// IsWeakModelInList is the config-aware variant. Callers pass the
|
||||
// configured weak-model list (typically cfg.Models.WeakModels) and
|
||||
// the function applies the same rule-based checks (`:free` suffix /
|
||||
// infix) plus the literal list match.
|
||||
//
|
||||
// Empty list still applies the rule-based checks — only the literal
|
||||
// match becomes a no-op. That preserves "free-tier always weak"
|
||||
// regardless of what the operator configured.
|
||||
func IsWeakModelInList(model string, weakList []string) bool {
|
||||
if strings.HasSuffix(model, ":free") || strings.Contains(model, ":free/") {
|
||||
return true
|
||||
}
|
||||
switch model {
|
||||
case "qwen3.5:latest", "qwen3:latest":
|
||||
return true
|
||||
for _, w := range weakList {
|
||||
if w == model {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -75,6 +93,10 @@ type DowngradeInput struct {
|
||||
Model string
|
||||
ForcedMode bool
|
||||
ForceFullOverride bool
|
||||
// WeakModels overrides the default weak-model list (DefaultWeakModels)
|
||||
// when non-nil. Typically populated from cfg.Models.WeakModels at
|
||||
// startup. nil = use DefaultWeakModels (backward compat).
|
||||
WeakModels []string
|
||||
}
|
||||
|
||||
// DowngradeDecision is the output. DowngradedFrom is non-empty
|
||||
@@ -106,7 +128,11 @@ func MaybeDowngrade(in DowngradeInput) DowngradeDecision {
|
||||
out.Reason = EnvForceFullEnrichment + " bypass"
|
||||
return out
|
||||
}
|
||||
if IsWeakModel(in.Model) {
|
||||
weakList := in.WeakModels
|
||||
if weakList == nil {
|
||||
weakList = DefaultWeakModels
|
||||
}
|
||||
if IsWeakModelInList(in.Model, weakList) {
|
||||
out.Reason = "weak model; matrix composition demonstrably helped (2026-04-26 free-tier bake-off)"
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user