package core import ( "context" "fmt" "ragflow/internal/harness/core/schema" ) // FailoverConfig configures model failover behavior. type FailoverConfig[M MessageType] struct { // Models contains backup models tried in order after the primary. Models []Model[M] // ShouldFailover is called to decide whether to try the next model. ShouldFailover func(ctx context.Context, err error) bool // GetFailoverModel is called to dynamically select a failover model. GetFailoverModel func(ctx context.Context, err error) Model[M] } type FailoverConfigMsg = FailoverConfig[*schema.Message] // failoverModel provides failover across multiple chat models. type failoverModel[M MessageType] struct { models []Model[M] shouldFailover func(ctx context.Context, err error) bool getFailoverModel func(ctx context.Context, err error) Model[M] } func newFailoverModel[M MessageType](models []Model[M], cfg *FailoverConfig[M]) Model[M] { var sf func(ctx context.Context, err error) bool var gf func(ctx context.Context, err error) Model[M] if cfg != nil { sf = cfg.ShouldFailover gf = cfg.GetFailoverModel } return &failoverModel[M]{ models: models, shouldFailover: sf, getFailoverModel: gf, } } func (m *failoverModel[M]) Generate(ctx context.Context, input []M, opts ...ModelOption) (M, error) { var lastErr error for i, model := range m.models { if i > 0 && m.shouldFailover != nil && !m.shouldFailover(ctx, lastErr) { var zero M return zero, fmt.Errorf("failover skipped: %w", lastErr) } r, err := model.Generate(ctx, input, opts...) if err == nil { return r, nil } lastErr = fmt.Errorf("model[%d]: %w", i, err) } var zero M return zero, fmt.Errorf("all %d models failed: %w", len(m.models), lastErr) } func (m *failoverModel[M]) Stream(ctx context.Context, input []M, opts ...ModelOption) (*schema.StreamReader[M], error) { var lastErr error for i, model := range m.models { if i > 0 && m.shouldFailover != nil && !m.shouldFailover(ctx, lastErr) { return nil, fmt.Errorf("failover skipped: %w", lastErr) } s, err := model.Stream(ctx, input, opts...) if err == nil { return s, nil } lastErr = fmt.Errorf("model[%d]: %w", i, err) } return nil, fmt.Errorf("all %d models failed to stream: %w", len(m.models), lastErr) } func (m *failoverModel[M]) BindTools(tools []*schema.ToolInfo) error { for _, model := range m.models { if err := model.BindTools(tools); err != nil { return err } } return nil } // WithModelFailover creates a failover-wrapped model. func WithModelFailover[M MessageType](primary Model[M], secondaries ...Model[M]) Model[M] { all := append([]Model[M]{primary}, secondaries...) return newFailoverModel(all, nil) }