Enhance assistant filtering and management in Neo API
- Added support for filtering assistants by keywords, connector, and mentionable status in the handleAssistantList function. - Introduced a new parseBoolValue utility function to handle various boolean formats for filtering. - Updated the AssistantFilter structure to include an AssistantID field and a Select field for specifying returned fields. - Refactored process functions to replace 'add' with 'create' for consistency and clarity in assistant management. - Implemented new processAssistantFind function to retrieve assistants by ID, improving data retrieval capabilities. - Enhanced tests to cover new filtering options and ensure robust functionality across assistant management operations.
This commit is contained in:
parent
94f3ddb158
commit
882277d974
6 changed files with 258 additions and 33 deletions
65
neo/api.go
65
neo/api.go
|
|
@ -863,6 +863,37 @@ func (neo *DSL) handleAssistantList(c *gin.Context) {
|
|||
filter.Tags = strings.Split(tags, ",")
|
||||
}
|
||||
|
||||
// Parse keywords
|
||||
if keywords := c.Query("keywords"); keywords != "" {
|
||||
filter.Keywords = keywords
|
||||
}
|
||||
|
||||
// Parse connector
|
||||
if connector := c.Query("connector"); connector != "" {
|
||||
filter.Connector = connector
|
||||
}
|
||||
|
||||
// Parse select fields
|
||||
if selectFields := c.Query("select"); selectFields != "" {
|
||||
filter.Select = strings.Split(selectFields, ",")
|
||||
}
|
||||
|
||||
// Parse mentionable (support various boolean formats)
|
||||
if mentionable := c.Query("mentionable"); mentionable != "" {
|
||||
val := parseBoolValue(mentionable)
|
||||
if val != nil {
|
||||
filter.Mentionable = val
|
||||
}
|
||||
}
|
||||
|
||||
// Parse automated (support various boolean formats)
|
||||
if automated := c.Query("automated"); automated != "" {
|
||||
val := parseBoolValue(automated)
|
||||
if val != nil {
|
||||
filter.Automated = val
|
||||
}
|
||||
}
|
||||
|
||||
response, err := neo.Conversation.GetAssistants(filter)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
|
||||
|
|
@ -874,6 +905,22 @@ func (neo *DSL) handleAssistantList(c *gin.Context) {
|
|||
c.Done()
|
||||
}
|
||||
|
||||
// parseBoolValue parses various string formats into a boolean pointer
|
||||
// Supports: 1, 0, "1", "0", "true", "false", etc.
|
||||
func parseBoolValue(value string) *bool {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
switch value {
|
||||
case "1", "true", "yes", "on":
|
||||
v := true
|
||||
return &v
|
||||
case "0", "false", "no", "off":
|
||||
v := false
|
||||
return &v
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// handleAssistantDetail handles getting a single assistant's details
|
||||
func (neo *DSL) handleAssistantDetail(c *gin.Context) {
|
||||
assistantID := c.Param("id")
|
||||
|
|
@ -884,8 +931,9 @@ func (neo *DSL) handleAssistantDetail(c *gin.Context) {
|
|||
}
|
||||
|
||||
filter := conversation.AssistantFilter{
|
||||
Page: 1,
|
||||
PageSize: 1,
|
||||
AssistantID: assistantID,
|
||||
Page: 1,
|
||||
PageSize: 1,
|
||||
}
|
||||
|
||||
response, err := neo.Conversation.GetAssistants(filter)
|
||||
|
|
@ -895,22 +943,13 @@ func (neo *DSL) handleAssistantDetail(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// Find the assistant by ID
|
||||
var assistant map[string]interface{}
|
||||
for _, item := range response.Data {
|
||||
if id, ok := item["id"].(string); ok && id == assistantID {
|
||||
assistant = item
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if assistant == nil {
|
||||
if len(response.Data) == 0 {
|
||||
c.JSON(404, gin.H{"message": "assistant not found", "code": 404})
|
||||
c.Done()
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]interface{}{"data": assistant})
|
||||
c.JSON(200, map[string]interface{}{"data": response.Data[0]})
|
||||
c.Done()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,13 +46,15 @@ type ChatGroupResponse struct {
|
|||
// AssistantFilter represents the assistant filter structure
|
||||
// Used for filtering and pagination when retrieving assistant lists
|
||||
type AssistantFilter struct {
|
||||
Tags []string `json:"tags,omitempty"` // Filter by tags
|
||||
Keywords string `json:"keywords,omitempty"` // Search in name and description
|
||||
Connector string `json:"connector,omitempty"` // Filter by connector
|
||||
Mentionable *bool `json:"mentionable,omitempty"` // Filter by mentionable status
|
||||
Automated *bool `json:"automated,omitempty"` // Filter by automation status
|
||||
Page int `json:"page,omitempty"` // Page number, starting from 1
|
||||
PageSize int `json:"pagesize,omitempty"` // Items per page
|
||||
Tags []string `json:"tags,omitempty"` // Filter by tags
|
||||
Keywords string `json:"keywords,omitempty"` // Search in name and description
|
||||
Connector string `json:"connector,omitempty"` // Filter by connector
|
||||
AssistantID string `json:"assistant_id,omitempty"` // Filter by assistant ID
|
||||
Mentionable *bool `json:"mentionable,omitempty"` // Filter by mentionable status
|
||||
Automated *bool `json:"automated,omitempty"` // Filter by automation status
|
||||
Page int `json:"page,omitempty"` // Page number, starting from 1
|
||||
PageSize int `json:"pagesize,omitempty"` // Items per page
|
||||
Select []string `json:"select,omitempty"` // Fields to return, returns all fields if empty
|
||||
}
|
||||
|
||||
// AssistantResponse represents the assistant response structure
|
||||
|
|
|
|||
|
|
@ -829,6 +829,11 @@ func (conv *Xun) GetAssistants(filter AssistantFilter) (*AssistantResponse, erro
|
|||
qb.Where("connector", filter.Connector)
|
||||
}
|
||||
|
||||
// Apply assistant_id filter if provided
|
||||
if filter.AssistantID != "" {
|
||||
qb.Where("assistant_id", filter.AssistantID)
|
||||
}
|
||||
|
||||
// Apply mentionable filter if provided
|
||||
if filter.Mentionable != nil {
|
||||
qb.Where("mentionable", *filter.Mentionable)
|
||||
|
|
@ -865,6 +870,15 @@ func (conv *Xun) GetAssistants(filter AssistantFilter) (*AssistantResponse, erro
|
|||
prevPage = 0
|
||||
}
|
||||
|
||||
// Apply select fields if provided
|
||||
if filter.Select != nil && len(filter.Select) > 0 {
|
||||
selectFields := make([]interface{}, len(filter.Select))
|
||||
for i, field := range filter.Select {
|
||||
selectFields[i] = field
|
||||
}
|
||||
qb.Select(selectFields...)
|
||||
}
|
||||
|
||||
// Get paginated results
|
||||
rows, err := qb.OrderBy("created_at", "desc").
|
||||
Offset(offset).
|
||||
|
|
@ -879,7 +893,24 @@ func (conv *Xun) GetAssistants(filter AssistantFilter) (*AssistantResponse, erro
|
|||
jsonFields := []string{"tags", "options", "prompts", "flows", "files", "functions", "permissions"}
|
||||
for i, row := range rows {
|
||||
data[i] = row
|
||||
conv.parseJSONFields(data[i], jsonFields)
|
||||
// Only parse JSON fields if they are selected or no select filter is provided
|
||||
if filter.Select == nil || len(filter.Select) == 0 {
|
||||
conv.parseJSONFields(data[i], jsonFields)
|
||||
} else {
|
||||
// Parse only selected JSON fields
|
||||
selectedJSONFields := []string{}
|
||||
for _, field := range jsonFields {
|
||||
for _, selected := range filter.Select {
|
||||
if selected == field {
|
||||
selectedJSONFields = append(selectedJSONFields, field)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(selectedJSONFields) > 0 {
|
||||
conv.parseJSONFields(data[i], selectedJSONFields)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &AssistantResponse{
|
||||
|
|
|
|||
|
|
@ -775,6 +775,46 @@ func TestXunAssistantPagination(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.Greater(t, len(resp.Data), 0)
|
||||
|
||||
// Test filtering by assistant_id
|
||||
// First get an assistant_id from previous results
|
||||
firstAssistantID := resp.Data[0]["assistant_id"].(string)
|
||||
|
||||
// Test exact match with assistant_id
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
AssistantID: firstAssistantID,
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
assert.Equal(t, firstAssistantID, resp.Data[0]["assistant_id"])
|
||||
|
||||
// Test assistant_id with other filters
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
AssistantID: firstAssistantID,
|
||||
Select: []string{"name", "assistant_id", "description"},
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(resp.Data))
|
||||
assert.Equal(t, firstAssistantID, resp.Data[0]["assistant_id"])
|
||||
// Verify only selected fields are returned
|
||||
assert.Contains(t, resp.Data[0], "name")
|
||||
assert.Contains(t, resp.Data[0], "assistant_id")
|
||||
assert.Contains(t, resp.Data[0], "description")
|
||||
assert.NotContains(t, resp.Data[0], "tags")
|
||||
assert.NotContains(t, resp.Data[0], "options")
|
||||
|
||||
// Test non-existent assistant_id
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
AssistantID: "non-existent-id",
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(resp.Data))
|
||||
|
||||
// Test combined filters
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
Tags: []string{"tag0"},
|
||||
|
|
@ -786,4 +826,51 @@ func TestXunAssistantPagination(t *testing.T) {
|
|||
PageSize: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Test filtering with select fields
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
Select: []string{"name", "description", "tags"},
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 10, len(resp.Data))
|
||||
// Verify only selected fields are returned
|
||||
for _, item := range resp.Data {
|
||||
// These fields should exist
|
||||
assert.Contains(t, item, "name")
|
||||
assert.Contains(t, item, "description")
|
||||
assert.Contains(t, item, "tags")
|
||||
// These fields should not exist
|
||||
assert.NotContains(t, item, "options")
|
||||
assert.NotContains(t, item, "prompts")
|
||||
assert.NotContains(t, item, "flows")
|
||||
assert.NotContains(t, item, "files")
|
||||
assert.NotContains(t, item, "functions")
|
||||
assert.NotContains(t, item, "permissions")
|
||||
}
|
||||
|
||||
// Test filtering with select fields and other filters combined
|
||||
resp, err = conv.GetAssistants(AssistantFilter{
|
||||
Tags: []string{"tag0"},
|
||||
Keywords: "Assistant",
|
||||
Select: []string{"name", "tags"},
|
||||
Page: 1,
|
||||
PageSize: 10,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
// Verify only selected fields are returned
|
||||
for _, item := range resp.Data {
|
||||
// These fields should exist
|
||||
assert.Contains(t, item, "name")
|
||||
assert.Contains(t, item, "tags")
|
||||
// These fields should not exist
|
||||
assert.NotContains(t, item, "description")
|
||||
assert.NotContains(t, item, "options")
|
||||
assert.NotContains(t, item, "prompts")
|
||||
assert.NotContains(t, item, "flows")
|
||||
assert.NotContains(t, item, "files")
|
||||
assert.NotContains(t, item, "functions")
|
||||
assert.NotContains(t, item, "permissions")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,11 @@ func GetNeo() *DSL {
|
|||
func init() {
|
||||
process.RegisterGroup("neo", map[string]process.Handler{
|
||||
"write": ProcessWrite,
|
||||
"assistant.add": processAssistantAdd,
|
||||
"assistant.create": processAssistantCreate,
|
||||
"assistant.save": processAssistantSave,
|
||||
"assistant.delete": processAssistantDelete,
|
||||
"assistant.search": processAssistantSearch,
|
||||
"assistant.find": processAssistantFind,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -56,8 +57,8 @@ func ProcessWrite(process *process.Process) interface{} {
|
|||
return nil
|
||||
}
|
||||
|
||||
// processAssistantAdd process the assistant add request
|
||||
func processAssistantAdd(process *process.Process) interface{} {
|
||||
// processAssistantCreate process the assistant create request
|
||||
func processAssistantCreate(process *process.Process) interface{} {
|
||||
process.ValidateArgNums(1)
|
||||
data := process.ArgsMap(0)
|
||||
|
||||
|
|
@ -68,7 +69,7 @@ func processAssistantAdd(process *process.Process) interface{} {
|
|||
|
||||
id, err := neo.Conversation.SaveAssistant(data)
|
||||
if err != nil {
|
||||
exception.New("Failed to add assistant: %s", 500, err.Error()).Throw()
|
||||
exception.New("Failed to create assistant: %s", 500, err.Error()).Throw()
|
||||
}
|
||||
|
||||
return id
|
||||
|
|
@ -175,3 +176,31 @@ func processAssistantSearch(process *process.Process) interface{} {
|
|||
|
||||
return res
|
||||
}
|
||||
|
||||
// processAssistantFind process the assistant find request
|
||||
func processAssistantFind(process *process.Process) interface{} {
|
||||
process.ValidateArgNums(1)
|
||||
assistantID := process.ArgsString(0)
|
||||
|
||||
neo := GetNeo()
|
||||
if neo.Conversation == nil {
|
||||
exception.New("Neo conversation is not initialized", 500).Throw()
|
||||
}
|
||||
|
||||
filter := conversation.AssistantFilter{
|
||||
AssistantID: assistantID,
|
||||
Page: 1,
|
||||
PageSize: 1,
|
||||
}
|
||||
|
||||
res, err := neo.Conversation.GetAssistants(filter)
|
||||
if err != nil {
|
||||
exception.New("Failed to find assistant: %s", 500, err.Error()).Throw()
|
||||
}
|
||||
|
||||
if len(res.Data) == 0 {
|
||||
exception.New("Assistant not found: %s", 404, assistantID).Throw()
|
||||
}
|
||||
|
||||
return res.Data[0]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ func TestProcessAssistantCRUD(t *testing.T) {
|
|||
"automated": true,
|
||||
}
|
||||
|
||||
// Test processAssistantAdd with string JSON
|
||||
p, err := process.Of("neo.assistant.add", assistant)
|
||||
// Test processAssistantCreate with string JSON
|
||||
p, err := process.Of("neo.assistant.create", assistant)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -97,6 +97,33 @@ func TestProcessAssistantCRUD(t *testing.T) {
|
|||
assistantID := output
|
||||
assert.NotNil(t, assistantID)
|
||||
|
||||
// Test processAssistantFind
|
||||
p, err = process.Of("neo.assistant.find", assistantID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
output, err = p.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
foundAssistant := output.(map[string]interface{})
|
||||
assert.Equal(t, assistantID, foundAssistant["assistant_id"])
|
||||
assert.Equal(t, "Test Assistant", foundAssistant["name"])
|
||||
assert.Equal(t, []interface{}{"tag1", "tag2", "tag3"}, foundAssistant["tags"])
|
||||
assert.Equal(t, map[string]interface{}{"model": "gpt-4"}, foundAssistant["options"])
|
||||
|
||||
// Test processAssistantFind with non-existent ID
|
||||
p, err = process.Of("neo.assistant.find", "non-existent-id")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = p.Exec()
|
||||
assert.NotNil(t, err)
|
||||
assert.Contains(t, err.Error(), "Assistant not found")
|
||||
|
||||
// Test with native type JSON fields
|
||||
assistant2 := map[string]interface{}{
|
||||
"name": "Test Assistant 2",
|
||||
|
|
@ -115,8 +142,8 @@ func TestProcessAssistantCRUD(t *testing.T) {
|
|||
"automated": true,
|
||||
}
|
||||
|
||||
// Test processAssistantAdd with native types
|
||||
p, err = process.Of("neo.assistant.add", assistant2)
|
||||
// Test processAssistantCreate with native types
|
||||
p, err = process.Of("neo.assistant.create", assistant2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -146,8 +173,8 @@ func TestProcessAssistantCRUD(t *testing.T) {
|
|||
"automated": true,
|
||||
}
|
||||
|
||||
// Test processAssistantAdd with nil fields
|
||||
p, err = process.Of("neo.assistant.add", assistant3)
|
||||
// Test processAssistantCreate with nil fields
|
||||
p, err = process.Of("neo.assistant.create", assistant3)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -324,7 +351,7 @@ func TestProcessAssistantSearchPagination(t *testing.T) {
|
|||
"automated": i%3 == 0,
|
||||
}
|
||||
|
||||
p, err := process.Of("neo.assistant.add", assistant)
|
||||
p, err := process.Of("neo.assistant.create", assistant)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -438,7 +465,7 @@ func TestProcessAssistantValidation(t *testing.T) {
|
|||
defer test.Clean()
|
||||
|
||||
// Test missing required fields
|
||||
p, err := process.Of("neo.assistant.add", map[string]interface{}{})
|
||||
p, err := process.Of("neo.assistant.create", map[string]interface{}{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -455,6 +482,16 @@ func TestProcessAssistantValidation(t *testing.T) {
|
|||
_, err = p.Exec()
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Test invalid assistant ID for find
|
||||
p, err = process.Of("neo.assistant.find", "non-existent-id")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = p.Exec()
|
||||
assert.NotNil(t, err)
|
||||
assert.Contains(t, err.Error(), "Assistant not found")
|
||||
|
||||
// Test invalid page number
|
||||
p, err = process.Of("neo.assistant.search", map[string]interface{}{
|
||||
"page": -1,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue