Add modes and default mode fields to Assistant model
- Introduced `modes` field to support multiple operational modes for the assistant, allowing for flexible functionality (e.g., chat, task). - Added `default_mode` field to specify the primary mode of operation for the assistant. - Updated the `Map`, `Clone`, and `Update` methods to handle the new fields appropriately. - Enhanced the `ToAssistantModel` conversion function to include the new fields. - Added tests to validate the integration and functionality of modes and default mode within the assistant model.
This commit is contained in:
parent
da48896bdf
commit
ed62033761
10 changed files with 347 additions and 159 deletions
|
|
@ -86,6 +86,8 @@ func (ast *Assistant) Map() map[string]interface{} {
|
|||
"mcp": ast.MCP,
|
||||
"workflow": ast.Workflow,
|
||||
"tags": ast.Tags,
|
||||
"modes": ast.Modes,
|
||||
"default_mode": ast.DefaultMode,
|
||||
"mentionable": ast.Mentionable,
|
||||
"automated": ast.Automated,
|
||||
"placeholder": ast.Placeholder,
|
||||
|
|
@ -169,6 +171,15 @@ func (ast *Assistant) Clone() *Assistant {
|
|||
copy(clone.Tags, ast.Tags)
|
||||
}
|
||||
|
||||
// Deep copy modes
|
||||
if ast.Modes != nil {
|
||||
clone.Modes = make([]string, len(ast.Modes))
|
||||
copy(clone.Modes, ast.Modes)
|
||||
}
|
||||
|
||||
// Copy default_mode (simple string)
|
||||
clone.DefaultMode = ast.DefaultMode
|
||||
|
||||
// Deep copy KB
|
||||
if ast.KB != nil {
|
||||
clone.KB = &store.KnowledgeBase{}
|
||||
|
|
@ -363,6 +374,12 @@ func (ast *Assistant) Update(data map[string]interface{}) error {
|
|||
if v, ok := data["tags"].([]string); ok {
|
||||
ast.Tags = v
|
||||
}
|
||||
if v, ok := data["modes"].([]string); ok {
|
||||
ast.Modes = v
|
||||
}
|
||||
if v, ok := data["default_mode"].(string); ok {
|
||||
ast.DefaultMode = v
|
||||
}
|
||||
if v, ok := data["options"].(map[string]interface{}); ok {
|
||||
ast.Options = v
|
||||
}
|
||||
|
|
|
|||
|
|
@ -302,6 +302,22 @@ func ToAssistantModel(v interface{}) (*AssistantModel, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Modes (string array)
|
||||
if modes, ok := data["modes"]; ok && modes != nil {
|
||||
raw, err := jsoniter.Marshal(modes)
|
||||
if err == nil {
|
||||
var m []string
|
||||
if err := jsoniter.Unmarshal(raw, &m); err == nil {
|
||||
model.Modes = m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultMode (string)
|
||||
if defaultMode, ok := data["default_mode"].(string); ok {
|
||||
model.DefaultMode = defaultMode
|
||||
}
|
||||
|
||||
// Options (map)
|
||||
if options, ok := data["options"].(map[string]interface{}); ok {
|
||||
model.Options = options
|
||||
|
|
|
|||
|
|
@ -533,18 +533,20 @@ func TestToAssistantModel(t *testing.T) {
|
|||
"connectors": []string{"openai", "anthropic"},
|
||||
"filters": []string{"vision", "tool_calls"},
|
||||
},
|
||||
"path": "/path/to/assistant",
|
||||
"description": "Test description",
|
||||
"share": "team",
|
||||
"built_in": true,
|
||||
"readonly": false,
|
||||
"public": true,
|
||||
"mentionable": true,
|
||||
"automated": false,
|
||||
"sort": 100,
|
||||
"created_at": int64(1609459200),
|
||||
"updated_at": int64(1609459300),
|
||||
"tags": []string{"tag1", "tag2"},
|
||||
"path": "/path/to/assistant",
|
||||
"description": "Test description",
|
||||
"share": "team",
|
||||
"built_in": true,
|
||||
"readonly": false,
|
||||
"public": true,
|
||||
"mentionable": true,
|
||||
"automated": false,
|
||||
"sort": 100,
|
||||
"created_at": int64(1609459200),
|
||||
"updated_at": int64(1609459300),
|
||||
"tags": []string{"tag1", "tag2"},
|
||||
"modes": []string{"chat", "task"},
|
||||
"default_mode": "chat",
|
||||
"options": map[string]interface{}{
|
||||
"temperature": 0.7,
|
||||
},
|
||||
|
|
@ -656,6 +658,15 @@ func TestToAssistantModel(t *testing.T) {
|
|||
if len(result.Tags) != 2 {
|
||||
t.Errorf("Expected 2 tags, got %d", len(result.Tags))
|
||||
}
|
||||
if len(result.Modes) != 2 {
|
||||
t.Errorf("Expected 2 modes, got %d", len(result.Modes))
|
||||
}
|
||||
if result.Modes[0] != "chat" {
|
||||
t.Errorf("Expected first mode 'chat', got '%s'", result.Modes[0])
|
||||
}
|
||||
if result.DefaultMode != "chat" {
|
||||
t.Errorf("Expected default_mode 'chat', got '%s'", result.DefaultMode)
|
||||
}
|
||||
if result.Options == nil {
|
||||
t.Error("Expected Options to be set")
|
||||
}
|
||||
|
|
@ -729,6 +740,8 @@ func TestToAssistantModel(t *testing.T) {
|
|||
data := map[string]interface{}{
|
||||
"assistant_id": "test-id",
|
||||
"tags": nil,
|
||||
"modes": nil,
|
||||
"default_mode": "",
|
||||
"options": nil,
|
||||
"prompts": nil,
|
||||
"kb": nil,
|
||||
|
|
@ -751,6 +764,12 @@ func TestToAssistantModel(t *testing.T) {
|
|||
if result.Tags != nil {
|
||||
t.Error("Expected Tags to be nil")
|
||||
}
|
||||
if result.Modes != nil {
|
||||
t.Error("Expected Modes to be nil")
|
||||
}
|
||||
if result.DefaultMode != "" {
|
||||
t.Error("Expected DefaultMode to be empty")
|
||||
}
|
||||
if result.Options != nil {
|
||||
t.Error("Expected Options to be nil")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ var AssistantAllowedFields = map[string]bool{
|
|||
"mcp": true,
|
||||
"source": true,
|
||||
"tags": true,
|
||||
"modes": true,
|
||||
"default_mode": true,
|
||||
"readonly": true,
|
||||
"public": true,
|
||||
"share": true,
|
||||
|
|
@ -50,7 +52,9 @@ var AssistantDefaultFields = []string{
|
|||
"avatar",
|
||||
"connector",
|
||||
"description",
|
||||
"tags", // Tags for categorization (lightweight)
|
||||
"tags", // Tags for categorization (lightweight)
|
||||
"modes", // Supported modes (lightweight)
|
||||
"default_mode", // Default mode (lightweight)
|
||||
"sort",
|
||||
"built_in",
|
||||
"readonly",
|
||||
|
|
@ -93,6 +97,8 @@ var AssistantFullFields = []string{
|
|||
"mcp",
|
||||
"source",
|
||||
"tags",
|
||||
"modes",
|
||||
"default_mode",
|
||||
"readonly",
|
||||
"public",
|
||||
"share",
|
||||
|
|
|
|||
|
|
@ -129,6 +129,8 @@ func TestAssistantAllowedFields(t *testing.T) {
|
|||
"uses",
|
||||
"connector_options",
|
||||
"source",
|
||||
"modes",
|
||||
"default_mode",
|
||||
}
|
||||
for _, field := range complexFields {
|
||||
if !AssistantAllowedFields[field] {
|
||||
|
|
@ -147,6 +149,8 @@ func TestAssistantDefaultFields(t *testing.T) {
|
|||
"kb", // Knowledge base is essential for assistant functionality
|
||||
"db", // Database is essential for assistant functionality
|
||||
"mcp", // MCP servers are essential for assistant functionality
|
||||
"modes", // Supported modes are essential for mode filtering
|
||||
"default_mode", // Default mode is essential for mode selection
|
||||
"__yao_created_by", // Permission fields are essential for access control
|
||||
"__yao_updated_by",
|
||||
"__yao_team_id",
|
||||
|
|
@ -167,7 +171,7 @@ func TestAssistantDefaultFields(t *testing.T) {
|
|||
|
||||
t.Run("DoesNotContainSensitiveFields", func(t *testing.T) {
|
||||
// Default fields should not include complex/large fields by default
|
||||
// Note: kb, db, mcp, and tags are lightweight and included in defaults
|
||||
// Note: kb, db, mcp, tags, modes, and default_mode are lightweight and included in defaults
|
||||
sensitiveFields := []string{
|
||||
"options",
|
||||
"prompts",
|
||||
|
|
@ -237,6 +241,8 @@ func TestAssistantFullFields(t *testing.T) {
|
|||
"uses",
|
||||
"connector_options",
|
||||
"source",
|
||||
"modes",
|
||||
"default_mode",
|
||||
}
|
||||
|
||||
fullFieldsMap := make(map[string]bool)
|
||||
|
|
|
|||
|
|
@ -263,6 +263,8 @@ type AssistantModel struct {
|
|||
Sort int `json:"sort,omitempty"` // Assistant Sort
|
||||
Description string `json:"description,omitempty"` // Assistant Description
|
||||
Tags []string `json:"tags,omitempty"` // Assistant Tags
|
||||
Modes []string `json:"modes,omitempty"` // Supported modes (e.g., ["task", "chat"]), null means all modes are supported
|
||||
DefaultMode string `json:"default_mode,omitempty"` // Default mode, can be empty
|
||||
Readonly bool `json:"readonly,omitempty"` // Whether this assistant is readonly
|
||||
Public bool `json:"public,omitempty"` // Whether this assistant is shared across all teams in the platform
|
||||
Share string `json:"share,omitempty"` // Assistant sharing scope (private/team)
|
||||
|
|
|
|||
|
|
@ -156,6 +156,21 @@ func (conv *Xun) SaveAssistant(assistant *types.AssistantModel) (string, error)
|
|||
data["tags"] = jsonStr
|
||||
}
|
||||
|
||||
if assistant.Modes != nil {
|
||||
jsonStr, err := jsoniter.MarshalToString(assistant.Modes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal modes: %w", err)
|
||||
}
|
||||
data["modes"] = jsonStr
|
||||
}
|
||||
|
||||
// DefaultMode is a simple string field
|
||||
if assistant.DefaultMode != "" {
|
||||
data["default_mode"] = assistant.DefaultMode
|
||||
} else {
|
||||
data["default_mode"] = nil
|
||||
}
|
||||
|
||||
// Handle interface{} fields - they should already be in the correct format
|
||||
jsonFields := map[string]interface{}{
|
||||
"prompts": assistant.Prompts,
|
||||
|
|
@ -226,14 +241,14 @@ func (conv *Xun) UpdateAssistant(assistantID string, updates map[string]interfac
|
|||
data := make(map[string]interface{})
|
||||
|
||||
// List of fields that need JSON marshaling
|
||||
jsonFields := []string{"options", "tags", "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"}
|
||||
jsonFieldSet := make(map[string]bool)
|
||||
for _, field := range jsonFields {
|
||||
jsonFieldSet[field] = true
|
||||
}
|
||||
|
||||
// List of nullable string fields
|
||||
nullableStringFields := []string{"name", "avatar", "description", "path", "source", "__yao_created_by", "__yao_updated_by", "__yao_team_id", "__yao_tenant_id"}
|
||||
nullableStringFields := []string{"name", "avatar", "description", "path", "source", "default_mode", "__yao_created_by", "__yao_updated_by", "__yao_team_id", "__yao_tenant_id"}
|
||||
nullableFieldSet := make(map[string]bool)
|
||||
for _, field := range nullableStringFields {
|
||||
nullableFieldSet[field] = true
|
||||
|
|
@ -499,7 +514,7 @@ func (conv *Xun) GetAssistant(assistantID string, fields []string, locale ...str
|
|||
}
|
||||
|
||||
// Parse JSON fields
|
||||
jsonFields := []string{"tags", "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"}
|
||||
conv.parseJSONFields(data, jsonFields)
|
||||
|
||||
// Convert map to types.AssistantModel
|
||||
|
|
@ -514,6 +529,7 @@ func (conv *Xun) GetAssistant(assistantID string, fields []string, locale ...str
|
|||
BuiltIn: getBool(data, "built_in"),
|
||||
Sort: getInt(data, "sort"),
|
||||
Description: getString(data, "description"),
|
||||
DefaultMode: getString(data, "default_mode"),
|
||||
Readonly: getBool(data, "readonly"),
|
||||
Public: getBool(data, "public"),
|
||||
Share: getString(data, "share"),
|
||||
|
|
@ -538,6 +554,16 @@ func (conv *Xun) GetAssistant(assistantID string, fields []string, locale ...str
|
|||
}
|
||||
}
|
||||
|
||||
// Handle Modes
|
||||
if modes, ok := data["modes"].([]interface{}); ok {
|
||||
model.Modes = make([]string, len(modes))
|
||||
for i, mode := range modes {
|
||||
if s, ok := mode.(string); ok {
|
||||
model.Modes[i] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Options
|
||||
if options, ok := data["options"].(map[string]interface{}); ok {
|
||||
model.Options = options
|
||||
|
|
|
|||
|
|
@ -2507,6 +2507,87 @@ func TestUpdateAssistant(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("UpdateModesAndDefaultMode", func(t *testing.T) {
|
||||
// Create assistant
|
||||
assistant := &types.AssistantModel{
|
||||
Name: "Modes 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 modes and default_mode
|
||||
updates := map[string]interface{}{
|
||||
"modes": []string{"chat", "task", "analyze"},
|
||||
"default_mode": "chat",
|
||||
}
|
||||
|
||||
err = store.UpdateAssistant(id, updates)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to update modes: %v", err)
|
||||
}
|
||||
|
||||
// Verify updates - modes and default_mode are in default fields
|
||||
retrieved, err := store.GetAssistant(id, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve assistant: %v", err)
|
||||
}
|
||||
|
||||
if retrieved.Modes == nil || len(retrieved.Modes) != 3 {
|
||||
t.Errorf("Expected 3 modes, got %v", retrieved.Modes)
|
||||
}
|
||||
if retrieved.Modes[0] != "chat" {
|
||||
t.Errorf("Expected first mode 'chat', got '%s'", retrieved.Modes[0])
|
||||
}
|
||||
if retrieved.DefaultMode != "chat" {
|
||||
t.Errorf("Expected default_mode 'chat', got '%s'", retrieved.DefaultMode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("UpdateModesOnly", func(t *testing.T) {
|
||||
// Create assistant with default_mode
|
||||
assistant := &types.AssistantModel{
|
||||
Name: "Modes Only Test",
|
||||
Type: "assistant",
|
||||
Connector: "openai",
|
||||
Share: "private",
|
||||
DefaultMode: "task",
|
||||
}
|
||||
|
||||
id, err := store.SaveAssistant(assistant)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create assistant: %v", err)
|
||||
}
|
||||
|
||||
// Update only modes
|
||||
updates := map[string]interface{}{
|
||||
"modes": []string{"chat", "task"},
|
||||
}
|
||||
|
||||
err = store.UpdateAssistant(id, updates)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to update modes: %v", err)
|
||||
}
|
||||
|
||||
// Verify updates - default_mode should remain unchanged
|
||||
retrieved, err := store.GetAssistant(id, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve assistant: %v", err)
|
||||
}
|
||||
|
||||
if len(retrieved.Modes) != 2 {
|
||||
t.Errorf("Expected 2 modes, got %d", len(retrieved.Modes))
|
||||
}
|
||||
if retrieved.DefaultMode != "task" {
|
||||
t.Errorf("Expected default_mode to remain 'task', got '%s'", retrieved.DefaultMode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("UpdateMCPWithToolsAndResources", func(t *testing.T) {
|
||||
// Create assistant
|
||||
assistant := &types.AssistantModel{
|
||||
|
|
|
|||
284
data/bindata.go
284
data/bindata.go
File diff suppressed because one or more lines are too long
|
|
@ -175,6 +175,21 @@
|
|||
"comment": "Assistant tags",
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"name": "modes",
|
||||
"type": "json",
|
||||
"label": "Modes",
|
||||
"comment": "Supported modes (e.g., chat, task), null means all modes are supported",
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"name": "default_mode",
|
||||
"type": "string",
|
||||
"label": "Default Mode",
|
||||
"comment": "Default mode for the assistant",
|
||||
"length": 50,
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"name": "readonly",
|
||||
"type": "boolean",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue