-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathfallback_router.go
More file actions
194 lines (167 loc) · 5.16 KB
/
Copy pathfallback_router.go
File metadata and controls
194 lines (167 loc) · 5.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package model
import (
"context"
"time"
)
// FallbackRouter tries models in order until one succeeds.
// It implements a circuit breaker pattern to avoid repeatedly trying failed models.
type FallbackRouter struct {
models []Model
breakers []*CircuitBreaker
failureThreshold int
resetTimeout time.Duration
}
// FallbackRouterOption configures a FallbackRouter.
type FallbackRouterOption func(*FallbackRouter)
// WithFailureThreshold sets the number of failures before tripping the circuit.
func WithFailureThreshold(threshold int) FallbackRouterOption {
return func(r *FallbackRouter) {
r.failureThreshold = threshold
}
}
// WithResetTimeout sets the duration before a tripped circuit resets.
func WithResetTimeout(timeout time.Duration) FallbackRouterOption {
return func(r *FallbackRouter) {
r.resetTimeout = timeout
}
}
// NewFallbackRouter creates a new fallback router with circuit breakers.
// Models are tried in order; the first available (non-tripped) model is returned.
func NewFallbackRouter(models []Model, opts ...FallbackRouterOption) *FallbackRouter {
r := &FallbackRouter{
models: models,
failureThreshold: 3,
resetTimeout: 30 * time.Second,
}
for _, opt := range opts {
opt(r)
}
// Create circuit breakers for each model
r.breakers = make([]*CircuitBreaker, len(models))
for i := range models {
r.breakers[i] = NewCircuitBreaker(r.failureThreshold, r.resetTimeout)
}
return r
}
// Route returns the first available model (with non-open circuit).
func (r *FallbackRouter) Route(ctx context.Context, req *Request) (Model, error) {
for i, model := range r.models {
if !r.breakers[i].IsOpen() {
return model, nil
}
}
return nil, ErrNoModelAvailable
}
// RecordSuccess records a successful request for the given model.
func (r *FallbackRouter) RecordSuccess(m Model) {
for i, model := range r.models {
if model == m {
r.breakers[i].RecordSuccess()
return
}
}
}
// RecordFailure records a failed request for the given model.
func (r *FallbackRouter) RecordFailure(m Model) {
for i, model := range r.models {
if model == m {
r.breakers[i].RecordFailure()
return
}
}
}
// CircuitState returns the circuit breaker state for the given model.
func (r *FallbackRouter) CircuitState(m Model) CircuitState {
for i, model := range r.models {
if model == m {
return r.breakers[i].State()
}
}
return CircuitClosed
}
// ResetCircuit resets the circuit breaker for the given model.
func (r *FallbackRouter) ResetCircuit(m Model) {
for i, model := range r.models {
if model == m {
r.breakers[i].Reset()
return
}
}
}
// ResetAllCircuits resets all circuit breakers.
func (r *FallbackRouter) ResetAllCircuits() {
for _, breaker := range r.breakers {
breaker.Reset()
}
}
// AvailableModels returns models with non-open circuits.
func (r *FallbackRouter) AvailableModels() []Model {
var available []Model
for i, model := range r.models {
if !r.breakers[i].IsOpen() {
available = append(available, model)
}
}
return available
}
// FallbackRoutedModel extends RoutedModel to automatically record successes/failures.
// It wraps FallbackRouter and updates circuit breakers based on generation results.
type FallbackRoutedModel struct {
*RoutedModel
fallbackRouter *FallbackRouter
}
// NewFallbackRoutedModel creates a RoutedModel that automatically manages circuit breakers.
func NewFallbackRoutedModel(router *FallbackRouter, opts ...RoutedModelOption) *FallbackRoutedModel {
rm := &FallbackRoutedModel{
fallbackRouter: router,
}
// Wrap the router to track successes
wrappedOpts := append([]RoutedModelOption{}, opts...)
rm.RoutedModel = NewRoutedModel(router, wrappedOpts...)
return rm
}
// RecordSuccess forwards to the fallback router.
func (m *FallbackRoutedModel) RecordSuccess(model Model) {
m.fallbackRouter.RecordSuccess(model)
}
// RecordFailure forwards to the fallback router.
func (m *FallbackRoutedModel) RecordFailure(model Model) {
m.fallbackRouter.RecordFailure(model)
}
// PriorityRouter routes to models based on priority order with health checking.
// Unlike FallbackRouter, it doesn't use circuit breakers but allows explicit health checks.
type PriorityRouter struct {
models []Model
healthCheck func(ctx context.Context, m Model) bool
}
// PriorityRouterOption configures a PriorityRouter.
type PriorityRouterOption func(*PriorityRouter)
// WithHealthCheck sets a function to check if a model is healthy.
func WithHealthCheck(fn func(ctx context.Context, m Model) bool) PriorityRouterOption {
return func(r *PriorityRouter) {
r.healthCheck = fn
}
}
// NewPriorityRouter creates a new priority-based router.
// Models are tried in order; the first healthy model is selected.
func NewPriorityRouter(models []Model, opts ...PriorityRouterOption) *PriorityRouter {
r := &PriorityRouter{
models: models,
}
for _, opt := range opts {
opt(r)
}
return r
}
// Route returns the first healthy model in priority order.
func (r *PriorityRouter) Route(ctx context.Context, req *Request) (Model, error) {
for _, model := range r.models {
if r.healthCheck != nil {
if !r.healthCheck(ctx, model) {
continue
}
}
return model, nil
}
return nil, ErrNoModelAvailable
}