package router import ( "encoding/json" "strings" "gophergate/internal/db" ) // HeuristicRule defines a pattern-based routing rule. type HeuristicRule struct { Pattern string `json:"pattern"` TargetIdx int `json:"target"` CaseSensitive bool `json:"case_sensitive,omitempty"` } func routeHeuristic(group db.ModelGroup, targets []string, userMessage string) (*Decision, error) { selected := targets[0] reason := "default (first target)" // If heuristic_rules is set, use them if group.HeuristicRules != nil && *group.HeuristicRules != "" { var rules []HeuristicRule if err := json.Unmarshal([]byte(*group.HeuristicRules), &rules); err == nil { searchMsg := userMessage for _, rule := range rules { pattern := rule.Pattern msg := searchMsg if !rule.CaseSensitive { pattern = strings.ToLower(pattern) msg = strings.ToLower(msg) } if strings.Contains(msg, pattern) { if rule.TargetIdx >= 0 && rule.TargetIdx < len(targets) { selected = targets[rule.TargetIdx] reason = "matched heuristic rule: " + rule.Pattern break } } } } } // Built-in fallback heuristics if reason == "default (first target)" && len(targets) > 1 { msgLower := strings.ToLower(userMessage) complexIndicators := []string{ "step by step", "explain in detail", "reason through", "think carefully", "analyze", "debug", "write code", "implement", "refactor", "architecture", } for _, indicator := range complexIndicators { if strings.Contains(msgLower, indicator) { selected = targets[len(targets)-1] reason = "complex task indicator: " + indicator break } } } return &Decision{ SelectedModel: selected, Strategy: "heuristic", Reason: reason, }, nil }