- Added global uses configuration to the assistant, allowing for centralized management of vision, audio, search, and fetch settings. - Updated the Assistant struct and related methods to support the new Uses configuration, improving flexibility in assistant operations. - Refactored the Stream method to utilize the new CompletionResponse type, enhancing response handling. - Introduced new methods for building requests and managing capabilities, streamlining the assistant's interaction with various connectors.
262 lines
8.5 KiB
Go
262 lines
8.5 KiB
Go
package assistant_test
|
|
|
|
import (
|
|
stdContext "context"
|
|
"testing"
|
|
|
|
"github.com/yaoapp/gou/plan"
|
|
"github.com/yaoapp/yao/agent/assistant"
|
|
"github.com/yaoapp/yao/agent/context"
|
|
"github.com/yaoapp/yao/agent/testutils"
|
|
"github.com/yaoapp/yao/openapi/oauth/types"
|
|
)
|
|
|
|
// newTestContext creates a Context for testing with commonly used fields pre-populated
|
|
func newTestContext(chatID, assistantID string) *context.Context {
|
|
return &context.Context{
|
|
Context: stdContext.Background(),
|
|
Space: plan.NewMemorySharedSpace(),
|
|
ChatID: chatID,
|
|
AssistantID: assistantID,
|
|
Connector: "",
|
|
Locale: "en-us",
|
|
Theme: "light",
|
|
Client: context.Client{
|
|
Type: "web",
|
|
UserAgent: "TestAgent/1.0",
|
|
IP: "127.0.0.1",
|
|
},
|
|
Referer: context.RefererAPI,
|
|
Accept: context.AcceptWebCUI,
|
|
Route: "/test/route",
|
|
Metadata: map[string]interface{}{
|
|
"test": "context_metadata",
|
|
},
|
|
Authorized: &types.AuthorizedInfo{
|
|
Subject: "test-user",
|
|
ClientID: "test-client-id",
|
|
UserID: "test-user-123",
|
|
TeamID: "test-team-456",
|
|
TenantID: "test-tenant-789",
|
|
SessionID: "test-session-id",
|
|
},
|
|
}
|
|
}
|
|
|
|
// TestBuildRequest tests the BuildRequest function
|
|
func TestBuildRequest(t *testing.T) {
|
|
testutils.Prepare(t)
|
|
defer testutils.Clean(t)
|
|
|
|
agent, err := assistant.Get("tests.buildrequest")
|
|
if err != nil {
|
|
t.Fatalf("Failed to get tests.buildrequest assistant: %s", err.Error())
|
|
}
|
|
|
|
if agent.Script == nil {
|
|
t.Fatalf("The tests.buildrequest assistant has no script")
|
|
}
|
|
|
|
ctx := newTestContext("chat-test-buildrequest", "tests.buildrequest")
|
|
|
|
// Test 1: No override from hook - should use ast.Options and ctx values
|
|
t.Run("NoOverride", func(t *testing.T) {
|
|
inputMessages := []context.Message{{Role: "user", Content: "no_override"}}
|
|
|
|
// Call Create hook
|
|
createResponse, err := agent.Script.Create(ctx, inputMessages)
|
|
if err != nil {
|
|
t.Fatalf("Failed to call Create hook: %s", err.Error())
|
|
}
|
|
|
|
// Build LLM request
|
|
_, options, err := agent.BuildRequest(ctx, inputMessages, createResponse)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build LLM request: %s", err.Error())
|
|
}
|
|
|
|
// Verify options - should use ast.Options values
|
|
if options.Temperature == nil {
|
|
t.Error("Expected temperature from ast.Options, got nil")
|
|
} else if *options.Temperature != 0.5 {
|
|
t.Errorf("Expected temperature 0.5 from ast.Options, got: %f", *options.Temperature)
|
|
}
|
|
|
|
if options.MaxTokens == nil {
|
|
t.Error("Expected max_tokens from ast.Options, got nil")
|
|
} else if *options.MaxTokens != 1000 {
|
|
t.Errorf("Expected max_tokens 1000 from ast.Options, got: %d", *options.MaxTokens)
|
|
}
|
|
|
|
if options.TopP == nil {
|
|
t.Error("Expected top_p from ast.Options, got nil")
|
|
} else if *options.TopP != 0.9 {
|
|
t.Errorf("Expected top_p 0.9 from ast.Options, got: %f", *options.TopP)
|
|
}
|
|
|
|
// Verify ctx values
|
|
if options.Route != "/test/route" {
|
|
t.Errorf("Expected route '/test/route' from ctx, got: %s", options.Route)
|
|
}
|
|
|
|
if options.Metadata == nil {
|
|
t.Error("Expected metadata from ctx, got nil")
|
|
} else if options.Metadata["test"] != "context_metadata" {
|
|
t.Errorf("Expected metadata from ctx, got: %v", options.Metadata)
|
|
}
|
|
|
|
t.Log("✓ No override: ast.Options and ctx values used correctly")
|
|
})
|
|
|
|
// Test 2: Override temperature - hook value should take priority
|
|
t.Run("OverrideTemperature", func(t *testing.T) {
|
|
inputMessages := []context.Message{{Role: "user", Content: "override_temperature"}}
|
|
|
|
createResponse, err := agent.Script.Create(ctx, inputMessages)
|
|
if err != nil {
|
|
t.Fatalf("Failed to call Create hook: %s", err.Error())
|
|
}
|
|
|
|
_, options, err := agent.BuildRequest(ctx, inputMessages, createResponse)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build LLM request: %s", err.Error())
|
|
}
|
|
|
|
// Verify temperature override
|
|
if options.Temperature == nil {
|
|
t.Error("Expected temperature, got nil")
|
|
} else if *options.Temperature != 0.9 {
|
|
t.Errorf("Expected temperature 0.9 from hook, got: %f", *options.Temperature)
|
|
}
|
|
|
|
// Other values should still come from ast.Options
|
|
if options.MaxTokens == nil {
|
|
t.Error("Expected max_tokens from ast.Options, got nil")
|
|
} else if *options.MaxTokens != 1000 {
|
|
t.Errorf("Expected max_tokens 1000 from ast.Options, got: %d", *options.MaxTokens)
|
|
}
|
|
|
|
t.Log("✓ Temperature override: hook value takes priority over ast.Options")
|
|
})
|
|
|
|
// Test 3: Override all - all hook values should take priority
|
|
t.Run("OverrideAll", func(t *testing.T) {
|
|
inputMessages := []context.Message{{Role: "user", Content: "override_all"}}
|
|
|
|
createResponse, err := agent.Script.Create(ctx, inputMessages)
|
|
if err != nil {
|
|
t.Fatalf("Failed to call Create hook: %s", err.Error())
|
|
}
|
|
|
|
_, options, err := agent.BuildRequest(ctx, inputMessages, createResponse)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build LLM request: %s", err.Error())
|
|
}
|
|
|
|
// Verify all overrides
|
|
if options.Temperature == nil || *options.Temperature != 0.8 {
|
|
t.Errorf("Expected temperature 0.8 from hook, got: %v", options.Temperature)
|
|
}
|
|
|
|
if options.MaxTokens == nil || *options.MaxTokens != 2000 {
|
|
t.Errorf("Expected max_tokens 2000 from hook, got: %v", options.MaxTokens)
|
|
}
|
|
|
|
if options.MaxCompletionTokens == nil || *options.MaxCompletionTokens != 1800 {
|
|
t.Errorf("Expected max_completion_tokens 1800 from hook, got: %v", options.MaxCompletionTokens)
|
|
}
|
|
|
|
if options.Audio == nil {
|
|
t.Error("Expected audio from hook, got nil")
|
|
} else {
|
|
if options.Audio.Voice != "alloy" {
|
|
t.Errorf("Expected voice 'alloy', got: %s", options.Audio.Voice)
|
|
}
|
|
if options.Audio.Format != "mp3" {
|
|
t.Errorf("Expected format 'mp3', got: %s", options.Audio.Format)
|
|
}
|
|
}
|
|
|
|
if options.Route != "/hook/route" {
|
|
t.Errorf("Expected route '/hook/route' from hook, got: %s", options.Route)
|
|
}
|
|
|
|
if options.Metadata == nil {
|
|
t.Error("Expected metadata from hook, got nil")
|
|
} else {
|
|
if options.Metadata["source"] != "hook" {
|
|
t.Errorf("Expected metadata['source'] = 'hook', got: %v", options.Metadata["source"])
|
|
}
|
|
}
|
|
|
|
t.Log("✓ Override all: all hook values take priority")
|
|
})
|
|
|
|
// Test 4: Override route and metadata - tests CUI context priority
|
|
t.Run("OverrideRouteMetadata", func(t *testing.T) {
|
|
inputMessages := []context.Message{{Role: "user", Content: "override_route_metadata"}}
|
|
|
|
createResponse, err := agent.Script.Create(ctx, inputMessages)
|
|
if err != nil {
|
|
t.Fatalf("Failed to call Create hook: %s", err.Error())
|
|
}
|
|
|
|
_, options, err := agent.BuildRequest(ctx, inputMessages, createResponse)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build LLM request: %s", err.Error())
|
|
}
|
|
|
|
// Verify route override
|
|
if options.Route != "/custom/route" {
|
|
t.Errorf("Expected route '/custom/route' from hook, got: %s", options.Route)
|
|
}
|
|
|
|
// Verify metadata merge (ctx metadata should be merged with hook metadata)
|
|
if options.Metadata == nil {
|
|
t.Error("Expected metadata, got nil")
|
|
} else {
|
|
// Hook metadata should be present
|
|
if options.Metadata["custom"] != true {
|
|
t.Errorf("Expected metadata['custom'] = true from hook, got: %v", options.Metadata["custom"])
|
|
}
|
|
if options.Metadata["hook_data"] != "test" {
|
|
t.Errorf("Expected metadata['hook_data'] = 'test' from hook, got: %v", options.Metadata["hook_data"])
|
|
}
|
|
// Original ctx metadata should still be there (merged)
|
|
if options.Metadata["test"] != "context_metadata" {
|
|
t.Errorf("Expected original ctx metadata to be preserved, got: %v", options.Metadata)
|
|
}
|
|
}
|
|
|
|
// Other values should still come from ast.Options
|
|
if options.Temperature == nil || *options.Temperature != 0.5 {
|
|
t.Errorf("Expected temperature 0.5 from ast.Options, got: %v", options.Temperature)
|
|
}
|
|
|
|
t.Log("✓ Route and metadata override: hook values take priority, metadata merged")
|
|
})
|
|
|
|
// Test 5: Nil createResponse - should use ast.Options and ctx values
|
|
t.Run("NilCreateResponse", func(t *testing.T) {
|
|
// Create a fresh context for this test
|
|
freshCtx := newTestContext("chat-test-nil", "tests.buildrequest")
|
|
inputMessages := []context.Message{{Role: "user", Content: "test message"}}
|
|
|
|
_, options, err := agent.BuildRequest(freshCtx, inputMessages, nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build LLM request: %s", err.Error())
|
|
}
|
|
|
|
// Should use ast.Options values
|
|
if options.Temperature == nil || *options.Temperature != 0.5 {
|
|
t.Errorf("Expected temperature 0.5 from ast.Options, got: %v", options.Temperature)
|
|
}
|
|
|
|
// Should use ctx values
|
|
if options.Route != "/test/route" {
|
|
t.Errorf("Expected route '/test/route' from ctx, got: %s", options.Route)
|
|
}
|
|
|
|
t.Log("✓ Nil createResponse: ast.Options and ctx values used")
|
|
})
|
|
}
|