mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-10 05:14:48 +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
146 lines
4.3 KiB
Go
146 lines
4.3 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"ragflow/internal/harness/core/schema"
|
|
)
|
|
|
|
type countingModel struct {
|
|
calls int
|
|
err error
|
|
}
|
|
|
|
func (m *countingModel) Generate(ctx context.Context, msgs []Message, opts ...modelOption) (Message, error) {
|
|
m.calls++
|
|
if m.err != nil {
|
|
return nil, m.err
|
|
}
|
|
return &schema.Message{Role: schema.RoleAssistant, Content: "ok"}, nil
|
|
}
|
|
func (m *countingModel) Stream(ctx context.Context, msgs []Message, opts ...modelOption) (*schema.StreamReader[Message], error) {
|
|
msg, _ := m.Generate(ctx, msgs, opts...)
|
|
return schema.StreamReaderFromArray([]Message{msg}), nil
|
|
}
|
|
func (m *countingModel) BindTools(tools []*schema.ToolInfo) error { return nil }
|
|
|
|
func TestWithModelRetry_NilConfig(t *testing.T) {
|
|
model := &countingModel{}
|
|
wrapped := WithModelRetry(model, nil)
|
|
if wrapped != model {
|
|
t.Error("nil config should return original model")
|
|
}
|
|
}
|
|
|
|
func TestWithModelRetry_ZeroMaxRetries(t *testing.T) {
|
|
model := &countingModel{}
|
|
cfg := &ModelRetryConfig{MaxRetries: 0}
|
|
wrapped := WithModelRetry(model, cfg)
|
|
if wrapped != model {
|
|
t.Error("zero max retries should return original model")
|
|
}
|
|
}
|
|
|
|
func TestWithModelRetry_SuccessFirstTry(t *testing.T) {
|
|
model := &countingModel{}
|
|
cfg := &ModelRetryConfig{MaxRetries: 3}
|
|
wrapped := WithModelRetry(model, cfg)
|
|
|
|
ctx := context.Background()
|
|
msgs := []Message{schema.UserMessage("hi")}
|
|
resp, err := wrapped.Generate(ctx, msgs)
|
|
if err != nil {
|
|
t.Fatalf("Generate: %v", err)
|
|
}
|
|
if resp.Content != "ok" {
|
|
t.Errorf("content = %s", resp.Content)
|
|
}
|
|
if model.calls != 1 {
|
|
t.Errorf("expected 1 call, got %d", model.calls)
|
|
}
|
|
}
|
|
|
|
func TestWithModelRetry_RetriesOnFailure(t *testing.T) {
|
|
callCount := 0
|
|
model := &failingModel{failTimes: 2, callCount: &callCount}
|
|
cfg := &ModelRetryConfig{MaxRetries: 5, IsRetryAble: func(_ context.Context, err error) bool { return true }}
|
|
wrapped := WithModelRetry(model, cfg)
|
|
|
|
ctx := context.Background()
|
|
msgs := []Message{schema.UserMessage("retry me")}
|
|
_, err := wrapped.Generate(ctx, msgs)
|
|
if err != nil {
|
|
t.Fatalf("Generate after retries: %v", err)
|
|
}
|
|
if callCount < 2 {
|
|
t.Errorf("expected >= 2 calls, got %d", callCount)
|
|
}
|
|
}
|
|
|
|
func TestWithModelRetry_Exhausted(t *testing.T) {
|
|
model := &alwaysFailModel{}
|
|
cfg := &ModelRetryConfig{MaxRetries: 2, IsRetryAble: func(_ context.Context, err error) bool { return true }}
|
|
wrapped := WithModelRetry(model, cfg)
|
|
|
|
ctx := context.Background()
|
|
_, err := wrapped.Generate(ctx, []Message{schema.UserMessage("")})
|
|
if err == nil {
|
|
t.Error("expected error after exhausting retries")
|
|
}
|
|
var rErr *RetryExhaustedError
|
|
if !errors.As(err, &rErr) {
|
|
t.Errorf("expected RetryExhaustedError, got %T", err)
|
|
}
|
|
}
|
|
|
|
func TestRetryExhaustedError_Unwrap(t *testing.T) {
|
|
e := &RetryExhaustedError{LastErr: errors.New("boom"), TotalRetries: 3}
|
|
if errors.Unwrap(e) != ErrExceedMaxRetries {
|
|
t.Error("Unwrap should return ErrExceedMaxRetries")
|
|
}
|
|
if e.Error() == "" {
|
|
t.Error("non-empty Error() expected")
|
|
}
|
|
}
|
|
|
|
func TestWillRetryError(t *testing.T) {
|
|
e := &WillRetryError{ErrStr: "retrying", RetryAttempt: 2}
|
|
if e.Error() != "retrying" {
|
|
t.Error("wrong message")
|
|
}
|
|
if e.RejectReason() != nil {
|
|
t.Error("RejectReason should be nil by default")
|
|
}
|
|
}
|
|
|
|
type failingModel struct {
|
|
failTimes int
|
|
callCount *int
|
|
}
|
|
|
|
func (m *failingModel) Generate(_ context.Context, _ []Message, _ ...modelOption) (Message, error) {
|
|
*m.callCount++
|
|
if *m.callCount <= m.failTimes {
|
|
return nil, errors.New("transient failure")
|
|
}
|
|
return &schema.Message{Content: "success"}, nil
|
|
}
|
|
func (m *failingModel) Stream(ctx context.Context, msgs []Message, opts ...modelOption) (*schema.StreamReader[Message], error) {
|
|
msg, err := m.Generate(ctx, msgs, opts...)
|
|
return schema.StreamReaderFromArray([]Message{msg}), err
|
|
}
|
|
func (m *failingModel) BindTools(tools []*schema.ToolInfo) error { return nil }
|
|
|
|
type alwaysFailModel struct{}
|
|
|
|
func (m *alwaysFailModel) Generate(_ context.Context, _ []Message, _ ...modelOption) (Message, error) {
|
|
return nil, errors.New("permanent failure")
|
|
}
|
|
func (m *alwaysFailModel) Stream(ctx context.Context, msgs []Message, opts ...modelOption) (*schema.StreamReader[Message], error) {
|
|
_, err := m.Generate(ctx, msgs, opts...)
|
|
return schema.StreamReaderFromArray([]Message{}), err
|
|
}
|
|
func (m *alwaysFailModel) BindTools(tools []*schema.ToolInfo) error { return nil }
|