yao/agent/load.go
Max cc5e888f5a feat(system): add Vision and Voice capabilities to System configuration
- Introduced Vision and Voice fields in the SystemConfig and System types to support new capabilities.
- Updated resolveEnvStrings function to handle environment variables for Vision and Voice.
- Enhanced unit tests to validate the new Vision and Voice configurations, ensuring correct environment variable resolution.
- Added GetVisionConnector and GetVoiceConnector functions to retrieve connectors for vision and voice capabilities.
2026-04-10 11:22:18 +08:00

504 lines
13 KiB
Go

package agent
import (
"fmt"
"path/filepath"
"github.com/yaoapp/gou/application"
"github.com/yaoapp/gou/connector"
"github.com/yaoapp/gou/helper"
"github.com/yaoapp/yao/agent/assistant"
"github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/i18n"
robottypes "github.com/yaoapp/yao/agent/robot/types"
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"
"github.com/yaoapp/yao/agent/store/xun"
"github.com/yaoapp/yao/agent/types"
"github.com/yaoapp/yao/config"
)
var agentDSL *types.DSL
// Load load AIGC
func Load(cfg config.Config) error {
setting := types.DSL{
Cache: "__yao.agent.cache", // default is "__yao.agent.cache"
StoreSetting: store.Setting{
MaxSize: 20,
TTL: 90 * 24 * 60 * 60, // 90 days in seconds
},
}
bytes, err := application.App.Read(filepath.Join("agent", "agent.yml"))
if err != nil {
return err
}
err = application.Parse("agent.yml", bytes, &setting)
if err != nil {
return err
}
if setting.StoreSetting.MaxSize == 0 {
setting.StoreSetting.MaxSize = 20 // default is 20
}
// Resolve $ENV.XXX references in system and uses fields
resolveEnvStrings(&setting)
// Default Assistant, Agent is the developer name, Mohe is the brand name of the assistant
if setting.Uses == nil {
setting.Uses = &types.Uses{Default: "mohe"} // Agent is the developer name, Mohe is the brand name of the assistant
}
// Title Assistant (default to system agent)
if setting.Uses.Title == "" {
setting.Uses.Title = "__yao.title"
}
// Prompt Assistant (default to system agent)
if setting.Uses.Prompt == "" {
setting.Uses.Prompt = "__yao.prompt"
}
// RobotPrompt Assistant (default to system agent)
if setting.Uses.RobotPrompt == "" {
setting.Uses.RobotPrompt = "__yao.robot_prompt"
}
agentDSL = &setting
// Register global phase agent resolver for robot pipeline.
// Robot executor falls back to this when no per-robot override is configured.
robottypes.GlobalPhaseAgentResolver = func(phase robottypes.Phase) string {
if agentDSL == nil || agentDSL.Uses == nil {
return ""
}
return agentDSL.Uses.GetPhaseAgent(string(phase))
}
// Store Setting
err = initStore()
if err != nil {
return err
}
// Initialize Global I18n
err = initGlobalI18n()
if err != nil {
return err
}
// Initialize Global Prompts
err = initGlobalPrompts()
if err != nil {
return err
}
// Initialize KB Configuration
err = initKBConfig()
if err != nil {
return err
}
// Initialize Search Configuration
err = initSearchConfig()
if err != nil {
return err
}
// Initialize Assistant
err = initAssistant()
if err != nil {
return err
}
return nil
}
// GetAgent returns the Agent settings
func GetAgent() *types.DSL {
return agentDSL
}
// initGlobalI18n initialize the global i18n
func initGlobalI18n() error {
locales, err := i18n.GetLocales("agent")
if err != nil {
return err
}
i18n.Locales["__global__"] = locales.Flatten()
return nil
}
// initGlobalPrompts initialize the global prompts from agent/prompts.yml
func initGlobalPrompts() error {
prompts, _, err := store.LoadGlobalPrompts()
if err != nil {
return err
}
agentDSL.GlobalPrompts = prompts
return nil
}
// GetGlobalPrompts returns the global prompts
// ctx: context variables for parsing $CTX.* variables
func GetGlobalPrompts(ctx map[string]string) []store.Prompt {
if agentDSL == nil || len(agentDSL.GlobalPrompts) == 0 {
return nil
}
return store.Prompts(agentDSL.GlobalPrompts).Parse(ctx)
}
// initStore initialize the store
func initStore() error {
var err error
if agentDSL.StoreSetting.Connector == "default" || agentDSL.StoreSetting.Connector == "" {
agentDSL.Store, err = xun.NewXun(agentDSL.StoreSetting)
return err
}
// other connector
conn, err := connector.Select(agentDSL.StoreSetting.Connector)
if err != nil {
return fmt.Errorf("load connectors error: %s", err.Error())
}
if conn.Is(connector.DATABASE) {
agentDSL.Store, err = xun.NewXun(agentDSL.StoreSetting)
return err
} else if conn.Is(connector.REDIS) {
agentDSL.Store = storeRedis.NewRedis()
return nil
} else if conn.Is(connector.MONGO) {
agentDSL.Store = storeMongo.NewMongo()
return nil
}
return fmt.Errorf("Agent store connector %s not support", agentDSL.StoreSetting.Connector)
}
// initAssistant initialize the assistant
func initAssistant() error {
// Set Storage
assistant.SetStorage(agentDSL.Store)
// Set Store Setting (MaxSize, TTL, etc.)
assistant.SetStoreSetting(&agentDSL.StoreSetting)
// Set global Uses configuration
if agentDSL.Uses != nil {
globalUses := &context.Uses{
Vision: agentDSL.Uses.Vision,
Audio: agentDSL.Uses.Audio,
Search: agentDSL.Uses.Search,
Fetch: agentDSL.Uses.Fetch,
Web: agentDSL.Uses.Web,
Keyword: agentDSL.Uses.Keyword,
QueryDSL: agentDSL.Uses.QueryDSL,
Rerank: agentDSL.Uses.Rerank,
}
assistant.SetGlobalUses(globalUses)
}
// Set global prompts
if len(agentDSL.GlobalPrompts) > 0 {
assistant.SetGlobalPrompts(agentDSL.GlobalPrompts)
}
if agentDSL.KB != nil {
assistant.SetGlobalKBSetting(agentDSL.KB)
}
if agentDSL.Search != nil {
assistant.SetGlobalSearchConfig(agentDSL.Search)
}
// Set system agents configuration
if agentDSL.System != nil {
assistant.SetSystemConfig(&assistant.SystemConfig{
Default: agentDSL.System.Default,
Keyword: agentDSL.System.Keyword,
QueryDSL: agentDSL.System.QueryDSL,
Title: agentDSL.System.Title,
Prompt: agentDSL.System.Prompt,
NeedSearch: agentDSL.System.NeedSearch,
Entity: agentDSL.System.Entity,
Vision: agentDSL.System.Vision,
Voice: agentDSL.System.Voice,
})
}
// Load System Agents (from bindata: __yao.keyword, __yao.querydsl, etc.)
if err := assistant.LoadSystemAgents(); err != nil {
return err
}
// Load Built-in Assistants (from application /assistants directory)
err := assistant.LoadBuiltIn()
if err != nil {
return err
}
// Default Assistant
defaultAssistant, err := defaultAssistant()
if err != nil {
return err
}
agentDSL.Assistant = defaultAssistant
return nil
}
// initKBConfig initialize the knowledge base configuration from agent/kb.yml
func initKBConfig() error {
path := filepath.Join("agent", "kb.yml")
if exists, _ := application.App.Exists(path); !exists {
return nil // KB config is optional
}
// Read the KB configuration
bytes, err := application.App.Read(path)
if err != nil {
return err
}
var kbSetting store.KBSetting
err = application.Parse("kb.yml", bytes, &kbSetting)
if err != nil {
return err
}
agentDSL.KB = &kbSetting
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 == "" {
return nil, fmt.Errorf("default assistant not found")
}
return assistant.Get(agentDSL.Uses.Default)
}
// resolveEnvStrings resolves $ENV.XXX references in agent.yml string fields.
// agent.yml is parsed via yaml.Unmarshal which does not handle $ENV substitution,
// unlike connector files which call helper.EnvString explicitly during Register.
func resolveEnvStrings(setting *types.DSL) {
if setting.System != nil {
setting.System.Default = helper.EnvString(setting.System.Default)
setting.System.Keyword = helper.EnvString(setting.System.Keyword)
setting.System.QueryDSL = helper.EnvString(setting.System.QueryDSL)
setting.System.Title = helper.EnvString(setting.System.Title)
setting.System.Prompt = helper.EnvString(setting.System.Prompt)
setting.System.RobotPrompt = helper.EnvString(setting.System.RobotPrompt)
setting.System.NeedSearch = helper.EnvString(setting.System.NeedSearch)
setting.System.Entity = helper.EnvString(setting.System.Entity)
setting.System.Vision = helper.EnvString(setting.System.Vision)
setting.System.Voice = helper.EnvString(setting.System.Voice)
}
if setting.Uses != nil {
setting.Uses.Default = helper.EnvString(setting.Uses.Default)
setting.Uses.Title = helper.EnvString(setting.Uses.Title)
setting.Uses.Prompt = helper.EnvString(setting.Uses.Prompt)
setting.Uses.RobotPrompt = helper.EnvString(setting.Uses.RobotPrompt)
setting.Uses.Vision = helper.EnvString(setting.Uses.Vision)
setting.Uses.Audio = helper.EnvString(setting.Uses.Audio)
setting.Uses.Search = helper.EnvString(setting.Uses.Search)
setting.Uses.Fetch = helper.EnvString(setting.Uses.Fetch)
setting.Uses.Web = helper.EnvString(setting.Uses.Web)
setting.Uses.Keyword = helper.EnvString(setting.Uses.Keyword)
setting.Uses.QueryDSL = helper.EnvString(setting.Uses.QueryDSL)
setting.Uses.Rerank = helper.EnvString(setting.Uses.Rerank)
setting.Uses.Inspiration = helper.EnvString(setting.Uses.Inspiration)
setting.Uses.Goals = helper.EnvString(setting.Uses.Goals)
setting.Uses.Tasks = helper.EnvString(setting.Uses.Tasks)
setting.Uses.Delivery = helper.EnvString(setting.Uses.Delivery)
setting.Uses.Learning = helper.EnvString(setting.Uses.Learning)
setting.Uses.Host = helper.EnvString(setting.Uses.Host)
setting.Uses.Validation = helper.EnvString(setting.Uses.Validation)
}
setting.Cache = helper.EnvString(setting.Cache)
}