// -------------------------------------------------------------------------
// The NEW way: define the AI as a swappable dependency behind an interface.
// -------------------------------------------------------------------------
// Classifier is our contract. An LLM-backed implementation, a mock, or a
// rules-based fallback can all satisfy it. Business logic depends on THIS,
// never on a concrete model client.
type Classifier interface {
Classify(ctx context.Context, line string) (Severity, error)
}
// ruleClassifier adapts our deterministic rules to the interface, so we can
// use it as a cheap, offline fallback when the model is unavailable.
type ruleClassifier struct{}
func (ruleClassifier) Classify(_ context.Context, line string) (Severity, error) {
return classifyByRules(line), nil
}
// mockLLMClassifier stands in for a real model. It is intentionally a little
// "fuzzy": it generalizes beyond the literal keywords the rules check for,
// the way a learned model would. (later chapter replaces this with a real call.)
type mockLLMClassifier struct{}
func (mockLLMClassifier) Classify(ctx context.Context, line string) (Severity, error) {
if err := ctx.Err(); err != nil {
return SeverityUnknown,
err // respect cancellation/timeouts like real I/O
}
l := strings.ToLower(line)
switch {
case strings.Contains(l, "crashloopbackoff"),
strings.Contains(l, "evicted"),
strings.Contains(l, "cannot allocate memory"):
// A model trained on ops text "knows" these mean trouble, even though
// none of them contain the word "panic" or "error".
return SeverityCritical, nil
case strings.Contains(l, "retry"), strings.Contains(l, "degraded"),
strings.Contains(l, "slow"):
return SeverityWarning, nil
default:
return classifyByRules(line), nil // fall back to the rules
}
}