- Added a new method to set store settings during assistant initialization, allowing for configuration of storage parameters such as MaxSize and TTL. - Updated context creation methods to streamline the setup process, ensuring that essential fields are populated consistently across various test contexts. - Revised tests to validate the new initialization behavior and context management, ensuring proper handling of assistant settings and context properties.
410 lines
14 KiB
Go
410 lines
14 KiB
Go
package assistant_test
|
|
|
|
import (
|
|
stdContext "context"
|
|
"testing"
|
|
|
|
"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 {
|
|
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",
|
|
}
|
|
|
|
ctx := context.New(stdContext.Background(), authorized, chatID)
|
|
ctx.AssistantID = assistantID
|
|
ctx.Locale = "en-us"
|
|
ctx.Theme = "light"
|
|
ctx.Client = context.Client{
|
|
Type: "web",
|
|
UserAgent: "TestAgent/1.0",
|
|
IP: "127.0.0.1",
|
|
}
|
|
ctx.Referer = context.RefererAPI
|
|
ctx.Accept = context.AcceptWebCUI
|
|
ctx.Route = "/test/route"
|
|
ctx.Metadata = map[string]interface{}{
|
|
"test": "context_metadata",
|
|
}
|
|
return ctx
|
|
}
|
|
|
|
// 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.HookScript == 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.HookScript.Create(ctx, inputMessages, &context.Options{})
|
|
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.HookScript.Create(ctx, inputMessages, &context.Options{})
|
|
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.HookScript.Create(ctx, inputMessages, &context.Options{})
|
|
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.HookScript.Create(ctx, inputMessages, &context.Options{})
|
|
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")
|
|
})
|
|
|
|
// Test 6: ResponseFormat with *context.ResponseFormat
|
|
t.Run("ResponseFormatStruct", func(t *testing.T) {
|
|
freshCtx := newTestContext("chat-test-response-format", "tests.buildrequest")
|
|
inputMessages := []context.Message{{Role: "user", Content: "test message"}}
|
|
|
|
// Create a test agent with response_format in Options
|
|
testAgent := *agent
|
|
strict := true
|
|
testAgent.Options = map[string]interface{}{
|
|
"temperature": 0.7,
|
|
"response_format": &context.ResponseFormat{
|
|
Type: context.ResponseFormatJSONSchema,
|
|
JSONSchema: &context.JSONSchema{
|
|
Name: "test_schema",
|
|
Description: "Test schema description",
|
|
Schema: map[string]interface{}{
|
|
"type": "object",
|
|
"properties": map[string]interface{}{
|
|
"name": map[string]interface{}{
|
|
"type": "string",
|
|
},
|
|
},
|
|
},
|
|
Strict: &strict,
|
|
},
|
|
},
|
|
}
|
|
|
|
_, options, err := testAgent.BuildRequest(freshCtx, inputMessages, nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build LLM request: %s", err.Error())
|
|
}
|
|
|
|
// Verify ResponseFormat
|
|
if options.ResponseFormat == nil {
|
|
t.Fatal("Expected ResponseFormat, got nil")
|
|
}
|
|
|
|
if options.ResponseFormat.Type != context.ResponseFormatJSONSchema {
|
|
t.Errorf("Expected type 'json_schema', got: %s", options.ResponseFormat.Type)
|
|
}
|
|
|
|
if options.ResponseFormat.JSONSchema == nil {
|
|
t.Fatal("Expected JSONSchema, got nil")
|
|
}
|
|
|
|
if options.ResponseFormat.JSONSchema.Name != "test_schema" {
|
|
t.Errorf("Expected schema name 'test_schema', got: %s", options.ResponseFormat.JSONSchema.Name)
|
|
}
|
|
|
|
if options.ResponseFormat.JSONSchema.Description != "Test schema description" {
|
|
t.Errorf("Expected schema description 'Test schema description', got: %s", options.ResponseFormat.JSONSchema.Description)
|
|
}
|
|
|
|
if options.ResponseFormat.JSONSchema.Strict == nil || *options.ResponseFormat.JSONSchema.Strict != true {
|
|
t.Errorf("Expected strict = true, got: %v", options.ResponseFormat.JSONSchema.Strict)
|
|
}
|
|
|
|
t.Log("✓ ResponseFormat with *context.ResponseFormat struct works correctly")
|
|
})
|
|
|
|
// Test 7: ResponseFormat with legacy map[string]interface{}
|
|
t.Run("ResponseFormatLegacyMap", func(t *testing.T) {
|
|
freshCtx := newTestContext("chat-test-response-format-map", "tests.buildrequest")
|
|
inputMessages := []context.Message{{Role: "user", Content: "test message"}}
|
|
|
|
// Create a test agent with legacy map format
|
|
testAgent := *agent
|
|
testAgent.Options = map[string]interface{}{
|
|
"temperature": 0.7,
|
|
"response_format": map[string]interface{}{
|
|
"type": "json_schema",
|
|
"json_schema": map[string]interface{}{
|
|
"name": "legacy_schema",
|
|
"description": "Legacy schema format",
|
|
"schema": map[string]interface{}{
|
|
"type": "object",
|
|
"properties": map[string]interface{}{
|
|
"email": map[string]interface{}{
|
|
"type": "string",
|
|
},
|
|
},
|
|
},
|
|
"strict": true,
|
|
},
|
|
},
|
|
}
|
|
|
|
_, options, err := testAgent.BuildRequest(freshCtx, inputMessages, nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build LLM request: %s", err.Error())
|
|
}
|
|
|
|
// Verify ResponseFormat was converted from map
|
|
if options.ResponseFormat == nil {
|
|
t.Fatal("Expected ResponseFormat, got nil")
|
|
}
|
|
|
|
if options.ResponseFormat.Type != context.ResponseFormatJSONSchema {
|
|
t.Errorf("Expected type 'json_schema', got: %s", options.ResponseFormat.Type)
|
|
}
|
|
|
|
if options.ResponseFormat.JSONSchema == nil {
|
|
t.Fatal("Expected JSONSchema, got nil")
|
|
}
|
|
|
|
if options.ResponseFormat.JSONSchema.Name != "legacy_schema" {
|
|
t.Errorf("Expected schema name 'legacy_schema', got: %s", options.ResponseFormat.JSONSchema.Name)
|
|
}
|
|
|
|
if options.ResponseFormat.JSONSchema.Description != "Legacy schema format" {
|
|
t.Errorf("Expected schema description 'Legacy schema format', got: %s", options.ResponseFormat.JSONSchema.Description)
|
|
}
|
|
|
|
t.Log("✓ ResponseFormat with legacy map[string]interface{} format works correctly")
|
|
})
|
|
|
|
// Test 8: ResponseFormat with simple type (text or json_object)
|
|
t.Run("ResponseFormatSimpleType", func(t *testing.T) {
|
|
freshCtx := newTestContext("chat-test-response-format-simple", "tests.buildrequest")
|
|
inputMessages := []context.Message{{Role: "user", Content: "test message"}}
|
|
|
|
// Create a test agent with simple response_format
|
|
testAgent := *agent
|
|
testAgent.Options = map[string]interface{}{
|
|
"temperature": 0.7,
|
|
"response_format": map[string]interface{}{
|
|
"type": "json_object",
|
|
},
|
|
}
|
|
|
|
_, options, err := testAgent.BuildRequest(freshCtx, inputMessages, nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to build LLM request: %s", err.Error())
|
|
}
|
|
|
|
// Verify ResponseFormat
|
|
if options.ResponseFormat == nil {
|
|
t.Fatal("Expected ResponseFormat, got nil")
|
|
}
|
|
|
|
if options.ResponseFormat.Type != context.ResponseFormatJSON {
|
|
t.Errorf("Expected type 'json_object', got: %s", options.ResponseFormat.Type)
|
|
}
|
|
|
|
if options.ResponseFormat.JSONSchema != nil {
|
|
t.Errorf("Expected JSONSchema to be nil for simple type, got: %v", options.ResponseFormat.JSONSchema)
|
|
}
|
|
|
|
t.Log("✓ ResponseFormat with simple type (json_object) works correctly")
|
|
})
|
|
}
|