Refactor assistant types and tools support

- Renamed `Functions` to `Tools` across multiple files
- Added `ConnectorSetting` to support vision and tools flags
- Updated loading and serialization logic for tools
- Modified message contents handling for better JSON parsing
- Adjusted test cases to reflect tool-related changes
This commit is contained in:
Max 2025-02-01 17:04:45 +08:00
parent 6ac0406ad4
commit 455e39fa54
8 changed files with 133 additions and 88 deletions

View file

@ -441,7 +441,13 @@ func (ast *Assistant) saveChatHistory(ctx chatctx.Context, messages []chatMessag
// contents // contents
fmt.Println("---contents ---") fmt.Println("---contents ---")
utils.Dump(contents) if contents.Data != nil {
fmt.Println("---contents.Data ---")
for _, content := range contents.Data {
fmt.Println(content.Map())
}
fmt.Println("---contents.Data end ---")
}
fmt.Println("---contents end ---") fmt.Println("---contents end ---")
// Add mentions // Add mentions
@ -465,9 +471,9 @@ func (ast *Assistant) withOptions(options map[string]interface{}) map[string]int
} }
} }
// Add functions // Add tools
if ast.Functions != nil && len(ast.Functions) > 0 { if ast.Tools != nil && len(ast.Tools) > 0 {
options["tools"] = ast.Functions options["tools"] = ast.Tools
if options["tool_choice"] == nil { if options["tool_choice"] == nil {
options["tool_choice"] = "auto" options["tool_choice"] = "auto"
} }
@ -554,8 +560,8 @@ func (ast *Assistant) requestMessages(ctx context.Context, messages []chatMessag
for index, message := range messages { for index, message := range messages {
// Ignore the function call message // Ignore the tool call message
if message.Type == "function" { if message.Type == "tool_calls" {
continue continue
} }

View file

@ -132,7 +132,7 @@ func (ast *Assistant) Map() map[string]interface{} {
"description": ast.Description, "description": ast.Description,
"options": ast.Options, "options": ast.Options,
"prompts": ast.Prompts, "prompts": ast.Prompts,
"functions": ast.Functions, "tools": ast.Tools,
"tags": ast.Tags, "tags": ast.Tags,
"mentionable": ast.Mentionable, "mentionable": ast.Mentionable,
"automated": ast.Automated, "automated": ast.Automated,
@ -199,6 +199,12 @@ func (ast *Assistant) Clone() *Assistant {
copy(clone.Prompts, ast.Prompts) copy(clone.Prompts, ast.Prompts)
} }
// Deep copy tools
if ast.Tools != nil {
clone.Tools = make([]Tool, len(ast.Tools))
copy(clone.Tools, ast.Tools)
}
// Deep copy flows // Deep copy flows
if ast.Flows != nil { if ast.Flows != nil {
clone.Flows = make([]map[string]interface{}, len(ast.Flows)) clone.Flows = make([]map[string]interface{}, len(ast.Flows))
@ -232,6 +238,24 @@ func (ast *Assistant) Update(data map[string]interface{}) error {
if v, ok := data["connector"].(string); ok { if v, ok := data["connector"].(string); ok {
ast.Connector = v ast.Connector = v
} }
if v, has := data["tools"]; has {
switch tools := v.(type) {
case []Tool:
ast.Tools = tools
default:
raw, err := jsoniter.Marshal(tools)
if err != nil {
return err
}
ast.Tools = []Tool{}
err = jsoniter.Unmarshal(raw, &ast.Tools)
if err != nil {
return err
}
}
}
if v, ok := data["type"].(string); ok { if v, ok := data["type"].(string); ok {
ast.Type = v ast.Type = v
} }

View file

@ -276,16 +276,15 @@ func LoadPath(path string) (*Assistant, error) {
data["updated_at"] = max(updatedAt, ts) data["updated_at"] = max(updatedAt, ts)
} }
// load functions // load tools
functionsfile := filepath.Join(path, "functions.json") toolsfile := filepath.Join(path, "tools.yao")
if has, _ := app.Exists(functionsfile); has { if has, _ := app.Exists(toolsfile); has {
functions, ts, err := loadFunctions(functionsfile) tools, ts, err := loadTools(toolsfile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
data["functions"] = functions data["tools"] = tools
updatedAt = max(updatedAt, ts) updatedAt = max(updatedAt, ts)
data["updated_at"] = updatedAt
} }
// load flow // load flow
@ -440,22 +439,24 @@ func loadMap(data map[string]interface{}) (*Assistant, error) {
assistant.Prompts = prompts assistant.Prompts = prompts
} }
// functions // tools
if funcs, has := data["functions"]; has { if tools, has := data["tools"]; has {
switch vv := funcs.(type) { switch vv := tools.(type) {
case []Function: case []Tool:
assistant.Functions = vv assistant.Tools = vv
default: default:
raw, err := jsoniter.Marshal(vv) raw, err := jsoniter.Marshal(tools)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("tools format error %s", err.Error())
} }
var functions []Function
err = jsoniter.Unmarshal(raw, &functions) var tools []Tool
err = jsoniter.Unmarshal(raw, &tools)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("tools format error %s", err.Error())
} }
assistant.Functions = functions assistant.Tools = tools
} }
} }
@ -501,32 +502,6 @@ func loadMap(data map[string]interface{}) (*Assistant, error) {
return assistant, nil return assistant, nil
} }
func loadFunctions(file string) ([]Function, int64, error) {
app, err := fs.Get("app")
if err != nil {
return nil, 0, err
}
ts, err := app.ModTime(file)
if err != nil {
return nil, 0, err
}
raw, err := app.ReadFile(file)
if err != nil {
return nil, 0, err
}
var functions []Function
err = jsoniter.Unmarshal(raw, &functions)
if err != nil {
return nil, 0, err
}
return functions, ts.UnixNano(), nil
}
func loadPrompts(file string, root string) (string, int64, error) { func loadPrompts(file string, root string) (string, int64, error) {
app, err := fs.Get("app") app, err := fs.Get("app")
@ -629,3 +604,33 @@ func (ast *Assistant) initialize() error {
return nil return nil
} }
func loadTools(file string) ([]Tool, int64, error) {
app, err := fs.Get("app")
if err != nil {
return nil, 0, err
}
content, err := app.ReadFile(file)
if err != nil {
return nil, 0, err
}
ts, err := app.ModTime(file)
if err != nil {
return nil, 0, err
}
if len(content) == 0 {
return []Tool{}, ts.UnixNano(), nil
}
var tools []Tool
err = jsoniter.Unmarshal(content, &tools)
if err != nil {
return nil, 0, err
}
return tools, ts.UnixNano(), nil
}

View file

@ -94,8 +94,8 @@ type Prompt struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
} }
// Function a function // Tool represents a tool
type Function struct { type Tool struct {
Type string `json:"type"` Type string `json:"type"`
Function struct { Function struct {
Name string `json:"name"` Name string `json:"name"`
@ -129,7 +129,7 @@ type Assistant struct {
Automated bool `json:"automated,omitempty"` // Whether this assistant is automated Automated bool `json:"automated,omitempty"` // Whether this assistant is automated
Options map[string]interface{} `json:"options,omitempty"` // AI Options Options map[string]interface{} `json:"options,omitempty"` // AI Options
Prompts []Prompt `json:"prompts,omitempty"` // AI Prompts Prompts []Prompt `json:"prompts,omitempty"` // AI Prompts
Functions []Function `json:"functions,omitempty"` // Assistant Functions Tools []Tool `json:"tools,omitempty"` // Assistant Tools
Flows []map[string]interface{} `json:"flows,omitempty"` // Assistant Flows Flows []map[string]interface{} `json:"flows,omitempty"` // Assistant Flows
Placeholder *Placeholder `json:"placeholder,omitempty"` // Assistant Placeholder Placeholder *Placeholder `json:"placeholder,omitempty"` // Assistant Placeholder
Script *v8.Script `json:"-" yaml:"-"` // Assistant Script Script *v8.Script `json:"-" yaml:"-"` // Assistant Script

View file

@ -1,6 +1,8 @@
package message package message
import ( import (
"fmt"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
) )
@ -25,7 +27,7 @@ type Data struct {
ID string `json:"id"` // the id of the content ID string `json:"id"` // the id of the content
Function string `json:"function"` // the function name Function string `json:"function"` // the function name
Bytes []byte `json:"bytes"` // the content bytes Bytes []byte `json:"bytes"` // the content bytes
Arguments []byte `json:"arguments"` // the function arguments Arguments []byte `json:"arguments,omitempty"` // the function arguments
Props map[string]interface{} `json:"props"` // the props Props map[string]interface{} `json:"props"` // the props
} }
@ -159,7 +161,8 @@ func (data *Data) Map() (map[string]interface{}, error) {
v["props"] = data.Props v["props"] = data.Props
} }
if data.Arguments != nil { if data.Arguments != nil && len(data.Arguments) > 0 {
fmt.Println("data.Arguments", string(data.Arguments))
var vv interface{} = nil var vv interface{} = nil
err := jsoniter.Unmarshal(data.Arguments, &vv) err := jsoniter.Unmarshal(data.Arguments, &vv)
if err != nil { if err != nil {
@ -192,7 +195,7 @@ func (data *Data) MarshalJSON() ([]byte, error) {
v["props"] = data.Props v["props"] = data.Props
} }
if data.Arguments != nil { if data.Arguments != nil && len(data.Arguments) > 0 {
var vv interface{} = nil var vv interface{} = nil
err := jsoniter.Unmarshal(data.Arguments, &vv) err := jsoniter.Unmarshal(data.Arguments, &vv)
if err != nil { if err != nil {

View file

@ -237,7 +237,7 @@ func (conv *Xun) initAssistantTable() error {
table.JSON("prompts").Null() // assistant prompts table.JSON("prompts").Null() // assistant prompts
table.JSON("flows").Null() // assistant flows table.JSON("flows").Null() // assistant flows
table.JSON("files").Null() // assistant files table.JSON("files").Null() // assistant files
table.JSON("functions").Null() // assistant functions table.JSON("tools").Null() // assistant tools
table.JSON("tags").Null() // assistant tags table.JSON("tags").Null() // assistant tags
table.Boolean("readonly").SetDefault(false).Index() // assistant readonly table.Boolean("readonly").SetDefault(false).Index() // assistant readonly
table.JSON("permissions").Null() // assistant permissions table.JSON("permissions").Null() // assistant permissions
@ -259,7 +259,7 @@ func (conv *Xun) initAssistantTable() error {
return err return err
} }
fields := []string{"id", "assistant_id", "type", "name", "avatar", "connector", "description", "path", "sort", "built_in", "placeholder", "options", "prompts", "flows", "files", "functions", "tags", "mentionable", "created_at", "updated_at"} fields := []string{"id", "assistant_id", "type", "name", "avatar", "connector", "description", "path", "sort", "built_in", "placeholder", "options", "prompts", "flows", "files", "tools", "tags", "mentionable", "created_at", "updated_at"}
for _, field := range fields { for _, field := range fields {
if !tab.HasColumn(field) { if !tab.HasColumn(field) {
return fmt.Errorf("%s is required", field) return fmt.Errorf("%s is required", field)
@ -767,7 +767,7 @@ func (conv *Xun) SaveAssistant(assistant map[string]interface{}) (interface{}, e
} }
// Process JSON fields // Process JSON fields
jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions", "placeholder"} jsonFields := []string{"tags", "options", "prompts", "flows", "files", "tools", "permissions", "placeholder"}
for _, field := range jsonFields { for _, field := range jsonFields {
if val, ok := assistantCopy[field]; ok && val != nil { if val, ok := assistantCopy[field]; ok && val != nil {
// If it's a string, try to parse it first // If it's a string, try to parse it first
@ -954,7 +954,7 @@ func (conv *Xun) GetAssistants(filter AssistantFilter) (*AssistantResponse, erro
// Convert rows to map slice and parse JSON fields // Convert rows to map slice and parse JSON fields
data := make([]map[string]interface{}, len(rows)) data := make([]map[string]interface{}, len(rows))
jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions", "placeholder"} jsonFields := []string{"tags", "options", "prompts", "flows", "files", "tools", "permissions", "placeholder"}
for i, row := range rows { for i, row := range rows {
data[i] = row data[i] = row
// Only parse JSON fields if they are selected or no select filter is provided // Only parse JSON fields if they are selected or no select filter is provided
@ -1008,7 +1008,7 @@ func (conv *Xun) GetAssistant(assistantID string) (map[string]interface{}, error
} }
// Parse JSON fields // Parse JSON fields
jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions", "placeholder"} jsonFields := []string{"tags", "options", "prompts", "flows", "files", "tools", "permissions", "placeholder"}
conv.parseJSONFields(data, jsonFields) conv.parseJSONFields(data, jsonFields)
return data, nil return data, nil

View file

@ -526,7 +526,7 @@ func TestXunAssistantCRUD(t *testing.T) {
"prompts": []string{"prompt1", "prompt2"}, "prompts": []string{"prompt1", "prompt2"},
"flows": []string{"flow1", "flow2"}, "flows": []string{"flow1", "flow2"},
"files": []string{"file1", "file2"}, "files": []string{"file1", "file2"},
"functions": []map[string]interface{}{{"name": "func1"}, {"name": "func2"}}, "tools": []map[string]interface{}{{"name": "tool1"}, {"name": "tool2"}},
"permissions": map[string]interface{}{"read": true, "write": true}, "permissions": map[string]interface{}{"read": true, "write": true},
"placeholder": map[string]interface{}{ "placeholder": map[string]interface{}{
"title": "Test Title 2", "title": "Test Title 2",
@ -557,7 +557,7 @@ func TestXunAssistantCRUD(t *testing.T) {
"prompts": nil, "prompts": nil,
"flows": nil, "flows": nil,
"files": nil, "files": nil,
"functions": nil, "tools": nil,
"permissions": nil, "permissions": nil,
"placeholder": nil, "placeholder": nil,
"mentionable": true, "mentionable": true,
@ -580,7 +580,7 @@ func TestXunAssistantCRUD(t *testing.T) {
assert.Nil(t, assistant3Data["prompts"]) assert.Nil(t, assistant3Data["prompts"])
assert.Nil(t, assistant3Data["flows"]) assert.Nil(t, assistant3Data["flows"])
assert.Nil(t, assistant3Data["files"]) assert.Nil(t, assistant3Data["files"])
assert.Nil(t, assistant3Data["functions"]) assert.Nil(t, assistant3Data["tools"])
assert.Nil(t, assistant3Data["permissions"]) assert.Nil(t, assistant3Data["permissions"])
assert.Nil(t, assistant3Data["placeholder"]) assert.Nil(t, assistant3Data["placeholder"])
assert.Equal(t, int64(1), assistant3Data["mentionable"]) assert.Equal(t, int64(1), assistant3Data["mentionable"])

View file

@ -31,6 +31,13 @@ type DSL struct {
RAG *rag.RAG `json:"-" yaml:"-"` RAG *rag.RAG `json:"-" yaml:"-"`
Vision *vision.Vision `json:"-" yaml:"-"` Vision *vision.Vision `json:"-" yaml:"-"`
GuardHandlers []gin.HandlerFunc `json:"-" yaml:"-"` GuardHandlers []gin.HandlerFunc `json:"-" yaml:"-"`
Connectors map[string]ConnectorSetting `json:"-" yaml:"-"`
}
// ConnectorSetting the connector setting
type ConnectorSetting struct {
Vision bool `json:"vision,omitempty" yaml:"vision,omitempty"`
Tools bool `json:"tools,omitempty" yaml:"tools,omitempty"`
} }
// VisionSetting the vision setting // VisionSetting the vision setting