-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathself_healing.go
More file actions
461 lines (377 loc) · 11.8 KB
/
Copy pathself_healing.go
File metadata and controls
461 lines (377 loc) · 11.8 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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
package sdk
import (
"context"
"fmt"
"maps"
"sync"
"time"
errors "github.com/xraph/go-utils/errs"
logger "github.com/xraph/go-utils/log"
"github.com/xraph/go-utils/metrics"
)
// SelfHealingAgent wraps an agent with automatic error recovery and learning.
type SelfHealingAgent struct {
agent *Agent
logger logger.Logger
metrics metrics.Metrics
// Error tracking
errorHistory []ErrorRecord
recoveryStats map[string]*RecoveryStats
mu sync.RWMutex
// Configuration
config SelfHealingConfig
// Learning
strategies []RecoveryStrategy
learnedPattern map[string]RecoveryAction
}
// SelfHealingConfig configures self-healing behavior.
type SelfHealingConfig struct {
MaxRetries int
RetryDelay time.Duration
BackoffMultiplier float64
MaxRetryDelay time.Duration
EnableLearning bool
ErrorHistorySize int
AdaptiveThreshold int // Number of failures before adapting
HealthCheckInterval time.Duration
AutoRecover bool
FallbackEnabled bool
CircuitBreakerEnabled bool
CircuitBreakerThreshold int
}
// ErrorRecord tracks a single error occurrence.
type ErrorRecord struct {
Timestamp time.Duration
Error error
Context map[string]any
RecoveryUsed string
Recovered bool
Attempts int
}
// RecoveryStats tracks recovery success rates.
type RecoveryStats struct {
TotalAttempts int
SuccessfulRecoveries int
FailedRecoveries int
AverageAttempts float64
LastSuccess time.Time
LastFailure time.Time
}
// RecoveryStrategy defines a strategy for recovering from errors.
type RecoveryStrategy interface {
Name() string
CanHandle(err error) bool
Recover(ctx context.Context, agent *Agent, err error) error
Priority() int
}
// RecoveryAction represents a learned recovery action.
type RecoveryAction struct {
StrategyName string
SuccessRate float64
Confidence float64
LastUsed time.Time
}
// NewSelfHealingAgent creates a new self-healing agent.
func NewSelfHealingAgent(agent *Agent, config SelfHealingConfig, logger logger.Logger, metrics metrics.Metrics) *SelfHealingAgent {
if config.MaxRetries == 0 {
config.MaxRetries = 3
}
if config.RetryDelay == 0 {
config.RetryDelay = time.Second
}
if config.BackoffMultiplier == 0 {
config.BackoffMultiplier = 2.0
}
if config.MaxRetryDelay == 0 {
config.MaxRetryDelay = 30 * time.Second
}
if config.ErrorHistorySize == 0 {
config.ErrorHistorySize = 100
}
if config.AdaptiveThreshold == 0 {
config.AdaptiveThreshold = 3
}
if config.CircuitBreakerThreshold == 0 {
config.CircuitBreakerThreshold = 5
}
sha := &SelfHealingAgent{
agent: agent,
logger: logger,
metrics: metrics,
config: config,
errorHistory: make([]ErrorRecord, 0, config.ErrorHistorySize),
recoveryStats: make(map[string]*RecoveryStats),
strategies: make([]RecoveryStrategy, 0),
learnedPattern: make(map[string]RecoveryAction),
}
// Register default strategies
sha.RegisterStrategy(&RetryStrategy{})
sha.RegisterStrategy(&ResetStateStrategy{})
sha.RegisterStrategy(&SimplifyPromptStrategy{})
sha.RegisterStrategy(&FallbackModelStrategy{})
return sha
}
// RegisterStrategy registers a recovery strategy.
func (sha *SelfHealingAgent) RegisterStrategy(strategy RecoveryStrategy) {
sha.mu.Lock()
defer sha.mu.Unlock()
sha.strategies = append(sha.strategies, strategy)
}
// Process processes a request with self-healing.
func (sha *SelfHealingAgent) Process(ctx context.Context, input string) (string, error) {
var lastErr error
delay := sha.config.RetryDelay
for attempt := 0; attempt <= sha.config.MaxRetries; attempt++ {
// Execute the agent
response, err := sha.agent.Execute(ctx, input)
if err == nil {
// Success! Record it
if attempt > 0 {
sha.recordRecovery(lastErr, true, attempt)
}
return response.Content, nil
}
lastErr = err
if sha.logger != nil {
sha.logger.Warn("agent execution failed",
F("attempt", attempt+1),
F("error", err),
)
}
// Try to recover
if sha.config.AutoRecover && attempt < sha.config.MaxRetries {
recoveryErr := sha.tryRecover(ctx, err)
if recoveryErr == nil {
if sha.logger != nil {
sha.logger.Info("recovery successful",
F("attempt", attempt+1),
)
}
// Wait before retry
time.Sleep(delay)
delay = time.Duration(float64(delay) * sha.config.BackoffMultiplier)
if delay > sha.config.MaxRetryDelay {
delay = sha.config.MaxRetryDelay
}
continue
}
if sha.logger != nil {
sha.logger.Error("recovery failed",
F("attempt", attempt+1),
F("recovery_error", recoveryErr),
)
}
}
// Wait before retry
if attempt < sha.config.MaxRetries {
time.Sleep(delay)
delay = time.Duration(float64(delay) * sha.config.BackoffMultiplier)
if delay > sha.config.MaxRetryDelay {
delay = sha.config.MaxRetryDelay
}
}
}
// All retries failed
sha.recordRecovery(lastErr, false, sha.config.MaxRetries+1)
return "", fmt.Errorf("all recovery attempts failed: %w", lastErr)
}
// tryRecover attempts to recover from an error.
func (sha *SelfHealingAgent) tryRecover(ctx context.Context, err error) error {
// Check if we have a learned pattern for this error
if sha.config.EnableLearning {
errorType := fmt.Sprintf("%T", err)
if action, exists := sha.learnedPattern[errorType]; exists {
// Use the learned strategy
for _, strategy := range sha.strategies {
if strategy.Name() == action.StrategyName {
if sha.logger != nil {
sha.logger.Info("using learned recovery strategy",
F("strategy", strategy.Name()),
F("success_rate", action.SuccessRate),
)
}
return strategy.Recover(ctx, sha.agent, err)
}
}
}
}
// Try strategies in priority order
for _, strategy := range sha.strategies {
if !strategy.CanHandle(err) {
continue
}
if sha.logger != nil {
sha.logger.Debug("trying recovery strategy",
F("strategy", strategy.Name()),
)
}
recoveryErr := strategy.Recover(ctx, sha.agent, err)
if recoveryErr == nil {
// Strategy succeeded!
sha.updateRecoveryStats(strategy.Name(), true)
if sha.config.EnableLearning {
sha.learnPattern(err, strategy.Name())
}
return nil
}
sha.updateRecoveryStats(strategy.Name(), false)
}
return errors.New("no recovery strategy succeeded")
}
// recordRecovery records a recovery attempt.
func (sha *SelfHealingAgent) recordRecovery(err error, recovered bool, attempts int) {
sha.mu.Lock()
defer sha.mu.Unlock()
record := ErrorRecord{
Timestamp: time.Duration(time.Now().Unix()),
Error: err,
Recovered: recovered,
Attempts: attempts,
Context: make(map[string]any),
}
sha.errorHistory = append(sha.errorHistory, record)
// Trim history if too large
if len(sha.errorHistory) > sha.config.ErrorHistorySize {
sha.errorHistory = sha.errorHistory[len(sha.errorHistory)-sha.config.ErrorHistorySize:]
}
// Update metrics
if sha.metrics != nil {
if recovered {
sha.metrics.Counter("forge.ai.sdk.selfhealing.recoveries", metrics.WithLabel("status", "success")).Inc()
} else {
sha.metrics.Counter("forge.ai.sdk.selfhealing.recoveries", metrics.WithLabel("status", "failure")).Inc()
}
sha.metrics.Gauge("forge.ai.sdk.selfhealing.attempts", metrics.WithLabel("type", "total")).Set(float64(attempts))
}
}
// updateRecoveryStats updates recovery statistics for a strategy.
func (sha *SelfHealingAgent) updateRecoveryStats(strategyName string, success bool) {
sha.mu.Lock()
defer sha.mu.Unlock()
stats, exists := sha.recoveryStats[strategyName]
if !exists {
stats = &RecoveryStats{}
sha.recoveryStats[strategyName] = stats
}
stats.TotalAttempts++
if success {
stats.SuccessfulRecoveries++
stats.LastSuccess = time.Now()
} else {
stats.FailedRecoveries++
stats.LastFailure = time.Now()
}
stats.AverageAttempts = float64(stats.TotalAttempts) / float64(stats.SuccessfulRecoveries+1)
}
// learnPattern learns from a successful recovery.
func (sha *SelfHealingAgent) learnPattern(err error, strategyName string) {
sha.mu.Lock()
defer sha.mu.Unlock()
errorType := fmt.Sprintf("%T", err)
action, exists := sha.learnedPattern[errorType]
if !exists {
action = RecoveryAction{
StrategyName: strategyName,
SuccessRate: 1.0,
Confidence: 0.5,
LastUsed: time.Now(),
}
} else {
// Update success rate
if action.StrategyName == strategyName {
action.SuccessRate = (action.SuccessRate*0.9 + 1.0*0.1)
action.Confidence = min(action.Confidence+0.1, 1.0)
} else {
// Different strategy worked, reduce confidence
newConfidence := action.Confidence - 0.2
if newConfidence < 0.0 {
newConfidence = 0.0
}
action.Confidence = newConfidence
if action.Confidence < 0.5 {
// Switch to new strategy
action.StrategyName = strategyName
action.SuccessRate = 1.0
action.Confidence = 0.5
}
}
action.LastUsed = time.Now()
}
sha.learnedPattern[errorType] = action
}
// GetRecoveryStats returns recovery statistics.
func (sha *SelfHealingAgent) GetRecoveryStats() map[string]*RecoveryStats {
sha.mu.RLock()
defer sha.mu.RUnlock()
stats := make(map[string]*RecoveryStats)
for k, v := range sha.recoveryStats {
statsCopy := *v
stats[k] = &statsCopy
}
return stats
}
// GetErrorHistory returns the error history.
func (sha *SelfHealingAgent) GetErrorHistory() []ErrorRecord {
sha.mu.RLock()
defer sha.mu.RUnlock()
history := make([]ErrorRecord, len(sha.errorHistory))
copy(history, sha.errorHistory)
return history
}
// GetLearnedPatterns returns learned patterns.
func (sha *SelfHealingAgent) GetLearnedPatterns() map[string]RecoveryAction {
sha.mu.RLock()
defer sha.mu.RUnlock()
patterns := make(map[string]RecoveryAction)
maps.Copy(patterns, sha.learnedPattern)
return patterns
}
// --- Built-in Recovery Strategies ---
// RetryStrategy simply retries the operation.
type RetryStrategy struct{}
func (s *RetryStrategy) Name() string { return "retry" }
func (s *RetryStrategy) Priority() int { return 10 }
func (s *RetryStrategy) CanHandle(err error) bool { return true }
func (s *RetryStrategy) Recover(ctx context.Context, agent *Agent, err error) error {
// Just a simple retry, the actual retry logic is in Process()
return nil
}
// ResetStateStrategy resets the agent state.
type ResetStateStrategy struct{}
func (s *ResetStateStrategy) Name() string { return "reset_state" }
func (s *ResetStateStrategy) Priority() int { return 20 }
func (s *ResetStateStrategy) CanHandle(err error) bool {
// Handle state-related errors
return true
}
func (s *ResetStateStrategy) Recover(ctx context.Context, agent *Agent, err error) error {
// Clear conversation history to reset context
agent.state.History = agent.state.History[:0]
return nil
}
// SimplifyPromptStrategy simplifies the prompt to reduce complexity.
type SimplifyPromptStrategy struct{}
func (s *SimplifyPromptStrategy) Name() string { return "simplify_prompt" }
func (s *SimplifyPromptStrategy) Priority() int { return 30 }
func (s *SimplifyPromptStrategy) CanHandle(err error) bool {
// Handle complexity-related errors
return true
}
func (s *SimplifyPromptStrategy) Recover(ctx context.Context, agent *Agent, err error) error {
// Add system message to simplify responses
agent.state.Data["simplified_mode"] = true
return nil
}
// FallbackModelStrategy switches to a fallback model.
type FallbackModelStrategy struct{}
func (s *FallbackModelStrategy) Name() string { return "fallback_model" }
func (s *FallbackModelStrategy) Priority() int { return 40 }
func (s *FallbackModelStrategy) CanHandle(err error) bool {
// Handle model-related errors
return true
}
func (s *FallbackModelStrategy) Recover(ctx context.Context, agent *Agent, err error) error {
// Switch to a more reliable model (this would need model config)
agent.state.Data["use_fallback_model"] = true
return nil
}