package core import ( "context" "io" "ragflow/internal/harness/core/schema" ) // ---- EventSenderModelWrapper ---- type eventSenderModelWrapper[M MessageType] struct { inner Model[M] execCtx *reActExecCtx } func wrapModelWithEventSender[M MessageType](inner Model[M], ec *reActExecCtx) Model[M] { return &eventSenderModelWrapper[M]{inner: inner, execCtx: ec} } func (w *eventSenderModelWrapper[M]) Generate(ctx context.Context, msgs []M, opts ...ModelOption) (M, error) { if w.execCtx != nil && w.execCtx.suppressEventSend { return w.inner.Generate(ctx, msgs, opts...) } resp, err := w.inner.Generate(ctx, msgs, opts...) if err != nil { return resp, err } if w.execCtx != nil && w.execCtx.generator != nil && !isNilMessage(resp) { w.execCtx.send(typedModelOutputEvent(resp, nil)) } return resp, nil } func (w *eventSenderModelWrapper[M]) Stream(ctx context.Context, msgs []M, opts ...ModelOption) (*schema.StreamReader[M], error) { s, err := w.inner.Stream(ctx, msgs, opts...) if err != nil { return nil, err } if w.execCtx != nil && w.execCtx.suppressEventSend { return s, nil } r := schema.NewStreamReader[M]() go func() { defer r.Close() defer s.Close() var chunks []M for { c, err := s.Recv() if err == io.EOF { break } if err != nil { r.Send(c, err) return } chunks = append(chunks, c) r.Send(c, nil) } if len(chunks) > 0 && w.execCtx != nil { if merged, e := mergeChunks(chunks); e == nil { w.execCtx.send(typedModelOutputEvent(merged, nil)) } } }() return r, nil } func (w *eventSenderModelWrapper[M]) BindTools(tools []*schema.ToolInfo) error { return w.inner.BindTools(tools) } // ---- CallbackInjectionModelWrapper (for tracing/monitoring) ---- type callbackModelWrapper[M MessageType] struct { inner Model[M] } func (w *callbackModelWrapper[M]) Generate(ctx context.Context, msgs []M, opts ...ModelOption) (M, error) { msgs = injectMessageID(msgs) cbs := getCallbacks(ctx) if len(cbs) > 0 { input := &AgentCallbackInput{} if len(msgs) > 0 { switch any(msgs[0]).(type) { case *schema.Message: msgSlice := make([]Message, len(msgs)) for i, m := range msgs { msgSlice[i] = any(m).(*schema.Message) } input.Input = &AgentInput{Messages: msgSlice} } } for _, cb := range cbs { cb.onStart(ctx, input) } } resp, err := w.inner.Generate(ctx, msgs, opts...) if len(cbs) > 0 { if err != nil { for _, cb := range cbs { if cb.onError != nil { cb.onError(ctx, err) } } } evIter, evGen := NewAsyncIteratorPair[*AgentEvent]() if err == nil { evGen.Send(&AgentEvent{ Output: &AgentOutput{MessageOutput: &MessageVariant{Message: any(resp).(*schema.Message)}}, }) } else { evGen.Send(&AgentEvent{Err: err}) } evGen.Close() output := &AgentCallbackOutput{Events: evIter} for _, cb := range cbs { cb.onEnd(ctx, output) } } return resp, err } func (w *callbackModelWrapper[M]) Stream(ctx context.Context, msgs []M, opts ...ModelOption) (*schema.StreamReader[M], error) { cbs := getCallbacks(ctx) if len(cbs) > 0 { input := &AgentCallbackInput{} if len(msgs) > 0 { switch any(msgs[0]).(type) { case *schema.Message: msgSlice := make([]Message, len(msgs)) for i, m := range msgs { msgSlice[i] = any(m).(*schema.Message) } input.Input = &AgentInput{Messages: msgSlice} } } for _, cb := range cbs { cb.onStart(ctx, input) } } s, err := w.inner.Stream(ctx, msgs, opts...) if err != nil { if len(cbs) > 0 { for _, cb := range cbs { if cb.onError != nil { cb.onError(ctx, err) } } evIter, evGen := NewAsyncIteratorPair[*AgentEvent]() evGen.Send(&AgentEvent{Err: err}) evGen.Close() output := &AgentCallbackOutput{Events: evIter} for _, cb := range cbs { cb.onEnd(ctx, output) } } return nil, err } // Wrap stream to fire OnEnd on completion r := schema.NewStreamReader[M]() go func() { defer r.Close() defer s.Close() var allChunks []M for { c, e := s.Recv() if e == io.EOF { break } if e != nil { r.Send(c, e) return } allChunks = append(allChunks, c) r.Send(c, nil) } if len(cbs) > 0 && len(allChunks) > 0 { merged, _ := mergeChunks(allChunks) evIter, evGen := NewAsyncIteratorPair[*AgentEvent]() evGen.Send(&AgentEvent{ Output: &AgentOutput{MessageOutput: &MessageVariant{Message: any(merged).(*schema.Message)}}, }) evGen.Close() output := &AgentCallbackOutput{Events: evIter} for _, cb := range cbs { cb.onEnd(ctx, output) } } }() return r, nil } func (w *callbackModelWrapper[M]) BindTools(tools []*schema.ToolInfo) error { return w.inner.BindTools(tools) } // ---- Model Wrapper Chain Builder ---- // BuildModelWrapperChain builds the complete wrapper chain: // // base → failover → retry → eventSender → user wrappers → callback // // The chain is built from innermost (closest to model) to outermost. // allToolInfos contains the merged tool infos from config.Tools + ToolContributor middlewares. func BuildModelWrapperChain[M MessageType](base Model[M], ec *reActExecCtx, cfg *ReActConfig[M], allToolInfos []*schema.ToolInfo) Model[M] { model := base // 1. Event sender (skip if user middlewares provide their own to avoid duplicates) if !HasUserEventSenderModelWrapper(cfg.Middlewares) { model = wrapModelWithEventSender(model, ec) } // 2. Retry (wraps event sender so retries replay the entire inner chain) if cfg.RetryConfig != nil { model = newTypedRetryModelWrapper(model, cfg.RetryConfig) } // 3. Failover (wraps retry so each failover attempt gets retry behavior) if cfg.FailoverConfig != nil && len(cfg.FailoverConfig.Models) > 0 { allModels := append([]Model[M]{base}, cfg.FailoverConfig.Models...) model = newFailoverModel(allModels, cfg.FailoverConfig) } // 4. User middleware wrappers (outermost) for _, mw := range cfg.Middlewares { if mw == nil { continue } mc := &TypedModelContext[M]{ Tools: allToolInfos, ModelRetryConfig: cfg.RetryConfig, ModelFailoverConfig: cfg.FailoverConfig, } wrapped, err := mw.WrapModel(context.Background(), model, mc) if err == nil && wrapped != nil { model = wrapped } } // 5. State wrapper: message deep copy + ID injection + cancel check (guards against middleware side-effects) var cancelCtx *cancelContext if ec != nil { cancelCtx = ec.cancelCtx } model = newTypedStateModelWrapper(model, cancelCtx) // 6. Callback injection (outermost — wraps everything) model = &callbackModelWrapper[M]{inner: model} return model } // injectMessageID assigns a unique message ID to each message if not already present. // Operates on copies to avoid data races when messages are shared across parallel goroutines. func injectMessageID[M MessageType](msgs []M) []M { for i, msg := range msgs { switch v := any(msg).(type) { case *schema.Message: if v.Extra != nil && GetMessageID(v.Extra) != "" { continue // already has ID, skip } // Deep-copy so concurrent access is safe for shared messages. cp := copyMessage(msg) copied := any(cp).(*schema.Message) copied.Extra = EnsureMessageID(copied.Extra) if c2, ok := any(copied).(M); ok { msgs[i] = c2 } } } return msgs }