Enhance assistant management and JSON field handling in Neo API
- Updated SaveAssistant and processAssistantAdd methods to return assistant IDs, improving response consistency. - Implemented logic to handle JSON fields as both strings and native types, ensuring proper storage and retrieval of assistant attributes. - Refactored tests to cover various JSON field formats, including string, native types, and nil values, enhancing test coverage and reliability. - Improved the assistant management structure to support mixed JSON formats, ensuring robust functionality across different storage backends.
This commit is contained in:
parent
af65a2d5fa
commit
94f3ddb158
9 changed files with 463 additions and 336 deletions
|
|
@ -923,13 +923,18 @@ func (neo *DSL) handleAssistantSave(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
err := neo.Conversation.SaveAssistant(assistant)
|
||||
id, err := neo.Conversation.SaveAssistant(assistant)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
// Update the assistant map with the returned ID if it's not already set
|
||||
if _, ok := assistant["assistant_id"]; !ok {
|
||||
assistant["assistant_id"] = id
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{"message": "ok", "data": assistant})
|
||||
c.Done()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +1,59 @@
|
|||
package conversation
|
||||
|
||||
// Mongo conversation
|
||||
// Mongo represents a MongoDB-based conversation storage
|
||||
type Mongo struct{}
|
||||
|
||||
// NewMongo create a new conversation
|
||||
// NewMongo creates a new MongoDB conversation storage
|
||||
func NewMongo() *Mongo {
|
||||
return &Mongo{}
|
||||
}
|
||||
|
||||
// UpdateChatTitle update the chat title
|
||||
func (conv *Mongo) UpdateChatTitle(sid string, cid string, title string) error {
|
||||
return nil
|
||||
// GetChats retrieves a list of chats
|
||||
func (m *Mongo) GetChats(sid string, filter ChatFilter) (*ChatGroupResponse, error) {
|
||||
return &ChatGroupResponse{}, nil
|
||||
}
|
||||
|
||||
// GetChats get the chat list
|
||||
func (conv *Mongo) GetChats(sid string, filter ChatFilter) (*ChatGroupResponse, error) {
|
||||
return &ChatGroupResponse{
|
||||
Groups: []ChatGroup{},
|
||||
Page: filter.Page,
|
||||
PageSize: filter.PageSize,
|
||||
Total: 0,
|
||||
LastPage: 1,
|
||||
}, nil
|
||||
// GetChat retrieves a single chat's information
|
||||
func (m *Mongo) GetChat(sid string, cid string) (*ChatInfo, error) {
|
||||
return &ChatInfo{}, nil
|
||||
}
|
||||
|
||||
// GetHistory get the history
|
||||
func (conv *Mongo) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||
// GetHistory retrieves chat history
|
||||
func (m *Mongo) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
// SaveHistory save the history
|
||||
func (conv *Mongo) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error {
|
||||
// SaveHistory saves chat history
|
||||
func (m *Mongo) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRequest get the request
|
||||
func (conv *Mongo) GetRequest(sid string, rid string) ([]map[string]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SaveRequest save the request
|
||||
func (conv *Mongo) SaveRequest(sid string, rid string, cid string, messages []map[string]interface{}) error {
|
||||
// DeleteChat deletes a single chat
|
||||
func (m *Mongo) DeleteChat(sid string, cid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChat get the chat info and its history
|
||||
func (conv *Mongo) GetChat(sid string, cid string) (*ChatInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DeleteChat deletes a specific chat and its history
|
||||
func (conv *Mongo) DeleteChat(sid string, cid string) error {
|
||||
// DeleteAllChats deletes all chats
|
||||
func (m *Mongo) DeleteAllChats(sid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAllChats deletes all chats and their histories for a user
|
||||
func (conv *Mongo) DeleteAllChats(sid string) error {
|
||||
// UpdateChatTitle updates chat title
|
||||
func (m *Mongo) UpdateChatTitle(sid string, cid string, title string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveAssistant creates or updates an assistant
|
||||
func (conv *Mongo) SaveAssistant(assistant map[string]interface{}) error {
|
||||
// SaveAssistant saves assistant information
|
||||
func (m *Mongo) SaveAssistant(assistant map[string]interface{}) (interface{}, error) {
|
||||
return assistant["assistant_id"], nil
|
||||
}
|
||||
|
||||
// DeleteAssistant deletes an assistant
|
||||
func (m *Mongo) DeleteAssistant(assistantID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAssistant deletes an assistant by assistant_id
|
||||
func (conv *Mongo) DeleteAssistant(assistantID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAssistants retrieves assistants with pagination and tag filtering
|
||||
func (conv *Mongo) GetAssistants(filter AssistantFilter) (*AssistantResponse, error) {
|
||||
return &AssistantResponse{
|
||||
Data: []map[string]interface{}{},
|
||||
Page: filter.Page,
|
||||
PageSize: filter.PageSize,
|
||||
PageCnt: 0,
|
||||
Next: 0,
|
||||
Prev: 0,
|
||||
Total: 0,
|
||||
}, nil
|
||||
// GetAssistants retrieves a list of assistants
|
||||
func (m *Mongo) GetAssistants(filter AssistantFilter) (*AssistantResponse, error) {
|
||||
return &AssistantResponse{}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +1,59 @@
|
|||
package conversation
|
||||
|
||||
// Redis conversation
|
||||
// Redis represents a Redis-based conversation storage
|
||||
type Redis struct{}
|
||||
|
||||
// NewRedis create a new conversation
|
||||
// NewRedis creates a new Redis conversation storage
|
||||
func NewRedis() *Redis {
|
||||
return &Redis{}
|
||||
}
|
||||
|
||||
// UpdateChatTitle update the chat title
|
||||
func (conv *Redis) UpdateChatTitle(sid string, cid string, title string) error {
|
||||
return nil
|
||||
// GetChats retrieves a list of chats
|
||||
func (r *Redis) GetChats(sid string, filter ChatFilter) (*ChatGroupResponse, error) {
|
||||
return &ChatGroupResponse{}, nil
|
||||
}
|
||||
|
||||
// GetChats get the chat list
|
||||
func (conv *Redis) GetChats(sid string, filter ChatFilter) (*ChatGroupResponse, error) {
|
||||
return &ChatGroupResponse{
|
||||
Groups: []ChatGroup{},
|
||||
Page: filter.Page,
|
||||
PageSize: filter.PageSize,
|
||||
Total: 0,
|
||||
LastPage: 1,
|
||||
}, nil
|
||||
// GetChat retrieves a single chat's information
|
||||
func (r *Redis) GetChat(sid string, cid string) (*ChatInfo, error) {
|
||||
return &ChatInfo{}, nil
|
||||
}
|
||||
|
||||
// GetHistory get the history
|
||||
func (conv *Redis) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||
// GetHistory retrieves chat history
|
||||
func (r *Redis) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
// SaveHistory save the history
|
||||
func (conv *Redis) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error {
|
||||
// SaveHistory saves chat history
|
||||
func (r *Redis) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRequest get the request
|
||||
func (conv *Redis) GetRequest(sid string, rid string) ([]map[string]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SaveRequest save the request
|
||||
func (conv *Redis) SaveRequest(sid string, rid string, cid string, messages []map[string]interface{}) error {
|
||||
// DeleteChat deletes a single chat
|
||||
func (r *Redis) DeleteChat(sid string, cid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChat get the chat info and its history
|
||||
func (conv *Redis) GetChat(sid string, cid string) (*ChatInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DeleteChat deletes a specific chat and its history
|
||||
func (conv *Redis) DeleteChat(sid string, cid string) error {
|
||||
// DeleteAllChats deletes all chats
|
||||
func (r *Redis) DeleteAllChats(sid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAllChats deletes all chats and their histories for a user
|
||||
func (conv *Redis) DeleteAllChats(sid string) error {
|
||||
// UpdateChatTitle updates chat title
|
||||
func (r *Redis) UpdateChatTitle(sid string, cid string, title string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveAssistant creates or updates an assistant
|
||||
func (conv *Redis) SaveAssistant(assistant map[string]interface{}) error {
|
||||
// SaveAssistant saves assistant information
|
||||
func (r *Redis) SaveAssistant(assistant map[string]interface{}) (interface{}, error) {
|
||||
return assistant["assistant_id"], nil
|
||||
}
|
||||
|
||||
// DeleteAssistant deletes an assistant
|
||||
func (r *Redis) DeleteAssistant(assistantID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAssistant deletes an assistant by assistant_id
|
||||
func (conv *Redis) DeleteAssistant(assistantID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAssistants retrieves assistants with pagination and tag filtering
|
||||
func (conv *Redis) GetAssistants(filter AssistantFilter) (*AssistantResponse, error) {
|
||||
return &AssistantResponse{
|
||||
Data: []map[string]interface{}{},
|
||||
Page: filter.Page,
|
||||
PageSize: filter.PageSize,
|
||||
PageCnt: 0,
|
||||
Next: 0,
|
||||
Prev: 0,
|
||||
Total: 0,
|
||||
}, nil
|
||||
// GetAssistants retrieves a list of assistants
|
||||
func (r *Redis) GetAssistants(filter AssistantFilter) (*AssistantResponse, error) {
|
||||
return &AssistantResponse{}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ type Conversation interface {
|
|||
// SaveAssistant saves assistant information
|
||||
// assistant: Assistant information
|
||||
// Returns: Potential error
|
||||
SaveAssistant(assistant map[string]interface{}) error
|
||||
SaveAssistant(assistant map[string]interface{}) (interface{}, error)
|
||||
|
||||
// DeleteAssistant deletes an assistant
|
||||
// assistantID: Assistant ID
|
||||
|
|
|
|||
|
|
@ -1,84 +1,59 @@
|
|||
package conversation
|
||||
|
||||
// Weaviate Database conversation
|
||||
// Weaviate represents a Weaviate-based conversation storage
|
||||
type Weaviate struct{}
|
||||
|
||||
// NewWeaviate create a new conversation
|
||||
// NewWeaviate creates a new Weaviate conversation storage
|
||||
func NewWeaviate() *Weaviate {
|
||||
return &Weaviate{}
|
||||
}
|
||||
|
||||
// UpdateChatTitle update the chat title
|
||||
func (conv *Weaviate) UpdateChatTitle(sid string, cid string, title string) error {
|
||||
return nil
|
||||
// GetChats retrieves a list of chats
|
||||
func (w *Weaviate) GetChats(sid string, filter ChatFilter) (*ChatGroupResponse, error) {
|
||||
return &ChatGroupResponse{}, nil
|
||||
}
|
||||
|
||||
// GetChats get the chat list
|
||||
func (conv *Weaviate) GetChats(sid string, filter ChatFilter) (*ChatGroupResponse, error) {
|
||||
return &ChatGroupResponse{
|
||||
Groups: []ChatGroup{},
|
||||
Page: filter.Page,
|
||||
PageSize: filter.PageSize,
|
||||
Total: 0,
|
||||
LastPage: 1,
|
||||
}, nil
|
||||
// GetChat retrieves a single chat's information
|
||||
func (w *Weaviate) GetChat(sid string, cid string) (*ChatInfo, error) {
|
||||
return &ChatInfo{}, nil
|
||||
}
|
||||
|
||||
// GetHistory get the history
|
||||
func (conv *Weaviate) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||
// GetHistory retrieves chat history
|
||||
func (w *Weaviate) GetHistory(sid string, cid string) ([]map[string]interface{}, error) {
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
// SaveHistory save the history
|
||||
func (conv *Weaviate) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error {
|
||||
// SaveHistory saves chat history
|
||||
func (w *Weaviate) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRequest get the request
|
||||
func (conv *Weaviate) GetRequest(sid string, rid string) ([]map[string]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SaveRequest save the request
|
||||
func (conv *Weaviate) SaveRequest(sid string, rid string, cid string, messages []map[string]interface{}) error {
|
||||
// DeleteChat deletes a single chat
|
||||
func (w *Weaviate) DeleteChat(sid string, cid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChat get the chat info and its history
|
||||
func (conv *Weaviate) GetChat(sid string, cid string) (*ChatInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DeleteChat deletes a specific chat and its history
|
||||
func (conv *Weaviate) DeleteChat(sid string, cid string) error {
|
||||
// DeleteAllChats deletes all chats
|
||||
func (w *Weaviate) DeleteAllChats(sid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAllChats deletes all chats and their histories for a user
|
||||
func (conv *Weaviate) DeleteAllChats(sid string) error {
|
||||
// UpdateChatTitle updates chat title
|
||||
func (w *Weaviate) UpdateChatTitle(sid string, cid string, title string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveAssistant creates or updates an assistant
|
||||
func (conv *Weaviate) SaveAssistant(assistant map[string]interface{}) error {
|
||||
// SaveAssistant saves assistant information
|
||||
func (w *Weaviate) SaveAssistant(assistant map[string]interface{}) (interface{}, error) {
|
||||
return assistant["assistant_id"], nil
|
||||
}
|
||||
|
||||
// DeleteAssistant deletes an assistant
|
||||
func (w *Weaviate) DeleteAssistant(assistantID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAssistant deletes an assistant by assistant_id
|
||||
func (conv *Weaviate) DeleteAssistant(assistantID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAssistants retrieves assistants with pagination and tag filtering
|
||||
func (conv *Weaviate) GetAssistants(filter AssistantFilter) (*AssistantResponse, error) {
|
||||
return &AssistantResponse{
|
||||
|
||||
Data: []map[string]interface{}{},
|
||||
Page: filter.Page,
|
||||
PageSize: filter.PageSize,
|
||||
PageCnt: 0,
|
||||
Next: 0,
|
||||
Prev: 0,
|
||||
Total: 0,
|
||||
}, nil
|
||||
// GetAssistants retrieves a list of assistants
|
||||
func (w *Weaviate) GetAssistants(filter AssistantFilter) (*AssistantResponse, error) {
|
||||
return &AssistantResponse{}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -662,53 +662,115 @@ func (conv *Xun) DeleteAllChats(sid string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// processJSONField processes a field that should be stored as JSON string
|
||||
func (conv *Xun) processJSONField(field interface{}) (interface{}, error) {
|
||||
if field == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch v := field.(type) {
|
||||
case string:
|
||||
return v, nil
|
||||
default:
|
||||
jsonStr, err := jsoniter.MarshalToString(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal %v to JSON: %v", field, err)
|
||||
}
|
||||
return jsonStr, nil
|
||||
}
|
||||
}
|
||||
|
||||
// parseJSONFields parses JSON string fields into their corresponding Go types
|
||||
func (conv *Xun) parseJSONFields(data map[string]interface{}, fields []string) {
|
||||
for _, field := range fields {
|
||||
if val := data[field]; val != nil {
|
||||
if strVal, ok := val.(string); ok && strVal != "" {
|
||||
var parsed interface{}
|
||||
if err := jsoniter.UnmarshalFromString(strVal, &parsed); err == nil {
|
||||
data[field] = parsed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SaveAssistant saves assistant information
|
||||
func (conv *Xun) SaveAssistant(assistant map[string]interface{}) error {
|
||||
func (conv *Xun) SaveAssistant(assistant map[string]interface{}) (interface{}, error) {
|
||||
// Validate required fields
|
||||
requiredFields := []string{"name", "type", "connector"}
|
||||
for _, field := range requiredFields {
|
||||
if _, ok := assistant[field]; !ok {
|
||||
return fmt.Errorf("field %s is required", field)
|
||||
return nil, fmt.Errorf("field %s is required", field)
|
||||
}
|
||||
if assistant[field] == nil || assistant[field] == "" {
|
||||
return fmt.Errorf("field %s cannot be empty", field)
|
||||
return nil, fmt.Errorf("field %s cannot be empty", field)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate tags format
|
||||
if tags, ok := assistant["tags"].(string); ok {
|
||||
log.Trace("Saving assistant with tags: %s", tags)
|
||||
// Create a copy of the assistant map to avoid modifying the original
|
||||
assistantCopy := make(map[string]interface{})
|
||||
for k, v := range assistant {
|
||||
assistantCopy[k] = v
|
||||
}
|
||||
|
||||
// Process JSON fields
|
||||
jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions"}
|
||||
for _, field := range jsonFields {
|
||||
if val, ok := assistantCopy[field]; ok && val != nil {
|
||||
// If it's a string, try to parse it first
|
||||
if strVal, ok := val.(string); ok && strVal != "" {
|
||||
var parsed interface{}
|
||||
if err := jsoniter.UnmarshalFromString(strVal, &parsed); err == nil {
|
||||
assistantCopy[field] = parsed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate assistant_id if not provided
|
||||
if _, ok := assistant["assistant_id"]; !ok {
|
||||
assistant["assistant_id"] = uuid.New().String()
|
||||
if _, ok := assistantCopy["assistant_id"]; !ok {
|
||||
assistantCopy["assistant_id"] = uuid.New().String()
|
||||
}
|
||||
|
||||
// Check if assistant exists
|
||||
exists, err := conv.query.New().
|
||||
Table(conv.getAssistantTable()).
|
||||
Where("assistant_id", assistant["assistant_id"]).
|
||||
Where("assistant_id", assistantCopy["assistant_id"]).
|
||||
Exists()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert JSON fields to strings for storage
|
||||
for _, field := range jsonFields {
|
||||
if val, ok := assistantCopy[field]; ok && val != nil {
|
||||
jsonStr, err := jsoniter.MarshalToString(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal %s to JSON: %v", field, err)
|
||||
}
|
||||
assistantCopy[field] = jsonStr
|
||||
}
|
||||
}
|
||||
|
||||
// Update or insert
|
||||
if exists {
|
||||
assistant["updated_at"] = time.Now()
|
||||
_, err = conv.query.New().
|
||||
_, err := conv.query.New().
|
||||
Table(conv.getAssistantTable()).
|
||||
Where("assistant_id", assistant["assistant_id"]).
|
||||
Update(assistant)
|
||||
} else {
|
||||
assistant["created_at"] = time.Now()
|
||||
err = conv.query.New().
|
||||
Table(conv.getAssistantTable()).
|
||||
Insert(assistant)
|
||||
Where("assistant_id", assistantCopy["assistant_id"]).
|
||||
Update(assistantCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return assistantCopy["assistant_id"], nil
|
||||
}
|
||||
|
||||
return err
|
||||
err = conv.query.New().
|
||||
Table(conv.getAssistantTable()).
|
||||
Insert(assistantCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return assistantCopy["assistant_id"], nil
|
||||
}
|
||||
|
||||
// DeleteAssistant deletes an assistant by assistant_id
|
||||
|
|
@ -812,10 +874,12 @@ func (conv *Xun) GetAssistants(filter AssistantFilter) (*AssistantResponse, erro
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Convert rows to map slice
|
||||
// Convert rows to map slice and parse JSON fields
|
||||
data := make([]map[string]interface{}, len(rows))
|
||||
jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions"}
|
||||
for i, row := range rows {
|
||||
data[i] = row
|
||||
conv.parseJSONFields(data[i], jsonFields)
|
||||
}
|
||||
|
||||
return &AssistantResponse{
|
||||
|
|
|
|||
|
|
@ -478,22 +478,10 @@ func TestXunAssistantCRUD(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test creating a new assistant
|
||||
tagsJSON, err := jsoniter.MarshalToString([]string{"tag1", "tag2", "tag3"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
optionsJSON, err := jsoniter.MarshalToString(map[string]interface{}{
|
||||
"model": "gpt-4",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mentionable := true
|
||||
automated := true
|
||||
|
||||
// Test creating a new assistant with different JSON field formats
|
||||
// Test case 1: JSON fields as strings
|
||||
tagsJSON := `["tag1", "tag2", "tag3"]`
|
||||
optionsJSON := `{"model": "gpt-4"}`
|
||||
assistant := map[string]interface{}{
|
||||
"name": "Test Assistant",
|
||||
"type": "assistant",
|
||||
|
|
@ -502,95 +490,152 @@ func TestXunAssistantCRUD(t *testing.T) {
|
|||
"description": "Test Description",
|
||||
"tags": tagsJSON,
|
||||
"options": optionsJSON,
|
||||
"mentionable": mentionable,
|
||||
"automated": automated,
|
||||
"mentionable": true,
|
||||
"automated": true,
|
||||
}
|
||||
|
||||
// Test SaveAssistant (Create)
|
||||
err = conv.SaveAssistant(assistant)
|
||||
// Test SaveAssistant (Create) with string JSON
|
||||
v, err := conv.SaveAssistant(assistant)
|
||||
assert.Nil(t, err)
|
||||
assistantID := assistant["assistant_id"].(string)
|
||||
assistantID := v.(string)
|
||||
assert.NotEmpty(t, assistantID)
|
||||
|
||||
// Test GetAssistants with no filter
|
||||
// Test case 2: JSON fields as native types
|
||||
assistant2 := map[string]interface{}{
|
||||
"name": "Test Assistant 2",
|
||||
"type": "assistant",
|
||||
"avatar": "https://example.com/avatar2.png",
|
||||
"connector": "openai",
|
||||
"description": "Test Description 2",
|
||||
"tags": []string{"tag1", "tag2", "tag3"},
|
||||
"options": map[string]interface{}{"model": "gpt-4"},
|
||||
"prompts": []string{"prompt1", "prompt2"},
|
||||
"flows": []string{"flow1", "flow2"},
|
||||
"files": []string{"file1", "file2"},
|
||||
"functions": []map[string]interface{}{{"name": "func1"}, {"name": "func2"}},
|
||||
"permissions": map[string]interface{}{"read": true, "write": true},
|
||||
"mentionable": true,
|
||||
"automated": true,
|
||||
}
|
||||
|
||||
// Test SaveAssistant (Create) with native types
|
||||
v, err = conv.SaveAssistant(assistant2)
|
||||
assert.Nil(t, err)
|
||||
assistant2ID := v.(string)
|
||||
assert.NotEmpty(t, assistant2ID)
|
||||
|
||||
// Test case 3: Test with nil JSON fields
|
||||
assistant3 := map[string]interface{}{
|
||||
"name": "Test Assistant 3",
|
||||
"type": "assistant",
|
||||
"connector": "openai",
|
||||
"description": "Test Description 3",
|
||||
"tags": nil,
|
||||
"options": nil,
|
||||
"prompts": nil,
|
||||
"flows": nil,
|
||||
"files": nil,
|
||||
"functions": nil,
|
||||
"permissions": nil,
|
||||
"mentionable": true,
|
||||
"automated": true,
|
||||
}
|
||||
|
||||
// Test SaveAssistant (Create) with nil fields
|
||||
v, err = conv.SaveAssistant(assistant3)
|
||||
assert.Nil(t, err)
|
||||
assistant3ID := v.(string)
|
||||
assert.NotEmpty(t, assistant3ID)
|
||||
|
||||
// Test GetAssistants to verify JSON fields are properly stored
|
||||
resp, err := conv.GetAssistants(AssistantFilter{})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
assert.Equal(t, 3, len(resp.Data))
|
||||
|
||||
// Test GetAssistants with tag filter (single tag)
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
Tags: []string{"tag1"},
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
// Verify first assistant (string JSON)
|
||||
found := false
|
||||
for _, item := range resp.Data {
|
||||
if item["assistant_id"].(string) == assistantID {
|
||||
found = true
|
||||
// Now we expect parsed JSON values instead of JSON strings
|
||||
assert.Equal(t, []interface{}{"tag1", "tag2", "tag3"}, item["tags"])
|
||||
assert.Equal(t, map[string]interface{}{"model": "gpt-4"}, item["options"])
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found)
|
||||
|
||||
// Test GetAssistants with tag filter (multiple tags)
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
Tags: []string{"tag1", "tag4"},
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
// Verify second assistant (native types converted to JSON)
|
||||
found = false
|
||||
for _, item := range resp.Data {
|
||||
if item["assistant_id"].(string) == assistant2ID {
|
||||
found = true
|
||||
// Now we expect parsed JSON values directly
|
||||
assert.Equal(t, []interface{}{"tag1", "tag2", "tag3"}, item["tags"])
|
||||
assert.Equal(t, map[string]interface{}{"model": "gpt-4"}, item["options"])
|
||||
|
||||
// Test GetAssistants with non-existent tag
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
Tags: []string{"nonexistent"},
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(resp.Data))
|
||||
// Verify other JSON fields
|
||||
assert.Equal(t, []interface{}{"prompt1", "prompt2"}, item["prompts"])
|
||||
assert.Equal(t, []interface{}{"flow1", "flow2"}, item["flows"])
|
||||
assert.Equal(t, []interface{}{"file1", "file2"}, item["files"])
|
||||
assert.Equal(t,
|
||||
[]interface{}{
|
||||
map[string]interface{}{"name": "func1"},
|
||||
map[string]interface{}{"name": "func2"},
|
||||
},
|
||||
item["functions"])
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"read": true,
|
||||
"write": true,
|
||||
},
|
||||
item["permissions"])
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found)
|
||||
|
||||
// Test GetAssistants with keyword filter
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
Keywords: "Test",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
// Verify third assistant (nil fields)
|
||||
found = false
|
||||
for _, item := range resp.Data {
|
||||
if item["assistant_id"].(string) == assistant3ID {
|
||||
found = true
|
||||
assert.Nil(t, item["tags"])
|
||||
assert.Nil(t, item["options"])
|
||||
assert.Nil(t, item["prompts"])
|
||||
assert.Nil(t, item["flows"])
|
||||
assert.Nil(t, item["files"])
|
||||
assert.Nil(t, item["functions"])
|
||||
assert.Nil(t, item["permissions"])
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found)
|
||||
|
||||
// Test GetAssistants with connector filter
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
Connector: "openai",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
|
||||
// Test GetAssistants with mentionable filter
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
Mentionable: &mentionable,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
|
||||
// Test GetAssistants with automated filter
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
Automated: &automated,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
|
||||
// Test GetAssistants with combined filters
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
Keywords: "Test",
|
||||
Connector: "openai",
|
||||
Mentionable: &mentionable,
|
||||
Automated: &automated,
|
||||
Tags: []string{"tag1"},
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
|
||||
// Test SaveAssistant (Update)
|
||||
assistant["name"] = "Updated Assistant"
|
||||
err = conv.SaveAssistant(assistant)
|
||||
// Test updating with mixed JSON formats
|
||||
assistant2["assistant_id"] = assistant2ID
|
||||
_, err = conv.SaveAssistant(assistant2)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Verify update
|
||||
resp, err = conv.GetAssistants(AssistantFilter{})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
item := resp.Data[0]
|
||||
assert.Equal(t, "Updated Assistant", item["name"])
|
||||
for _, item := range resp.Data {
|
||||
if item["assistant_id"].(string) == assistant2ID {
|
||||
// Now we expect parsed JSON values
|
||||
assert.Equal(t, []interface{}{"tag1", "tag2", "tag3"}, item["tags"])
|
||||
assert.Equal(t, map[string]interface{}{"model": "gpt-4"}, item["options"])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Test DeleteAssistant
|
||||
err = conv.DeleteAssistant(assistantID)
|
||||
assert.Nil(t, err)
|
||||
err = conv.DeleteAssistant(assistant2ID)
|
||||
assert.Nil(t, err)
|
||||
err = conv.DeleteAssistant(assistant3ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
resp, err = conv.GetAssistants(AssistantFilter{})
|
||||
assert.Nil(t, err)
|
||||
|
|
@ -647,7 +692,7 @@ func TestXunAssistantPagination(t *testing.T) {
|
|||
"mentionable": mentionable,
|
||||
"automated": automated,
|
||||
}
|
||||
err = conv.SaveAssistant(assistant)
|
||||
_, err = conv.SaveAssistant(assistant)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,12 +66,12 @@ func processAssistantAdd(process *process.Process) interface{} {
|
|||
exception.New("Neo conversation is not initialized", 500).Throw()
|
||||
}
|
||||
|
||||
err := neo.Conversation.SaveAssistant(data)
|
||||
id, err := neo.Conversation.SaveAssistant(data)
|
||||
if err != nil {
|
||||
exception.New("Failed to add assistant: %s", 500, err.Error()).Throw()
|
||||
}
|
||||
|
||||
return data
|
||||
return id
|
||||
}
|
||||
|
||||
// processAssistantSave process the assistant save request
|
||||
|
|
@ -84,12 +84,12 @@ func processAssistantSave(process *process.Process) interface{} {
|
|||
exception.New("Neo conversation is not initialized", 500).Throw()
|
||||
}
|
||||
|
||||
err := neo.Conversation.SaveAssistant(data)
|
||||
id, err := neo.Conversation.SaveAssistant(data)
|
||||
if err != nil {
|
||||
exception.New("Failed to save assistant: %s", 500, err.Error()).Throw()
|
||||
}
|
||||
|
||||
return data
|
||||
return id
|
||||
}
|
||||
|
||||
// processAssistantDelete process the assistant delete request
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/gou/process"
|
||||
"github.com/yaoapp/kun/any"
|
||||
|
|
@ -69,19 +68,9 @@ func TestProcessAssistantCRUD(t *testing.T) {
|
|||
prepare(t)
|
||||
defer test.Clean()
|
||||
|
||||
// Create an assistant
|
||||
tagsJSON, err := jsoniter.MarshalToString([]string{"tag1", "tag2", "tag3"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
optionsJSON, err := jsoniter.MarshalToString(map[string]interface{}{
|
||||
"model": "gpt-4",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create an assistant with string JSON fields
|
||||
tagsJSON := `["tag1", "tag2", "tag3"]`
|
||||
optionsJSON := `{"model": "gpt-4"}`
|
||||
assistant := map[string]interface{}{
|
||||
"name": "Test Assistant",
|
||||
"type": "assistant",
|
||||
|
|
@ -94,7 +83,7 @@ func TestProcessAssistantCRUD(t *testing.T) {
|
|||
"automated": true,
|
||||
}
|
||||
|
||||
// Test processAssistantAdd
|
||||
// Test processAssistantAdd with string JSON
|
||||
p, err := process.Of("neo.assistant.add", assistant)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -105,12 +94,73 @@ func TestProcessAssistantCRUD(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res := any.Of(output).Map()
|
||||
assert.Equal(t, "Test Assistant", res.Get("name"))
|
||||
assert.NotEmpty(t, res.Get("assistant_id"))
|
||||
assistantID := res.Get("assistant_id").(string)
|
||||
assistantID := output
|
||||
assert.NotNil(t, assistantID)
|
||||
|
||||
// Test processAssistantSearch - no filter
|
||||
// Test with native type JSON fields
|
||||
assistant2 := map[string]interface{}{
|
||||
"name": "Test Assistant 2",
|
||||
"type": "assistant",
|
||||
"avatar": "https://example.com/avatar2.png",
|
||||
"connector": "openai",
|
||||
"description": "Test Description 2",
|
||||
"tags": []string{"tag1", "tag2", "tag3"},
|
||||
"options": map[string]interface{}{"model": "gpt-4"},
|
||||
"prompts": []string{"prompt1", "prompt2"},
|
||||
"flows": []string{"flow1", "flow2"},
|
||||
"files": []string{"file1", "file2"},
|
||||
"functions": []map[string]interface{}{{"name": "func1"}, {"name": "func2"}},
|
||||
"permissions": map[string]interface{}{"read": true, "write": true},
|
||||
"mentionable": true,
|
||||
"automated": true,
|
||||
}
|
||||
|
||||
// Test processAssistantAdd with native types
|
||||
p, err = process.Of("neo.assistant.add", assistant2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
output, err = p.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assistant2ID := output
|
||||
assert.NotNil(t, assistant2ID)
|
||||
|
||||
// Test with nil JSON fields
|
||||
assistant3 := map[string]interface{}{
|
||||
"name": "Test Assistant 3",
|
||||
"type": "assistant",
|
||||
"connector": "openai",
|
||||
"description": "Test Description 3",
|
||||
"tags": nil,
|
||||
"options": nil,
|
||||
"prompts": nil,
|
||||
"flows": nil,
|
||||
"files": nil,
|
||||
"functions": nil,
|
||||
"permissions": nil,
|
||||
"mentionable": true,
|
||||
"automated": true,
|
||||
}
|
||||
|
||||
// Test processAssistantAdd with nil fields
|
||||
p, err = process.Of("neo.assistant.add", assistant3)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
output, err = p.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assistant3ID := output
|
||||
assert.NotNil(t, assistant3ID)
|
||||
|
||||
// Test processAssistantSearch to verify all assistants
|
||||
p, err = process.Of("neo.assistant.search")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -126,20 +176,68 @@ func TestProcessAssistantCRUD(t *testing.T) {
|
|||
if total == nil {
|
||||
total = int64(0)
|
||||
}
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Equal(t, int64(3), total)
|
||||
|
||||
items := searchRes.Get("data")
|
||||
if items == nil {
|
||||
items = []map[string]interface{}{}
|
||||
}
|
||||
assert.Equal(t, 1, len(items.([]map[string]interface{})))
|
||||
assert.Equal(t, 3, len(items.([]map[string]interface{})))
|
||||
|
||||
// Test processAssistantSearch - with filter
|
||||
p, err = process.Of("neo.assistant.search", map[string]interface{}{
|
||||
"tags": []string{"tag1"},
|
||||
"page": 1,
|
||||
"pagesize": 10,
|
||||
})
|
||||
// Verify each assistant's JSON fields
|
||||
for _, item := range items.([]map[string]interface{}) {
|
||||
switch item["assistant_id"].(string) {
|
||||
case assistantID:
|
||||
assert.Equal(t, []interface{}{"tag1", "tag2", "tag3"}, item["tags"])
|
||||
assert.Equal(t, map[string]interface{}{"model": "gpt-4"}, item["options"])
|
||||
case assistant2ID:
|
||||
assert.Equal(t, []interface{}{"tag1", "tag2", "tag3"}, item["tags"])
|
||||
assert.Equal(t, map[string]interface{}{"model": "gpt-4"}, item["options"])
|
||||
assert.Equal(t, []interface{}{"prompt1", "prompt2"}, item["prompts"])
|
||||
assert.Equal(t, []interface{}{"flow1", "flow2"}, item["flows"])
|
||||
assert.Equal(t, []interface{}{"file1", "file2"}, item["files"])
|
||||
assert.Equal(t,
|
||||
[]interface{}{
|
||||
map[string]interface{}{"name": "func1"},
|
||||
map[string]interface{}{"name": "func2"},
|
||||
},
|
||||
item["functions"])
|
||||
assert.Equal(t,
|
||||
map[string]interface{}{
|
||||
"read": true,
|
||||
"write": true,
|
||||
},
|
||||
item["permissions"])
|
||||
case assistant3ID:
|
||||
assert.Nil(t, item["tags"])
|
||||
assert.Nil(t, item["options"])
|
||||
assert.Nil(t, item["prompts"])
|
||||
assert.Nil(t, item["flows"])
|
||||
assert.Nil(t, item["files"])
|
||||
assert.Nil(t, item["functions"])
|
||||
assert.Nil(t, item["permissions"])
|
||||
}
|
||||
}
|
||||
|
||||
// Test updating with mixed JSON formats
|
||||
assistant2["assistant_id"] = assistant2ID
|
||||
assistant2["tags"] = `["tag4", "tag5"]`
|
||||
assistant2["options"] = map[string]interface{}{"model": "gpt-3.5"}
|
||||
p, err = process.Of("neo.assistant.save", assistant2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
output, err = p.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
savedID := output
|
||||
assert.NotNil(t, savedID)
|
||||
|
||||
// Double check with a new search
|
||||
p, err = process.Of("neo.assistant.search")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -150,33 +248,17 @@ func TestProcessAssistantCRUD(t *testing.T) {
|
|||
}
|
||||
|
||||
searchRes = any.Of(output).Map()
|
||||
total = searchRes.Get("total")
|
||||
if total == nil {
|
||||
total = int64(0)
|
||||
}
|
||||
assert.Equal(t, int64(1), total)
|
||||
|
||||
items = searchRes.Get("data")
|
||||
if items == nil {
|
||||
items = []map[string]interface{}{}
|
||||
found := false
|
||||
for _, item := range items.([]map[string]interface{}) {
|
||||
if item["assistant_id"].(string) == assistant2ID {
|
||||
found = true
|
||||
assert.Equal(t, []interface{}{"tag4", "tag5"}, item["tags"])
|
||||
assert.Equal(t, map[string]interface{}{"model": "gpt-3.5"}, item["options"])
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 1, len(items.([]map[string]interface{})))
|
||||
|
||||
// Test processAssistantSave (Update)
|
||||
assistant["assistant_id"] = assistantID
|
||||
assistant["name"] = "Updated Assistant"
|
||||
p, err = process.Of("neo.assistant.save", assistant)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
output, err = p.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res = any.Of(output).Map()
|
||||
assert.Equal(t, "Updated Assistant", res.Get("name"))
|
||||
assert.True(t, found)
|
||||
|
||||
// Test processAssistantDelete
|
||||
p, err = process.Of("neo.assistant.delete", assistantID)
|
||||
|
|
@ -192,7 +274,22 @@ func TestProcessAssistantCRUD(t *testing.T) {
|
|||
deleteRes := any.Of(output).Map()
|
||||
assert.Equal(t, "ok", deleteRes.Get("message"))
|
||||
|
||||
// Verify deletion with search
|
||||
// Delete remaining assistants
|
||||
p, err = process.Of("neo.assistant.delete", assistant2ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = p.Exec()
|
||||
assert.Nil(t, err)
|
||||
|
||||
p, err = process.Of("neo.assistant.delete", assistant3ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = p.Exec()
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Verify all assistants are deleted
|
||||
p, err = process.Of("neo.assistant.search")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -209,12 +306,6 @@ func TestProcessAssistantCRUD(t *testing.T) {
|
|||
total = int64(0)
|
||||
}
|
||||
assert.Equal(t, int64(0), total)
|
||||
|
||||
items = searchRes.Get("data")
|
||||
if items == nil {
|
||||
items = []map[string]interface{}{}
|
||||
}
|
||||
assert.Equal(t, 0, len(items.([]map[string]interface{})))
|
||||
}
|
||||
|
||||
func TestProcessAssistantSearchPagination(t *testing.T) {
|
||||
|
|
@ -223,17 +314,12 @@ func TestProcessAssistantSearchPagination(t *testing.T) {
|
|||
|
||||
// Create multiple assistants for pagination testing
|
||||
for i := 0; i < 25; i++ {
|
||||
tagsJSON, err := jsoniter.MarshalToString([]string{fmt.Sprintf("tag%d", i%5)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assistant := map[string]interface{}{
|
||||
"name": fmt.Sprintf("Assistant %d", i),
|
||||
"type": "assistant",
|
||||
"connector": fmt.Sprintf("connector%d", i%3),
|
||||
"description": fmt.Sprintf("Description %d", i),
|
||||
"tags": tagsJSON,
|
||||
"tags": []string{fmt.Sprintf("tag%d", i%5)},
|
||||
"mentionable": i%2 == 0,
|
||||
"automated": i%3 == 0,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue