- 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.
366 lines
13 KiB
Go
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)
|
|
}
|