yao/agent/assistant/load_merge_test.go
Max d264c7a784 Implement search configuration management and testing enhancements
- Introduced a new search configuration structure, allowing for detailed settings for web, knowledge base, database, citation, and weights.
- Updated the `Assistant` model to include a `Search` field, enabling assistant-specific search configurations.
- Enhanced the loading and merging logic for search configurations, ensuring global defaults can be overridden by assistant-specific settings.
- Added comprehensive tests for loading, saving, and updating assistants with search configurations, verifying the integrity of search settings.
- Updated documentation in DESIGN.md to reflect the new search configuration hierarchy and usage, clarifying the interaction between global and assistant-level settings.
2025-12-13 11:19:20 +08:00

366 lines
13 KiB
Go

package assistant_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/yaoapp/yao/agent/assistant"
"github.com/yaoapp/yao/agent/testutils"
)
// TestLoadPathMerge tests loading the merge test assistant
// This verifies that global config is properly merged with assistant-specific config
func TestLoadPathMerge(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean(t)
ast, err := assistant.LoadPath("/assistants/tests/merge")
require.NoError(t, err)
require.NotNil(t, ast)
assert.Equal(t, "tests.merge", ast.ID)
assert.Equal(t, "Merge Config Test Assistant", ast.Name)
// Uses configuration - should merge global with assistant-specific
// Global (from agent/agent.yml):
// vision: "workers.system.vision"
// search: "workers.system.search"
// fetch: "workers.system.fetch"
// audio: (not set)
// querydsl: (not set)
// rerank: (not set)
// Assistant:
// web: "mcp:custom-web"
// keyword: "mcp:custom-keyword"
// Result: assistant values override, global values inherited
assert.NotNil(t, ast.Uses)
// Assistant overrides
assert.Equal(t, "mcp:custom-web", ast.Uses.Web) // overridden by assistant
assert.Equal(t, "mcp:custom-keyword", ast.Uses.Keyword) // overridden by assistant
// Inherited from global (agent/agent.yml)
assert.Equal(t, "workers.system.vision", ast.Uses.Vision) // inherited from global
assert.Equal(t, "workers.system.search", ast.Uses.Search) // inherited from global
assert.Equal(t, "workers.system.fetch", ast.Uses.Fetch) // inherited from global
// Not set in either global or assistant (should be empty)
assert.Empty(t, ast.Uses.Audio) // not set anywhere
assert.Empty(t, ast.Uses.QueryDSL) // not set anywhere
assert.Empty(t, ast.Uses.Rerank) // not set anywhere
// Search configuration - should merge global with assistant-specific
// Global (from agent/search.yml):
// web.provider=tavily, web.max_results=10
// kb.threshold=0.7, kb.graph=false
// db.max_results=20
// keyword.max_keywords=10, keyword.language=auto
// rerank.top_n=10
// citation.format=#ref:{id}, citation.auto_inject_prompt=true
// weights: user=1.0, hook=0.8, auto=0.6
// options.skip_threshold=5
// Assistant:
// web.provider=custom-provider, web.max_results=25
// kb.collections=[merge-test-kb], kb.threshold=0.85
assert.NotNil(t, ast.Search)
// Web config - assistant overrides global
assert.NotNil(t, ast.Search.Web)
assert.Equal(t, "custom-provider", ast.Search.Web.Provider) // overridden
assert.Equal(t, 25, ast.Search.Web.MaxResults) // overridden
// KB config - assistant overrides global
assert.NotNil(t, ast.Search.KB)
assert.Equal(t, []string{"merge-test-kb"}, ast.Search.KB.Collections) // overridden
assert.Equal(t, 0.85, ast.Search.KB.Threshold) // overridden
assert.False(t, ast.Search.KB.Graph) // inherited from global
// DB config - should inherit from global (assistant doesn't define it)
assert.NotNil(t, ast.Search.DB)
assert.Equal(t, 20, ast.Search.DB.MaxResults) // inherited from global
// Keyword config - should inherit from global
assert.NotNil(t, ast.Search.Keyword)
assert.Equal(t, 10, ast.Search.Keyword.MaxKeywords) // inherited from global
assert.Equal(t, "auto", ast.Search.Keyword.Language) // inherited from global
// Rerank config - should inherit from global
assert.NotNil(t, ast.Search.Rerank)
assert.Equal(t, 10, ast.Search.Rerank.TopN) // inherited from global
// Citation config - should inherit from global
assert.NotNil(t, ast.Search.Citation)
assert.Equal(t, "#ref:{id}", ast.Search.Citation.Format) // inherited from global
assert.True(t, ast.Search.Citation.AutoInjectPrompt) // inherited from global
// Weights config - should inherit from global
assert.NotNil(t, ast.Search.Weights)
assert.Equal(t, 1.0, ast.Search.Weights.User) // inherited from global
assert.Equal(t, 0.8, ast.Search.Weights.Hook) // inherited from global
assert.Equal(t, 0.6, ast.Search.Weights.Auto) // inherited from global
// Options config - should inherit from global
assert.NotNil(t, ast.Search.Options)
assert.Equal(t, 5, ast.Search.Options.SkipThreshold) // inherited from global
}
// TestLoadPathMergeOverride tests loading the merge-override test assistant
// This verifies that assistant config completely overrides global config
func TestLoadPathMergeOverride(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean(t)
ast, err := assistant.LoadPath("/assistants/tests/merge-override")
require.NoError(t, err)
require.NotNil(t, ast)
assert.Equal(t, "tests.merge-override", ast.ID)
assert.Equal(t, "Merge Override Test Assistant", ast.Name)
// Uses configuration - all fields should be overridden by assistant
assert.NotNil(t, ast.Uses)
assert.Equal(t, "mcp:custom-vision", ast.Uses.Vision)
assert.Equal(t, "mcp:custom-audio", ast.Uses.Audio)
assert.Equal(t, "mcp:custom-search", ast.Uses.Search)
assert.Equal(t, "mcp:custom-fetch", ast.Uses.Fetch)
assert.Equal(t, "mcp:custom-web", ast.Uses.Web)
assert.Equal(t, "mcp:custom-keyword", ast.Uses.Keyword)
assert.Equal(t, "mcp:custom-querydsl", ast.Uses.QueryDSL)
assert.Equal(t, "mcp:custom-rerank", ast.Uses.Rerank)
// Search configuration - all fields should be overridden by assistant
assert.NotNil(t, ast.Search)
// Web config - all overridden
assert.NotNil(t, ast.Search.Web)
assert.Equal(t, "override-provider", ast.Search.Web.Provider)
assert.Equal(t, "$ENV.OVERRIDE_API_KEY", ast.Search.Web.APIKeyEnv)
assert.Equal(t, 100, ast.Search.Web.MaxResults)
// KB config - all overridden
assert.NotNil(t, ast.Search.KB)
assert.Equal(t, []string{"override-kb"}, ast.Search.KB.Collections)
assert.Equal(t, 0.99, ast.Search.KB.Threshold)
assert.True(t, ast.Search.KB.Graph)
// DB config - all overridden
assert.NotNil(t, ast.Search.DB)
assert.Equal(t, []string{"override-model"}, ast.Search.DB.Models)
assert.Equal(t, 200, ast.Search.DB.MaxResults)
// Keyword config - all overridden
assert.NotNil(t, ast.Search.Keyword)
assert.Equal(t, 20, ast.Search.Keyword.MaxKeywords)
assert.Equal(t, "zh", ast.Search.Keyword.Language)
// QueryDSL config - overridden
assert.NotNil(t, ast.Search.QueryDSL)
assert.True(t, ast.Search.QueryDSL.Strict)
// Rerank config - overridden
assert.NotNil(t, ast.Search.Rerank)
assert.Equal(t, 20, ast.Search.Rerank.TopN)
// Citation config - all overridden
assert.NotNil(t, ast.Search.Citation)
assert.Equal(t, "[override:{id}]", ast.Search.Citation.Format)
assert.False(t, ast.Search.Citation.AutoInjectPrompt)
assert.Equal(t, "Override citation prompt", ast.Search.Citation.CustomPrompt)
// Weights config - all overridden
assert.NotNil(t, ast.Search.Weights)
assert.Equal(t, 2.0, ast.Search.Weights.User)
assert.Equal(t, 1.5, ast.Search.Weights.Hook)
assert.Equal(t, 1.0, ast.Search.Weights.Auto)
// Options config - overridden
assert.NotNil(t, ast.Search.Options)
assert.Equal(t, 10, ast.Search.Options.SkipThreshold)
}
// TestLoadPathMergeEmpty tests loading the merge-empty test assistant
// This verifies that assistant with no uses/search config inherits all from global
func TestLoadPathMergeEmpty(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean(t)
ast, err := assistant.LoadPath("/assistants/tests/merge-empty")
require.NoError(t, err)
require.NotNil(t, ast)
assert.Equal(t, "tests.merge-empty", ast.ID)
assert.Equal(t, "Merge Empty Test Assistant", ast.Name)
// Uses configuration - all inherited from global (agent/agent.yml)
assert.NotNil(t, ast.Uses)
assert.Equal(t, "workers.system.vision", ast.Uses.Vision) // from global
assert.Equal(t, "workers.system.search", ast.Uses.Search) // from global
assert.Equal(t, "workers.system.fetch", ast.Uses.Fetch) // from global
assert.Empty(t, ast.Uses.Audio) // not set in global
assert.Empty(t, ast.Uses.Web) // not set in global
assert.Empty(t, ast.Uses.Keyword) // not set in global
assert.Empty(t, ast.Uses.QueryDSL) // not set in global
assert.Empty(t, ast.Uses.Rerank) // not set in global
// Search configuration - all inherited from global (agent/search.yml)
assert.NotNil(t, ast.Search)
// Web config - from global
assert.NotNil(t, ast.Search.Web)
assert.Equal(t, "tavily", ast.Search.Web.Provider)
assert.Equal(t, 10, ast.Search.Web.MaxResults)
// KB config - from global
assert.NotNil(t, ast.Search.KB)
assert.Equal(t, 0.7, ast.Search.KB.Threshold)
assert.False(t, ast.Search.KB.Graph)
// DB config - from global
assert.NotNil(t, ast.Search.DB)
assert.Equal(t, 20, ast.Search.DB.MaxResults)
// Keyword config - from global
assert.NotNil(t, ast.Search.Keyword)
assert.Equal(t, 10, ast.Search.Keyword.MaxKeywords)
assert.Equal(t, "auto", ast.Search.Keyword.Language)
// Rerank config - from global
assert.NotNil(t, ast.Search.Rerank)
assert.Equal(t, 10, ast.Search.Rerank.TopN)
// Citation config - from global
assert.NotNil(t, ast.Search.Citation)
assert.Equal(t, "#ref:{id}", ast.Search.Citation.Format)
assert.True(t, ast.Search.Citation.AutoInjectPrompt)
// Weights config - from global
assert.NotNil(t, ast.Search.Weights)
assert.Equal(t, 1.0, ast.Search.Weights.User)
assert.Equal(t, 0.8, ast.Search.Weights.Hook)
assert.Equal(t, 0.6, ast.Search.Weights.Auto)
// Options config - from global
assert.NotNil(t, ast.Search.Options)
assert.Equal(t, 5, ast.Search.Options.SkipThreshold)
}
// TestLoadPathUsesAndSearchMerge tests loading fullfields assistant
// This verifies that uses and search configs are properly loaded and merged
func TestLoadPathUsesAndSearchMerge(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean(t)
ast, err := assistant.LoadPath("/assistants/tests/fullfields")
require.NoError(t, err)
require.NotNil(t, ast)
// Uses configuration - assistant-specific values
assert.NotNil(t, ast.Uses)
assert.Equal(t, "agent", ast.Uses.Vision)
assert.Equal(t, "mcp:audio-server", ast.Uses.Audio)
assert.Equal(t, "agent", ast.Uses.Fetch)
assert.Equal(t, "builtin", ast.Uses.Web)
assert.Equal(t, "builtin", ast.Uses.Keyword)
assert.Equal(t, "builtin", ast.Uses.QueryDSL)
assert.Equal(t, "builtin", ast.Uses.Rerank)
// Search configuration - assistant-specific values
assert.NotNil(t, ast.Search)
// Web config - from assistant
assert.NotNil(t, ast.Search.Web)
assert.Equal(t, "tavily", ast.Search.Web.Provider)
assert.Equal(t, 15, ast.Search.Web.MaxResults)
// KB config - from assistant
assert.NotNil(t, ast.Search.KB)
assert.Equal(t, []string{"docs", "faq"}, ast.Search.KB.Collections)
assert.Equal(t, 0.8, ast.Search.KB.Threshold)
assert.True(t, ast.Search.KB.Graph)
// DB config - from assistant
assert.NotNil(t, ast.Search.DB)
assert.Equal(t, []string{"user", "product"}, ast.Search.DB.Models)
assert.Equal(t, 50, ast.Search.DB.MaxResults)
// Citation config - from assistant
assert.NotNil(t, ast.Search.Citation)
assert.Equal(t, "#ref:{id}", ast.Search.Citation.Format)
assert.True(t, ast.Search.Citation.AutoInjectPrompt)
// Weights config - from assistant
assert.NotNil(t, ast.Search.Weights)
assert.Equal(t, 1.0, ast.Search.Weights.User)
assert.Equal(t, 0.9, ast.Search.Weights.Hook)
assert.Equal(t, 0.7, ast.Search.Weights.Auto)
}
// TestLoadPathSearchAssistant tests loading the dedicated search test assistant
func TestLoadPathSearchAssistant(t *testing.T) {
testutils.Prepare(t)
defer testutils.Clean(t)
ast, err := assistant.LoadPath("/assistants/tests/search")
require.NoError(t, err)
require.NotNil(t, ast)
assert.Equal(t, "tests.search", ast.ID)
assert.Equal(t, "Search Config Test Assistant", ast.Name)
// Uses configuration
assert.NotNil(t, ast.Uses)
assert.Equal(t, "builtin", ast.Uses.Web)
assert.Equal(t, "builtin", ast.Uses.Keyword)
assert.Equal(t, "builtin", ast.Uses.QueryDSL)
assert.Equal(t, "builtin", ast.Uses.Rerank)
// Search configuration
assert.NotNil(t, ast.Search)
// Web config
assert.NotNil(t, ast.Search.Web)
assert.Equal(t, "serper", ast.Search.Web.Provider)
assert.Equal(t, "$ENV.SERPER_API_KEY", ast.Search.Web.APIKeyEnv)
assert.Equal(t, 20, ast.Search.Web.MaxResults)
// KB config
assert.NotNil(t, ast.Search.KB)
assert.Equal(t, []string{"knowledge-base", "documents"}, ast.Search.KB.Collections)
assert.Equal(t, 0.75, ast.Search.KB.Threshold)
assert.False(t, ast.Search.KB.Graph)
// DB config
assert.NotNil(t, ast.Search.DB)
assert.Equal(t, []string{"article", "comment"}, ast.Search.DB.Models)
assert.Equal(t, 30, ast.Search.DB.MaxResults)
// Keyword config
assert.NotNil(t, ast.Search.Keyword)
assert.Equal(t, 8, ast.Search.Keyword.MaxKeywords)
assert.Equal(t, "auto", ast.Search.Keyword.Language)
// QueryDSL config
assert.NotNil(t, ast.Search.QueryDSL)
assert.True(t, ast.Search.QueryDSL.Strict)
// Rerank config
assert.NotNil(t, ast.Search.Rerank)
assert.Equal(t, 5, ast.Search.Rerank.TopN)
// Citation config
assert.NotNil(t, ast.Search.Citation)
assert.Equal(t, "#cite:{id}", ast.Search.Citation.Format)
assert.False(t, ast.Search.Citation.AutoInjectPrompt)
assert.Equal(t, "Please cite sources using #cite:{id} format.", ast.Search.Citation.CustomPrompt)
// Weights config
assert.NotNil(t, ast.Search.Weights)
assert.Equal(t, 1.0, ast.Search.Weights.User)
assert.Equal(t, 0.85, ast.Search.Weights.Hook)
assert.Equal(t, 0.65, ast.Search.Weights.Auto)
// Options config
assert.NotNil(t, ast.Search.Options)
assert.Equal(t, 3, ast.Search.Options.SkipThreshold)
}