diff --git a/.gitignore b/.gitignore index 2612949d..9913d063 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ xgen/v1.0/* *-unit-test docker/build/test db +!agent/search/handlers/db *.sh data/bindata.go.bak share/const.go.bak @@ -49,3 +50,4 @@ share/const.goe openapi/*.md coverage.html agent/assistant/hook/*.test.md +agent/search/TODO.md diff --git a/agent/assistant/assistant.go b/agent/assistant/assistant.go index 5336ec15..b048a92b 100644 --- a/agent/assistant/assistant.go +++ b/agent/assistant/assistant.go @@ -8,6 +8,7 @@ import ( "github.com/yaoapp/yao/agent/content" agentContext "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/i18n" + searchTypes "github.com/yaoapp/yao/agent/search/types" store "github.com/yaoapp/yao/agent/store/types" sui "github.com/yaoapp/yao/sui/core" ) @@ -115,6 +116,8 @@ func (ast *Assistant) Map() map[string]interface{} { "automated": ast.Automated, "placeholder": ast.Placeholder, "locales": ast.Locales, + "uses": ast.Uses, + "search": ast.Search, "created_at": store.ToMySQLTime(ast.CreatedAt), "updated_at": store.ToMySQLTime(ast.UpdatedAt), } @@ -183,7 +186,6 @@ func (ast *Assistant) Clone() *Assistant { CreatedAt: ast.CreatedAt, UpdatedAt: ast.UpdatedAt, }, - Search: ast.Search, HookScript: ast.HookScript, openai: ast.openai, } @@ -346,6 +348,86 @@ func (ast *Assistant) Clone() *Assistant { } } + // Deep copy uses + if ast.Uses != nil { + clone.Uses = &agentContext.Uses{ + Vision: ast.Uses.Vision, + Audio: ast.Uses.Audio, + Search: ast.Uses.Search, + Fetch: ast.Uses.Fetch, + Web: ast.Uses.Web, + Keyword: ast.Uses.Keyword, + QueryDSL: ast.Uses.QueryDSL, + Rerank: ast.Uses.Rerank, + } + } + + // Deep copy search config + if ast.Search != nil { + clone.Search = &searchTypes.Config{} + if ast.Search.Web != nil { + clone.Search.Web = &searchTypes.WebConfig{ + Provider: ast.Search.Web.Provider, + APIKeyEnv: ast.Search.Web.APIKeyEnv, + MaxResults: ast.Search.Web.MaxResults, + } + } + if ast.Search.KB != nil { + clone.Search.KB = &searchTypes.KBConfig{ + Threshold: ast.Search.KB.Threshold, + Graph: ast.Search.KB.Graph, + } + if ast.Search.KB.Collections != nil { + clone.Search.KB.Collections = make([]string, len(ast.Search.KB.Collections)) + copy(clone.Search.KB.Collections, ast.Search.KB.Collections) + } + } + if ast.Search.DB != nil { + clone.Search.DB = &searchTypes.DBConfig{ + MaxResults: ast.Search.DB.MaxResults, + } + if ast.Search.DB.Models != nil { + clone.Search.DB.Models = make([]string, len(ast.Search.DB.Models)) + copy(clone.Search.DB.Models, ast.Search.DB.Models) + } + } + if ast.Search.Keyword != nil { + clone.Search.Keyword = &searchTypes.KeywordConfig{ + MaxKeywords: ast.Search.Keyword.MaxKeywords, + Language: ast.Search.Keyword.Language, + } + } + if ast.Search.QueryDSL != nil { + clone.Search.QueryDSL = &searchTypes.QueryDSLConfig{ + Strict: ast.Search.QueryDSL.Strict, + } + } + if ast.Search.Rerank != nil { + clone.Search.Rerank = &searchTypes.RerankConfig{ + TopN: ast.Search.Rerank.TopN, + } + } + if ast.Search.Citation != nil { + clone.Search.Citation = &searchTypes.CitationConfig{ + Format: ast.Search.Citation.Format, + AutoInjectPrompt: ast.Search.Citation.AutoInjectPrompt, + CustomPrompt: ast.Search.Citation.CustomPrompt, + } + } + if ast.Search.Weights != nil { + clone.Search.Weights = &searchTypes.WeightsConfig{ + User: ast.Search.Weights.User, + Hook: ast.Search.Weights.Hook, + Auto: ast.Search.Weights.Auto, + } + } + if ast.Search.Options != nil { + clone.Search.Options = &searchTypes.OptionsConfig{ + SkipThreshold: ast.Search.Options.SkipThreshold, + } + } + } + return clone } @@ -512,5 +594,29 @@ func (ast *Assistant) Update(data map[string]interface{}) error { ast.Workflow = workflow } + // Uses + if v, has := data["uses"]; has { + uses, err := store.ToUses(v) + if err != nil { + return err + } + ast.Uses = uses + } + + // Search + if v, has := data["search"]; has { + search, err := store.ToSearchConfig(v) + if err != nil { + return err + } + ast.Search = search + } + return ast.Validate() } + +// GetMergedSearchConfig returns the search config for this assistant +// Note: The config is already merged with global config during loading (loadMap) +func (ast *Assistant) GetMergedSearchConfig() *searchTypes.Config { + return ast.Search +} diff --git a/agent/assistant/build.go b/agent/assistant/build.go index 396091b4..260b22ba 100644 --- a/agent/assistant/build.go +++ b/agent/assistant/build.go @@ -532,40 +532,10 @@ func (ast *Assistant) applyCreateResponseOptions(options *context.CompletionOpti // getUses get the Uses configuration with priority: assistant.Uses > global settings // Note: createResponse.Uses (applied in applyCreateResponseOptions) has even higher priority -// Final priority order: createResponse.Uses > assistant.Uses > global settings +// getUses returns the Uses config for this assistant +// Note: The config is already merged with global config during loading (loadMap) func (ast *Assistant) getUses() *context.Uses { - // Priority 1: Assistant-specific Uses configuration - if ast.Uses != nil { - // Create a merged Uses by starting with global, then override with assistant-specific - merged := &context.Uses{} - - // Start with global settings - if globalUses != nil { - merged.Vision = globalUses.Vision - merged.Audio = globalUses.Audio - merged.Search = globalUses.Search - merged.Fetch = globalUses.Fetch - } - - // Override with assistant-specific settings (only if not empty) - if ast.Uses.Vision != "" { - merged.Vision = ast.Uses.Vision - } - if ast.Uses.Audio != "" { - merged.Audio = ast.Uses.Audio - } - if ast.Uses.Search != "" { - merged.Search = ast.Uses.Search - } - if ast.Uses.Fetch != "" { - merged.Fetch = ast.Uses.Fetch - } - - return merged - } - - // Priority 2: Global settings only - return globalUses + return ast.Uses } // applyMCPTools adds MCP tools to completion options and returns samples prompt diff --git a/agent/assistant/load.go b/agent/assistant/load.go index 6d62130f..38ebe832 100644 --- a/agent/assistant/load.go +++ b/agent/assistant/load.go @@ -13,6 +13,7 @@ import ( "github.com/yaoapp/gou/fs" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/i18n" + searchTypes "github.com/yaoapp/yao/agent/search/types" store "github.com/yaoapp/yao/agent/store/types" "github.com/yaoapp/yao/openai" "gopkg.in/yaml.v3" @@ -22,12 +23,12 @@ import ( var loaded = NewCache(200) // 200 is the default capacity var storage store.Store = nil var storeSetting *store.Setting = nil // store setting from agent.yml -var search interface{} = nil var modelCapabilities map[string]gouOpenAI.Capabilities = map[string]gouOpenAI.Capabilities{} -var defaultConnector string = "" // default connector -var globalUses *context.Uses = nil // global uses configuration from agent.yml -var globalPrompts []store.Prompt = nil // global prompts from agent/prompts.yml -var globalKBSetting *store.KBSetting = nil // global KB setting from agent/kb.yml +var defaultConnector string = "" // default connector +var globalUses *context.Uses = nil // global uses configuration from agent.yml +var globalPrompts []store.Prompt = nil // global prompts from agent/prompts.yml +var globalKBSetting *store.KBSetting = nil // global KB setting from agent/kb.yml +var globalSearchConfig *searchTypes.Config = nil // global search config from agent/search.yml // LoadBuiltIn load the built-in assistants func LoadBuiltIn() error { @@ -183,6 +184,16 @@ func GetGlobalKBSetting() *store.KBSetting { return globalKBSetting } +// SetGlobalSearchConfig set the global search config from agent/search.yml +func SetGlobalSearchConfig(config *searchTypes.Config) { + globalSearchConfig = config +} + +// GetGlobalSearchConfig returns the global search config +func GetGlobalSearchConfig() *searchTypes.Config { + return globalSearchConfig +} + // SetCache set the cache func SetCache(capacity int) { ClearCache() @@ -585,19 +596,24 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { } } - // Search options + // Search configuration (from package.yao search block) + // This contains search options like web.max_results, kb.threshold, citation.format, etc. + // Merge hierarchy: global config < assistant config if v, ok := data["search"].(map[string]interface{}); ok { - assistant.Search = &SearchOption{} + var assistantSearch searchTypes.Config raw, err := jsoniter.Marshal(v) if err != nil { return nil, err } - - // Unmarshal the raw data - err = jsoniter.Unmarshal(raw, assistant.Search) + err = jsoniter.Unmarshal(raw, &assistantSearch) if err != nil { return nil, err } + // Merge with global search config + assistant.Search = mergeSearchConfig(globalSearchConfig, &assistantSearch) + } else if globalSearchConfig != nil { + // No assistant-specific config, use global + assistant.Search = globalSearchConfig } // prompts @@ -681,12 +697,14 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { } // uses (wrapper configurations for vision, audio, etc.) + // Merge hierarchy: global uses < assistant uses if uses, has := data["uses"]; has { + var assistantUses *context.Uses switch v := uses.(type) { case *context.Uses: - assistant.Uses = v + assistantUses = v case context.Uses: - assistant.Uses = &v + assistantUses = &v default: raw, err := jsoniter.Marshal(v) if err != nil { @@ -697,8 +715,13 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { if err != nil { return nil, err } - assistant.Uses = &usesConfig + assistantUses = &usesConfig } + // Merge with global uses + assistant.Uses = mergeUses(globalUses, assistantUses) + } else if globalUses != nil { + // No assistant-specific uses, use global + assistant.Uses = globalUses } // Load scripts (hook script and other scripts) @@ -761,3 +784,204 @@ func (ast *Assistant) initialize() error { return nil } + +// mergeUses merges two Uses configs (base < override) +func mergeUses(base, override *context.Uses) *context.Uses { + if base == nil { + return override + } + if override == nil { + return base + } + + result := *base // Copy base + + // Override with non-empty values + if override.Vision != "" { + result.Vision = override.Vision + } + if override.Audio != "" { + result.Audio = override.Audio + } + if override.Search != "" { + result.Search = override.Search + } + if override.Fetch != "" { + result.Fetch = override.Fetch + } + if override.Web != "" { + result.Web = override.Web + } + if override.Keyword != "" { + result.Keyword = override.Keyword + } + if override.QueryDSL != "" { + result.QueryDSL = override.QueryDSL + } + if override.Rerank != "" { + result.Rerank = override.Rerank + } + + return &result +} + +// mergeSearchConfig merges two search configs (base < override) +func mergeSearchConfig(base, override *searchTypes.Config) *searchTypes.Config { + if base == nil { + return override + } + if override == nil { + return base + } + + result := *base // Copy base + + // Merge Web config + if override.Web != nil { + if result.Web == nil { + result.Web = override.Web + } else { + merged := *result.Web + if override.Web.Provider != "" { + merged.Provider = override.Web.Provider + } + if override.Web.APIKeyEnv != "" { + merged.APIKeyEnv = override.Web.APIKeyEnv + } + if override.Web.MaxResults > 0 { + merged.MaxResults = override.Web.MaxResults + } + result.Web = &merged + } + } + + // Merge KB config + if override.KB != nil { + if result.KB == nil { + result.KB = override.KB + } else { + merged := *result.KB + if len(override.KB.Collections) > 0 { + merged.Collections = override.KB.Collections + } + if override.KB.Threshold > 0 { + merged.Threshold = override.KB.Threshold + } + if override.KB.Graph { + merged.Graph = override.KB.Graph + } + result.KB = &merged + } + } + + // Merge DB config + if override.DB != nil { + if result.DB == nil { + result.DB = override.DB + } else { + merged := *result.DB + if len(override.DB.Models) > 0 { + merged.Models = override.DB.Models + } + if override.DB.MaxResults > 0 { + merged.MaxResults = override.DB.MaxResults + } + result.DB = &merged + } + } + + // Merge Keyword config + if override.Keyword != nil { + if result.Keyword == nil { + result.Keyword = override.Keyword + } else { + merged := *result.Keyword + if override.Keyword.MaxKeywords > 0 { + merged.MaxKeywords = override.Keyword.MaxKeywords + } + if override.Keyword.Language != "" { + merged.Language = override.Keyword.Language + } + result.Keyword = &merged + } + } + + // Merge QueryDSL config + if override.QueryDSL != nil { + if result.QueryDSL == nil { + result.QueryDSL = override.QueryDSL + } else { + merged := *result.QueryDSL + if override.QueryDSL.Strict { + merged.Strict = override.QueryDSL.Strict + } + result.QueryDSL = &merged + } + } + + // Merge Rerank config + if override.Rerank != nil { + if result.Rerank == nil { + result.Rerank = override.Rerank + } else { + merged := *result.Rerank + if override.Rerank.TopN > 0 { + merged.TopN = override.Rerank.TopN + } + result.Rerank = &merged + } + } + + // Merge Citation config + if override.Citation != nil { + if result.Citation == nil { + result.Citation = override.Citation + } else { + merged := *result.Citation + if override.Citation.Format != "" { + merged.Format = override.Citation.Format + } + // AutoInjectPrompt is a bool, so we check if it's explicitly set + // by checking if the whole Citation block was provided + merged.AutoInjectPrompt = override.Citation.AutoInjectPrompt + if override.Citation.CustomPrompt != "" { + merged.CustomPrompt = override.Citation.CustomPrompt + } + result.Citation = &merged + } + } + + // Merge Weights config + if override.Weights != nil { + if result.Weights == nil { + result.Weights = override.Weights + } else { + merged := *result.Weights + if override.Weights.User > 0 { + merged.User = override.Weights.User + } + if override.Weights.Hook > 0 { + merged.Hook = override.Weights.Hook + } + if override.Weights.Auto > 0 { + merged.Auto = override.Weights.Auto + } + result.Weights = &merged + } + } + + // Merge Options config + if override.Options != nil { + if result.Options == nil { + result.Options = override.Options + } else { + merged := *result.Options + if override.Options.SkipThreshold > 0 { + merged.SkipThreshold = override.Options.SkipThreshold + } + result.Options = &merged + } + } + + return &result +} diff --git a/agent/assistant/load_merge_test.go b/agent/assistant/load_merge_test.go new file mode 100644 index 00000000..db630c3f --- /dev/null +++ b/agent/assistant/load_merge_test.go @@ -0,0 +1,366 @@ +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) +} diff --git a/agent/assistant/load_store_test.go b/agent/assistant/load_store_test.go index 75d376ac..86283ac1 100644 --- a/agent/assistant/load_store_test.go +++ b/agent/assistant/load_store_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/yaoapp/yao/agent/assistant" "github.com/yaoapp/yao/agent/context" + searchTypes "github.com/yaoapp/yao/agent/search/types" store "github.com/yaoapp/yao/agent/store/types" "github.com/yaoapp/yao/agent/testutils" "github.com/yaoapp/yao/openapi/oauth/types" @@ -933,3 +934,246 @@ function Create(ctx: any, messages: any[]): any { assert.False(t, *res.DisableGlobalPrompts) }) } + +// TestLoadStoreWithSearchConfig tests loading assistant with search configuration from database +func TestLoadStoreWithSearchConfig(t *testing.T) { + testutils.Prepare(t) + defer testutils.Clean(t) + + assistantID := "test.store-with-search" + now := time.Now().UnixNano() + + ast := &assistant.Assistant{ + AssistantModel: store.AssistantModel{ + ID: assistantID, + Name: "Test With Search Config", + Type: "assistant", + Connector: "gpt-4o", + Uses: &context.Uses{ + Vision: "agent", + Audio: "mcp:audio-server", + Fetch: "agent", + Web: "builtin", + Keyword: "builtin", + QueryDSL: "builtin", + Rerank: "builtin", + }, + Search: &searchTypes.Config{ + Web: &searchTypes.WebConfig{ + Provider: "tavily", + MaxResults: 15, + }, + KB: &searchTypes.KBConfig{ + Collections: []string{"docs", "faq"}, + Threshold: 0.8, + Graph: true, + }, + DB: &searchTypes.DBConfig{ + Models: []string{"user", "product"}, + MaxResults: 50, + }, + Keyword: &searchTypes.KeywordConfig{ + MaxKeywords: 8, + Language: "auto", + }, + QueryDSL: &searchTypes.QueryDSLConfig{ + Strict: true, + }, + Rerank: &searchTypes.RerankConfig{ + TopN: 5, + }, + Citation: &searchTypes.CitationConfig{ + Format: "#cite:{id}", + AutoInjectPrompt: false, + CustomPrompt: "Please cite sources.", + }, + Weights: &searchTypes.WeightsConfig{ + User: 1.0, + Hook: 0.85, + Auto: 0.65, + }, + Options: &searchTypes.OptionsConfig{ + SkipThreshold: 3, + }, + }, + CreatedAt: now, + UpdatedAt: now, + }, + } + + err := ast.Save() + require.NoError(t, err) + + defer func() { + storage := assistant.GetStorage() + if storage != nil { + storage.DeleteAssistant(assistantID) + } + assistant.GetCache().Clear() + }() + + assistant.GetCache().Clear() + + loaded, err := assistant.Get(assistantID) + require.NoError(t, err) + require.NotNil(t, loaded) + + // Verify Uses + require.NotNil(t, loaded.Uses) + assert.Equal(t, "agent", loaded.Uses.Vision) + assert.Equal(t, "mcp:audio-server", loaded.Uses.Audio) + assert.Equal(t, "agent", loaded.Uses.Fetch) + assert.Equal(t, "builtin", loaded.Uses.Web) + assert.Equal(t, "builtin", loaded.Uses.Keyword) + assert.Equal(t, "builtin", loaded.Uses.QueryDSL) + assert.Equal(t, "builtin", loaded.Uses.Rerank) + + // Verify Search config + require.NotNil(t, loaded.Search) + + // Web config + require.NotNil(t, loaded.Search.Web) + assert.Equal(t, "tavily", loaded.Search.Web.Provider) + assert.Equal(t, 15, loaded.Search.Web.MaxResults) + + // KB config + require.NotNil(t, loaded.Search.KB) + assert.Equal(t, []string{"docs", "faq"}, loaded.Search.KB.Collections) + assert.Equal(t, 0.8, loaded.Search.KB.Threshold) + assert.True(t, loaded.Search.KB.Graph) + + // DB config + require.NotNil(t, loaded.Search.DB) + assert.Equal(t, []string{"user", "product"}, loaded.Search.DB.Models) + assert.Equal(t, 50, loaded.Search.DB.MaxResults) + + // Keyword config + require.NotNil(t, loaded.Search.Keyword) + assert.Equal(t, 8, loaded.Search.Keyword.MaxKeywords) + assert.Equal(t, "auto", loaded.Search.Keyword.Language) + + // QueryDSL config + require.NotNil(t, loaded.Search.QueryDSL) + assert.True(t, loaded.Search.QueryDSL.Strict) + + // Rerank config + require.NotNil(t, loaded.Search.Rerank) + assert.Equal(t, 5, loaded.Search.Rerank.TopN) + + // Citation config + require.NotNil(t, loaded.Search.Citation) + assert.Equal(t, "#cite:{id}", loaded.Search.Citation.Format) + assert.False(t, loaded.Search.Citation.AutoInjectPrompt) + assert.Equal(t, "Please cite sources.", loaded.Search.Citation.CustomPrompt) + + // Weights config + require.NotNil(t, loaded.Search.Weights) + assert.Equal(t, 1.0, loaded.Search.Weights.User) + assert.Equal(t, 0.85, loaded.Search.Weights.Hook) + assert.Equal(t, 0.65, loaded.Search.Weights.Auto) + + // Options config + require.NotNil(t, loaded.Search.Options) + assert.Equal(t, 3, loaded.Search.Options.SkipThreshold) +} + +// TestLoadStoreWithPartialSearchConfig tests loading assistant with partial search configuration +func TestLoadStoreWithPartialSearchConfig(t *testing.T) { + testutils.Prepare(t) + defer testutils.Clean(t) + + assistantID := "test.store-partial-search" + now := time.Now().UnixNano() + + ast := &assistant.Assistant{ + AssistantModel: store.AssistantModel{ + ID: assistantID, + Name: "Test Partial Search Config", + Type: "assistant", + Connector: "gpt-4o", + Search: &searchTypes.Config{ + Web: &searchTypes.WebConfig{ + Provider: "serper", + }, + // Only web config, others are nil + }, + CreatedAt: now, + UpdatedAt: now, + }, + } + + err := ast.Save() + require.NoError(t, err) + + defer func() { + storage := assistant.GetStorage() + if storage != nil { + storage.DeleteAssistant(assistantID) + } + assistant.GetCache().Clear() + }() + + assistant.GetCache().Clear() + + loaded, err := assistant.Get(assistantID) + require.NoError(t, err) + require.NotNil(t, loaded) + + // Verify Search config + require.NotNil(t, loaded.Search) + + // Web config should be set + require.NotNil(t, loaded.Search.Web) + assert.Equal(t, "serper", loaded.Search.Web.Provider) + + // Other configs should be nil + assert.Nil(t, loaded.Search.KB) + assert.Nil(t, loaded.Search.DB) + assert.Nil(t, loaded.Search.Keyword) + assert.Nil(t, loaded.Search.QueryDSL) + assert.Nil(t, loaded.Search.Rerank) + assert.Nil(t, loaded.Search.Citation) + assert.Nil(t, loaded.Search.Weights) + assert.Nil(t, loaded.Search.Options) +} + +// TestLoadStoreWithoutSearchConfig tests loading assistant without search configuration +func TestLoadStoreWithoutSearchConfig(t *testing.T) { + testutils.Prepare(t) + defer testutils.Clean(t) + + assistantID := "test.store-no-search" + now := time.Now().UnixNano() + + ast := &assistant.Assistant{ + AssistantModel: store.AssistantModel{ + ID: assistantID, + Name: "Test No Search Config", + Type: "assistant", + Connector: "gpt-4o", + // No Search config + CreatedAt: now, + UpdatedAt: now, + }, + } + + err := ast.Save() + require.NoError(t, err) + + defer func() { + storage := assistant.GetStorage() + if storage != nil { + storage.DeleteAssistant(assistantID) + } + assistant.GetCache().Clear() + }() + + assistant.GetCache().Clear() + + loaded, err := assistant.Get(assistantID) + require.NoError(t, err) + require.NotNil(t, loaded) + + // Search config should be nil + assert.Nil(t, loaded.Search) +} diff --git a/agent/assistant/types.go b/agent/assistant/types.go index 2d22c047..f5a39b44 100644 --- a/agent/assistant/types.go +++ b/agent/assistant/types.go @@ -21,12 +21,6 @@ type API interface { GetPlaceholder(locale string) *store.Placeholder } -// SearchOption the search option -type SearchOption struct { - WebSearch *bool `json:"web_search,omitempty" yaml:"web_search,omitempty"` // Whether to search the web - Knowledge *bool `json:"knowledge,omitempty" yaml:"knowledge,omitempty"` // Whether to search the knowledge -} - // Script the script scripts except hook script type Script struct { *v8.Script @@ -35,16 +29,13 @@ type Script struct { // Assistant the assistant type Assistant struct { store.AssistantModel - Search *SearchOption `json:"search,omitempty" yaml:"search,omitempty"` // Whether this assistant supports search - HookScript *hook.Script `json:"-" yaml:"-"` // Hook Script (index.ts) - Scripts map[string]*Script `json:"-" yaml:"-"` // Other scripts + HookScript *hook.Script `json:"-" yaml:"-"` // Hook Script (index.ts) + Scripts map[string]*Script `json:"-" yaml:"-"` // Other scripts // Internal // =============================== openai *api.OpenAI // OpenAI API - search bool // Whether this assistant supports search vision bool // Whether this assistant supports vision - // toolCalls bool // Whether this assistant supports tool_calls } // MCPTool represents a simplified MCP tool for building LLM requests diff --git a/agent/context/types_llm.go b/agent/context/types_llm.go index ed5d09b2..760caeec 100644 --- a/agent/context/types_llm.go +++ b/agent/context/types_llm.go @@ -10,8 +10,14 @@ import ( type Uses struct { Vision string `json:"vision,omitempty"` // Vision processing tool. Format: "agent" or "mcp:server_id" Audio string `json:"audio,omitempty"` // Audio processing tool. Format: "agent" or "mcp:server_id" - Search string `json:"search,omitempty"` // Search tool. Format: "agent" or "mcp:server_id" + Search string `json:"search,omitempty"` // Search tool. Format: "builtin", "disabled", "", "mcp:." Fetch string `json:"fetch,omitempty"` // Fetch/retrieval tool. Format: "agent" or "mcp:server_id" + + // Search-related processing tools (NLP) + Web string `json:"web,omitempty"` // Web search handler: "builtin", "", "mcp:." + Keyword string `json:"keyword,omitempty"` // Keyword extraction: "builtin", "", "mcp:." + QueryDSL string `json:"querydsl,omitempty"` // QueryDSL generation: "builtin", "", "mcp:." + Rerank string `json:"rerank,omitempty"` // Result reranking: "builtin", "", "mcp:." } // VisionFormat specifies the vision input format diff --git a/agent/load.go b/agent/load.go index dafac72e..cba97c2f 100644 --- a/agent/load.go +++ b/agent/load.go @@ -10,6 +10,8 @@ import ( "github.com/yaoapp/yao/agent/assistant" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/i18n" + searchDefaults "github.com/yaoapp/yao/agent/search/defaults" + searchTypes "github.com/yaoapp/yao/agent/search/types" storeMongo "github.com/yaoapp/yao/agent/store/mongo" storeRedis "github.com/yaoapp/yao/agent/store/redis" store "github.com/yaoapp/yao/agent/store/types" @@ -92,6 +94,12 @@ func Load(cfg config.Config) error { return err } + // Initialize Search Configuration + err = initSearchConfig() + if err != nil { + return err + } + // Initialize Assistant err = initAssistant() if err != nil { @@ -222,6 +230,10 @@ func initAssistant() error { assistant.SetGlobalKBSetting(agentDSL.KB) } + if agentDSL.Search != nil { + assistant.SetGlobalSearchConfig(agentDSL.Search) + } + // Load Built-in Assistants err := assistant.LoadBuiltIn() if err != nil { @@ -261,6 +273,177 @@ func initKBConfig() error { return nil } +// initSearchConfig initialize the search configuration from agent/search.yml +func initSearchConfig() error { + // Start with system defaults + agentDSL.Search = searchDefaults.SystemDefaults + + path := filepath.Join("agent", "search.yml") + if exists, _ := application.App.Exists(path); !exists { + return nil // Search config is optional, use defaults + } + + // Read the search configuration + bytes, err := application.App.Read(path) + if err != nil { + return err + } + + var searchConfig searchTypes.Config + err = application.Parse("search.yml", bytes, &searchConfig) + if err != nil { + return err + } + + // Merge with defaults + agentDSL.Search = mergeSearchConfig(searchDefaults.SystemDefaults, &searchConfig) + return nil +} + +// mergeSearchConfig merges two search configs (base < override) +func mergeSearchConfig(base, override *searchTypes.Config) *searchTypes.Config { + if base == nil { + return override + } + if override == nil { + return base + } + + result := *base // Copy base + + // Merge Web config + if override.Web != nil { + if result.Web == nil { + result.Web = override.Web + } else { + if override.Web.Provider != "" { + result.Web.Provider = override.Web.Provider + } + if override.Web.APIKeyEnv != "" { + result.Web.APIKeyEnv = override.Web.APIKeyEnv + } + if override.Web.MaxResults > 0 { + result.Web.MaxResults = override.Web.MaxResults + } + } + } + + // Merge KB config + if override.KB != nil { + if result.KB == nil { + result.KB = override.KB + } else { + if len(override.KB.Collections) > 0 { + result.KB.Collections = override.KB.Collections + } + if override.KB.Threshold > 0 { + result.KB.Threshold = override.KB.Threshold + } + if override.KB.Graph { + result.KB.Graph = override.KB.Graph + } + } + } + + // Merge DB config + if override.DB != nil { + if result.DB == nil { + result.DB = override.DB + } else { + if len(override.DB.Models) > 0 { + result.DB.Models = override.DB.Models + } + if override.DB.MaxResults > 0 { + result.DB.MaxResults = override.DB.MaxResults + } + } + } + + // Merge Keyword config + if override.Keyword != nil { + if result.Keyword == nil { + result.Keyword = override.Keyword + } else { + if override.Keyword.MaxKeywords > 0 { + result.Keyword.MaxKeywords = override.Keyword.MaxKeywords + } + if override.Keyword.Language != "" { + result.Keyword.Language = override.Keyword.Language + } + } + } + + // Merge QueryDSL config + if override.QueryDSL != nil { + result.QueryDSL = override.QueryDSL + } + + // Merge Rerank config + if override.Rerank != nil { + if result.Rerank == nil { + result.Rerank = override.Rerank + } else { + if override.Rerank.TopN > 0 { + result.Rerank.TopN = override.Rerank.TopN + } + } + } + + // Merge Citation config + if override.Citation != nil { + if result.Citation == nil { + result.Citation = override.Citation + } else { + if override.Citation.Format != "" { + result.Citation.Format = override.Citation.Format + } + // AutoInjectPrompt is a bool, need to check if explicitly set + result.Citation.AutoInjectPrompt = override.Citation.AutoInjectPrompt + if override.Citation.CustomPrompt != "" { + result.Citation.CustomPrompt = override.Citation.CustomPrompt + } + } + } + + // Merge Weights config + if override.Weights != nil { + if result.Weights == nil { + result.Weights = override.Weights + } else { + if override.Weights.User > 0 { + result.Weights.User = override.Weights.User + } + if override.Weights.Hook > 0 { + result.Weights.Hook = override.Weights.Hook + } + if override.Weights.Auto > 0 { + result.Weights.Auto = override.Weights.Auto + } + } + } + + // Merge Options config + if override.Options != nil { + if result.Options == nil { + result.Options = override.Options + } else { + if override.Options.SkipThreshold > 0 { + result.Options.SkipThreshold = override.Options.SkipThreshold + } + } + } + + return &result +} + +// GetSearchConfig returns the global search configuration +func GetSearchConfig() *searchTypes.Config { + if agentDSL == nil { + return searchDefaults.SystemDefaults + } + return agentDSL.Search +} + // defaultAssistant get the default assistant func defaultAssistant() (*assistant.Assistant, error) { if agentDSL.Uses == nil || agentDSL.Uses.Default == "" { diff --git a/agent/load_test.go b/agent/load_test.go index 7f238951..fd34d01f 100644 --- a/agent/load_test.go +++ b/agent/load_test.go @@ -94,6 +94,49 @@ func TestLoad(t *testing.T) { assert.Equal(t, "__yao.utf8", agent.KB.Chat.DocumentDefaults.Converter.ProviderID) assert.Equal(t, "standard-text", agent.KB.Chat.DocumentDefaults.Converter.OptionID) }) + + t.Run("LoadSearchConfig", func(t *testing.T) { + // Search configuration should be loaded from agent/search.yml + assert.NotNil(t, agent.Search) + + // Verify web config + assert.NotNil(t, agent.Search.Web) + assert.Equal(t, "tavily", agent.Search.Web.Provider) + assert.Equal(t, 10, agent.Search.Web.MaxResults) + + // Verify KB config + assert.NotNil(t, agent.Search.KB) + assert.Equal(t, 0.7, agent.Search.KB.Threshold) + assert.False(t, agent.Search.KB.Graph) + + // Verify DB config + assert.NotNil(t, agent.Search.DB) + assert.Equal(t, 20, agent.Search.DB.MaxResults) + + // Verify keyword config + assert.NotNil(t, agent.Search.Keyword) + assert.Equal(t, 10, agent.Search.Keyword.MaxKeywords) + assert.Equal(t, "auto", agent.Search.Keyword.Language) + + // Verify rerank config + assert.NotNil(t, agent.Search.Rerank) + assert.Equal(t, 10, agent.Search.Rerank.TopN) + + // Verify citation config + assert.NotNil(t, agent.Search.Citation) + assert.Equal(t, "#ref:{id}", agent.Search.Citation.Format) + assert.True(t, agent.Search.Citation.AutoInjectPrompt) + + // Verify weights config + assert.NotNil(t, agent.Search.Weights) + assert.Equal(t, 1.0, agent.Search.Weights.User) + assert.Equal(t, 0.8, agent.Search.Weights.Hook) + assert.Equal(t, 0.6, agent.Search.Weights.Auto) + + // Verify options config + assert.NotNil(t, agent.Search.Options) + assert.Equal(t, 5, agent.Search.Options.SkipThreshold) + }) } func TestGetGlobalPrompts(t *testing.T) { diff --git a/agent/search/DESIGN.md b/agent/search/DESIGN.md index e2b770bc..88f93681 100644 --- a/agent/search/DESIGN.md +++ b/agent/search/DESIGN.md @@ -1089,7 +1089,7 @@ function Create(ctx, messages, options) { Configuration follows a three-layer hierarchy (later overrides earlier): 1. **System Built-in Defaults** - Hardcoded sensible defaults -2. **Global Configuration** - `agent/agent.yml` (uses) + `agent/search.yao` (search options) +2. **Global Configuration** - `agent/agent.yml` (uses) + `agent/search.yml` (search options) 3. **Assistant Configuration** - `assistants//package.yao` (uses + search options) ### Uses Configuration @@ -1158,7 +1158,7 @@ package defaults import "github.com/yaoapp/yao/agent/search/types" // SystemDefaults provides hardcoded default values -// Used by agent/load.go for merging with agent/search.yao +// Used by agent/load.go for merging with agent/search.yml var SystemDefaults = &types.Config{ // Web search defaults Web: &types.WebConfig{ @@ -1251,12 +1251,12 @@ import ( var searchConfig *searchTypes.Config -// initSearchConfig initialize the search configuration from agent/search.yao +// initSearchConfig initialize the search configuration from agent/search.yml func initSearchConfig() error { // Start with system defaults searchConfig = searchDefaults.SystemDefaults - path := filepath.Join("agent", "search.yao") + path := filepath.Join("agent", "search.yml") if exists, _ := application.App.Exists(path); !exists { return nil // Use defaults } @@ -1268,7 +1268,7 @@ func initSearchConfig() error { } var cfg searchTypes.Config - err = application.Parse("search.yao", bytes, &cfg) + err = application.Parse("search.yml", bytes, &cfg) if err != nil { return err } @@ -1304,62 +1304,54 @@ func (ast *Assistant) GetMergedSearchConfig() *searchTypes.Config { ### Global Configuration -`agent/search.yao` - Override system defaults for all assistants: +`agent/search.yml` - Override system defaults for all assistants: -```jsonc -{ - // Web search settings - "web": { - "provider": "tavily", // "tavily", "serper" (builtin providers only) - "api_key_env": "TAVILY_API_KEY", - "max_results": 10 - }, +```yaml +# Global Search Configuration +# These settings apply to all assistants unless overridden by assistant-specific configurations. - // Knowledge base search settings - "kb": { - "threshold": 0.7, // Similarity threshold - "graph": false // Enable GraphRAG association - }, +# Web search settings +web: + provider: "tavily" # "tavily", "serper" (builtin providers only) + api_key_env: "TAVILY_API_KEY" + max_results: 10 - // Database search settings - "db": { - "max_results": 20 - }, +# Knowledge base search settings +kb: + threshold: 0.7 # Similarity threshold + graph: false # Enable GraphRAG association - // Keyword extraction options (uses.keyword) - "keyword": { - "max_keywords": 10, - "language": "auto" // "auto", "en", "zh", etc. - }, +# Database search settings +db: + max_results: 20 - // QueryDSL generation options (uses.querydsl) - "querydsl": { - "strict": false // Strict mode: fail if generation fails - }, +# Keyword extraction options (uses.keyword) +keyword: + max_keywords: 10 + language: "auto" # "auto", "en", "zh", etc. - // Rerank options (uses.rerank) - "rerank": { - "top_n": 10 // Return top N results after reranking - }, +# QueryDSL generation options (uses.querydsl) +querydsl: + strict: false # Strict mode: fail if generation fails - // Citation format for LLM references - "citation": { - "format": "#ref:{id}", - "auto_inject_prompt": true // Auto-inject citation instructions to system prompt - }, +# Rerank options (uses.rerank) +rerank: + top_n: 10 # Return top N results after reranking - // Source weighting for result merging - "weights": { - "user": 1.0, // User-provided DataContent (highest priority) - "hook": 0.8, // Hook ctx.search.*() results - "auto": 0.6 // Auto search results - }, +# Citation format for LLM references +citation: + format: "#ref:{id}" + auto_inject_prompt: true # Auto-inject citation instructions to system prompt - // Search behavior options - "options": { - "skip_threshold": 5 // Skip auto search if user provides >= N results - } -} +# Source weighting for result merging +weights: + user: 1.0 # User-provided DataContent (highest priority) + hook: 0.8 # Hook ctx.search.*() results + auto: 0.6 # Auto search results + +# Search behavior options +options: + skip_threshold: 5 # Skip auto search if user provides >= N results ``` ### Assistant Configuration @@ -1380,7 +1372,7 @@ func (ast *Assistant) GetMergedSearchConfig() *searchTypes.Config { "rerank": "mcp:my-server.rerank" // Use MCP tool for reranking }, - // Search configuration (overrides agent/search.yao) + // Search configuration (overrides agent/search.yml) "search": { // Overrides global web settings "web": { @@ -2017,7 +2009,7 @@ if (result.error) { Configuration is merged with later layers overriding earlier ones: 1. **System Built-in** - Hardcoded defaults (lowest priority) -2. **Global-level** - `agent/agent.yml` (uses) + `agent/search.yao` (search options) +2. **Global-level** - `agent/agent.yml` (uses) + `agent/search.yml` (search options) 3. **Assistant-level** - `assistants//package.yao` (uses + search) 4. **Hook-level** - CreateHook return `uses.search` value 5. **Request-level** - `options.uses.search` in Stream() call (highest priority) @@ -2301,19 +2293,15 @@ Stream() **Configuration:** -Global defaults (`agent/search.yao`): +Global defaults (`agent/search.yml`): -```jsonc -{ - "weights": { - "user": 1.0, // User-provided DataContent - "hook": 0.8, // Hook ctx.search.*() results - "auto": 0.6 // Auto search results - }, - "options": { - "skip_threshold": 5 // Skip auto search if user provides >= N results - } -} +```yaml +weights: + user: 1.0 # User-provided DataContent + hook: 0.8 # Hook ctx.search.*() results + auto: 0.6 # Auto search results +options: + skip_threshold: 5 # Skip auto search if user provides >= N results ``` Assistant-level override (`assistants//package.yao`): diff --git a/agent/search/citation.go b/agent/search/citation.go new file mode 100644 index 00000000..22a735ed --- /dev/null +++ b/agent/search/citation.go @@ -0,0 +1,27 @@ +package search + +import ( + "fmt" + "sync/atomic" +) + +// CitationGenerator generates unique citation IDs +type CitationGenerator struct { + counter uint64 +} + +// NewCitationGenerator creates a new citation generator +func NewCitationGenerator() *CitationGenerator { + return &CitationGenerator{} +} + +// Next generates the next citation ID +func (g *CitationGenerator) Next() string { + n := atomic.AddUint64(&g.counter, 1) + return fmt.Sprintf("ref_%03d", n) +} + +// Reset resets the counter (for testing) +func (g *CitationGenerator) Reset() { + atomic.StoreUint64(&g.counter, 0) +} diff --git a/agent/search/defaults/defaults.go b/agent/search/defaults/defaults.go new file mode 100644 index 00000000..3a3ebe37 --- /dev/null +++ b/agent/search/defaults/defaults.go @@ -0,0 +1,63 @@ +package defaults + +import "github.com/yaoapp/yao/agent/search/types" + +// SystemDefaults provides hardcoded default values +// Used by agent/load.go for merging with agent/search.yao +var SystemDefaults = &types.Config{ + // Web search defaults + Web: &types.WebConfig{ + Provider: "tavily", + MaxResults: 10, + }, + + // KB search defaults + KB: &types.KBConfig{ + Threshold: 0.7, + Graph: false, + }, + + // DB search defaults + DB: &types.DBConfig{ + MaxResults: 20, + }, + + // Keyword extraction options (uses.keyword) + Keyword: &types.KeywordConfig{ + MaxKeywords: 10, + Language: "auto", + }, + + // QueryDSL generation options (uses.querydsl) + QueryDSL: &types.QueryDSLConfig{ + Strict: false, + }, + + // Rerank options (uses.rerank) + Rerank: &types.RerankConfig{ + TopN: 10, + }, + + // Citation + Citation: &types.CitationConfig{ + Format: "#ref:{id}", + AutoInjectPrompt: true, + }, + + // Source weights + Weights: &types.WeightsConfig{ + User: 1.0, + Hook: 0.8, + Auto: 0.6, + }, + + // Behavior options + Options: &types.OptionsConfig{ + SkipThreshold: 5, + }, +} + +// GetWeight returns the weight for a source type using default config +func GetWeight(source types.SourceType) float64 { + return SystemDefaults.GetWeight(source) +} diff --git a/agent/search/handlers/db/handler.go b/agent/search/handlers/db/handler.go new file mode 100644 index 00000000..0d85925a --- /dev/null +++ b/agent/search/handlers/db/handler.go @@ -0,0 +1,34 @@ +package db + +import ( + "github.com/yaoapp/yao/agent/search/types" +) + +// Handler implements DB search +type Handler struct { + usesQueryDSL string // "builtin", "", "mcp:." + config *types.DBConfig // DB search configuration +} + +// NewHandler creates a new DB search handler +func NewHandler(usesQueryDSL string, cfg *types.DBConfig) *Handler { + return &Handler{usesQueryDSL: usesQueryDSL, config: cfg} +} + +// Type returns the search type this handler supports +func (h *Handler) Type() types.SearchType { + return types.SearchTypeDB +} + +// Search converts NL to QueryDSL and executes +// TODO: Implement actual search logic +func (h *Handler) Search(req *types.Request) (*types.Result, error) { + // Skeleton implementation - returns empty result + return &types.Result{ + Type: types.SearchTypeDB, + Query: req.Query, + Source: req.Source, + Items: []*types.ResultItem{}, + Total: 0, + }, nil +} diff --git a/agent/search/handlers/kb/handler.go b/agent/search/handlers/kb/handler.go new file mode 100644 index 00000000..005597d9 --- /dev/null +++ b/agent/search/handlers/kb/handler.go @@ -0,0 +1,33 @@ +package kb + +import ( + "github.com/yaoapp/yao/agent/search/types" +) + +// Handler implements KB search +type Handler struct { + config *types.KBConfig // KB search configuration +} + +// NewHandler creates a new KB search handler +func NewHandler(cfg *types.KBConfig) *Handler { + return &Handler{config: cfg} +} + +// Type returns the search type this handler supports +func (h *Handler) Type() types.SearchType { + return types.SearchTypeKB +} + +// Search executes vector search and optional graph association +// TODO: Implement actual search logic +func (h *Handler) Search(req *types.Request) (*types.Result, error) { + // Skeleton implementation - returns empty result + return &types.Result{ + Type: types.SearchTypeKB, + Query: req.Query, + Source: req.Source, + Items: []*types.ResultItem{}, + Total: 0, + }, nil +} diff --git a/agent/search/handlers/web/handler.go b/agent/search/handlers/web/handler.go new file mode 100644 index 00000000..35e93282 --- /dev/null +++ b/agent/search/handlers/web/handler.go @@ -0,0 +1,34 @@ +package web + +import ( + "github.com/yaoapp/yao/agent/search/types" +) + +// Handler implements web search +type Handler struct { + usesWeb string // "builtin", "", "mcp:." + config *types.WebConfig // Web search configuration +} + +// NewHandler creates a new web search handler +func NewHandler(usesWeb string, cfg *types.WebConfig) *Handler { + return &Handler{usesWeb: usesWeb, config: cfg} +} + +// Type returns the search type this handler supports +func (h *Handler) Type() types.SearchType { + return types.SearchTypeWeb +} + +// Search executes web search based on uses.web mode +// TODO: Implement actual search logic +func (h *Handler) Search(req *types.Request) (*types.Result, error) { + // Skeleton implementation - returns empty result + return &types.Result{ + Type: types.SearchTypeWeb, + Query: req.Query, + Source: req.Source, + Items: []*types.ResultItem{}, + Total: 0, + }, nil +} diff --git a/agent/search/interfaces/handler.go b/agent/search/interfaces/handler.go new file mode 100644 index 00000000..0820f10c --- /dev/null +++ b/agent/search/interfaces/handler.go @@ -0,0 +1,14 @@ +package interfaces + +import ( + "github.com/yaoapp/yao/agent/search/types" +) + +// Handler defines the interface for search implementations +type Handler interface { + // Type returns the search type this handler supports + Type() types.SearchType + + // Search executes the search and returns results + Search(req *types.Request) (*types.Result, error) +} diff --git a/agent/search/interfaces/nlp.go b/agent/search/interfaces/nlp.go new file mode 100644 index 00000000..a387244e --- /dev/null +++ b/agent/search/interfaces/nlp.go @@ -0,0 +1,20 @@ +package interfaces + +import ( + "github.com/yaoapp/yao/agent/search/types" +) + +// KeywordExtractor extracts keywords for web search +type KeywordExtractor interface { + // Extract extracts search keywords from user message + Extract(content string, opts *types.KeywordOptions) ([]string, error) +} + +// QueryDSLGenerator generates QueryDSL for DB search +type QueryDSLGenerator interface { + // Generate converts natural language to QueryDSL + Generate(query string, schemas []*types.ModelSchema) (*types.QueryDSL, error) +} + +// Note: Embedding is handled by KB collection's own config (embedding provider + model), +// not defined here. See KB handler for details. diff --git a/agent/search/interfaces/reranker.go b/agent/search/interfaces/reranker.go new file mode 100644 index 00000000..58f3dd3e --- /dev/null +++ b/agent/search/interfaces/reranker.go @@ -0,0 +1,11 @@ +package interfaces + +import ( + "github.com/yaoapp/yao/agent/search/types" +) + +// Reranker reorders search results by relevance +type Reranker interface { + // Rerank reorders results based on query relevance + Rerank(query string, items []*types.ResultItem, opts *types.RerankOptions) ([]*types.ResultItem, error) +} diff --git a/agent/search/interfaces/searcher.go b/agent/search/interfaces/searcher.go new file mode 100644 index 00000000..ae22746d --- /dev/null +++ b/agent/search/interfaces/searcher.go @@ -0,0 +1,17 @@ +package interfaces + +import ( + "github.com/yaoapp/yao/agent/search/types" +) + +// Searcher is the main interface exposed to external callers +type Searcher interface { + // Search executes a single search request + Search(req *types.Request) (*types.Result, error) + + // SearchMultiple executes multiple searches (potentially in parallel) + SearchMultiple(reqs []*types.Request) ([]*types.Result, error) + + // BuildReferences converts search results to unified Reference format for LLM + BuildReferences(results []*types.Result) []*types.Reference +} diff --git a/agent/search/reference.go b/agent/search/reference.go new file mode 100644 index 00000000..19a3ab5b --- /dev/null +++ b/agent/search/reference.go @@ -0,0 +1,102 @@ +package search + +import ( + "fmt" + "strings" + + "github.com/yaoapp/yao/agent/search/types" +) + +// DefaultCitationPrompt is the default prompt for citation instructions +const DefaultCitationPrompt = `You have access to reference data in tags. Each has: +- id: Citation identifier +- type: Data type (web/kb/db) +- weight: Relevance weight (1.0=highest priority, 0.6=lowest) +- source: Origin (user=user-provided, hook=assistant-searched, auto=auto-searched) + +Prioritize higher-weight references when answering. + +When citing a reference, use this exact HTML format: +[{id}] + +Example: According to the product data[ref_001], the price is $999.` + +// BuildReferences converts search results to unified Reference format +func BuildReferences(results []*types.Result) []*types.Reference { + var refs []*types.Reference + for _, result := range results { + if result == nil { + continue + } + for _, item := range result.Items { + if item == nil { + continue + } + refs = append(refs, &types.Reference{ + ID: item.CitationID, + Type: item.Type, + Source: item.Source, + Weight: item.Weight, + Score: item.Score, + Title: item.Title, + Content: item.Content, + URL: item.URL, + }) + } + } + return refs +} + +// FormatReferencesXML formats references as XML for LLM context +func FormatReferencesXML(refs []*types.Reference) string { + if len(refs) == 0 { + return "" + } + + var sb strings.Builder + sb.WriteString("\n") + + for _, ref := range refs { + if ref == nil { + continue + } + sb.WriteString(fmt.Sprintf(``, + ref.ID, ref.Type, ref.Weight, ref.Source)) + sb.WriteString("\n") + + if ref.Title != "" { + sb.WriteString(ref.Title) + sb.WriteString("\n") + } + sb.WriteString(ref.Content) + if ref.URL != "" { + sb.WriteString("\nURL: ") + sb.WriteString(ref.URL) + } + sb.WriteString("\n\n") + } + + sb.WriteString("") + return sb.String() +} + +// GetCitationPrompt returns the citation instruction prompt +func GetCitationPrompt(cfg *types.CitationConfig) string { + if cfg == nil { + return DefaultCitationPrompt + } + if cfg.CustomPrompt != "" { + return cfg.CustomPrompt + } + return DefaultCitationPrompt +} + +// BuildReferenceContext builds the complete reference context for LLM +func BuildReferenceContext(results []*types.Result, cfg *types.CitationConfig) *types.ReferenceContext { + refs := BuildReferences(results) + return &types.ReferenceContext{ + References: refs, + XML: FormatReferencesXML(refs), + Prompt: GetCitationPrompt(cfg), + } +} diff --git a/agent/search/registry.go b/agent/search/registry.go new file mode 100644 index 00000000..a3984b94 --- /dev/null +++ b/agent/search/registry.go @@ -0,0 +1,29 @@ +package search + +import ( + "github.com/yaoapp/yao/agent/search/interfaces" + "github.com/yaoapp/yao/agent/search/types" +) + +// Registry manages search handlers +type Registry struct { + handlers map[types.SearchType]interfaces.Handler +} + +// NewRegistry creates a new handler registry +func NewRegistry() *Registry { + return &Registry{ + handlers: make(map[types.SearchType]interfaces.Handler), + } +} + +// Register registers a handler for a search type +func (r *Registry) Register(handler interfaces.Handler) { + r.handlers[handler.Type()] = handler +} + +// Get returns the handler for a search type +func (r *Registry) Get(t types.SearchType) (interfaces.Handler, bool) { + h, ok := r.handlers[t] + return h, ok +} diff --git a/agent/search/search.go b/agent/search/search.go new file mode 100644 index 00000000..23896756 --- /dev/null +++ b/agent/search/search.go @@ -0,0 +1,125 @@ +package search + +import ( + "sync" + + "github.com/yaoapp/yao/agent/search/handlers/db" + "github.com/yaoapp/yao/agent/search/handlers/kb" + "github.com/yaoapp/yao/agent/search/handlers/web" + "github.com/yaoapp/yao/agent/search/interfaces" + "github.com/yaoapp/yao/agent/search/types" +) + +// Searcher is the main search implementation +type Searcher struct { + config *types.Config // Merged config (global + assistant) + handlers map[types.SearchType]interfaces.Handler + reranker interfaces.Reranker + citation *CitationGenerator +} + +// Uses contains the search-specific uses configuration +// These are extracted from context.Uses and search config +type Uses struct { + Search string // "builtin", "disabled", "", "mcp:." + Web string // "builtin", "", "mcp:." + Keyword string // "builtin", "", "mcp:." + QueryDSL string // "builtin", "", "mcp:." + Rerank string // "builtin", "", "mcp:." +} + +// New creates a new Searcher instance +// cfg: merged config from agent/load.go + assistant config +// uses: merged uses configuration (global → assistant → hook) +func New(cfg *types.Config, uses *Uses) *Searcher { + if uses == nil { + uses = &Uses{} + } + if cfg == nil { + cfg = &types.Config{} + } + + return &Searcher{ + config: cfg, + handlers: map[types.SearchType]interfaces.Handler{ + types.SearchTypeWeb: web.NewHandler(uses.Web, cfg.Web), + types.SearchTypeKB: kb.NewHandler(cfg.KB), + types.SearchTypeDB: db.NewHandler(uses.QueryDSL, cfg.DB), + }, + reranker: newBuiltinReranker(), // TODO: use uses.Rerank to select reranker + citation: NewCitationGenerator(), + } +} + +// Search executes a single search request +func (s *Searcher) Search(req *types.Request) (*types.Result, error) { + handler, ok := s.handlers[req.Type] + if !ok { + return &types.Result{Error: "unsupported search type"}, nil + } + + // Execute search + result, err := handler.Search(req) + if err != nil { + return &types.Result{Error: err.Error()}, nil + } + + // Assign weights based on source + for _, item := range result.Items { + item.Weight = s.config.GetWeight(req.Source) + } + + // Rerank if requested + if req.Rerank != nil && s.reranker != nil { + result.Items, _ = s.reranker.Rerank(req.Query, result.Items, req.Rerank) + } + + // Generate citation IDs + for _, item := range result.Items { + item.CitationID = s.citation.Next() + } + + return result, nil +} + +// SearchMultiple executes multiple searches in parallel +func (s *Searcher) SearchMultiple(reqs []*types.Request) ([]*types.Result, error) { + results := make([]*types.Result, len(reqs)) + var wg sync.WaitGroup + var mu sync.Mutex + + for i, req := range reqs { + wg.Add(1) + go func(idx int, r *types.Request) { + defer wg.Done() + result, _ := s.Search(r) + mu.Lock() + results[idx] = result + mu.Unlock() + }(i, req) + } + + wg.Wait() + return results, nil +} + +// BuildReferences converts search results to unified Reference format +func (s *Searcher) BuildReferences(results []*types.Result) []*types.Reference { + return BuildReferences(results) +} + +// builtinReranker is a simple score-based reranker +type builtinReranker struct{} + +func newBuiltinReranker() *builtinReranker { + return &builtinReranker{} +} + +func (r *builtinReranker) Rerank(query string, items []*types.ResultItem, opts *types.RerankOptions) ([]*types.ResultItem, error) { + // Simple implementation: sort by score (already sorted in most cases) + // TODO: Implement proper reranking logic + if opts != nil && opts.TopN > 0 && opts.TopN < len(items) { + return items[:opts.TopN], nil + } + return items, nil +} diff --git a/agent/search/types/config.go b/agent/search/types/config.go new file mode 100644 index 00000000..78443bc9 --- /dev/null +++ b/agent/search/types/config.go @@ -0,0 +1,117 @@ +package types + +// Config represents the complete search configuration +type Config struct { + Web *WebConfig `json:"web,omitempty" yaml:"web,omitempty"` + KB *KBConfig `json:"kb,omitempty" yaml:"kb,omitempty"` + DB *DBConfig `json:"db,omitempty" yaml:"db,omitempty"` + Keyword *KeywordConfig `json:"keyword,omitempty" yaml:"keyword,omitempty"` + QueryDSL *QueryDSLConfig `json:"querydsl,omitempty" yaml:"querydsl,omitempty"` + Rerank *RerankConfig `json:"rerank,omitempty" yaml:"rerank,omitempty"` + Citation *CitationConfig `json:"citation,omitempty" yaml:"citation,omitempty"` + Weights *WeightsConfig `json:"weights,omitempty" yaml:"weights,omitempty"` + Options *OptionsConfig `json:"options,omitempty" yaml:"options,omitempty"` +} + +// WebConfig for web search settings +// Note: uses.web determines the mode (builtin/agent/mcp) +// Provider is only used when uses.web = "builtin" +type WebConfig struct { + Provider string `json:"provider,omitempty" yaml:"provider,omitempty"` // "tavily" or "serper" (for builtin mode) + APIKeyEnv string `json:"api_key_env,omitempty" yaml:"api_key_env,omitempty"` // Environment variable for API key + MaxResults int `json:"max_results,omitempty" yaml:"max_results,omitempty"` // Max results (default: 10) +} + +// KBConfig for knowledge base search settings +type KBConfig struct { + Collections []string `json:"collections,omitempty" yaml:"collections,omitempty"` // Default collections + Threshold float64 `json:"threshold,omitempty" yaml:"threshold,omitempty"` // Similarity threshold (default: 0.7) + Graph bool `json:"graph,omitempty" yaml:"graph,omitempty"` // Enable GraphRAG (default: false) +} + +// DBConfig for database search settings +type DBConfig struct { + Models []string `json:"models,omitempty" yaml:"models,omitempty"` // Default models + MaxResults int `json:"max_results,omitempty" yaml:"max_results,omitempty"` // Max results (default: 20) +} + +// KeywordConfig for keyword extraction +type KeywordConfig struct { + MaxKeywords int `json:"max_keywords,omitempty" yaml:"max_keywords,omitempty"` // Max keywords (default: 10) + Language string `json:"language,omitempty" yaml:"language,omitempty"` // "auto", "en", "zh", etc. +} + +// KeywordOptions for keyword extraction (runtime options) +type KeywordOptions struct { + MaxKeywords int `json:"max_keywords,omitempty"` + Language string `json:"language,omitempty"` +} + +// QueryDSLConfig for QueryDSL generation from natural language +type QueryDSLConfig struct { + Strict bool `json:"strict,omitempty" yaml:"strict,omitempty"` // Fail if generation fails (default: false) +} + +// RerankConfig for reranking +type RerankConfig struct { + TopN int `json:"top_n,omitempty" yaml:"top_n,omitempty"` // Return top N (default: 10) +} + +// CitationConfig for citation format +type CitationConfig struct { + Format string `json:"format,omitempty" yaml:"format,omitempty"` // Default: "#ref:{id}" + AutoInjectPrompt bool `json:"auto_inject_prompt,omitempty" yaml:"auto_inject_prompt,omitempty"` // Auto-inject prompt (default: true) + CustomPrompt string `json:"custom_prompt,omitempty" yaml:"custom_prompt,omitempty"` // Custom prompt template +} + +// WeightsConfig for source weighting +type WeightsConfig struct { + User float64 `json:"user,omitempty" yaml:"user,omitempty"` // User-provided (default: 1.0) + Hook float64 `json:"hook,omitempty" yaml:"hook,omitempty"` // Hook results (default: 0.8) + Auto float64 `json:"auto,omitempty" yaml:"auto,omitempty"` // Auto search (default: 0.6) +} + +// OptionsConfig for search behavior +type OptionsConfig struct { + SkipThreshold int `json:"skip_threshold,omitempty" yaml:"skip_threshold,omitempty"` // Skip auto search if user provides >= N results +} + +// GetWeight returns the weight for a source type +func (c *Config) GetWeight(source SourceType) float64 { + if c == nil || c.Weights == nil { + return getDefaultWeight(source) + } + switch source { + case SourceUser: + if c.Weights.User > 0 { + return c.Weights.User + } + return 1.0 + case SourceHook: + if c.Weights.Hook > 0 { + return c.Weights.Hook + } + return 0.8 + case SourceAuto: + if c.Weights.Auto > 0 { + return c.Weights.Auto + } + return 0.6 + default: + return 0.6 + } +} + +// getDefaultWeight returns default weight for a source type +func getDefaultWeight(source SourceType) float64 { + switch source { + case SourceUser: + return 1.0 + case SourceHook: + return 0.8 + case SourceAuto: + return 0.6 + default: + return 0.6 + } +} diff --git a/agent/search/types/graph.go b/agent/search/types/graph.go new file mode 100644 index 00000000..263c2634 --- /dev/null +++ b/agent/search/types/graph.go @@ -0,0 +1,12 @@ +package types + +// GraphNode represents a related entity from knowledge graph +type GraphNode struct { + ID string `json:"id"` + Type string `json:"type"` // Entity type + Name string `json:"name"` // Entity name + Description string `json:"description,omitempty"` // Entity description + Relation string `json:"relation,omitempty"` // Relationship to query + Score float64 `json:"score,omitempty"` // Relevance score + Metadata map[string]interface{} `json:"metadata,omitempty"` +} diff --git a/agent/search/types/reference.go b/agent/search/types/reference.go new file mode 100644 index 00000000..9d6752df --- /dev/null +++ b/agent/search/types/reference.go @@ -0,0 +1,22 @@ +package types + +// Reference is the unified structure for all data sources +// Used to build LLM context from search results +type Reference struct { + ID string `json:"id"` // Unique citation ID: "ref_001", "ref_002" + Type SearchType `json:"type"` // Data type: "web", "kb", "db" + Source SourceType `json:"source"` // Origin: "user", "hook", "auto" + Weight float64 `json:"weight"` // Relevance weight (1.0=highest, 0.6=lowest) + Score float64 `json:"score"` // Relevance score (0-1) + Title string `json:"title"` // Optional title + Content string `json:"content"` // Main content + URL string `json:"url"` // Optional URL + Meta map[string]interface{} `json:"meta"` // Additional metadata +} + +// ReferenceContext holds the formatted references for LLM input +type ReferenceContext struct { + References []*Reference `json:"references"` // All references + XML string `json:"xml"` // Formatted XML + Prompt string `json:"prompt"` // Citation instruction prompt +} diff --git a/agent/search/types/types.go b/agent/search/types/types.go new file mode 100644 index 00000000..adad0157 --- /dev/null +++ b/agent/search/types/types.go @@ -0,0 +1,141 @@ +package types + +// SearchType represents the type of search +type SearchType string + +const ( + SearchTypeWeb SearchType = "web" // Web/Internet search + SearchTypeKB SearchType = "kb" // Knowledge base vector search + SearchTypeDB SearchType = "db" // Database search (Yao Model/QueryDSL) +) + +// SourceType represents where the search result came from +type SourceType string + +const ( + SourceUser SourceType = "user" // User-provided DataContent (highest priority) + SourceHook SourceType = "hook" // Hook ctx.search.*() results + SourceAuto SourceType = "auto" // Auto search results (lowest priority) +) + +// Request represents a search request +type Request struct { + // Common fields + Query string `json:"query"` // Search query (natural language) + Type SearchType `json:"type"` // Search type: "web", "kb", or "db" + Limit int `json:"limit,omitempty"` // Max results (default: 10) + Source SourceType `json:"source"` // Source of this request (user/hook/auto) + + // Web search specific + Sites []string `json:"sites,omitempty"` // Restrict to specific sites + TimeRange string `json:"time_range,omitempty"` // "day", "week", "month", "year" + + // Knowledge base specific + Collections []string `json:"collections,omitempty"` // KB collection IDs + Threshold float64 `json:"threshold,omitempty"` // Similarity threshold (0-1) + Graph bool `json:"graph,omitempty"` // Enable graph association + + // Database search specific + Models []string `json:"models,omitempty"` // Model IDs (e.g., "user", "agents.mybot.product") + Wheres []QueryWhere `json:"wheres,omitempty"` // Pre-defined filters (optional) + Orders []QueryOrder `json:"orders,omitempty"` // Sort orders (optional) + Select []string `json:"select,omitempty"` // Fields to return (optional) + + // Reranking + Rerank *RerankOptions `json:"rerank,omitempty"` +} + +// QueryWhere represents a filter condition for DB search +type QueryWhere struct { + Field string `json:"field"` // Field name + Op string `json:"op,omitempty"` // Operator: "=", "like", ">", "<", "in", etc. (default: "=") + Value interface{} `json:"value"` // Filter value +} + +// QueryOrder represents a sort order for DB search +type QueryOrder struct { + Field string `json:"field"` // Field name + Order string `json:"order,omitempty"` // "asc" or "desc" (default: "desc") +} + +// RerankOptions controls result reranking +// Reranker type is determined by uses.rerank in agent/agent.yml +type RerankOptions struct { + TopN int `json:"top_n,omitempty"` // Return top N after reranking +} + +// Result represents the search result +type Result struct { + Type SearchType `json:"type"` // Search type + Query string `json:"query"` // Original query + Source SourceType `json:"source"` // Source of this result + Items []*ResultItem `json:"items"` // Result items + Total int `json:"total"` // Total matches + Duration int64 `json:"duration_ms"` // Search duration in ms + Error string `json:"error,omitempty"` // Error message if failed + + // Graph associations (KB only, if enabled) + GraphNodes []*GraphNode `json:"graph_nodes,omitempty"` +} + +// ResultItem represents a single search result item +type ResultItem struct { + // Citation + CitationID string `json:"citation_id"` // Unique ID for LLM reference: "ref_001" + + // Weighting + Source SourceType `json:"source"` // Source type: "user", "hook", "auto" + Weight float64 `json:"weight"` // Source weight (from config) + Score float64 `json:"score,omitempty"` // Relevance score (0-1) + + // Common fields + Type SearchType `json:"type"` // Search type for this item + Title string `json:"title,omitempty"` // Title/headline + Content string `json:"content"` // Main content/snippet + URL string `json:"url,omitempty"` // Source URL + + // KB specific + DocumentID string `json:"document_id,omitempty"` // Source document ID + Collection string `json:"collection,omitempty"` // Collection name + + // DB specific + Model string `json:"model,omitempty"` // Model ID + RecordID interface{} `json:"record_id,omitempty"` // Record primary key + Data map[string]interface{} `json:"data,omitempty"` // Full record data + + // Metadata + Metadata map[string]interface{} `json:"metadata,omitempty"` // Additional metadata +} + +// ProcessedQuery represents a processed query ready for execution +type ProcessedQuery struct { + Type SearchType `json:"type"` + Keywords []string `json:"keywords,omitempty"` // For web search + Vector []float32 `json:"vector,omitempty"` // For KB search + DSL *QueryDSL `json:"dsl,omitempty"` // For DB search +} + +// QueryDSL represents a Yao QueryDSL for database search +type QueryDSL struct { + Model string `json:"model"` // Target model + Select []string `json:"select,omitempty"` // Fields to return + Wheres []QueryWhere `json:"wheres,omitempty"` // Filter conditions + Orders []QueryOrder `json:"orders,omitempty"` // Sort orders + Limit int `json:"limit,omitempty"` // Max results +} + +// ModelSchema represents a Yao Model schema for DSL generation +type ModelSchema struct { + ID string `json:"id"` // Model ID + Name string `json:"name"` // Model name + Description string `json:"description"` // Model description + Fields []FieldSchema `json:"fields"` // Field definitions +} + +// FieldSchema represents a field in the model schema +type FieldSchema struct { + Name string `json:"name"` // Field name + Type string `json:"type"` // Field type + Description string `json:"description"` // Field description + Searchable bool `json:"searchable"` // Whether field is searchable +} diff --git a/agent/store/types/convert.go b/agent/store/types/convert.go index 0d3b3974..604dcda1 100644 --- a/agent/store/types/convert.go +++ b/agent/store/types/convert.go @@ -9,7 +9,9 @@ import ( "github.com/spf13/cast" "github.com/yaoapp/gou/connector" "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/i18n" + searchTypes "github.com/yaoapp/yao/agent/search/types" ) // ToKnowledgeBase converts various types to KnowledgeBase @@ -616,3 +618,59 @@ func ToPromptPresets(v interface{}) (map[string][]Prompt, error) { return result, nil } } + +// ToUses converts various types to context.Uses +func ToUses(v interface{}) (*context.Uses, error) { + if v == nil { + return nil, nil + } + + switch uses := v.(type) { + case *context.Uses: + return uses, nil + + case context.Uses: + return &uses, nil + + default: + raw, err := jsoniter.Marshal(uses) + if err != nil { + return nil, fmt.Errorf("uses format error: %s", err.Error()) + } + + var result context.Uses + err = jsoniter.Unmarshal(raw, &result) + if err != nil { + return nil, fmt.Errorf("uses format error: %s", err.Error()) + } + return &result, nil + } +} + +// ToSearchConfig converts various types to searchTypes.Config +func ToSearchConfig(v interface{}) (*searchTypes.Config, error) { + if v == nil { + return nil, nil + } + + switch cfg := v.(type) { + case *searchTypes.Config: + return cfg, nil + + case searchTypes.Config: + return &cfg, nil + + default: + raw, err := jsoniter.Marshal(cfg) + if err != nil { + return nil, fmt.Errorf("search config format error: %s", err.Error()) + } + + var result searchTypes.Config + err = jsoniter.Unmarshal(raw, &result) + if err != nil { + return nil, fmt.Errorf("search config format error: %s", err.Error()) + } + return &result, nil + } +} diff --git a/agent/store/types/fields.go b/agent/store/types/fields.go index 2e6845cf..54e5ca57 100644 --- a/agent/store/types/fields.go +++ b/agent/store/types/fields.go @@ -33,6 +33,7 @@ var AssistantAllowedFields = map[string]bool{ "share": true, "locales": true, "uses": true, + "search": true, "automated": true, "mentionable": true, "created_at": true, @@ -104,6 +105,7 @@ var AssistantFullFields = []string{ "share", "locales", "uses", + "search", "automated", "mentionable", "created_at", diff --git a/agent/store/types/types.go b/agent/store/types/types.go index ef1e08a9..f5465c13 100644 --- a/agent/store/types/types.go +++ b/agent/store/types/types.go @@ -9,6 +9,7 @@ import ( "github.com/yaoapp/xun/dbal/query" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/i18n" + searchTypes "github.com/yaoapp/yao/agent/search/types" ) // Setting represents the conversation configuration structure @@ -436,6 +437,7 @@ type AssistantModel struct { Source string `json:"source,omitempty"` // Hook script source code Locales i18n.Map `json:"locales,omitempty"` // Assistant Locales Uses *context.Uses `json:"uses,omitempty"` // Assistant-specific wrapper configurations for vision, audio, etc. If not set, use global settings + Search *searchTypes.Config `json:"search,omitempty"` // Search configuration (web, kb, db, citation, weights, etc.) CreatedAt int64 `json:"created_at"` // Creation timestamp UpdatedAt int64 `json:"updated_at"` // Last update timestamp diff --git a/agent/store/xun/assistant.go b/agent/store/xun/assistant.go index e9939fc5..abe4eb49 100644 --- a/agent/store/xun/assistant.go +++ b/agent/store/xun/assistant.go @@ -10,6 +10,7 @@ import ( "github.com/yaoapp/xun/dbal/query" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/i18n" + searchTypes "github.com/yaoapp/yao/agent/search/types" "github.com/yaoapp/yao/agent/store/types" ) @@ -183,6 +184,7 @@ func (store *Xun) SaveAssistant(assistant *types.AssistantModel) (string, error) "placeholder": assistant.Placeholder, "locales": assistant.Locales, "uses": assistant.Uses, + "search": assistant.Search, } for field, value := range jsonFields { @@ -241,7 +243,7 @@ func (store *Xun) UpdateAssistant(assistantID string, updates map[string]interfa data := make(map[string]interface{}) // List of fields that need JSON marshaling - jsonFields := []string{"options", "tags", "modes", "prompts", "prompt_presets", "connector_options", "kb", "db", "mcp", "workflow", "placeholder", "locales", "uses"} + jsonFields := []string{"options", "tags", "modes", "prompts", "prompt_presets", "connector_options", "kb", "db", "mcp", "workflow", "placeholder", "locales", "uses", "search"} jsonFieldSet := make(map[string]bool) for _, field := range jsonFields { jsonFieldSet[field] = true @@ -441,7 +443,7 @@ func (store *Xun) GetAssistants(filter types.AssistantFilter, locale ...string) // Convert rows to types.AssistantModel slice assistants := make([]*types.AssistantModel, 0, len(rows)) - jsonFields := []string{"tags", "options", "prompts", "prompt_presets", "connector_options", "workflow", "kb", "mcp", "placeholder", "locales", "uses"} + jsonFields := []string{"tags", "options", "prompts", "prompt_presets", "connector_options", "workflow", "kb", "mcp", "placeholder", "locales", "uses", "search"} for _, row := range rows { data := row.ToMap() @@ -514,7 +516,7 @@ func (store *Xun) GetAssistant(assistantID string, fields []string, locale ...st } // Parse JSON fields - jsonFields := []string{"tags", "modes", "options", "prompts", "prompt_presets", "connector_options", "workflow", "kb", "db", "mcp", "placeholder", "locales", "uses"} + jsonFields := []string{"tags", "modes", "options", "prompts", "prompt_presets", "connector_options", "workflow", "kb", "db", "mcp", "placeholder", "locales", "uses", "search"} store.parseJSONFields(data, jsonFields) // Convert map to types.AssistantModel @@ -659,6 +661,16 @@ func (store *Xun) GetAssistant(assistantID string, fields []string, locale ...st } } + if search, has := data["search"]; has && search != nil { + raw, err := jsoniter.Marshal(search) + if err == nil { + var s searchTypes.Config + if err := jsoniter.Unmarshal(raw, &s); err == nil { + model.Search = &s + } + } + } + // Apply i18n translation if locale is provided if len(locale) > 0 && locale[0] != "" { store.translate(model, assistantID, locale[0]) diff --git a/agent/store/xun/assistant_test.go b/agent/store/xun/assistant_test.go index 238524d2..a2b7ac99 100644 --- a/agent/store/xun/assistant_test.go +++ b/agent/store/xun/assistant_test.go @@ -10,6 +10,7 @@ import ( "github.com/yaoapp/xun/dbal/query" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/i18n" + searchTypes "github.com/yaoapp/yao/agent/search/types" "github.com/yaoapp/yao/agent/store/types" "github.com/yaoapp/yao/agent/store/xun" "github.com/yaoapp/yao/config" @@ -452,6 +453,198 @@ func TestSaveAssistant(t *testing.T) { } }) + t.Run("SearchConfiguration", func(t *testing.T) { + // Test assistant with Search configuration + assistant := &types.AssistantModel{ + Name: "Search Config Test Assistant", + Type: "assistant", + Connector: "openai", + Share: "private", + Search: &searchTypes.Config{ + Web: &searchTypes.WebConfig{ + Provider: "tavily", + MaxResults: 15, + }, + KB: &searchTypes.KBConfig{ + Collections: []string{"docs", "faq"}, + Threshold: 0.8, + Graph: true, + }, + DB: &searchTypes.DBConfig{ + Models: []string{"user", "product"}, + MaxResults: 50, + }, + Citation: &searchTypes.CitationConfig{ + Format: "#ref:{id}", + AutoInjectPrompt: true, + }, + Weights: &searchTypes.WeightsConfig{ + User: 1.0, + Hook: 0.9, + Auto: 0.7, + }, + }, + } + + id, err := store.SaveAssistant(assistant) + if err != nil { + t.Fatalf("Failed to save assistant with search config: %v", err) + } + + // Retrieve and verify search configuration - search is NOT in default fields + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) + if err != nil { + t.Fatalf("Failed to retrieve assistant: %v", err) + } + + if retrieved.Search == nil { + t.Fatal("Expected search to be set") + } + + // Verify Web config + if retrieved.Search.Web == nil { + t.Fatal("Expected search.web to be set") + } + if retrieved.Search.Web.Provider != "tavily" { + t.Errorf("Expected web provider 'tavily', got '%s'", retrieved.Search.Web.Provider) + } + if retrieved.Search.Web.MaxResults != 15 { + t.Errorf("Expected web max_results 15, got %d", retrieved.Search.Web.MaxResults) + } + + // Verify KB config + if retrieved.Search.KB == nil { + t.Fatal("Expected search.kb to be set") + } + if len(retrieved.Search.KB.Collections) != 2 { + t.Errorf("Expected 2 KB collections, got %d", len(retrieved.Search.KB.Collections)) + } + if retrieved.Search.KB.Collections[0] != "docs" { + t.Errorf("Expected first collection 'docs', got '%s'", retrieved.Search.KB.Collections[0]) + } + if retrieved.Search.KB.Threshold != 0.8 { + t.Errorf("Expected KB threshold 0.8, got %f", retrieved.Search.KB.Threshold) + } + if !retrieved.Search.KB.Graph { + t.Error("Expected KB graph to be true") + } + + // Verify DB config + if retrieved.Search.DB == nil { + t.Fatal("Expected search.db to be set") + } + if len(retrieved.Search.DB.Models) != 2 { + t.Errorf("Expected 2 DB models, got %d", len(retrieved.Search.DB.Models)) + } + if retrieved.Search.DB.MaxResults != 50 { + t.Errorf("Expected DB max_results 50, got %d", retrieved.Search.DB.MaxResults) + } + + // Verify Citation config + if retrieved.Search.Citation == nil { + t.Fatal("Expected search.citation to be set") + } + if retrieved.Search.Citation.Format != "#ref:{id}" { + t.Errorf("Expected citation format '#ref:{id}', got '%s'", retrieved.Search.Citation.Format) + } + if !retrieved.Search.Citation.AutoInjectPrompt { + t.Error("Expected citation auto_inject_prompt to be true") + } + + // Verify Weights config + if retrieved.Search.Weights == nil { + t.Fatal("Expected search.weights to be set") + } + if retrieved.Search.Weights.User != 1.0 { + t.Errorf("Expected weights.user 1.0, got %f", retrieved.Search.Weights.User) + } + if retrieved.Search.Weights.Hook != 0.9 { + t.Errorf("Expected weights.hook 0.9, got %f", retrieved.Search.Weights.Hook) + } + if retrieved.Search.Weights.Auto != 0.7 { + t.Errorf("Expected weights.auto 0.7, got %f", retrieved.Search.Weights.Auto) + } + + t.Logf("Successfully saved and retrieved assistant with search configuration") + }) + + t.Run("NilSearchConfiguration", func(t *testing.T) { + // Test assistant without Search configuration + assistant := &types.AssistantModel{ + Name: "No Search Config Assistant", + Type: "assistant", + Connector: "openai", + Share: "private", + } + + id, err := store.SaveAssistant(assistant) + if err != nil { + t.Fatalf("Failed to save assistant without search: %v", err) + } + + // Retrieve and verify search is nil - request all fields to check search + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) + if err != nil { + t.Fatalf("Failed to retrieve assistant: %v", err) + } + + if retrieved.Search != nil { + t.Errorf("Expected search to be nil, got %+v", retrieved.Search) + } + }) + + t.Run("PartialSearchConfiguration", func(t *testing.T) { + // Test assistant with partial Search configuration + assistant := &types.AssistantModel{ + Name: "Partial Search Config Assistant", + Type: "assistant", + Connector: "openai", + Share: "private", + Search: &searchTypes.Config{ + Web: &searchTypes.WebConfig{ + Provider: "serper", + }, + // KB, DB, Citation, Weights not set + }, + } + + id, err := store.SaveAssistant(assistant) + if err != nil { + t.Fatalf("Failed to save assistant with partial search: %v", err) + } + + // Retrieve and verify - request all fields for search + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) + if err != nil { + t.Fatalf("Failed to retrieve assistant: %v", err) + } + + if retrieved.Search == nil { + t.Fatal("Expected search to be set") + } + + if retrieved.Search.Web == nil { + t.Fatal("Expected search.web to be set") + } + if retrieved.Search.Web.Provider != "serper" { + t.Errorf("Expected web provider 'serper', got '%s'", retrieved.Search.Web.Provider) + } + + // Other fields should be nil + if retrieved.Search.KB != nil { + t.Errorf("Expected search.kb to be nil, got %+v", retrieved.Search.KB) + } + if retrieved.Search.DB != nil { + t.Errorf("Expected search.db to be nil, got %+v", retrieved.Search.DB) + } + if retrieved.Search.Citation != nil { + t.Errorf("Expected search.citation to be nil, got %+v", retrieved.Search.Citation) + } + if retrieved.Search.Weights != nil { + t.Errorf("Expected search.weights to be nil, got %+v", retrieved.Search.Weights) + } + }) + t.Run("ConnectorOptions", func(t *testing.T) { // Test assistant with connector options optionalTrue := true @@ -2725,6 +2918,128 @@ func TestUpdateAssistant(t *testing.T) { } }) + t.Run("UpdateSearch", func(t *testing.T) { + // Create assistant without search + assistant := &types.AssistantModel{ + Name: "Search Update Test", + Type: "assistant", + Connector: "openai", + Share: "private", + } + + id, err := store.SaveAssistant(assistant) + if err != nil { + t.Fatalf("Failed to create assistant: %v", err) + } + + // Update with search configuration + updates := map[string]interface{}{ + "search": &searchTypes.Config{ + Web: &searchTypes.WebConfig{ + Provider: "tavily", + MaxResults: 20, + }, + KB: &searchTypes.KBConfig{ + Collections: []string{"knowledge"}, + Threshold: 0.75, + }, + }, + } + + err = store.UpdateAssistant(id, updates) + if err != nil { + t.Fatalf("Failed to update search: %v", err) + } + + // Verify updates - search is NOT in default fields + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) + if err != nil { + t.Fatalf("Failed to retrieve assistant: %v", err) + } + + if retrieved.Search == nil { + t.Fatal("Expected search to be set") + } + + if retrieved.Search.Web == nil { + t.Fatal("Expected search.web to be set") + } + if retrieved.Search.Web.Provider != "tavily" { + t.Errorf("Expected web provider 'tavily', got '%s'", retrieved.Search.Web.Provider) + } + if retrieved.Search.Web.MaxResults != 20 { + t.Errorf("Expected web max_results 20, got %d", retrieved.Search.Web.MaxResults) + } + + if retrieved.Search.KB == nil { + t.Fatal("Expected search.kb to be set") + } + if len(retrieved.Search.KB.Collections) != 1 { + t.Errorf("Expected 1 KB collection, got %d", len(retrieved.Search.KB.Collections)) + } + if retrieved.Search.KB.Threshold != 0.75 { + t.Errorf("Expected KB threshold 0.75, got %f", retrieved.Search.KB.Threshold) + } + + // Update to change search configuration + updates2 := map[string]interface{}{ + "search": &searchTypes.Config{ + Web: &searchTypes.WebConfig{ + Provider: "serper", + MaxResults: 30, + }, + Citation: &searchTypes.CitationConfig{ + Format: "#cite:{id}", + AutoInjectPrompt: false, + }, + }, + } + + err = store.UpdateAssistant(id, updates2) + if err != nil { + t.Fatalf("Failed to update search again: %v", err) + } + + // Verify second update - search is NOT in default fields + retrieved2, err := store.GetAssistant(id, types.AssistantFullFields) + if err != nil { + t.Fatalf("Failed to retrieve assistant: %v", err) + } + + if retrieved2.Search.Web.Provider != "serper" { + t.Errorf("Expected web provider 'serper', got '%s'", retrieved2.Search.Web.Provider) + } + if retrieved2.Search.Web.MaxResults != 30 { + t.Errorf("Expected web max_results 30, got %d", retrieved2.Search.Web.MaxResults) + } + if retrieved2.Search.Citation == nil { + t.Fatal("Expected search.citation to be set") + } + if retrieved2.Search.Citation.Format != "#cite:{id}" { + t.Errorf("Expected citation format '#cite:{id}', got '%s'", retrieved2.Search.Citation.Format) + } + + // Update to remove search (set to nil) + updates3 := map[string]interface{}{ + "search": nil, + } + + err = store.UpdateAssistant(id, updates3) + if err != nil { + t.Fatalf("Failed to set search to nil: %v", err) + } + + // Verify search is nil - search is NOT in default fields + retrieved3, err := store.GetAssistant(id, types.AssistantFullFields) + if err != nil { + t.Fatalf("Failed to retrieve assistant: %v", err) + } + + if retrieved3.Search != nil { + t.Errorf("Expected search to be nil, got %+v", retrieved3.Search) + } + }) + t.Run("UpdatePermissionFields", func(t *testing.T) { // Create assistant with permission fields assistant := &types.AssistantModel{ diff --git a/agent/types/types.go b/agent/types/types.go index c83ff390..20594889 100644 --- a/agent/types/types.go +++ b/agent/types/types.go @@ -3,6 +3,7 @@ package types import ( "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/yao/agent/assistant" + searchTypes "github.com/yaoapp/yao/agent/search/types" store "github.com/yaoapp/yao/agent/store/types" ) @@ -19,6 +20,7 @@ type DSL struct { // =============================== Models map[string]openai.Capabilities `json:"models,omitempty" yaml:"models,omitempty"` // The model capabilities configuration KB *store.KBSetting `json:"kb,omitempty" yaml:"kb,omitempty"` // The knowledge base configuration loaded from agent/kb.yml + Search *searchTypes.Config `json:"search,omitempty" yaml:"search,omitempty"` // The search configuration loaded from agent/search.yao // Internal // =============================== @@ -38,6 +40,12 @@ type Uses struct { Audio string `json:"audio,omitempty" yaml:"audio,omitempty"` // The assistant for processing audio (speech-to-text, text-to-speech). If the model doesn't support audio, use this to convert audio to text. Format: "agent" or "mcp:mcp_server_id" Search string `json:"search,omitempty" yaml:"search,omitempty"` // The assistant for searching the knowledge, global web search. If not set, and the assistant enable the knowledge, it will search the result from the knowledge automatically. Fetch string `json:"fetch,omitempty" yaml:"fetch,omitempty"` // The assistant for fetching the http/https/ftp/sftp/etc. file, and return the file's content. if not set, use the http process to fetch the file. + + // Search-related processing tools (NLP) + Web string `json:"web,omitempty" yaml:"web,omitempty"` // Web search handler: "builtin", "", "mcp:." + Keyword string `json:"keyword,omitempty" yaml:"keyword,omitempty"` // Keyword extraction: "builtin", "", "mcp:." + QueryDSL string `json:"querydsl,omitempty" yaml:"querydsl,omitempty"` // QueryDSL generation: "builtin", "", "mcp:." + Rerank string `json:"rerank,omitempty" yaml:"rerank,omitempty"` // Result reranking: "builtin", "", "mcp:." } // Mention Structure diff --git a/data/bindata.go b/data/bindata.go index d0520ac1..c6f04a69 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -320,7 +320,7 @@ func cuiSetupIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -340,7 +340,7 @@ func cuiV09IndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -360,7 +360,7 @@ func cuiV10IndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -380,7 +380,7 @@ func cuiV10Layouts__indexAsyncJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -400,7 +400,7 @@ func cuiV10UmiJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -420,7 +420,7 @@ func initEnv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -440,7 +440,7 @@ func initVscodeSettingsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -460,7 +460,7 @@ func initVscodeTypesRuntimeConsoleDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -480,7 +480,7 @@ func initVscodeTypesRuntimeExceptionDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -500,7 +500,7 @@ func initVscodeTypesRuntimeFsDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -520,7 +520,7 @@ func initVscodeTypesRuntimeGlobalDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -540,7 +540,7 @@ func initVscodeTypesRuntimeHttpDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -560,7 +560,7 @@ func initVscodeTypesRuntimeIoDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -580,7 +580,7 @@ func initVscodeTypesRuntimeLogDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -600,7 +600,7 @@ func initVscodeTypesRuntimeNeoDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -620,7 +620,7 @@ func initVscodeTypesRuntimeProcessFsDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -640,7 +640,7 @@ func initVscodeTypesRuntimeProcessHttpDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -660,7 +660,7 @@ func initVscodeTypesRuntimeProcessModelDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -680,7 +680,7 @@ func initVscodeTypesRuntimeProcessDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -700,7 +700,7 @@ func initVscodeTypesRuntimeQueryDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -720,7 +720,7 @@ func initVscodeTypesRuntimeStoreDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -740,7 +740,7 @@ func initVscodeTypesRuntimeSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -760,7 +760,7 @@ func initVscodeTypesRuntimeTimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -780,7 +780,7 @@ func initVscodeTypesRuntimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -800,7 +800,7 @@ func initVscodeTypesSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -820,7 +820,7 @@ func initAppYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -840,7 +840,7 @@ func initDataReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -860,7 +860,7 @@ func initDataTemplatesDefault__assetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -880,7 +880,7 @@ func initDataTemplatesDefault__assetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -900,7 +900,7 @@ func initDataTemplatesDefault__assetsImagesLogosLogo_colorSvg() (*asset, error) return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -920,7 +920,7 @@ func initDataTemplatesDefault__assetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -940,7 +940,7 @@ func initDataTemplatesDefault__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -960,7 +960,7 @@ func initDataTemplatesDefault__documentHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -980,7 +980,7 @@ func initDataTemplatesDefaultIndexIndexCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1000,7 +1000,7 @@ func initDataTemplatesDefaultIndexIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1020,7 +1020,7 @@ func initDataTemplatesDefaultIndexIndexJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1040,7 +1040,7 @@ func initDbReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1060,7 +1060,7 @@ func initFlowsMenuFlowYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1080,7 +1080,7 @@ func initFormsAccountFormYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1100,7 +1100,7 @@ func initIconsAppIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1120,7 +1120,7 @@ func initIconsAppIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1140,7 +1140,7 @@ func initIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1160,7 +1160,7 @@ func initLoginsAdminLoginYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1180,7 +1180,7 @@ func initLogsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1200,7 +1200,7 @@ func initModelsAdminUserModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1220,7 +1220,7 @@ func initModelsTestsPetModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1240,7 +1240,7 @@ func initNeoNeoYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1260,7 +1260,7 @@ func initPublicReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1280,7 +1280,7 @@ func initPublicAssetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1300,7 +1300,7 @@ func initPublicAssetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1320,7 +1320,7 @@ func initPublicAssetsImagesLogosLogo_colorSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1340,7 +1340,7 @@ func initPublicAssetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1360,7 +1360,7 @@ func initPublicAssetsLibsuiMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1380,7 +1380,7 @@ func initPublicAssetsLibsuiMinJsMap() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1400,7 +1400,7 @@ func initPublicIndexCfg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1420,7 +1420,7 @@ func initPublicIndexSui() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1440,7 +1440,7 @@ func initScriptsAccountTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1460,7 +1460,7 @@ func initScriptsAiNeoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1480,7 +1480,7 @@ func initScriptsTestsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1500,7 +1500,7 @@ func initScriptsUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1520,7 +1520,7 @@ func initSuisWebSuiYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1540,7 +1540,7 @@ func initTablesAccountTabYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1560,7 +1560,7 @@ func initTsconfigJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1580,7 +1580,7 @@ func libsuiAgentTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/agent.ts", size: 15267, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "libsui/agent.ts", size: 15267, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1600,7 +1600,7 @@ func libsuiIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/index.ts", size: 13049, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "libsui/index.ts", size: 13049, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1620,7 +1620,7 @@ func libsuiUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1640,7 +1640,7 @@ func libsuiYaoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1660,7 +1660,7 @@ func publicIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1680,7 +1680,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1700,7 +1700,7 @@ func yaoDataIcons404Png() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1720,7 +1720,7 @@ func yaoDataIconsIconIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1740,7 +1740,7 @@ func yaoDataIconsIconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1760,7 +1760,7 @@ func yaoDataIconsIconPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1780,7 +1780,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1800,7 +1800,7 @@ func yaoDataKbProvidersChunkingSemanticEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1820,7 +1820,7 @@ func yaoDataKbProvidersChunkingSemanticZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1840,7 +1840,7 @@ func yaoDataKbProvidersChunkingStructuredEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1860,7 +1860,7 @@ func yaoDataKbProvidersChunkingStructuredZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1880,7 +1880,7 @@ func yaoDataKbProvidersConverterMcpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1900,7 +1900,7 @@ func yaoDataKbProvidersConverterMcpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1920,7 +1920,7 @@ func yaoDataKbProvidersConverterOcrEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1940,7 +1940,7 @@ func yaoDataKbProvidersConverterOcrZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1960,7 +1960,7 @@ func yaoDataKbProvidersConverterOfficeEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1980,7 +1980,7 @@ func yaoDataKbProvidersConverterOfficeZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2000,7 +2000,7 @@ func yaoDataKbProvidersConverterUtf8EnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2020,7 +2020,7 @@ func yaoDataKbProvidersConverterUtf8ZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2040,7 +2040,7 @@ func yaoDataKbProvidersConverterVideoEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2060,7 +2060,7 @@ func yaoDataKbProvidersConverterVideoZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2080,7 +2080,7 @@ func yaoDataKbProvidersConverterVisionEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2100,7 +2100,7 @@ func yaoDataKbProvidersConverterVisionZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2120,7 +2120,7 @@ func yaoDataKbProvidersConverterWhisperEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2140,7 +2140,7 @@ func yaoDataKbProvidersConverterWhisperZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2160,7 +2160,7 @@ func yaoDataKbProvidersEmbeddingFastembedEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2180,7 +2180,7 @@ func yaoDataKbProvidersEmbeddingFastembedZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2200,7 +2200,7 @@ func yaoDataKbProvidersEmbeddingOpenaiEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2220,7 +2220,7 @@ func yaoDataKbProvidersEmbeddingOpenaiZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2240,7 +2240,7 @@ func yaoDataKbProvidersExtractionOpenaiEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2260,7 +2260,7 @@ func yaoDataKbProvidersExtractionOpenaiZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2280,7 +2280,7 @@ func yaoDataKbProvidersFetcherHttpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2300,7 +2300,7 @@ func yaoDataKbProvidersFetcherHttpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2320,7 +2320,7 @@ func yaoDataKbProvidersFetcherMcpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2340,7 +2340,7 @@ func yaoDataKbProvidersFetcherMcpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2360,7 +2360,7 @@ func yaoFieldsModelTransJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2380,7 +2380,7 @@ func yaoLangsEnUsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2400,7 +2400,7 @@ func yaoLangsZhCnGlobalYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2420,7 +2420,7 @@ func yaoLangsZhCnLoginsAdminLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2440,7 +2440,7 @@ func yaoLangsZhCnLoginsUserLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2460,7 +2460,7 @@ func yaoLangsZhHkGlobalYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2480,7 +2480,7 @@ func yaoLangsZhHkLoginsAdminLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2500,12 +2500,12 @@ func yaoLangsZhHkLoginsUserLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoModelsAgentAssistantModYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x59\x5f\x6f\xdc\x36\x0c\x7f\xcf\xa7\x20\xfc\xb4\x02\xd7\xb4\x1b\xb0\x61\xc9\xd3\xd2\x06\xd8\x82\x35\x6b\xd0\x3f\xeb\x43\x51\x18\x3c\x9b\xf6\x69\x91\x25\x4f\xa2\x93\x66\x41\xbe\xfb\x20\xd9\xe7\x3f\x67\xdd\x9d\xed\x6e\x2f\xed\x55\xfa\x91\xfa\x51\xa4\x48\x9a\x7d\x3c\x01\x88\x14\x16\x14\x9d\x43\x74\x61\xad\xb0\x8c\x8a\xa3\x95\x5b\x96\xb8\x26\x19\x58\x4f\xc9\x26\x46\x94\x2c\xb4\x1a\xec\x02\xe3\x5a\x12\x64\xda\x80\x65\x6d\x84\xca\xe1\xe2\x0a\xb0\xdd\x4e\xb4\xca\x44\x5e\x19\x74\x92\x16\x50\xa5\x50\x10\x63\x8a\x8c\xb5\x62\xc6\xdc\x46\xe7\xf0\x39\xc2\x9c\xdc\x61\x10\xd9\x07\xcb\x54\x44\x5f\xfc\xf6\xba\x12\x92\x85\x3b\x93\x4d\x45\x7e\xc9\x10\xa6\x5a\xc9\x87\xfe\x9a\xd5\x86\xa3\x73\x38\x3b\x3b\x3b\x6b\xb4\xae\xa5\x33\xef\xb1\x33\xd4\xeb\x8f\xb1\x33\x0b\xa2\x44\x17\x85\x3b\xd4\x19\xe4\x76\x7b\xbc\x6b\x05\xf0\xe4\xb5\x25\x5a\x56\x85\xf2\x34\x4f\x00\x00\x1e\xfd\x9f\xbd\x4b\x14\xa9\x37\xc6\xaf\xf1\x43\xe9\xd7\xae\x2e\xbb\xb5\xf1\xad\x42\x7f\xbb\xc7\xe3\xa3\x12\x7f\x57\xd4\x23\x22\x52\x52\x2c\x32\x41\x26\xf2\xf0\xa7\x55\x98\x42\x2b\x11\x87\xc8\x58\x76\xae\x59\x42\xe8\x22\xc4\xa4\xd3\x43\x2a\xe7\x4d\x74\x0e\x3f\xbc\x7c\xd9\x2e\xaa\x4a\xca\xe6\xfe\x33\x94\x96\xda\x8d\xca\x1b\xd7\xf3\x9b\x5f\x15\x2a\xa5\xaf\xcd\xe2\x41\x13\xbd\x31\xd3\x4d\xfb\x30\x80\x07\x4d\x1a\x6a\x0c\x1a\x93\x52\x86\x95\xe4\xc1\x15\x47\xf3\xb9\xfb\xbf\xa7\x73\xff\x63\x00\x0f\x72\x1f\x6a\x3c\xe6\x88\xa3\x04\xf1\x0e\x19\xcd\x9c\xc8\xd9\x11\x08\x92\xac\xb5\xc2\xc7\x77\x6f\xfe\x43\xaa\x89\x56\x8a\x12\xd6\x73\xd8\xbe\x1e\xcb\x04\x09\x37\xee\x86\xf6\x8c\x15\x88\x0c\x94\x66\xb0\xc4\x2b\xa8\x2c\x01\x6f\x08\x72\xa9\xd7\x28\xc7\xe8\x99\x2f\x63\x9a\x99\xb1\xf6\x79\xd7\x8e\xcd\xfd\xcb\x6a\x75\xc8\x58\x78\xbb\x2b\xd9\x33\xba\x43\x59\x92\x94\x38\x20\x34\x27\x9d\x37\x3f\x50\x42\x26\x31\x5f\x39\x3f\x0a\xcf\xbc\x33\xd5\x82\x14\x96\x57\x3e\xa5\x27\x58\xe2\x5a\x48\xc1\x0f\x90\x09\xc9\x64\x7a\x27\xce\xf1\x6c\xbf\xca\x4c\xf7\xed\x65\x48\x6a\x8f\x77\x03\xc8\xd6\x53\x3f\xed\x8f\xc7\xf9\x0f\xbe\x44\xde\xcc\xb0\xe1\x66\x00\x0f\x92\x77\xf5\x15\x73\x82\xa1\xe6\x6f\x7e\x4d\xbe\x74\x8e\x88\x0a\xc5\x94\x0f\x32\xfd\x96\xe9\xfb\x01\x3e\xcc\x54\x1b\x06\x6d\xd2\xbe\x7c\x97\x47\xb7\x45\x7a\xde\x7d\xfa\x46\x20\x16\x81\xb8\x58\x6b\x2d\x09\x43\xef\xe0\x95\x93\x81\xab\x70\x54\x7c\xda\x10\x6f\xc8\x00\x6f\x84\x05\x61\x01\xc1\x1f\xf1\x5c\x28\x08\x24\xfa\x8e\xfe\xb0\xa4\x4d\x8f\x07\x89\x09\x6d\xb4\x1c\x5c\xca\x91\x77\x7c\x13\x92\x09\xde\x78\x50\xfb\x9c\x28\x98\x9b\x62\x0e\x25\x96\x8e\xd6\x48\xeb\x1c\x4a\xa5\xd1\x45\xc9\xd3\x29\xdd\xec\xe2\x0f\x26\xf8\x91\xf6\xf9\xd4\xe2\xd2\x90\xa5\xd9\x0c\xe1\x66\x57\xac\x47\xb4\x81\x34\x9a\x41\x9b\x1c\x95\xf8\x87\x52\x58\x3f\x40\xa1\x53\x82\xef\xe8\x34\x3f\x5d\x41\xb2\x41\x5e\x01\xa3\xbd\x5d\x01\x71\x72\xfa\x6c\x61\xc2\x15\xd6\x41\xe3\xba\x9e\xc5\x7b\xaf\x7c\xff\x1b\xbb\xac\x35\xc0\xaf\x75\x45\x3c\xe4\x84\xf6\xc5\x69\x68\xce\xdd\xd6\xd1\xe6\x5c\xff\x19\xe1\xdf\xe3\xff\xf1\x02\xef\xb5\xb9\xcd\xa4\xbe\x9f\xec\xae\x4f\x23\x81\x60\x44\x8d\xf5\xce\xf1\xc0\xed\x7a\x32\x9f\xdf\x95\xbe\x97\x94\xe6\x04\xaf\xd0\x1e\x6b\x0f\x6f\x5b\xf0\x1a\xad\x2b\xdb\xb2\xa9\xf1\x4b\x4b\xf3\x74\x9e\x97\xc8\xb8\x3e\xce\x30\x6d\x60\x3e\xae\xe5\x42\x5a\x45\x52\x4e\xe6\x75\xfd\xfa\x06\xde\x93\xb9\x1b\xb4\x27\x3d\x6a\x6e\xdf\xd6\xfb\xbd\x86\xa7\x8e\xc9\xfe\x27\x19\x6b\xd7\x07\x2e\xe3\x6b\x75\x65\x92\xc0\x97\x00\xd3\x57\x0e\x16\xdb\x21\xbc\xc7\xf6\x37\xad\x6f\xa1\x6e\x67\xa0\xd6\x0a\x89\x4e\x17\xd2\xf2\x5f\xe1\x53\xef\xf1\xc3\x00\x1c\xfe\xb0\x1a\x40\x66\x39\x54\xa7\x34\x9d\xca\xf5\x10\xdd\xe3\xf2\xbe\x2a\x4b\x6d\x98\x52\x1f\x5e\x76\x9c\x37\x9f\xad\xc0\xf1\x82\x82\x50\x59\x40\xf7\xcb\x03\xd1\x10\xd8\xad\xf0\xd2\x36\xd6\xa7\xaa\xb8\x18\xf8\x63\x42\x1f\x5b\x97\xa6\xeb\x81\x58\xcf\xa6\x2d\xc0\x17\x82\x51\x5c\x06\x7a\xc2\x1f\x97\xb5\x84\xed\x84\x65\x46\x1d\x78\x37\x92\x09\x06\xc6\x56\x35\x58\x46\xae\xec\xb2\xf4\xbe\xa7\x28\x57\x6b\x29\x92\x39\x9c\x6f\xbc\x04\x5c\x8c\x6f\x70\x5f\x9f\xd8\x9b\xcb\x58\xb0\x1b\x34\x94\x02\x26\x46\xdb\x3a\x82\x98\xb0\xb0\x20\x94\x77\x4c\x29\x91\x33\x6d\x8a\xe3\x36\xce\xfa\x2c\xf4\xa7\x8e\xad\x24\x55\x15\xa1\x04\x32\x44\x87\xdb\xf5\x0d\xfa\xc1\x9d\x4d\x74\x7f\x1c\xa2\xb7\xd3\xbe\xcf\xcd\x0a\xb8\xc6\x47\xdc\x21\x53\xb4\x82\x17\x2f\xe0\xad\xf3\xe3\x9d\xb0\xc2\xa5\x49\xd6\xde\x68\x7d\xaf\xc8\x74\x78\x77\x21\x91\xc3\xfe\xd9\xc1\xb6\x17\x05\x05\x15\x6b\x32\xb6\x41\x7f\x09\x5c\x53\x77\xde\xbe\xab\x5a\x10\x27\x52\x27\x28\x67\xe4\x98\x37\xbb\xf8\xf0\x74\xec\xfb\x9f\x15\x8c\x54\xcf\x79\x75\x95\x9d\x41\xea\xa3\x3d\xc6\xe8\xb9\x2d\x29\x11\x99\x48\xe0\xde\x60\x59\x92\xd9\x9d\xc5\xba\x04\xe2\xbc\xa7\xd5\x0a\xb0\x4a\x85\xae\x3b\x49\xb8\xda\x99\x77\x34\x3d\x9a\x25\x66\xa1\x96\xa6\x75\xac\x58\x17\x38\xc8\xa8\xc7\xdf\xe7\xc5\x58\x28\x3c\x65\xda\xe2\x0e\x64\x95\x65\x5f\xf1\xee\x2c\xa1\x95\xb7\x72\x06\xf1\xeb\x90\xd8\xf1\x9c\x92\xa0\x02\xe7\x2a\x34\x2e\x87\xfc\x02\xcd\xe9\x7e\xd0\xb2\xc0\xa6\x93\xe6\x51\x45\x86\x64\xed\xf3\xe8\xbc\x31\x31\x72\x75\xb0\xfb\x67\xcf\xa8\x0d\xda\x6b\x54\xbd\x34\xee\xbb\x33\xb7\x13\xc7\x0f\xa8\x4f\xfd\x18\xfd\xd4\x89\x77\x90\x5b\x7a\xd8\x3f\x82\xce\xb4\x21\x91\xab\x11\xa0\xe5\x58\xcf\xd8\x3d\x7d\x3a\x38\x63\xff\x1a\xef\xcc\xf0\x63\x47\x3a\xde\xfe\x17\x41\xef\xa2\xdb\x71\x7d\x33\x33\xee\x8d\x0f\xbe\x04\x46\x1d\xee\xe2\x42\x6e\xba\x72\x3b\xfe\x9d\xe0\x60\x64\xec\x67\x5e\xed\xb4\xa0\x89\xb9\x83\x71\x14\x22\x6f\x77\x06\x29\x1d\xe9\x7a\xa7\xff\x68\xbe\x95\xb5\xd3\xe8\x52\xbc\x23\xde\x68\x75\xb9\x7d\x27\x4e\xda\x8c\xff\x08\x11\x8b\x82\x2c\x63\x51\xda\x6d\xa0\xb9\xe6\x35\xe3\x38\x25\x49\xec\x1d\x55\x27\x60\x88\x4a\x32\x85\xb0\xb6\x16\x75\x50\x78\x3a\x79\x3a\xf9\x37\x00\x00\xff\xff\xde\x50\xdd\x11\x66\x1a\x00\x00") +var _yaoModelsAgentAssistantModYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x59\xdf\x6f\xdc\xb6\x0f\x7f\xcf\x5f\x41\xf8\xa9\x05\xdc\xb4\xdf\x2f\xb0\x61\xc9\xd3\xd2\x06\xd8\x82\x35\x6b\xd0\xb4\xeb\x43\x51\x18\xb4\xcd\xf3\x69\x27\x4b\x9e\x44\xe7\x7a\x0b\xf2\xbf\x0f\x92\x7d\x67\xfb\xac\xfb\xe1\xcb\xf6\xd2\x5e\x24\x92\xfa\xf0\x87\x48\x8a\x7e\x3c\x03\x88\x14\x96\x14\x5d\x42\x74\x65\xad\xb0\x8c\x8a\xa3\xd8\x2d\x4b\x4c\x49\x06\xd6\x73\xb2\x99\x11\x15\x0b\xad\x06\xbb\xc0\x98\x4a\x82\x99\x36\x60\x59\x1b\xa1\x0a\xb8\xba\x01\xdc\x6c\x67\x5a\xcd\x44\x51\x1b\x74\x9c\x16\x50\xe5\x50\x12\x63\x8e\x8c\x8d\x60\xc6\xc2\x46\x97\xf0\x35\xc2\x82\xdc\x61\x10\xd9\x95\x65\x2a\xa3\x6f\x7e\x3b\xad\x85\x64\xe1\xce\x64\x53\x93\x5f\x32\x84\xb9\x56\x72\xd5\x5f\xb3\xda\x70\x74\x09\x17\x17\x17\x17\xad\xd4\x54\x3a\xf5\x1e\x3b\x45\xbd\xfc\x04\x3b\xb5\x20\xca\x74\x59\xba\x43\x9d\x42\x6e\xb7\x87\xbb\x11\x00\x4f\x5e\x5a\xa6\x65\x5d\x2a\x0f\xf3\x0c\x00\xe0\xd1\xff\xdb\x33\xa2\xc8\xbd\x32\x7e\x8d\x57\x95\x5f\xbb\xb9\xee\xd6\xc6\x56\x85\xfe\x76\x0f\xc7\x67\x25\xfe\xaa\xa9\x07\x44\xe4\xa4\x58\xcc\x04\x99\xc8\x93\x3f\xc5\x61\x08\x1b\x8e\x24\x04\xc6\xb2\x73\xcd\x29\x80\xae\x42\x48\x3a\x39\xa4\x0a\x9e\x47\x97\xf0\xff\x37\x6f\x36\x8b\xaa\x96\xb2\xb5\xff\x0c\xa5\xa5\xcd\x46\xed\x95\xeb\xf9\xcd\xaf\x0a\x95\xd3\xf7\x76\x71\xaf\x8a\x5e\x99\xe3\x55\xfb\x34\x20\x0f\xaa\x34\x94\x18\x54\x26\xa7\x19\xd6\x92\x07\x26\x8e\xa6\x63\xf7\xff\x1f\x8f\xfd\xf7\x01\x79\x10\xfb\x50\xe2\x21\x47\x1c\x04\x88\x0f\xc8\x68\xa6\x44\xce\x16\x43\x10\x64\x23\x15\x3e\x7f\x7c\xff\x2f\x42\xcd\xb4\x52\x94\xb1\x9e\x82\xf6\xdd\x98\x27\x08\xb8\x75\x37\x6c\xce\x88\x41\xcc\x40\x69\x06\x4b\x1c\x43\x6d\x09\x78\x4e\x50\x48\x9d\xa2\x1c\x53\x4f\xbc\x19\xc7\xa9\x99\x68\x9f\x77\xed\x58\xdd\x3f\xad\x56\xfb\x94\x85\x0f\xdb\x9c\x3d\xa5\x3b\x2a\x4b\x92\x32\x47\x08\xed\x49\x97\xed\x0f\x94\x30\x93\x58\xc4\xce\x8f\xc2\x23\xef\x54\xb5\x20\x85\xe5\xd8\xa7\xf4\x0c\x2b\x4c\x85\x14\xbc\x82\x99\x90\x4c\xa6\x77\xe2\x14\xcf\xf6\xab\xcc\xf1\xbe\xbd\x0e\x71\xed\xf0\x6e\x80\x72\xe3\xa9\x1f\x77\xc7\xe3\xf4\x0b\x5f\x21\xcf\x27\xe8\x70\x37\x20\x0f\x82\x77\xf5\x15\x0b\x82\xa1\xe4\x67\xdf\x26\x5f\x3a\x47\x40\x85\x62\x2a\x06\x99\x7e\x8d\xf4\x7e\x40\x1f\x46\xaa\x0d\x83\x36\x79\x9f\xbf\xcb\xa3\xeb\x22\x3d\xcd\x9e\xbe\x11\x48\x44\x20\x2e\x52\xad\x25\x61\xe8\x1e\xbc\x75\x3c\x70\x13\x8e\x8a\x2f\x73\xe2\x39\x19\xe0\xb9\xb0\x20\x2c\x20\xf8\x23\x5e\x09\x05\x81\x44\xdf\xc1\x1f\x96\xb4\xe3\xe3\x41\x62\x46\x73\x2d\x07\x46\x39\x70\x8f\xef\x42\x3c\x41\x8b\x07\xa5\x4f\x89\x82\xa9\x29\x66\x5f\x62\xe9\x60\x8d\xa4\x4e\x81\x54\x19\x5d\x56\x7c\x3c\xa4\xbb\x6d\xfa\xbd\x09\x7e\x24\x7d\x3a\xb4\xa4\x32\x64\x69\x32\x42\xb8\xdb\x66\xeb\x01\x6d\x49\x5a\xc9\xa0\x4d\x81\x4a\xfc\x4d\x39\xa4\x2b\x28\x75\x4e\xf0\x82\xce\x8b\xf3\x18\xb2\x39\x72\x0c\x8c\x76\x11\x03\x71\x76\xfe\xf2\xc4\x84\x2b\xac\x23\x4d\x9a\x7a\x96\xec\x34\xf9\xee\x3b\x76\xdd\x48\x80\x5f\x9a\x8a\xb8\xcf\x09\x9b\x1b\xa7\xa1\x3d\x77\x5d\x47\xdb\x73\xfd\x33\xc2\xdf\xc7\xff\xe2\x06\x2e\xb5\x59\xcc\xa4\x5e\x1e\xed\xae\x2f\x23\x86\x60\x44\x8d\xe5\x4e\xf1\xc0\x22\x3d\x1a\xcf\x6f\x4a\x2f\x25\xe5\x05\xc1\x5b\xb4\x87\xda\xc3\xc5\x86\x38\x45\xeb\xca\xb6\x6c\x6b\xfc\xa9\xa5\xf9\x78\x9c\xd7\xc8\x98\x1e\x46\x98\xb7\x64\x3e\xae\xe5\x89\xb0\xca\xac\x3a\x1a\xd7\xed\xbb\x3b\xb8\x27\xf3\x30\x68\x4f\x7a\xd0\xdc\xbe\x6d\xf6\x7b\x0d\x4f\x13\x93\xfd\x27\x19\x6b\xd7\x07\x9e\x86\xd7\xea\xda\x64\x81\x97\x00\xd3\x77\x0e\x16\xdb\x21\x79\x0f\xed\xaf\x5a\x2f\xa0\x69\x67\xa0\x91\x0a\x99\xce\x4f\x84\xe5\x5f\xe1\xc7\xda\xf1\xd3\x80\x38\xfc\xb0\x1a\x90\x4c\x72\xa8\xce\xe9\x78\x28\xb7\x43\xea\x1e\x96\xfb\xba\xaa\xb4\x61\xca\x7d\x78\xd9\x71\xde\x7c\x19\x83\xc3\x05\x25\xa1\xb2\x80\xee\x97\x27\x44\x43\x60\xd7\xcc\xa7\xb6\xb1\x3e\x55\x25\xe5\xc0\x1f\x47\xf4\xb1\x4d\x69\xba\x1d\xb0\xf5\x74\x5a\x13\xf8\x42\x30\x8a\xcb\x40\x4f\xf8\xc3\x69\x2d\xe1\x66\xc2\x32\xa1\x0e\x7c\x1c\xf1\x04\x03\x63\x2d\x1a\x2c\x23\xd7\xf6\xb4\xf4\xbe\xa3\x28\xd7\xa9\x14\xd9\x14\xcc\x77\x9e\x03\xae\xc6\x16\xdc\xd5\x27\xf6\xe6\x32\x16\xec\x1c\x0d\xe5\x80\x99\xd1\xb6\x89\x20\x26\x2c\x2d\x08\xe5\x1d\x53\x49\xe4\x99\x36\xe5\x61\x1d\x27\x3d\x0b\xfd\xa9\x63\x2d\x49\xd5\x65\x28\x81\x0c\xa9\xc3\xed\xfa\x1c\xfd\xe0\xce\x66\xba\x3f\x0e\xd1\xeb\x69\xdf\xd7\x76\x05\x5c\xe3\x23\x1e\x90\x29\x8a\xe1\xf5\x6b\xf8\xe0\xfc\xf8\x20\xac\x70\x69\x92\xb5\x57\x5a\x2f\x15\x99\x8e\xde\x19\x24\x72\xb4\x7f\x74\x64\x6b\x43\x41\x49\x65\x4a\xc6\xb6\xd4\xdf\x02\x66\xea\xce\xdb\x65\xaa\x13\xe2\x44\xea\x0c\xe5\x84\x1c\xf3\x7e\x9b\x3e\x3c\x1d\xfb\xdf\x4f\x0a\x46\xa2\xa7\xdc\xba\xda\x4e\x00\xf5\xd9\x1e\x42\xf4\xca\x56\x94\x89\x99\xc8\x60\x69\xb0\xaa\xc8\x6c\xcf\x62\x5d\x02\x71\xde\xd3\x2a\x06\xac\x73\xa1\x9b\x4e\x12\x6e\xb6\xe6\x1d\x6d\x8f\x66\x89\x59\xa8\x53\xd3\xba\x25\x34\x59\xe0\x41\xbc\x43\xbd\xfb\x2d\xf2\x7e\x62\xf7\x5b\x43\x65\xe0\xc5\x92\xd2\x18\x16\x69\x0c\x79\x1a\x43\x26\xd8\x2f\xc7\xb0\x24\x51\xcc\xd9\x3e\xab\x47\xc6\x9a\x75\x89\x83\x62\x70\x38\xb5\x5c\x8d\x99\xc2\x03\xb2\x35\xdd\x9e\x84\x78\xda\x00\xc2\x9d\x25\xb4\xf2\x5a\x4e\x00\x7e\x1b\x62\x3b\x9c\x0e\x33\x54\xe0\xa2\x0c\x8d\x4b\x7f\x3f\x43\x7b\xba\x9f\x11\x9d\xa0\xd3\x59\x9b\x0f\x22\x43\xb2\x09\xd7\xe8\xb2\x55\x31\x72\x25\xbc\xfb\xb3\xa7\xd4\x1c\xed\x2d\xaa\x5e\x05\xf2\x8d\xa5\xdb\x49\x92\x15\xea\x73\xff\x05\xe0\xdc\xb1\x77\x24\x0b\x5a\xed\x9e\x9e\xcf\xb4\x21\x51\xa8\x11\xc1\x06\x63\xf3\x79\xc0\xc3\xa7\xbd\x9f\x07\xbe\x27\x5b\x9f\x1f\x12\x07\x3a\x59\x7f\xdd\xe8\x19\x7a\xf3\xa5\xa1\x1d\x77\xf7\x26\x1f\xdf\x46\x6e\x6c\x0c\x17\x72\xd3\x8d\xdb\xf1\x57\x1c\x07\xd3\x6e\x3f\xae\xdb\x0c\x3a\xda\x98\xdb\x1b\x47\x21\xf0\x76\x6b\x06\xd4\x81\x6e\x76\xfa\x97\xe6\xb9\xa8\x9d\x44\x57\x9d\x1c\xf0\x56\xaa\x2b\x4b\x5b\x71\xb2\x29\x56\x8f\x10\xb1\x28\xc9\x32\x96\x95\x5d\x07\x9a\xeb\xbb\x67\x9c\xe4\x24\x89\xbd\xa3\x9a\xda\x01\x51\x45\xa6\x14\xd6\x36\xac\x8e\x14\x9e\xce\x9e\xce\xfe\x09\x00\x00\xff\xff\xc4\xdf\xb3\x3a\x21\x1b\x00\x00") func yaoModelsAgentAssistantModYaoBytes() ([]byte, error) { return bindataRead( @@ -2520,7 +2520,7 @@ func yaoModelsAgentAssistantModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 6758, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 6945, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2540,7 +2540,7 @@ func yaoModelsAgentChatModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2560,7 +2560,7 @@ func yaoModelsAgentMessageModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2580,7 +2580,7 @@ func yaoModelsAgentResumeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2600,7 +2600,7 @@ func yaoModelsAttachmentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4687, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4687, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2620,7 +2620,7 @@ func yaoModelsAuditModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2640,7 +2640,7 @@ func yaoModelsConfigModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2660,7 +2660,7 @@ func yaoModelsDslModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2680,7 +2680,7 @@ func yaoModelsInvitationModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2700,7 +2700,7 @@ func yaoModelsJobCategoryModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2720,7 +2720,7 @@ func yaoModelsJobExecutionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2740,7 +2740,7 @@ func yaoModelsJobJobModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6330, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6330, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2760,7 +2760,7 @@ func yaoModelsJobLogModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2780,7 +2780,7 @@ func yaoModelsKbCollectionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2800,7 +2800,7 @@ func yaoModelsKbDocumentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2820,7 +2820,7 @@ func yaoModelsMemberModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2840,7 +2840,7 @@ func yaoModelsRoleModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2860,7 +2860,7 @@ func yaoModelsTeamModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2880,7 +2880,7 @@ func yaoModelsUserOauth_accountModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2900,7 +2900,7 @@ func yaoModelsUserTypeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2920,7 +2920,7 @@ func yaoModelsUserModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2940,7 +2940,7 @@ func yaoReleaseAppYaz() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2960,7 +2960,7 @@ func yaoStoresAgentCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2980,7 +2980,7 @@ func yaoStoresAgentMemoryBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3000,7 +3000,7 @@ func yaoStoresCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3020,7 +3020,7 @@ func yaoStoresKbCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3040,7 +3040,7 @@ func yaoStoresKbStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/store.badger.yao", size: 349, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/stores/kb/store.badger.yao", size: 349, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3060,7 +3060,7 @@ func yaoStoresOauthCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3080,7 +3080,7 @@ func yaoStoresOauthClientBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/client.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/client.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3100,7 +3100,7 @@ func yaoStoresOauthStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/store.badger.yao", size: 376, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/store.badger.yao", size: 376, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3120,7 +3120,7 @@ func yaoStoresStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/store.badger.yao", size: 341, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/stores/store.badger.yao", size: 341, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3140,7 +3140,7 @@ func yaoUploadersAttachmentLocalYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1765337187, 0)} + info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1765593352, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/yao/models/agent/assistant.mod.yao b/yao/models/agent/assistant.mod.yao index 4e073fc9..8f83bde3 100644 --- a/yao/models/agent/assistant.mod.yao +++ b/yao/models/agent/assistant.mod.yao @@ -235,6 +235,13 @@ "comment": "Assistant-specific wrapper configurations for vision, audio, etc. If not set, use global settings", "nullable": true }, + { + "name": "search", + "type": "json", + "label": "Search", + "comment": "Search configuration (web, kb, db, citation, weights, etc.)", + "nullable": true + }, { "name": "automated", "type": "boolean",