Release Notes: v0.8.0¶
Release Date: 2026-04-03
Highlights¶
- Resilience Package: New provider-agnostic error classification and retry logic
- Smart Fallback: TTS and STT clients only switch providers on permanent errors
- 8 Error Categories: Actionable error handling with clear retry decisions
New Features¶
Resilience Package¶
The new resilience package provides provider-agnostic error handling:
Error Categories¶
8 categories for actionable error handling:
| Category | Description | Retryable |
|---|---|---|
transient |
Temporary failures (timeout, network) | Yes |
rate_limit |
Rate limiting (429) | Yes |
server |
Server errors (500, 502, 503) | Yes |
validation |
Invalid request (400, 422) | No |
auth |
Authentication/authorization (401, 403) | No |
not_found |
Resource not found (404) | No |
quota |
Quota exceeded | No |
unknown |
Unclassified errors | No |
ProviderError Type¶
Wrap provider errors with classification metadata:
err := resilience.NewProviderError("elevenlabs", "Synthesize", originalErr, resilience.ErrorInfo{
Category: resilience.CategoryRateLimit,
Retryable: true,
Code: "RATE_LIMITED",
Message: "Rate limit exceeded",
Suggestion: "Wait and retry the request",
RetryAfter: 60 * time.Second,
})
// Check error type
if pe, ok := resilience.IsProviderError(err); ok {
if pe.IsRetryable() {
// Retry logic
}
}
Retry with Configurable Backoff¶
Generic retry functions with backoff:
// Simple retry
err := resilience.Retry(ctx, config, func() error {
return doSomething()
})
// Retry with result
result, err := resilience.RetryWithResult(ctx, config, func() (*Result, error) {
return fetchData()
})
Configure retry behavior:
config := resilience.RetryConfig{
MaxAttempts: 5,
Backoff: &resilience.ExponentialBackoff{
Initial: 1 * time.Second,
Max: 30 * time.Second,
Multiplier: 2.0,
Jitter: 0.1,
},
Classifier: myClassifier,
OnRetry: func(attempt int, err error, delay time.Duration) {
log.Printf("Retry %d after %v", attempt, delay)
},
}
Backoff Strategies¶
Four backoff strategies included:
// Exponential backoff with jitter (recommended)
backoff := &resilience.ExponentialBackoff{
Initial: 1 * time.Second,
Max: 30 * time.Second,
Multiplier: 2.0,
Jitter: 0.1, // 10% random jitter
}
// Linear backoff
backoff := &resilience.LinearBackoff{
Initial: 1 * time.Second,
Increment: 1 * time.Second,
Max: 10 * time.Second,
}
// Constant delay
backoff := &resilience.ConstantBackoff{
Delay: 1 * time.Second,
}
// No delay (for testing)
backoff := &resilience.NoBackoff{}
Error Classification Interface¶
Implement custom classifiers:
type ErrorClassifier interface {
Classify(err error) ErrorInfo
}
// Built-in HTTP classifier
classifier := resilience.NewHTTPStatusClassifier()
info := classifier.Classify(httpError)
Smart Fallback in TTS/STT Clients¶
TTS and STT clients now use smart fallback logic:
import "github.com/plexusone/omnivoice-core/tts"
client := tts.NewClient(
tts.WithProvider("elevenlabs", elevenLabsProvider),
tts.WithProvider("deepgram", deepgramProvider),
tts.WithFallbacks("deepgram"),
)
// Smart fallback behavior:
// - Rate limit (429) → provider retries internally → succeed or fail
// - Auth error (401) → no retry → fallback to Deepgram
// - Server error (500) → provider retries internally → fallback if exhausted
result, err := client.Synthesize(ctx, "Hello world", config)
Fallback decision logic:
| Error Type | Provider Action | Client Action |
|---|---|---|
| Retryable (429, 500) | Retry with backoff | Wait for provider |
| Permanent (401, 404) | Return immediately | Fallback |
| Unknown | Return immediately | Fallback |
Use Cases¶
Building Resilient Voice Applications¶
func synthesizeSpeech(ctx context.Context, text string) ([]byte, error) {
// Provider handles retries internally
result, err := provider.Synthesize(ctx, text, config)
if err != nil {
if pe, ok := resilience.IsProviderError(err); ok {
switch pe.GetCategory() {
case resilience.CategoryAuth:
return nil, fmt.Errorf("authentication failed: %s", pe.Info.Suggestion)
case resilience.CategoryValidation:
return nil, fmt.Errorf("invalid request: %s", pe.Info.Message)
case resilience.CategoryRateLimit:
// Already retried, rate limit persists
return nil, fmt.Errorf("rate limited after retries: %w", err)
}
}
return nil, err
}
return result.Audio, nil
}
Custom Error Classification¶
type MyClassifier struct{}
func (c *MyClassifier) Classify(err error) resilience.ErrorInfo {
// Custom classification logic
if strings.Contains(err.Error(), "timeout") {
return resilience.ErrorInfo{
Category: resilience.CategoryTransient,
Retryable: true,
Message: "Request timed out",
Suggestion: "Check network connectivity",
}
}
return resilience.ErrorInfo{
Category: resilience.CategoryUnknown,
Retryable: false,
}
}
Testing Retry Logic¶
func TestRetryOnRateLimit(t *testing.T) {
attempts := 0
err := resilience.Retry(ctx, resilience.RetryConfig{
MaxAttempts: 3,
Backoff: &resilience.NoBackoff{}, // Fast for testing
Classifier: myClassifier,
}, func() error {
attempts++
if attempts < 3 {
return rateLimitError
}
return nil
})
if err != nil {
t.Errorf("Expected success after retries, got: %v", err)
}
if attempts != 3 {
t.Errorf("Expected 3 attempts, got %d", attempts)
}
}
API Reference¶
Types¶
| Type | Description |
|---|---|
ErrorCategory |
String enum for error categories |
ErrorInfo |
Metadata about an error (category, retryable, code, message, suggestion, retry-after) |
ProviderError |
Error wrapper with provider name, operation, and ErrorInfo |
RetryConfig |
Configuration for retry behavior |
RetryError |
Error returned when retries are exhausted |
Backoff |
Interface for backoff strategies |
Functions¶
| Function | Description |
|---|---|
NewProviderError(provider, op, err, info) |
Create a ProviderError |
IsProviderError(err) |
Extract ProviderError from error chain |
IsRetryable(err) |
Check if error is retryable |
Retry(ctx, config, fn) |
Execute with retries |
RetryWithResult[T](ctx, config, fn) |
Execute with retries, return result |
DefaultBackoff() |
Create default exponential backoff |
DefaultRetryConfig() |
Create default retry configuration |
NewHTTPStatusClassifier() |
Create HTTP status code classifier |
Constants¶
const (
CategoryTransient ErrorCategory = "transient"
CategoryRateLimit ErrorCategory = "rate_limit"
CategoryValidation ErrorCategory = "validation"
CategoryAuth ErrorCategory = "auth"
CategoryNotFound ErrorCategory = "not_found"
CategoryServer ErrorCategory = "server"
CategoryQuota ErrorCategory = "quota"
CategoryUnknown ErrorCategory = "unknown"
)
Installation¶
Migration Guide¶
From v0.7.0¶
No breaking changes to existing APIs. To use the new features:
- Update dependency:
-
(Optional) Implement
ErrorClassifierin your providers to enable smart fallback -
TTS/STT clients automatically use smart fallback when providers return
ProviderError
Full Changelog¶
See CHANGELOG.md for the complete list of changes.