Implement System Agents Configuration and Loading Mechanism
- Added configuration support for system agents in the assistant initialization process, allowing for custom connectors for agents like __yao.keyword and __yao.querydsl. - Implemented the loading mechanism for system agents from bindata, ensuring that essential agents are available during runtime. - Updated the LoadBuiltIn function to exclude system agents from being removed, enhancing the management of built-in and system agents. - Enhanced test coverage by introducing tests for loading system agents, verifying their presence and correctness in the cache. - Updated documentation to reflect the new system agents configuration and loading processes.
This commit is contained in:
parent
a2182eda1d
commit
c86ccb55b1
18 changed files with 1205 additions and 193 deletions
|
|
@ -45,7 +45,7 @@ func LoadBuiltIn() error {
|
||||||
// Get all existing built-in assistants
|
// Get all existing built-in assistants
|
||||||
deletedBuiltIn := map[string]bool{}
|
deletedBuiltIn := map[string]bool{}
|
||||||
|
|
||||||
// Remove the built-in assistants
|
// Remove the built-in assistants (exclude system agents with __yao. prefix)
|
||||||
if storage != nil {
|
if storage != nil {
|
||||||
|
|
||||||
builtIn := true
|
builtIn := true
|
||||||
|
|
@ -54,8 +54,12 @@ func LoadBuiltIn() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all existing built-in assistants
|
// Get all existing built-in assistants (exclude system agents)
|
||||||
for _, assistant := range res.Data {
|
for _, assistant := range res.Data {
|
||||||
|
// Skip system agents (they are managed by LoadSystemAgents)
|
||||||
|
if strings.HasPrefix(assistant.ID, "__yao.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
deletedBuiltIn[assistant.ID] = true
|
deletedBuiltIn[assistant.ID] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
367
agent/assistant/load_system.go
Normal file
367
agent/assistant/load_system.go
Normal file
|
|
@ -0,0 +1,367 @@
|
||||||
|
package assistant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou/application"
|
||||||
|
"github.com/yaoapp/gou/connector"
|
||||||
|
gouOpenAI "github.com/yaoapp/gou/connector/openai"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
|
"github.com/yaoapp/yao/agent/i18n"
|
||||||
|
store "github.com/yaoapp/yao/agent/store/types"
|
||||||
|
"github.com/yaoapp/yao/data"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// systemAgents defines the system agents loaded from bindata
|
||||||
|
// These are internal agents used by the system (e.g., keyword extraction, querydsl generation)
|
||||||
|
// The directory name is without __yao. prefix, prefix is added during loading
|
||||||
|
// Format: directory name -> bindata path prefix
|
||||||
|
var systemAgents = []string{
|
||||||
|
"keyword",
|
||||||
|
"querydsl",
|
||||||
|
"title",
|
||||||
|
"prompt",
|
||||||
|
"needsearch",
|
||||||
|
"entity",
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemConfig holds the system agents connector configuration
|
||||||
|
// This is set from agent.yml system block
|
||||||
|
type SystemConfig struct {
|
||||||
|
Default string // Default connector for all system agents
|
||||||
|
Keyword string // Connector for __yao.keyword agent
|
||||||
|
QueryDSL string // Connector for __yao.querydsl agent
|
||||||
|
Title string // Connector for __yao.title agent
|
||||||
|
Prompt string // Connector for __yao.prompt agent
|
||||||
|
NeedSearch string // Connector for __yao.needsearch agent
|
||||||
|
Entity string // Connector for __yao.entity agent
|
||||||
|
}
|
||||||
|
|
||||||
|
// systemConfig holds the system agents configuration (global variable like others in load.go)
|
||||||
|
var systemConfig *SystemConfig = nil
|
||||||
|
|
||||||
|
// SetSystemConfig sets the system agents configuration
|
||||||
|
func SetSystemConfig(config *SystemConfig) {
|
||||||
|
systemConfig = config
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemConfig returns the system agents configuration
|
||||||
|
func GetSystemConfig() *SystemConfig {
|
||||||
|
return systemConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadSystemAgents loads the system agents from bindata
|
||||||
|
// These are internal agents like __yao.keyword and __yao.querydsl
|
||||||
|
// They are loaded before application assistants
|
||||||
|
// Behavior is same as LoadBuiltIn, just reads from bindata instead of filesystem
|
||||||
|
func LoadSystemAgents() error {
|
||||||
|
|
||||||
|
// Get all existing system agents (for cleanup)
|
||||||
|
deletedSystem := map[string]bool{}
|
||||||
|
if storage != nil {
|
||||||
|
// System agents have "system" tag
|
||||||
|
tags := []string{"system"}
|
||||||
|
builtIn := true
|
||||||
|
res, err := storage.GetAssistants(store.AssistantFilter{
|
||||||
|
Tags: tags,
|
||||||
|
BuiltIn: &builtIn,
|
||||||
|
Select: []string{"assistant_id", "id"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Failed to get existing system agents: %v", err)
|
||||||
|
} else {
|
||||||
|
for _, assistant := range res.Data {
|
||||||
|
deletedSystem[assistant.ID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort := 1
|
||||||
|
for _, name := range systemAgents {
|
||||||
|
// Build agent ID with __yao. prefix
|
||||||
|
id := "__yao." + name
|
||||||
|
pathPrefix := "yao/assistants/" + name
|
||||||
|
|
||||||
|
assistant, err := loadSystemAgent(id, pathPrefix)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Failed to load system agent %s: %v", id, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sort order
|
||||||
|
if assistant.Sort == 0 {
|
||||||
|
assistant.Sort = sort
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to storage
|
||||||
|
if err := assistant.Save(); err != nil {
|
||||||
|
log.Warn("Failed to save system agent %s: %v", id, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the assistant
|
||||||
|
if err := assistant.initialize(); err != nil {
|
||||||
|
log.Warn("Failed to initialize system agent %s: %v", id, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sort++
|
||||||
|
loaded.Put(assistant)
|
||||||
|
log.Trace("Loaded system agent: %s", id)
|
||||||
|
|
||||||
|
// Remove from deleted list
|
||||||
|
delete(deletedSystem, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deleted system agents
|
||||||
|
if len(deletedSystem) > 0 {
|
||||||
|
assistantIDs := []string{}
|
||||||
|
for assistantID := range deletedSystem {
|
||||||
|
assistantIDs = append(assistantIDs, assistantID)
|
||||||
|
}
|
||||||
|
if _, err := storage.DeleteAssistants(store.AssistantFilter{AssistantIDs: assistantIDs}); err != nil {
|
||||||
|
log.Warn("Failed to delete obsolete system agents: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadSystemAgent loads a single system agent from bindata
|
||||||
|
// This follows the same pattern as LoadPath but reads from bindata
|
||||||
|
func loadSystemAgent(id, pathPrefix string) (*Assistant, error) {
|
||||||
|
// Read package.yao from bindata
|
||||||
|
pkgPath := pathPrefix + "/package.yao"
|
||||||
|
pkgContent, err := data.Read(pkgPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read %s: %w", pkgPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse package.yao
|
||||||
|
var pkgData map[string]interface{}
|
||||||
|
if err := application.Parse(pkgPath, pkgContent, &pkgData); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s: %w", pkgPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set assistant_id and path
|
||||||
|
pkgData["assistant_id"] = id
|
||||||
|
pkgData["path"] = "/" + pathPrefix
|
||||||
|
|
||||||
|
// Set type if not specified
|
||||||
|
if _, has := pkgData["type"]; !has {
|
||||||
|
pkgData["type"] = "assistant"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve connector for this system agent
|
||||||
|
connectorID := resolveSystemConnector(id)
|
||||||
|
if connectorID != "" {
|
||||||
|
pkgData["connector"] = connectorID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read prompts.yml from bindata (default prompts)
|
||||||
|
promptsPath := pathPrefix + "/prompts.yml"
|
||||||
|
promptsContent, err := data.Read(promptsPath)
|
||||||
|
if err == nil {
|
||||||
|
var prompts []store.Prompt
|
||||||
|
if err := yaml.Unmarshal(promptsContent, &prompts); err == nil && len(prompts) > 0 {
|
||||||
|
pkgData["prompts"] = prompts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read prompt_presets from prompts directory
|
||||||
|
presets := loadSystemPromptPresets(pathPrefix)
|
||||||
|
if len(presets) > 0 {
|
||||||
|
pkgData["prompt_presets"] = presets
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load scripts from src directory (hook script source and other scripts sources)
|
||||||
|
// These will be compiled by loadMap -> LoadScriptsFromData
|
||||||
|
hookScriptSource, scriptsSource := loadSystemScripts(pathPrefix)
|
||||||
|
if hookScriptSource != "" {
|
||||||
|
pkgData["script"] = hookScriptSource
|
||||||
|
}
|
||||||
|
if len(scriptsSource) > 0 {
|
||||||
|
pkgData["scripts"] = scriptsSource
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read locales
|
||||||
|
locales, err := loadSystemLocales(pathPrefix)
|
||||||
|
if err == nil && len(locales) > 0 {
|
||||||
|
pkgData["locales"] = locales
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as system agent
|
||||||
|
pkgData["readonly"] = true
|
||||||
|
pkgData["built_in"] = true
|
||||||
|
pkgData["tags"] = []string{"system"}
|
||||||
|
|
||||||
|
// Load from map (same as LoadPath, includes initialize())
|
||||||
|
return loadMap(pkgData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveSystemConnector resolves the connector for a system agent
|
||||||
|
// Priority: specific agent config > system.default > defaultConnector > fallback to first capable connector
|
||||||
|
func resolveSystemConnector(agentID string) string {
|
||||||
|
// Try specific agent config first
|
||||||
|
if systemConfig != nil {
|
||||||
|
switch agentID {
|
||||||
|
case "__yao.keyword":
|
||||||
|
if systemConfig.Keyword != "" {
|
||||||
|
return systemConfig.Keyword
|
||||||
|
}
|
||||||
|
case "__yao.querydsl":
|
||||||
|
if systemConfig.QueryDSL != "" {
|
||||||
|
return systemConfig.QueryDSL
|
||||||
|
}
|
||||||
|
case "__yao.title":
|
||||||
|
if systemConfig.Title != "" {
|
||||||
|
return systemConfig.Title
|
||||||
|
}
|
||||||
|
case "__yao.prompt":
|
||||||
|
if systemConfig.Prompt != "" {
|
||||||
|
return systemConfig.Prompt
|
||||||
|
}
|
||||||
|
case "__yao.needsearch":
|
||||||
|
if systemConfig.NeedSearch != "" {
|
||||||
|
return systemConfig.NeedSearch
|
||||||
|
}
|
||||||
|
case "__yao.entity":
|
||||||
|
if systemConfig.Entity != "" {
|
||||||
|
return systemConfig.Entity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try system default
|
||||||
|
if systemConfig.Default != "" {
|
||||||
|
return systemConfig.Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try global default connector
|
||||||
|
if defaultConnector != "" {
|
||||||
|
return defaultConnector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: find first connector that supports tool calling
|
||||||
|
return findCapableConnector()
|
||||||
|
}
|
||||||
|
|
||||||
|
// findCapableConnector finds the first connector that supports tool calling
|
||||||
|
func findCapableConnector() string {
|
||||||
|
// Get all registered connectors
|
||||||
|
for id, conn := range connector.Connectors {
|
||||||
|
if !conn.Is(connector.OPENAI) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check from modelCapabilities (user-defined in models.yml)
|
||||||
|
if caps, exists := modelCapabilities[id]; exists {
|
||||||
|
if caps.ToolCalls {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check capabilities from connector's Options
|
||||||
|
if connOpenAI, ok := conn.(*gouOpenAI.Connector); ok {
|
||||||
|
if connOpenAI.Options.Capabilities != nil && connOpenAI.Options.Capabilities.ToolCalls {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No capable connector found, return empty
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadSystemPromptPresets loads prompt presets from bindata prompts directory
|
||||||
|
func loadSystemPromptPresets(pathPrefix string) map[string][]store.Prompt {
|
||||||
|
presets := make(map[string][]store.Prompt)
|
||||||
|
promptsDir := pathPrefix + "/prompts"
|
||||||
|
|
||||||
|
// Try common preset files
|
||||||
|
presetFiles := []string{"chat.yml", "task.yml", "code.yml", "analysis.yml"}
|
||||||
|
for _, filename := range presetFiles {
|
||||||
|
presetPath := promptsDir + "/" + filename
|
||||||
|
content, err := data.Read(presetPath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var prompts []store.Prompt
|
||||||
|
if err := yaml.Unmarshal(content, &prompts); err == nil && len(prompts) > 0 {
|
||||||
|
presetName := strings.TrimSuffix(filename, ".yml")
|
||||||
|
presets[presetName] = prompts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return presets
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadSystemScripts loads scripts source from bindata src directory
|
||||||
|
// Returns hook script source and other scripts sources (as strings)
|
||||||
|
// These will be compiled by loadMap -> LoadScriptsFromData
|
||||||
|
func loadSystemScripts(pathPrefix string) (string, map[string]string) {
|
||||||
|
srcDir := pathPrefix + "/src"
|
||||||
|
|
||||||
|
// Try to load hook script (index.ts)
|
||||||
|
var hookScriptSource string
|
||||||
|
indexPath := srcDir + "/index.ts"
|
||||||
|
indexContent, err := data.Read(indexPath)
|
||||||
|
if err == nil && len(indexContent) > 0 {
|
||||||
|
hookScriptSource = string(indexContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to load other scripts
|
||||||
|
scripts := make(map[string]string)
|
||||||
|
scriptFiles := []string{"utils.ts", "helpers.ts", "tools.ts"}
|
||||||
|
for _, filename := range scriptFiles {
|
||||||
|
scriptPath := srcDir + "/" + filename
|
||||||
|
content, err := data.Read(scriptPath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptName := strings.TrimSuffix(filename, ".ts")
|
||||||
|
scripts[scriptName] = string(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(scripts) == 0 {
|
||||||
|
scripts = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return hookScriptSource, scripts
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadSystemLocales loads locales from bindata
|
||||||
|
func loadSystemLocales(pathPrefix string) (i18n.Map, error) {
|
||||||
|
locales := make(i18n.Map)
|
||||||
|
|
||||||
|
// Try to load common locale files
|
||||||
|
localeFiles := []string{"en-us.yml", "zh-cn.yml", "en.yml", "zh.yml"}
|
||||||
|
localesDir := pathPrefix + "/locales"
|
||||||
|
|
||||||
|
for _, filename := range localeFiles {
|
||||||
|
localePath := filepath.Join(localesDir, filename)
|
||||||
|
content, err := data.Read(localePath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse locale file
|
||||||
|
locale := strings.TrimSuffix(filename, ".yml")
|
||||||
|
var messages map[string]any
|
||||||
|
if err := yaml.Unmarshal(content, &messages); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
locales[locale] = i18n.I18n{
|
||||||
|
Locale: locale,
|
||||||
|
Messages: messages,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return locales, nil
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package assistant
|
package assistant_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/yaoapp/yao/agent"
|
||||||
|
"github.com/yaoapp/yao/agent/assistant"
|
||||||
store "github.com/yaoapp/yao/agent/store/types"
|
store "github.com/yaoapp/yao/agent/store/types"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/yao/config"
|
||||||
"github.com/yaoapp/yao/test"
|
"github.com/yaoapp/yao/test"
|
||||||
|
|
@ -14,13 +16,19 @@ func prepare(t *testing.T) {
|
||||||
test.Prepare(t, config.Conf)
|
test.Prepare(t, config.Conf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepareAgent(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
err := agent.Load(config.Conf)
|
||||||
|
require.NoError(t, err, "agent.Load should succeed")
|
||||||
|
}
|
||||||
|
|
||||||
// TestLoadPath tests loading assistant from path
|
// TestLoadPath tests loading assistant from path
|
||||||
func TestLoadPath(t *testing.T) {
|
func TestLoadPath(t *testing.T) {
|
||||||
prepare(t)
|
prepare(t)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
t.Run("LoadFullFieldsAssistant", func(t *testing.T) {
|
t.Run("LoadFullFieldsAssistant", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, assistant)
|
require.NotNil(t, assistant)
|
||||||
|
|
||||||
|
|
@ -67,7 +75,7 @@ func TestLoadPath(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("LoadConnectorOptions", func(t *testing.T) {
|
t.Run("LoadConnectorOptions", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, assistant)
|
require.NotNil(t, assistant)
|
||||||
|
|
||||||
|
|
@ -84,7 +92,7 @@ func TestLoadPath(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("LoadPromptPresets", func(t *testing.T) {
|
t.Run("LoadPromptPresets", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, assistant)
|
require.NotNil(t, assistant)
|
||||||
|
|
||||||
|
|
@ -116,7 +124,7 @@ func TestLoadPath(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("LoadKnowledgeBase", func(t *testing.T) {
|
t.Run("LoadKnowledgeBase", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, assistant)
|
require.NotNil(t, assistant)
|
||||||
|
|
||||||
|
|
@ -129,7 +137,7 @@ func TestLoadPath(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("LoadMCPServers", func(t *testing.T) {
|
t.Run("LoadMCPServers", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, assistant)
|
require.NotNil(t, assistant)
|
||||||
|
|
||||||
|
|
@ -143,7 +151,7 @@ func TestLoadPath(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("LoadWorkflow", func(t *testing.T) {
|
t.Run("LoadWorkflow", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, assistant)
|
require.NotNil(t, assistant)
|
||||||
|
|
||||||
|
|
@ -156,7 +164,7 @@ func TestLoadPath(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("LoadPlaceholder", func(t *testing.T) {
|
t.Run("LoadPlaceholder", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, assistant)
|
require.NotNil(t, assistant)
|
||||||
|
|
||||||
|
|
@ -169,7 +177,7 @@ func TestLoadPath(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("LoadLocales", func(t *testing.T) {
|
t.Run("LoadLocales", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, assistant)
|
require.NotNil(t, assistant)
|
||||||
|
|
||||||
|
|
@ -186,7 +194,7 @@ func TestLoadPath(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("LoadNonExistentAssistant", func(t *testing.T) {
|
t.Run("LoadNonExistentAssistant", func(t *testing.T) {
|
||||||
_, err := LoadPath("/assistants/non-existent")
|
_, err := assistant.LoadPath("/assistants/non-existent")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +204,7 @@ func TestLoadPathMCPTest(t *testing.T) {
|
||||||
prepare(t)
|
prepare(t)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
assistant, err := LoadPath("/assistants/tests/mcptest")
|
assistant, err := assistant.LoadPath("/assistants/tests/mcptest")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, assistant)
|
require.NotNil(t, assistant)
|
||||||
|
|
||||||
|
|
@ -220,7 +228,7 @@ func TestLoadPathBuildRequest(t *testing.T) {
|
||||||
prepare(t)
|
prepare(t)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
assistant, err := LoadPath("/assistants/tests/buildrequest")
|
assistant, err := assistant.LoadPath("/assistants/tests/buildrequest")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, assistant)
|
require.NotNil(t, assistant)
|
||||||
|
|
||||||
|
|
@ -238,57 +246,57 @@ func TestLoadPathBuildRequest(t *testing.T) {
|
||||||
// TestCache tests the assistant cache functionality
|
// TestCache tests the assistant cache functionality
|
||||||
func TestCache(t *testing.T) {
|
func TestCache(t *testing.T) {
|
||||||
// Clear any existing cache
|
// Clear any existing cache
|
||||||
ClearCache()
|
assistant.ClearCache()
|
||||||
|
|
||||||
// Set small cache for testing
|
// Set small cache for testing
|
||||||
SetCache(3)
|
assistant.SetCache(3)
|
||||||
assert.NotNil(t, loaded)
|
assert.NotNil(t, assistant.GetCache())
|
||||||
|
|
||||||
// Create test assistants
|
// Create test assistants
|
||||||
ast1 := &Assistant{AssistantModel: store.AssistantModel{ID: "id1", Name: "Assistant 1"}}
|
ast1 := &assistant.Assistant{AssistantModel: store.AssistantModel{ID: "id1", Name: "Assistant 1"}}
|
||||||
ast2 := &Assistant{AssistantModel: store.AssistantModel{ID: "id2", Name: "Assistant 2"}}
|
ast2 := &assistant.Assistant{AssistantModel: store.AssistantModel{ID: "id2", Name: "Assistant 2"}}
|
||||||
ast3 := &Assistant{AssistantModel: store.AssistantModel{ID: "id3", Name: "Assistant 3"}}
|
ast3 := &assistant.Assistant{AssistantModel: store.AssistantModel{ID: "id3", Name: "Assistant 3"}}
|
||||||
ast4 := &Assistant{AssistantModel: store.AssistantModel{ID: "id4", Name: "Assistant 4"}}
|
ast4 := &assistant.Assistant{AssistantModel: store.AssistantModel{ID: "id4", Name: "Assistant 4"}}
|
||||||
|
|
||||||
t.Run("PutAndGet", func(t *testing.T) {
|
t.Run("PutAndGet", func(t *testing.T) {
|
||||||
loaded.Put(ast1)
|
assistant.GetCache().Put(ast1)
|
||||||
assert.Equal(t, 1, loaded.Len())
|
assert.Equal(t, 1, assistant.GetCache().Len())
|
||||||
|
|
||||||
cached, exists := loaded.Get("id1")
|
cached, exists := assistant.GetCache().Get("id1")
|
||||||
assert.True(t, exists)
|
assert.True(t, exists)
|
||||||
assert.Equal(t, ast1, cached)
|
assert.Equal(t, ast1, cached)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CacheEviction", func(t *testing.T) {
|
t.Run("CacheEviction", func(t *testing.T) {
|
||||||
loaded.Put(ast2)
|
assistant.GetCache().Put(ast2)
|
||||||
loaded.Put(ast3)
|
assistant.GetCache().Put(ast3)
|
||||||
assert.Equal(t, 3, loaded.Len())
|
assert.Equal(t, 3, assistant.GetCache().Len())
|
||||||
|
|
||||||
// Access ast1 to make it recently used
|
// Access ast1 to make it recently used
|
||||||
loaded.Get("id1")
|
assistant.GetCache().Get("id1")
|
||||||
|
|
||||||
// Add ast4, should evict ast2 (least recently used)
|
// Add ast4, should evict ast2 (least recently used)
|
||||||
loaded.Put(ast4)
|
assistant.GetCache().Put(ast4)
|
||||||
assert.Equal(t, 3, loaded.Len())
|
assert.Equal(t, 3, assistant.GetCache().Len())
|
||||||
|
|
||||||
_, exists := loaded.Get("id2")
|
_, exists := assistant.GetCache().Get("id2")
|
||||||
assert.False(t, exists, "ast2 should be evicted")
|
assert.False(t, exists, "ast2 should be evicted")
|
||||||
|
|
||||||
_, exists = loaded.Get("id1")
|
_, exists = assistant.GetCache().Get("id1")
|
||||||
assert.True(t, exists, "ast1 should still exist")
|
assert.True(t, exists, "ast1 should still exist")
|
||||||
|
|
||||||
_, exists = loaded.Get("id4")
|
_, exists = assistant.GetCache().Get("id4")
|
||||||
assert.True(t, exists, "ast4 should exist")
|
assert.True(t, exists, "ast4 should exist")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ClearCache", func(t *testing.T) {
|
t.Run("ClearCache", func(t *testing.T) {
|
||||||
ClearCache()
|
assistant.ClearCache()
|
||||||
assert.Nil(t, loaded)
|
assert.Nil(t, assistant.GetCache())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("SetCacheAfterClear", func(t *testing.T) {
|
t.Run("SetCacheAfterClear", func(t *testing.T) {
|
||||||
SetCache(100)
|
assistant.SetCache(100)
|
||||||
assert.NotNil(t, loaded)
|
assert.NotNil(t, assistant.GetCache())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,7 +306,7 @@ func TestClone(t *testing.T) {
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
t.Run("CloneFullFieldsAssistant", func(t *testing.T) {
|
t.Run("CloneFullFieldsAssistant", func(t *testing.T) {
|
||||||
original, err := LoadPath("/assistants/tests/fullfields")
|
original, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
clone := original.Clone()
|
clone := original.Clone()
|
||||||
|
|
@ -328,7 +336,7 @@ func TestClone(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CloneNil", func(t *testing.T) {
|
t.Run("CloneNil", func(t *testing.T) {
|
||||||
var nilAssistant *Assistant
|
var nilAssistant *assistant.Assistant
|
||||||
assert.Nil(t, nilAssistant.Clone())
|
assert.Nil(t, nilAssistant.Clone())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -339,7 +347,7 @@ func TestUpdate(t *testing.T) {
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
t.Run("UpdateBasicFields", func(t *testing.T) {
|
t.Run("UpdateBasicFields", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
updates := map[string]interface{}{
|
updates := map[string]interface{}{
|
||||||
|
|
@ -357,7 +365,7 @@ func TestUpdate(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UpdateConnectorOptions", func(t *testing.T) {
|
t.Run("UpdateConnectorOptions", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
updates := map[string]interface{}{
|
updates := map[string]interface{}{
|
||||||
|
|
@ -377,7 +385,7 @@ func TestUpdate(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UpdatePromptPresets", func(t *testing.T) {
|
t.Run("UpdatePromptPresets", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
updates := map[string]interface{}{
|
updates := map[string]interface{}{
|
||||||
|
|
@ -398,7 +406,7 @@ func TestUpdate(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UpdateSource", func(t *testing.T) {
|
t.Run("UpdateSource", func(t *testing.T) {
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
updates := map[string]interface{}{
|
updates := map[string]interface{}{
|
||||||
|
|
@ -412,7 +420,7 @@ func TestUpdate(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UpdateNilAssistant", func(t *testing.T) {
|
t.Run("UpdateNilAssistant", func(t *testing.T) {
|
||||||
var nilAssistant *Assistant
|
var nilAssistant *assistant.Assistant
|
||||||
err := nilAssistant.Update(map[string]interface{}{"name": "test"})
|
err := nilAssistant.Update(map[string]interface{}{"name": "test"})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
@ -423,7 +431,7 @@ func TestMap(t *testing.T) {
|
||||||
prepare(t)
|
prepare(t)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
assistant, err := LoadPath("/assistants/tests/fullfields")
|
assistant, err := assistant.LoadPath("/assistants/tests/fullfields")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
m := assistant.Map()
|
m := assistant.Map()
|
||||||
|
|
@ -451,16 +459,103 @@ func TestMap(t *testing.T) {
|
||||||
assert.Equal(t, assistant.Source, m["source"])
|
assert.Equal(t, assistant.Source, m["source"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLoadSystemAgents tests loading system agents from bindata
|
||||||
|
func TestLoadSystemAgents(t *testing.T) {
|
||||||
|
prepareAgent(t)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
// Clear cache first
|
||||||
|
assistant.ClearCache()
|
||||||
|
assistant.SetCache(200)
|
||||||
|
|
||||||
|
t.Run("LoadSystemAgents", func(t *testing.T) {
|
||||||
|
err := assistant.LoadSystemAgents()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check __yao.keyword
|
||||||
|
keywordAst, keywordExists := assistant.GetCache().Get("__yao.keyword")
|
||||||
|
require.True(t, keywordExists, "__yao.keyword should be loaded")
|
||||||
|
assert.Equal(t, "__yao.keyword", keywordAst.ID)
|
||||||
|
assert.Equal(t, "Keyword Extraction", keywordAst.Name)
|
||||||
|
assert.True(t, keywordAst.Readonly)
|
||||||
|
assert.True(t, keywordAst.BuiltIn)
|
||||||
|
assert.Contains(t, keywordAst.Tags, "system")
|
||||||
|
assert.NotNil(t, keywordAst.Prompts)
|
||||||
|
assert.Greater(t, len(keywordAst.Prompts), 0)
|
||||||
|
|
||||||
|
// Check __yao.querydsl
|
||||||
|
querydslAst, querydslExists := assistant.GetCache().Get("__yao.querydsl")
|
||||||
|
require.True(t, querydslExists, "__yao.querydsl should be loaded")
|
||||||
|
assert.Equal(t, "__yao.querydsl", querydslAst.ID)
|
||||||
|
assert.Equal(t, "QueryDSL Generator", querydslAst.Name)
|
||||||
|
assert.True(t, querydslAst.Readonly)
|
||||||
|
assert.True(t, querydslAst.BuiltIn)
|
||||||
|
assert.Contains(t, querydslAst.Tags, "system")
|
||||||
|
assert.NotNil(t, querydslAst.Prompts)
|
||||||
|
assert.Greater(t, len(querydslAst.Prompts), 0)
|
||||||
|
|
||||||
|
// Check __yao.title
|
||||||
|
titleAst, titleExists := assistant.GetCache().Get("__yao.title")
|
||||||
|
require.True(t, titleExists, "__yao.title should be loaded")
|
||||||
|
assert.Equal(t, "__yao.title", titleAst.ID)
|
||||||
|
assert.Equal(t, "Title Generator", titleAst.Name)
|
||||||
|
assert.True(t, titleAst.Readonly)
|
||||||
|
assert.True(t, titleAst.BuiltIn)
|
||||||
|
|
||||||
|
// Check __yao.prompt
|
||||||
|
promptAst, promptExists := assistant.GetCache().Get("__yao.prompt")
|
||||||
|
require.True(t, promptExists, "__yao.prompt should be loaded")
|
||||||
|
assert.Equal(t, "__yao.prompt", promptAst.ID)
|
||||||
|
assert.Equal(t, "Prompt Optimizer", promptAst.Name)
|
||||||
|
assert.True(t, promptAst.Readonly)
|
||||||
|
assert.True(t, promptAst.BuiltIn)
|
||||||
|
|
||||||
|
// Check __yao.needsearch
|
||||||
|
needsearchAst, needsearchExists := assistant.GetCache().Get("__yao.needsearch")
|
||||||
|
require.True(t, needsearchExists, "__yao.needsearch should be loaded")
|
||||||
|
assert.Equal(t, "__yao.needsearch", needsearchAst.ID)
|
||||||
|
assert.Equal(t, "Need Search", needsearchAst.Name)
|
||||||
|
assert.True(t, needsearchAst.Readonly)
|
||||||
|
assert.True(t, needsearchAst.BuiltIn)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SystemAgentsSavedToStorage", func(t *testing.T) {
|
||||||
|
// System agents should be saved to storage
|
||||||
|
require.NotNil(t, assistant.GetStore(), "storage should be initialized")
|
||||||
|
|
||||||
|
// Check __yao.keyword in storage
|
||||||
|
builtIn := true
|
||||||
|
tags := []string{"system"}
|
||||||
|
res, err := assistant.GetStore().GetAssistants(store.AssistantFilter{
|
||||||
|
BuiltIn: &builtIn,
|
||||||
|
Tags: tags,
|
||||||
|
Select: []string{"assistant_id", "name"},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Greater(t, len(res.Data), 0, "System agents should be in storage")
|
||||||
|
|
||||||
|
// Verify at least one system agent exists
|
||||||
|
found := false
|
||||||
|
for _, ast := range res.Data {
|
||||||
|
if ast.ID == "__yao.keyword" || ast.ID == "__yao.querydsl" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, found, "System agents should be found in storage")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TestValidate tests the assistant Validate method
|
// TestValidate tests the assistant Validate method
|
||||||
func TestValidate(t *testing.T) {
|
func TestValidate(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
ast *Assistant
|
ast *assistant.Assistant
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "ValidAssistant",
|
name: "ValidAssistant",
|
||||||
ast: &Assistant{
|
ast: &assistant.Assistant{
|
||||||
AssistantModel: store.AssistantModel{
|
AssistantModel: store.AssistantModel{
|
||||||
ID: "test-id",
|
ID: "test-id",
|
||||||
Name: "Test Assistant",
|
Name: "Test Assistant",
|
||||||
|
|
@ -471,7 +566,7 @@ func TestValidate(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MissingID",
|
name: "MissingID",
|
||||||
ast: &Assistant{
|
ast: &assistant.Assistant{
|
||||||
AssistantModel: store.AssistantModel{
|
AssistantModel: store.AssistantModel{
|
||||||
Name: "Test Assistant",
|
Name: "Test Assistant",
|
||||||
Connector: "gpt-4o",
|
Connector: "gpt-4o",
|
||||||
|
|
@ -481,7 +576,7 @@ func TestValidate(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MissingName",
|
name: "MissingName",
|
||||||
ast: &Assistant{
|
ast: &assistant.Assistant{
|
||||||
AssistantModel: store.AssistantModel{
|
AssistantModel: store.AssistantModel{
|
||||||
ID: "test-id",
|
ID: "test-id",
|
||||||
Connector: "gpt-4o",
|
Connector: "gpt-4o",
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,25 @@ func initAssistant() error {
|
||||||
assistant.SetGlobalSearchConfig(agentDSL.Search)
|
assistant.SetGlobalSearchConfig(agentDSL.Search)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Built-in Assistants
|
// 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
err := assistant.LoadBuiltIn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,13 @@ type DSL struct {
|
||||||
StoreSetting store.Setting `json:"store" yaml:"store"` // The store setting of the assistant
|
StoreSetting store.Setting `json:"store" yaml:"store"` // The store setting of the assistant
|
||||||
Cache string `json:"cache" yaml:"cache"` // The cache store of the assistant, if not set, default is "__yao.agent.cache"
|
Cache string `json:"cache" yaml:"cache"` // The cache store of the assistant, if not set, default is "__yao.agent.cache"
|
||||||
|
|
||||||
|
// System Agents Connector Settings
|
||||||
|
// ===============================
|
||||||
|
// System configures connectors for system agents (__yao.keyword, __yao.querydsl, __yao.title, __yao.prompt)
|
||||||
|
// Each agent can have its own connector, or use the default
|
||||||
|
// If not set, fallback to the first connector that supports the required capabilities
|
||||||
|
System *System `json:"system,omitempty" yaml:"system,omitempty"`
|
||||||
|
|
||||||
// Global External Settings - model capabilities, tools, etc.
|
// Global External Settings - model capabilities, tools, etc.
|
||||||
// ===============================
|
// ===============================
|
||||||
Models map[string]openai.Capabilities `json:"models,omitempty" yaml:"models,omitempty"` // The model capabilities configuration
|
Models map[string]openai.Capabilities `json:"models,omitempty" yaml:"models,omitempty"` // The model capabilities configuration
|
||||||
|
|
@ -48,6 +55,18 @@ type Uses struct {
|
||||||
Rerank string `json:"rerank,omitempty" yaml:"rerank,omitempty"` // Result reranking: "builtin", "<assistant-id>", "mcp:<server>.<tool>"
|
Rerank string `json:"rerank,omitempty" yaml:"rerank,omitempty"` // Result reranking: "builtin", "<assistant-id>", "mcp:<server>.<tool>"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// System configures connectors for system agents
|
||||||
|
// ===============================
|
||||||
|
type System struct {
|
||||||
|
Default string `json:"default,omitempty" yaml:"default,omitempty"` // Default connector for all system agents
|
||||||
|
Keyword string `json:"keyword,omitempty" yaml:"keyword,omitempty"` // Connector for __yao.keyword agent
|
||||||
|
QueryDSL string `json:"querydsl,omitempty" yaml:"querydsl,omitempty"` // Connector for __yao.querydsl agent
|
||||||
|
Title string `json:"title,omitempty" yaml:"title,omitempty"` // Connector for __yao.title agent
|
||||||
|
Prompt string `json:"prompt,omitempty" yaml:"prompt,omitempty"` // Connector for __yao.prompt agent
|
||||||
|
NeedSearch string `json:"needsearch,omitempty" yaml:"needsearch,omitempty"` // Connector for __yao.needsearch agent
|
||||||
|
Entity string `json:"entity,omitempty" yaml:"entity,omitempty"` // Connector for __yao.entity agent
|
||||||
|
}
|
||||||
|
|
||||||
// Mention Structure
|
// Mention Structure
|
||||||
// ===============================
|
// ===============================
|
||||||
type Mention struct {
|
type Mention struct {
|
||||||
|
|
|
||||||
576
data/bindata.go
576
data/bindata.go
File diff suppressed because it is too large
Load diff
10
yao/assistants/entity/package.yao
Normal file
10
yao/assistants/entity/package.yao
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"name": "Entity Extraction",
|
||||||
|
"description": "Extract entities and relationships for knowledge graph",
|
||||||
|
"type": "worker",
|
||||||
|
"options": {
|
||||||
|
"max_tokens": 2000,
|
||||||
|
"temperature": 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
28
yao/assistants/entity/prompts.yml
Normal file
28
yao/assistants/entity/prompts.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
- role: system
|
||||||
|
content: |
|
||||||
|
Extract entities and relationships from text for knowledge graph construction.
|
||||||
|
|
||||||
|
## Task
|
||||||
|
1. Identify named entities (Person, Organization, Location, Product, Event, Concept, etc.)
|
||||||
|
2. Extract relationships between entities
|
||||||
|
3. Return structured JSON
|
||||||
|
|
||||||
|
## Response Format (JSON only)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"entities": [
|
||||||
|
{"id": "e1", "name": "Entity Name", "type": "Person|Org|Location|Product|Event|Concept", "properties": {}}
|
||||||
|
],
|
||||||
|
"relationships": [
|
||||||
|
{"source": "e1", "target": "e2", "type": "relationship_type", "properties": {}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Guidelines
|
||||||
|
- Use consistent entity IDs (e1, e2, ...)
|
||||||
|
- Normalize entity names (remove titles, standardize format)
|
||||||
|
- Common relationship types: WORKS_FOR, LOCATED_IN, OWNS, CREATED, RELATED_TO
|
||||||
|
- Keep properties minimal and relevant
|
||||||
|
- Same language as input for entity names
|
||||||
|
|
||||||
9
yao/assistants/keyword/package.yao
Normal file
9
yao/assistants/keyword/package.yao
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "Keyword Extraction",
|
||||||
|
"description": "Extract keywords from text content",
|
||||||
|
"type": "worker",
|
||||||
|
"options": {
|
||||||
|
"max_tokens": 500,
|
||||||
|
"temperature": 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
24
yao/assistants/keyword/prompts.yml
Normal file
24
yao/assistants/keyword/prompts.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Keyword Extraction Agent Prompts
|
||||||
|
- role: system
|
||||||
|
content: |
|
||||||
|
You are a keyword extraction specialist. Your task is to extract relevant keywords from the provided text.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
1. Analyze the input text carefully
|
||||||
|
2. Extract the most important and relevant keywords
|
||||||
|
3. Return keywords in JSON format
|
||||||
|
|
||||||
|
## Response Format
|
||||||
|
Always respond with valid JSON:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"keywords": ["keyword1", "keyword2", ...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Guidelines
|
||||||
|
- Extract 5-15 keywords depending on content length
|
||||||
|
- Prioritize nouns, proper nouns, and key concepts
|
||||||
|
- Include both single words and short phrases when relevant
|
||||||
|
- Exclude common stop words
|
||||||
|
- Maintain the original language of the content
|
||||||
9
yao/assistants/needsearch/package.yao
Normal file
9
yao/assistants/needsearch/package.yao
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "Need Search",
|
||||||
|
"description": "Determine if query requires external search",
|
||||||
|
"type": "worker",
|
||||||
|
"options": {
|
||||||
|
"max_tokens": 200,
|
||||||
|
"temperature": 0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
20
yao/assistants/needsearch/prompts.yml
Normal file
20
yao/assistants/needsearch/prompts.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Need Search Agent
|
||||||
|
- role: system
|
||||||
|
content: |
|
||||||
|
Classify if user query needs external search.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
NO SEARCH: greetings, chitchat, math, code generation, text processing, general knowledge
|
||||||
|
WEB: real-time data (weather, news, prices), current events, recent info
|
||||||
|
KB: documentation, how-to, configuration, FAQ
|
||||||
|
DB: user data, orders, records, business data
|
||||||
|
|
||||||
|
## Response (JSON only)
|
||||||
|
{"need_search": bool, "search_types": ["web"|"kb"|"db"], "confidence": 0-1}
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
"Hello" → {"need_search": false, "search_types": [], "confidence": 0.99}
|
||||||
|
"Today's weather" → {"need_search": true, "search_types": ["web"], "confidence": 0.95}
|
||||||
|
"Write a sort function" → {"need_search": false, "search_types": [], "confidence": 0.90}
|
||||||
|
"How to config DB" → {"need_search": true, "search_types": ["kb"], "confidence": 0.85}
|
||||||
|
"My orders" → {"need_search": true, "search_types": ["db"], "confidence": 0.95}
|
||||||
8
yao/assistants/prompt/package.yao
Normal file
8
yao/assistants/prompt/package.yao
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "Prompt Optimizer",
|
||||||
|
"description": "Transform user requirements into effective prompts",
|
||||||
|
"type": "worker",
|
||||||
|
"options": {
|
||||||
|
"temperature": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
25
yao/assistants/prompt/prompts.yml
Normal file
25
yao/assistants/prompt/prompts.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
- role: system
|
||||||
|
content: |
|
||||||
|
You are a prompt optimization assistant. Transform user requirements into professional prompts.
|
||||||
|
|
||||||
|
Process:
|
||||||
|
1. Extract key information and objectives
|
||||||
|
2. Reorganize with precise terminology
|
||||||
|
3. Add context and details
|
||||||
|
|
||||||
|
Include:
|
||||||
|
- Clear goal/task description
|
||||||
|
- Expected output format
|
||||||
|
- Quality requirements
|
||||||
|
- Reference information
|
||||||
|
|
||||||
|
Ensure:
|
||||||
|
- Clear and unambiguous
|
||||||
|
- Detailed and specific
|
||||||
|
- Well-structured
|
||||||
|
- Actionable
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Respond in same language as input
|
||||||
|
2. Output ONLY the optimized prompt
|
||||||
|
3. Ready to use as-is
|
||||||
9
yao/assistants/querydsl/package.yao
Normal file
9
yao/assistants/querydsl/package.yao
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "QueryDSL Generator",
|
||||||
|
"description": "Generate QueryDSL from natural language",
|
||||||
|
"type": "worker",
|
||||||
|
"options": {
|
||||||
|
"max_tokens": 2000,
|
||||||
|
"temperature": 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
43
yao/assistants/querydsl/prompts.yml
Normal file
43
yao/assistants/querydsl/prompts.yml
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# QueryDSL Generator Agent Prompts
|
||||||
|
- role: system
|
||||||
|
content: |
|
||||||
|
You are a QueryDSL generator. Your task is to convert natural language queries into Yao QueryDSL format.
|
||||||
|
|
||||||
|
## QueryDSL Structure
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"select": ["field1", "field2"],
|
||||||
|
"from": "table_name",
|
||||||
|
"wheres": [
|
||||||
|
{"field": "name", "op": "=", "value": "test"},
|
||||||
|
{"field": "status", "op": "in", "value": ["active", "pending"]}
|
||||||
|
],
|
||||||
|
"orders": [
|
||||||
|
{"field": "created_at", "sort": "desc"}
|
||||||
|
],
|
||||||
|
"limit": 20
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported Operators
|
||||||
|
- Comparison: =, !=, >, >=, <, <=
|
||||||
|
- Pattern: like, not like
|
||||||
|
- Range: in, not in, between
|
||||||
|
- Null check: is null, is not null
|
||||||
|
|
||||||
|
## Response Format
|
||||||
|
Always respond with valid JSON:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dsl": { ... },
|
||||||
|
"explain": "Brief explanation of the query",
|
||||||
|
"warnings": ["any warnings or notes"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Guidelines
|
||||||
|
- Generate valid QueryDSL based on the provided schema
|
||||||
|
- Use appropriate operators for the query intent
|
||||||
|
- Include only fields that exist in the schema
|
||||||
|
- Add helpful explanations for complex queries
|
||||||
|
|
||||||
8
yao/assistants/title/package.yao
Normal file
8
yao/assistants/title/package.yao
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "Title Generator",
|
||||||
|
"description": "Generate concise titles for conversations",
|
||||||
|
"type": "worker",
|
||||||
|
"options": {
|
||||||
|
"temperature": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
26
yao/assistants/title/prompts.yml
Normal file
26
yao/assistants/title/prompts.yml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
- role: system
|
||||||
|
content: |
|
||||||
|
Generate concise, meaningful titles for chat conversations.
|
||||||
|
|
||||||
|
Task:
|
||||||
|
1. Analyze content and identify main topic
|
||||||
|
2. Create brief, descriptive title
|
||||||
|
3. Match input language
|
||||||
|
4. Return ONLY the title, no explanation
|
||||||
|
|
||||||
|
Length:
|
||||||
|
- English: 2-6 words, 15-50 chars
|
||||||
|
- CJK: 2-10 chars
|
||||||
|
- Mixed: max 50 chars
|
||||||
|
|
||||||
|
Style:
|
||||||
|
- Be specific, avoid generic titles
|
||||||
|
- Use active voice
|
||||||
|
- Start with key topic
|
||||||
|
- Sentence case for English
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
"How to bake cookies?" → Chocolate Chip Cookie Recipe
|
||||||
|
"请教如何制作曲奇" → 巧克力曲奇制作
|
||||||
|
"Debug my React component" → React Component Debugging
|
||||||
|
"帮我调试React组件" → React组件调试
|
||||||
Loading…
Add table
Reference in a new issue