streamWithFallback tries the primary model, then fallbacks on failure.
(ctx context.Context, a *agent.Agent, messages []chat.Message, agentTools []tools.Tool)
| 388 | |
| 389 | // streamWithFallback tries the primary model, then fallbacks on failure. |
| 390 | func (rt *wasmRuntime) streamWithFallback(ctx context.Context, a *agent.Agent, messages []chat.Message, agentTools []tools.Tool) (*wasmStreamResult, error) { |
| 391 | prov := rt.providers[a.Name()] |
| 392 | fallbacks := rt.fallbacks[a.Name()] |
| 393 | |
| 394 | chain := append([]provider.Provider{prov}, fallbacks...) |
| 395 | |
| 396 | var lastErr error |
| 397 | for i, p := range chain { |
| 398 | if ctx.Err() != nil { |
| 399 | return nil, ctx.Err() |
| 400 | } |
| 401 | |
| 402 | if i > 0 { |
| 403 | rt.emitEvent(map[string]any{ |
| 404 | "type": "fallback", |
| 405 | "from": chain[i-1].ID(), |
| 406 | "to": p.ID(), |
| 407 | "attempt": float64(i + 1), |
| 408 | "reason": lastErr.Error(), |
| 409 | }) |
| 410 | // Brief backoff between fallback attempts. |
| 411 | select { |
| 412 | case <-ctx.Done(): |
| 413 | return nil, ctx.Err() |
| 414 | case <-time.After(time.Duration(i) * 500 * time.Millisecond): |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | result, err := rt.streamCompletion(ctx, p, messages, agentTools) |
| 419 | if err != nil { |
| 420 | lastErr = err |
| 421 | slog.WarnContext(ctx, "Model attempt failed", "model", p.ID(), "attempt", i+1, "error", err) |
| 422 | continue |
| 423 | } |
| 424 | return result, nil |
| 425 | } |
| 426 | |
| 427 | return nil, fmt.Errorf("all models failed: %w", lastErr) |
| 428 | } |
| 429 | |
| 430 | // streamCompletion runs one streaming completion call and emits deltas. |
| 431 | func (rt *wasmRuntime) streamCompletion(ctx context.Context, prov provider.Provider, messages []chat.Message, agentTools []tools.Tool) (*wasmStreamResult, error) { |
no test coverage detected