mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-03 01:01:56 +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
232 lines
6.4 KiB
Go
232 lines
6.4 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
// === Test struct for schema generation ===
|
|
|
|
type weatherArgs struct {
|
|
City string `json:"city" description:"The city name" required:"true"`
|
|
Country string `json:"country,omitempty" description:"Optional country name"`
|
|
Temp float64 `json:"temp" description:"Temperature" required:"true"`
|
|
Units string `json:"units" enum:"metric,imperial" description:"Temperature units"`
|
|
}
|
|
|
|
type emptyArgs struct{}
|
|
|
|
type nestedArgs struct {
|
|
Query string `json:"query" description:"Search query"`
|
|
Page int `json:"page" description:"Page number"`
|
|
Tags []string `json:"tags" description:"Filter tags"`
|
|
}
|
|
|
|
// ======================== GenerateToolInfo ========================
|
|
|
|
func TestGenerateToolInfo_Basic(t *testing.T) {
|
|
fn := func(ctx context.Context, args *weatherArgs) (string, error) {
|
|
return "sunny", nil
|
|
}
|
|
|
|
info, err := GenerateToolInfo[weatherArgs]("get_weather", "Get current weather", fn)
|
|
if err != nil {
|
|
t.Fatalf("GenerateToolInfo: %v", err)
|
|
}
|
|
if info.Name != "get_weather" {
|
|
t.Errorf("expected 'get_weather', got %s", info.Name)
|
|
}
|
|
if info.Description != "Get current weather" {
|
|
t.Errorf("expected 'Get current weather', got %s", info.Description)
|
|
}
|
|
if info.InputSchema == nil {
|
|
t.Fatal("expected non-nil InputSchema")
|
|
}
|
|
|
|
props, ok := info.InputSchema.(map[string]interface{})["properties"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("expected properties map")
|
|
}
|
|
|
|
// Verify each field exists with correct type
|
|
cityProp, ok := props["city"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("expected city property schema")
|
|
}
|
|
if cityProp["type"] != "string" {
|
|
t.Errorf("expected city type 'string', got %v", cityProp["type"])
|
|
}
|
|
if cityProp["description"] != "The city name" {
|
|
t.Errorf("expected city description 'The city name', got %v", cityProp["description"])
|
|
}
|
|
|
|
tempProp, ok := props["temp"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("expected temp property schema")
|
|
}
|
|
if tempProp["type"] != "number" {
|
|
t.Errorf("expected temp type 'number', got %v", tempProp["type"])
|
|
}
|
|
|
|
unitsProp, ok := props["units"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("expected units property schema")
|
|
}
|
|
if unitsProp["type"] != "string" {
|
|
t.Errorf("expected units type 'string', got %v", unitsProp["type"])
|
|
}
|
|
}
|
|
|
|
func TestGenerateToolInfo_EmptyStruct(t *testing.T) {
|
|
fn := func(ctx context.Context, args *emptyArgs) (string, error) {
|
|
return "no args", nil
|
|
}
|
|
|
|
info, err := GenerateToolInfo[emptyArgs]("noop", "Does nothing", fn)
|
|
if err != nil {
|
|
t.Fatalf("GenerateToolInfo: %v", err)
|
|
}
|
|
if info.Name != "noop" {
|
|
t.Errorf("expected 'noop', got %s", info.Name)
|
|
}
|
|
props := info.InputSchema.(map[string]interface{})["properties"].(map[string]interface{})
|
|
if len(props) != 0 {
|
|
t.Errorf("expected 0 properties, got %d", len(props))
|
|
}
|
|
}
|
|
|
|
func TestGenerateToolInfo_NestedTypes(t *testing.T) {
|
|
fn := func(ctx context.Context, args *nestedArgs) (string, error) {
|
|
return "nested", nil
|
|
}
|
|
|
|
info, err := GenerateToolInfo[nestedArgs]("search", "Search", fn)
|
|
if err != nil {
|
|
t.Fatalf("GenerateToolInfo: %v", err)
|
|
}
|
|
|
|
props := info.InputSchema.(map[string]interface{})["properties"].(map[string]interface{})
|
|
pageProp := props["page"].(map[string]interface{})
|
|
if pageProp["type"] != "integer" {
|
|
t.Errorf("expected page type 'integer', got %v", pageProp["type"])
|
|
}
|
|
|
|
tagsProp := props["tags"].(map[string]interface{})
|
|
if tagsProp["type"] != "array" {
|
|
t.Errorf("expected tags type 'array', got %v", tagsProp["type"])
|
|
}
|
|
}
|
|
|
|
func TestGenerateToolInfo_PointerStruct(t *testing.T) {
|
|
fn := func(ctx context.Context, args *weatherArgs) (string, error) { return "ok", nil }
|
|
|
|
info, err := GenerateToolInfo[*weatherArgs]("ptr_test", "test", fn)
|
|
if err != nil {
|
|
t.Fatalf("GenerateToolInfo pointer: %v", err)
|
|
}
|
|
if info.Name != "ptr_test" {
|
|
t.Errorf("expected 'ptr_test', got %s", info.Name)
|
|
}
|
|
}
|
|
|
|
// ======================== ReflectTool ========================
|
|
|
|
func TestReflectTool_Basic(t *testing.T) {
|
|
tool, err := ReflectTool("greet", "Greet someone",
|
|
func(ctx context.Context, args *weatherArgs) (string, error) {
|
|
return "Hello " + args.City, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ReflectTool: %v", err)
|
|
}
|
|
if tool.Name() != "greet" {
|
|
t.Errorf("expected 'greet', got %s", tool.Name())
|
|
}
|
|
if tool.Description() != "Greet someone" {
|
|
t.Errorf("expected 'Greet someone', got %s", tool.Description())
|
|
}
|
|
|
|
result, err := tool.Invoke(context.Background(), `{"city":"London"}`)
|
|
if err != nil {
|
|
t.Fatalf("Invoke: %v", err)
|
|
}
|
|
if result != "Hello London" {
|
|
t.Errorf("expected 'Hello London', got %s", result)
|
|
}
|
|
}
|
|
|
|
func TestReflectTool_Stream(t *testing.T) {
|
|
tool, err := ReflectTool("echo", "Echo",
|
|
func(ctx context.Context, args *weatherArgs) (string, error) {
|
|
return "echo: " + args.City, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ReflectTool: %v", err)
|
|
}
|
|
|
|
stream, err := tool.Stream(context.Background(), `{"city":"test"}`)
|
|
if err != nil {
|
|
t.Fatalf("Stream: %v", err)
|
|
}
|
|
if stream == nil {
|
|
t.Fatal("expected non-nil stream")
|
|
}
|
|
}
|
|
|
|
func TestReflectTool_ToolInfo(t *testing.T) {
|
|
tool, err := ReflectTool("info_test", "Info test",
|
|
func(ctx context.Context, args *weatherArgs) (string, error) {
|
|
return "info", nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ReflectTool: %v", err)
|
|
}
|
|
|
|
info := tool.ToolInfo()
|
|
if info.Name != "info_test" {
|
|
t.Errorf("expected 'info_test', got %s", info.Name)
|
|
}
|
|
}
|
|
|
|
func TestReflectTool_InvalidJSON(t *testing.T) {
|
|
tool, err := ReflectTool("bad_json", "Bad JSON",
|
|
func(ctx context.Context, args *weatherArgs) (string, error) {
|
|
return "ok", nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ReflectTool: %v", err)
|
|
}
|
|
|
|
_, err = tool.Invoke(context.Background(), `not valid json`)
|
|
if err == nil {
|
|
t.Error("expected error for invalid JSON")
|
|
}
|
|
}
|
|
|
|
func TestMustReflectTool(t *testing.T) {
|
|
tool := MustReflectTool("must", "Must tool",
|
|
func(ctx context.Context, args *weatherArgs) (string, error) {
|
|
return "must ok", nil
|
|
})
|
|
if tool.Name() != "must" {
|
|
t.Errorf("expected 'must', got %s", tool.Name())
|
|
}
|
|
}
|
|
|
|
func TestReflectTool_RegistryIntegration(t *testing.T) {
|
|
r := NewToolRegistry()
|
|
tool := MustReflectTool("registry_test", "Registry test",
|
|
func(ctx context.Context, args *weatherArgs) (string, error) {
|
|
return "registry ok", nil
|
|
})
|
|
r.Register(tool, WithCategory("test"))
|
|
|
|
found, ok := r.Lookup("registry_test")
|
|
if !ok {
|
|
t.Fatal("expected to find tool in registry")
|
|
}
|
|
if found.Name() != "registry_test" {
|
|
t.Errorf("expected 'registry_test', got %s", found.Name())
|
|
}
|
|
}
|