mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-06 11:28:38 +08:00
### What problem does this PR solve? - Tools management - Pregel engine wrapper for better usage - UT race - Coding style ### Type of change - [x] Refactoring
333 lines
9.2 KiB
Go
333 lines
9.2 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"ragflow/internal/harness/core/schema"
|
|
)
|
|
|
|
// ============================================================
|
|
// Middleware chain error recovery — failure in one layer
|
|
// ============================================================
|
|
|
|
func TestMiddleware_ChainErrorRecovery(t *testing.T) {
|
|
var callOrder []string
|
|
var mu sync.Mutex
|
|
record := func(s string) { mu.Lock(); callOrder = append(callOrder, s); mu.Unlock() }
|
|
|
|
failingMW := &testMiddleware{
|
|
beforeModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) {
|
|
record("failing_beforeModel")
|
|
return ctx, state, fmt.Errorf("middleware failure")
|
|
},
|
|
afterModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) {
|
|
record("failing_afterModel")
|
|
return ctx, state, nil
|
|
},
|
|
beforeAgent: func(ctx context.Context, rc *ReActAgentContext) (context.Context, *ReActAgentContext, error) {
|
|
record("failing_beforeAgent")
|
|
return ctx, rc, nil
|
|
},
|
|
afterAgent: func(ctx context.Context, state *ReActAgentState) (context.Context, error) {
|
|
record("failing_afterAgent")
|
|
return ctx, nil
|
|
},
|
|
}
|
|
|
|
normalMW := &testMiddleware{
|
|
beforeModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) {
|
|
record("normal_beforeModel")
|
|
return ctx, state, nil
|
|
},
|
|
afterModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) {
|
|
record("normal_afterModel")
|
|
return ctx, state, nil
|
|
},
|
|
beforeAgent: func(ctx context.Context, rc *ReActAgentContext) (context.Context, *ReActAgentContext, error) {
|
|
record("normal_beforeAgent")
|
|
return ctx, rc, nil
|
|
},
|
|
afterAgent: func(ctx context.Context, state *ReActAgentState) (context.Context, error) {
|
|
record("normal_afterAgent")
|
|
return ctx, nil
|
|
},
|
|
}
|
|
|
|
model := &mockModel{}
|
|
model.addResp("response")
|
|
|
|
agent := NewReActAgent(&ReActConfig[*schema.Message]{
|
|
Model: model,
|
|
Middlewares: []ReActMiddleware{failingMW, normalMW},
|
|
}).WithName("mw_chain")
|
|
agent.name = "mw_chain"
|
|
|
|
ctx := context.Background()
|
|
iter := agent.Run(ctx, &AgentInput{Messages: []Message{schema.UserMessage("test")}})
|
|
|
|
gotError := false
|
|
for {
|
|
ev, ok := iter.Next()
|
|
if !ok {
|
|
break
|
|
}
|
|
if ev.Err != nil {
|
|
gotError = true
|
|
t.Logf("middleware chain error: %v", ev.Err)
|
|
break
|
|
}
|
|
}
|
|
|
|
mu.Lock()
|
|
order := make([]string, len(callOrder))
|
|
copy(order, callOrder)
|
|
mu.Unlock()
|
|
|
|
t.Logf("middleware call order: %v", order)
|
|
|
|
if !gotError {
|
|
t.Error("expected error from failing middleware, got none")
|
|
}
|
|
|
|
found := false
|
|
for _, s := range order {
|
|
if s == "failing_beforeAgent" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Error("failingMW.BeforeAgent should have been called")
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// Interrupt signal tree serialization — deep nesting
|
|
// ============================================================
|
|
|
|
func TestInterrupt_TreeSerialization(t *testing.T) {
|
|
t.Run("deeply_nested_tree", func(t *testing.T) {
|
|
var root *InterruptSignal
|
|
current := &InterruptSignal{ID: "root", Info: "root", State: "root-state"}
|
|
root = current
|
|
for i := 0; i < 10; i++ {
|
|
child := &InterruptSignal{
|
|
ID: fmt.Sprintf("level_%d", i),
|
|
Info: fmt.Sprintf("info_%d", i),
|
|
State: fmt.Sprintf("state_%d", i),
|
|
}
|
|
current.Children = []*InterruptSignal{child}
|
|
current = child
|
|
}
|
|
|
|
id2addr, id2state := signalToMaps(root)
|
|
|
|
if len(id2addr) != 11 {
|
|
t.Errorf("expected 11 entries in id2addr (root + 10 levels), got %d", len(id2addr))
|
|
}
|
|
if len(id2state) != 11 {
|
|
t.Errorf("expected 11 entries in id2state, got %d", len(id2state))
|
|
}
|
|
t.Logf("deep tree serialization: %d addresses, %d states", len(id2addr), len(id2state))
|
|
})
|
|
|
|
t.Run("nil_signal", func(t *testing.T) {
|
|
id2addr, id2state := signalToMaps(nil)
|
|
if len(id2addr) != 0 || len(id2state) != 0 {
|
|
t.Error("expected empty maps for nil signal")
|
|
}
|
|
})
|
|
|
|
t.Run("empty_signal", func(t *testing.T) {
|
|
id2addr, id2state := signalToMaps(&InterruptSignal{})
|
|
if len(id2addr) != 0 || len(id2state) != 0 {
|
|
t.Error("expected empty maps for empty signal")
|
|
}
|
|
})
|
|
|
|
t.Run("from_interrupt_contexts_empty", func(t *testing.T) {
|
|
sig := FromInterruptContexts(nil)
|
|
if sig != nil {
|
|
t.Error("expected nil for empty input")
|
|
}
|
|
})
|
|
|
|
t.Run("from_interrupt_contexts", func(t *testing.T) {
|
|
ctxs := []*InterruptCtx{
|
|
{ID: "a", Info: "info-a"},
|
|
{ID: "b", Info: "info-b"},
|
|
}
|
|
sig := FromInterruptContexts(ctxs)
|
|
if len(sig.Children) != 2 {
|
|
t.Errorf("expected 2 children, got %d", len(sig.Children))
|
|
}
|
|
})
|
|
}
|
|
|
|
// ============================================================
|
|
// Session values concurrent safety — SetRunLocalValue from goroutines
|
|
// ============================================================
|
|
|
|
func TestSession_ConcurrentValueAccess(t *testing.T) {
|
|
model := &mockModel{}
|
|
model.addResp("done")
|
|
agent := NewReActAgent(&ReActConfig[*schema.Message]{Model: model}).WithName("session_conc")
|
|
agent.name = "session_conc"
|
|
|
|
ctx := context.Background()
|
|
iter := agent.Run(ctx, &AgentInput{Messages: []Message{schema.UserMessage("test")}})
|
|
for {
|
|
ev, ok := iter.Next()
|
|
if !ok {
|
|
break
|
|
}
|
|
_ = ev
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
errs := make(chan error, 20)
|
|
|
|
for i := 0; i < 20; i++ {
|
|
wg.Add(1)
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
err := SetRunLocalValue(context.Background(), fmt.Sprintf("key_%d", id), fmt.Sprintf("val_%d", id))
|
|
if err != nil && !errors.Is(err, errNotInAgentExec) {
|
|
errs <- fmt.Errorf("SetRunLocalValue: %w", err)
|
|
}
|
|
_, _, err = GetRunLocalValue(context.Background(), "test")
|
|
if err != nil && !errors.Is(err, errNotInAgentExec) {
|
|
errs <- fmt.Errorf("GetRunLocalValue: %w", err)
|
|
}
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
close(errs)
|
|
|
|
for err := range errs {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// Callback system — 100K events under streaming
|
|
// ============================================================
|
|
|
|
func TestCallback_HighVolumeEvents(t *testing.T) {
|
|
const numEvents = 1000
|
|
|
|
var callbackCount int32
|
|
|
|
model := &mockModel{}
|
|
model.addResp("response")
|
|
|
|
agent := NewReActAgent(&ReActConfig[*schema.Message]{Model: model}).WithName("cb_volume")
|
|
agent.name = "cb_volume"
|
|
|
|
runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: agent})
|
|
ctx := context.Background()
|
|
|
|
msgs := make([]Message, numEvents)
|
|
for i := 0; i < numEvents; i++ {
|
|
msgs[i] = schema.UserMessage(fmt.Sprintf("msg %d", i))
|
|
}
|
|
|
|
iter := runner.Run(ctx, msgs)
|
|
for {
|
|
ev, ok := iter.Next()
|
|
if !ok {
|
|
break
|
|
}
|
|
if ev.Err != nil {
|
|
t.Logf("callback volume error: %v", ev.Err)
|
|
break
|
|
}
|
|
}
|
|
|
|
t.Logf("callback count: %d", atomic.LoadInt32(&callbackCount))
|
|
}
|
|
|
|
// ============================================================
|
|
// Multiple middleware interaction
|
|
// ============================================================
|
|
|
|
func TestMiddleware_MultipleMiddlewareInteraction(t *testing.T) {
|
|
var callOrder []string
|
|
var mu sync.Mutex
|
|
record := func(s string) { mu.Lock(); callOrder = append(callOrder, s); mu.Unlock() }
|
|
|
|
mw1 := &testMiddleware{
|
|
beforeModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) {
|
|
record("mw1_beforeModel")
|
|
return ctx, state, nil
|
|
},
|
|
afterModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) {
|
|
record("mw1_afterModel")
|
|
return ctx, state, nil
|
|
},
|
|
beforeAgent: func(ctx context.Context, rc *ReActAgentContext) (context.Context, *ReActAgentContext, error) {
|
|
record("mw1_beforeAgent")
|
|
return ctx, rc, nil
|
|
},
|
|
afterAgent: func(ctx context.Context, state *ReActAgentState) (context.Context, error) {
|
|
record("mw1_afterAgent")
|
|
return ctx, nil
|
|
},
|
|
}
|
|
|
|
mw2 := &testMiddleware{
|
|
beforeModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) {
|
|
record("mw2_beforeModel")
|
|
return ctx, state, nil
|
|
},
|
|
afterModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) {
|
|
record("mw2_afterModel")
|
|
return ctx, state, nil
|
|
},
|
|
beforeAgent: func(ctx context.Context, rc *ReActAgentContext) (context.Context, *ReActAgentContext, error) {
|
|
record("mw2_beforeAgent")
|
|
return ctx, rc, nil
|
|
},
|
|
afterAgent: func(ctx context.Context, state *ReActAgentState) (context.Context, error) {
|
|
record("mw2_afterAgent")
|
|
return ctx, nil
|
|
},
|
|
}
|
|
|
|
model := &mockModel{}
|
|
model.addResp("response")
|
|
|
|
agent := NewReActAgent(&ReActConfig[*schema.Message]{
|
|
Model: model,
|
|
Middlewares: []ReActMiddleware{mw1, mw2},
|
|
}).WithName("multi_mw")
|
|
agent.name = "multi_mw"
|
|
|
|
ctx := context.Background()
|
|
iter := agent.Run(ctx, &AgentInput{Messages: []Message{schema.UserMessage("test")}})
|
|
for {
|
|
ev, ok := iter.Next()
|
|
if !ok {
|
|
break
|
|
}
|
|
if ev.Err != nil {
|
|
t.Fatalf("unexpected err: %v", ev.Err)
|
|
}
|
|
}
|
|
|
|
mu.Lock()
|
|
order := make([]string, len(callOrder))
|
|
copy(order, callOrder)
|
|
mu.Unlock()
|
|
|
|
if len(order) < 8 {
|
|
t.Errorf("expected at least 8 middleware hooks, got %d: %v", len(order), order)
|
|
} else {
|
|
t.Logf("multi-middleware call order: %v", order)
|
|
}
|
|
}
|