Files
marktvogt.de/backend/internal/pkg/ai/errors.go
T
vikingowl 24e072b63d feat(ai): pluggable provider interface, Ollama + Mistral impls, migrate Pass2 sites
Replaces the Mistral-only ai.Client with an ai.Provider interface backed by
Ollama and Mistral implementations. Migrates enrichment + similarity callers
to ai.Provider.Chat. Research endpoint returns 501 until commit 2 reinstates
it on the new orchestrator.
2026-04-24 16:35:18 +02:00

100 lines
2.4 KiB
Go

package ai
import (
"context"
"errors"
"fmt"
"net"
"strings"
)
type ErrorCode int
const (
ErrInternal ErrorCode = iota
ErrRateLimited
ErrQuotaExceeded
ErrTimeout
ErrInvalidRequest
ErrUnavailable
ErrSchemaViolation
)
func (c ErrorCode) String() string {
switch c {
case ErrInternal:
return "internal"
case ErrRateLimited:
return "rate_limited"
case ErrQuotaExceeded:
return "quota_exceeded"
case ErrTimeout:
return "timeout"
case ErrInvalidRequest:
return "invalid_request"
case ErrUnavailable:
return "unavailable"
case ErrSchemaViolation:
return "schema_violation"
default:
return "internal"
}
}
type ProviderError struct {
Code ErrorCode
Message string
Retryable bool
Inner error
RawOutput string
}
func (e *ProviderError) Error() string {
if e.Inner != nil {
return fmt.Sprintf("ai: %s: %s: %v", e.Code, e.Message, e.Inner)
}
return fmt.Sprintf("ai: %s: %s", e.Code, e.Message)
}
func (e *ProviderError) Unwrap() error { return e.Inner }
func ClassifyError(err error) *ProviderError {
if err == nil {
return nil
}
var pe *ProviderError
if errors.As(err, &pe) {
return pe
}
if errors.Is(err, context.DeadlineExceeded) {
return &ProviderError{Code: ErrTimeout, Message: "context deadline exceeded", Retryable: true, Inner: err}
}
msg := strings.ToLower(err.Error())
switch {
case strings.Contains(msg, "429"),
strings.Contains(msg, "too many requests"),
strings.Contains(msg, "rate limit"):
return &ProviderError{Code: ErrRateLimited, Message: err.Error(), Retryable: true, Inner: err}
case strings.Contains(msg, "deadline exceeded"),
strings.Contains(msg, "timeout"):
return &ProviderError{Code: ErrTimeout, Message: err.Error(), Retryable: true, Inner: err}
case strings.Contains(msg, "connection refused"),
strings.Contains(msg, "no such host"),
isNetError(err):
return &ProviderError{Code: ErrUnavailable, Message: err.Error(), Retryable: true, Inner: err}
case strings.Contains(msg, "quota"),
strings.Contains(msg, "insufficient"):
return &ProviderError{Code: ErrQuotaExceeded, Message: err.Error(), Retryable: false, Inner: err}
case strings.Contains(msg, "400"),
strings.Contains(msg, "invalid"):
return &ProviderError{Code: ErrInvalidRequest, Message: err.Error(), Retryable: false, Inner: err}
}
return &ProviderError{Code: ErrInternal, Message: err.Error(), Retryable: false, Inner: err}
}
func isNetError(err error) bool {
var ne net.Error
return errors.As(err, &ne)
}