Refactor conversation management and migrate to store-based architecture in Neo API

- Replaced the conversation handling logic with a new store-based approach, enhancing data management and retrieval capabilities.
- Updated all relevant methods to utilize the new store interface, including GetChats, GetChat, GetHistory, and SaveAssistant, ensuring consistent functionality across the API.
- Removed deprecated conversation-related files and structures, streamlining the codebase and improving maintainability.
- Enhanced the AssistantFilter and AssistantResponse types to support the new store architecture, improving filtering and pagination capabilities.
- Updated tests to cover the new store-based methods and ensure robust functionality across different storage backends.
This commit is contained in:
Max 2025-01-01 10:43:05 +08:00
parent 238347c834
commit e530ccc1cd
12 changed files with 222 additions and 214 deletions

View file

@ -15,8 +15,8 @@ import (
"github.com/yaoapp/gou/connector"
"github.com/yaoapp/gou/process"
"github.com/yaoapp/yao/helper"
"github.com/yaoapp/yao/neo/conversation"
"github.com/yaoapp/yao/neo/message"
"github.com/yaoapp/yao/neo/store"
)
// API registers the Neo API endpoints
@ -227,7 +227,7 @@ func (neo *DSL) handleChatList(c *gin.Context) {
}
// Create filter from query parameters
filter := conversation.ChatFilter{
filter := store.ChatFilter{
Keywords: c.Query("keywords"),
Order: c.Query("order"),
}
@ -245,7 +245,7 @@ func (neo *DSL) handleChatList(c *gin.Context) {
}
}
response, err := neo.Conversation.GetChats(sid, filter)
response, err := neo.Store.GetChats(sid, filter)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -266,7 +266,7 @@ func (neo *DSL) handleChatHistory(c *gin.Context) {
}
cid := c.Query("chat_id")
history, err := neo.Conversation.GetHistory(sid, cid)
history, err := neo.Store.GetHistory(sid, cid)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -450,7 +450,7 @@ func (neo *DSL) handleChatDetail(c *gin.Context) {
return
}
chat, err := neo.Conversation.GetChat(sid, chatID)
chat, err := neo.Store.GetChat(sid, chatID)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -475,14 +475,14 @@ func (neo *DSL) handleMentions(c *gin.Context) {
mentionable := true
// Query mentionable assistants
filter := conversation.AssistantFilter{
filter := store.AssistantFilter{
Keywords: keywords,
Mentionable: &mentionable,
Page: 1,
PageSize: 20,
}
response, err := neo.Conversation.GetAssistants(filter)
response, err := neo.Store.GetAssistants(filter)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -552,7 +552,7 @@ func (neo *DSL) handleChatUpdate(c *gin.Context) {
return
}
err := neo.Conversation.UpdateChatTitle(sid, chatID, body.Title)
err := neo.Store.UpdateChatTitle(sid, chatID, body.Title)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -579,7 +579,7 @@ func (neo *DSL) handleChatDelete(c *gin.Context) {
return
}
err := neo.Conversation.DeleteChat(sid, chatID)
err := neo.Store.DeleteChat(sid, chatID)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -599,7 +599,7 @@ func (neo *DSL) handleChatsDeleteAll(c *gin.Context) {
return
}
err := neo.Conversation.DeleteAllChats(sid)
err := neo.Store.DeleteAllChats(sid)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -840,7 +840,7 @@ func (neo *DSL) handleGenerateCustom(c *gin.Context) {
// handleAssistantList handles listing assistants
func (neo *DSL) handleAssistantList(c *gin.Context) {
// Parse filter parameters
filter := conversation.AssistantFilter{
filter := store.AssistantFilter{
Page: 1,
PageSize: 20,
}
@ -894,7 +894,7 @@ func (neo *DSL) handleAssistantList(c *gin.Context) {
}
}
response, err := neo.Conversation.GetAssistants(filter)
response, err := neo.Store.GetAssistants(filter)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -930,13 +930,13 @@ func (neo *DSL) handleAssistantDetail(c *gin.Context) {
return
}
filter := conversation.AssistantFilter{
filter := store.AssistantFilter{
AssistantID: assistantID,
Page: 1,
PageSize: 1,
}
response, err := neo.Conversation.GetAssistants(filter)
response, err := neo.Store.GetAssistants(filter)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -962,7 +962,7 @@ func (neo *DSL) handleAssistantSave(c *gin.Context) {
return
}
id, err := neo.Conversation.SaveAssistant(assistant)
id, err := neo.Store.SaveAssistant(assistant)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()
@ -987,7 +987,7 @@ func (neo *DSL) handleAssistantDelete(c *gin.Context) {
return
}
err := neo.Conversation.DeleteAssistant(assistantID)
err := neo.Store.DeleteAssistant(assistantID)
if err != nil {
c.JSON(500, gin.H{"message": err.Error(), "code": 500})
c.Done()

View file

@ -0,0 +1,26 @@
package assistant
import "github.com/yaoapp/yao/neo/store"
// loadedAssistant the loaded assistant
var loadedAssistant = map[string]*Assistant{}
// LoadLocal create a new assistant from local
func LoadLocal(path string) *Assistant {
return nil
}
// LoadZip create a new assistant from zip
func LoadZip(zip string) *Assistant {
return nil
}
// LoadRemote create a new assistant from remote
func LoadRemote(url string) *Assistant {
return nil
}
// LoadStore create a new assistant from store
func LoadStore(store store.Store) *Assistant {
return nil
}

View file

@ -9,7 +9,7 @@ import (
"github.com/yaoapp/gou/application"
"github.com/yaoapp/yao/config"
"github.com/yaoapp/yao/neo/assistant"
"github.com/yaoapp/yao/neo/conversation"
"github.com/yaoapp/yao/neo/store"
)
// Neo the neo AI assistant
@ -23,7 +23,7 @@ func Load(cfg config.Config) error {
Prompts: []assistant.Prompt{},
Option: map[string]interface{}{},
Allows: []string{},
ConversationSetting: conversation.Setting{
StoreSetting: store.Setting{
Table: "yao_neo_conversation",
Connector: "default",
},
@ -39,14 +39,14 @@ func Load(cfg config.Config) error {
return err
}
if setting.ConversationSetting.MaxSize == 0 {
setting.ConversationSetting.MaxSize = 100
if setting.StoreSetting.MaxSize == 0 {
setting.StoreSetting.MaxSize = 100
}
Neo = &setting
// Conversation Setting
err = Neo.createConversation()
// Store Setting
err = Neo.createStore()
if err != nil {
return err
}

View file

@ -13,8 +13,8 @@ import (
"github.com/yaoapp/yao/neo/assistant"
"github.com/yaoapp/yao/neo/assistant/local"
"github.com/yaoapp/yao/neo/assistant/openai"
"github.com/yaoapp/yao/neo/conversation"
"github.com/yaoapp/yao/neo/message"
"github.com/yaoapp/yao/neo/store"
"github.com/yaoapp/yao/share"
)
@ -473,7 +473,7 @@ func (neo *DSL) createDefaultAssistant() (assistant.API, error) {
// chatMessages get the chat messages
func (neo *DSL) chatMessages(ctx Context, content ...string) ([]map[string]interface{}, error) {
history, err := neo.Conversation.GetHistory(ctx.Sid, ctx.ChatID)
history, err := neo.Store.GetHistory(ctx.Sid, ctx.ChatID)
if err != nil {
return nil, err
}
@ -493,7 +493,7 @@ func (neo *DSL) chatMessages(ctx Context, content ...string) ([]map[string]inter
func (neo *DSL) saveHistory(sid string, chatID string, content []byte, messages []map[string]interface{}) {
if len(content) > 0 && sid != "" && len(messages) > 0 {
err := neo.Conversation.SaveHistory(
err := neo.Store.SaveHistory(
sid,
[]map[string]interface{}{
{"role": "user", "content": messages[len(messages)-1]["content"], "name": sid},
@ -509,39 +509,39 @@ func (neo *DSL) saveHistory(sid string, chatID string, content []byte, messages
}
}
// createConversation create a new conversation
func (neo *DSL) createConversation() error {
// createStore create a new store
func (neo *DSL) createStore() error {
var err error
if neo.ConversationSetting.Connector == "default" || neo.ConversationSetting.Connector == "" {
neo.Conversation, err = conversation.NewXun(neo.ConversationSetting)
if neo.StoreSetting.Connector == "default" || neo.StoreSetting.Connector == "" {
neo.Store, err = store.NewXun(neo.StoreSetting)
return err
}
// other connector
conn, err := connector.Select(neo.ConversationSetting.Connector)
conn, err := connector.Select(neo.StoreSetting.Connector)
if err != nil {
return err
}
if conn.Is(connector.DATABASE) {
neo.Conversation, err = conversation.NewXun(neo.ConversationSetting)
neo.Store, err = store.NewXun(neo.StoreSetting)
return err
} else if conn.Is(connector.REDIS) {
neo.Conversation = conversation.NewRedis()
neo.Store = store.NewRedis()
return nil
} else if conn.Is(connector.MONGO) {
neo.Conversation = conversation.NewMongo()
neo.Store = store.NewMongo()
return nil
} else if conn.Is(connector.WEAVIATE) {
neo.Conversation = conversation.NewWeaviate()
neo.Store = store.NewWeaviate()
return nil
}
return fmt.Errorf("%s conversation connector %s not support", neo.ID, neo.ConversationSetting.Connector)
return fmt.Errorf("%s store connector %s not support", neo.ID, neo.StoreSetting.Connector)
}
// sendMessage sends a message to the client

View file

@ -7,8 +7,8 @@ import (
"github.com/gin-gonic/gin"
"github.com/yaoapp/gou/process"
"github.com/yaoapp/kun/exception"
"github.com/yaoapp/yao/neo/conversation"
"github.com/yaoapp/yao/neo/message"
"github.com/yaoapp/yao/neo/store"
)
// GetNeo returns the Neo instance
@ -32,7 +32,6 @@ func init() {
// ProcessWrite process the write request
func ProcessWrite(process *process.Process) interface{} {
process.ValidateArgNums(2)
w, ok := process.Args[0].(gin.ResponseWriter)
@ -63,11 +62,11 @@ func processAssistantCreate(process *process.Process) interface{} {
data := process.ArgsMap(0)
neo := GetNeo()
if neo.Conversation == nil {
exception.New("Neo conversation is not initialized", 500).Throw()
if neo.Store == nil {
exception.New("Neo store is not initialized", 500).Throw()
}
id, err := neo.Conversation.SaveAssistant(data)
id, err := neo.Store.SaveAssistant(data)
if err != nil {
exception.New("Failed to create assistant: %s", 500, err.Error()).Throw()
}
@ -81,11 +80,11 @@ func processAssistantSave(process *process.Process) interface{} {
data := process.ArgsMap(0)
neo := GetNeo()
if neo.Conversation == nil {
exception.New("Neo conversation is not initialized", 500).Throw()
if neo.Store == nil {
exception.New("Neo store is not initialized", 500).Throw()
}
id, err := neo.Conversation.SaveAssistant(data)
id, err := neo.Store.SaveAssistant(data)
if err != nil {
exception.New("Failed to save assistant: %s", 500, err.Error()).Throw()
}
@ -99,11 +98,11 @@ func processAssistantDelete(process *process.Process) interface{} {
assistantID := process.ArgsString(0)
neo := GetNeo()
if neo.Conversation == nil {
exception.New("Neo conversation is not initialized", 500).Throw()
if neo.Store == nil {
exception.New("Neo store is not initialized", 500).Throw()
}
err := neo.Conversation.DeleteAssistant(assistantID)
err := neo.Store.DeleteAssistant(assistantID)
if err != nil {
exception.New("Failed to delete assistant: %s", 500, err.Error()).Throw()
}
@ -114,7 +113,7 @@ func processAssistantDelete(process *process.Process) interface{} {
// processAssistantSearch process the assistant search request
func processAssistantSearch(process *process.Process) interface{} {
params := process.ArgsMap(0)
filter := conversation.AssistantFilter{}
filter := store.AssistantFilter{}
// Parse page and pagesize
if page, ok := params["page"]; ok {
@ -165,11 +164,11 @@ func processAssistantSearch(process *process.Process) interface{} {
// Get assistants
neo := GetNeo()
if neo.Conversation == nil {
exception.New("Neo conversation is not initialized", 500).Throw()
if neo.Store == nil {
exception.New("Neo store is not initialized", 500).Throw()
}
res, err := neo.Conversation.GetAssistants(filter)
res, err := neo.Store.GetAssistants(filter)
if err != nil {
exception.New("get assistants error: %s", 500, err).Throw()
}
@ -183,17 +182,17 @@ func processAssistantFind(process *process.Process) interface{} {
assistantID := process.ArgsString(0)
neo := GetNeo()
if neo.Conversation == nil {
exception.New("Neo conversation is not initialized", 500).Throw()
if neo.Store == nil {
exception.New("Neo store is not initialized", 500).Throw()
}
filter := conversation.AssistantFilter{
filter := store.AssistantFilter{
AssistantID: assistantID,
Page: 1,
PageSize: 1,
}
res, err := neo.Conversation.GetAssistants(filter)
res, err := neo.Store.GetAssistants(filter)
if err != nil {
exception.New("Failed to find assistant: %s", 500, err.Error()).Throw()
}

View file

@ -1,10 +1,10 @@
package conversation
package store
// Mongo represents a MongoDB-based conversation storage
type Mongo struct{}
// NewMongo creates a new MongoDB conversation storage
func NewMongo() *Mongo {
// NewMongo create a new mongo store
func NewMongo() Store {
return &Mongo{}
}

View file

@ -1,10 +1,10 @@
package conversation
package store
// Redis represents a Redis-based conversation storage
type Redis struct{}
// NewRedis creates a new Redis conversation storage
func NewRedis() *Redis {
// NewRedis create a new redis store
func NewRedis() Store {
return &Redis{}
}

View file

@ -1,4 +1,4 @@
package conversation
package store
// Setting represents the conversation configuration structure
// Used to configure basic conversation parameters including connector, user field, table name, etc.
@ -69,9 +69,9 @@ type AssistantResponse struct {
Total int64 `json:"total"` // Total number of items
}
// Conversation defines the conversation storage interface
// Store defines the conversation storage interface
// Provides basic operations required for conversation management
type Conversation interface {
type Store interface {
// GetChats retrieves a list of chats
// sid: Session ID
// filter: Filter conditions

View file

@ -1,10 +1,10 @@
package conversation
package store
// Weaviate represents a Weaviate-based conversation storage
type Weaviate struct{}
// NewWeaviate creates a new Weaviate conversation storage
func NewWeaviate() *Weaviate {
// NewWeaviate create a new weaviate store
func NewWeaviate() Store {
return &Weaviate{}
}

View file

@ -1,4 +1,4 @@
package conversation
package store
import (
"fmt"
@ -45,8 +45,8 @@ type Xun struct {
// DeleteAssistant deletes an assistant by assistant_id
// GetAssistants retrieves a paginated list of assistants with filtering
// NewXun create a new conversation
func NewXun(setting Setting) (*Xun, error) {
// NewXun create a new xun store
func NewXun(setting Setting) (Store, error) {
conv := &Xun{setting: setting}
if setting.Connector == "default" {
conv.query = capsule.Global.Query()

View file

@ -1,4 +1,4 @@
package conversation
package store
import (
"fmt"
@ -38,7 +38,7 @@ func TestNewXunDefault(t *testing.T) {
// Add a small delay to ensure table is created
time.Sleep(100 * time.Millisecond)
conv, err := NewXun(Setting{
store, err := NewXun(Setting{
Connector: "default",
Table: "__unit_test_conversation",
})
@ -69,38 +69,47 @@ func TestNewXunDefault(t *testing.T) {
}
assert.Equal(t, true, has)
// validate the history table
tab, err := conv.schema.GetTable(conv.getHistoryTable())
if err != nil {
t.Fatal(err)
// Validate table structure by attempting operations
// Test history operations
messages := []map[string]interface{}{
{"role": "user", "content": "test message"},
}
err = store.SaveHistory("test_user", messages, "test_chat", nil)
assert.Nil(t, err)
history, err := store.GetHistory("test_user", "test_chat")
assert.Nil(t, err)
assert.NotEmpty(t, history)
// Test chat operations
err = store.UpdateChatTitle("test_user", "test_chat", "Test Chat")
assert.Nil(t, err)
chat, err := store.GetChat("test_user", "test_chat")
assert.Nil(t, err)
assert.NotNil(t, chat)
// Test assistant operations
assistant := map[string]interface{}{
"name": "Test Assistant",
"type": "assistant",
"connector": "test",
"description": "Test Description",
"tags": []string{"test"},
"mentionable": true,
"automated": true,
}
fields := []string{"id", "sid", "cid", "uid", "role", "name", "content", "context", "created_at", "updated_at", "expired_at"}
for _, field := range fields {
assert.Equal(t, true, tab.HasColumn(field))
}
id, err := store.SaveAssistant(assistant)
assert.Nil(t, err)
assert.NotNil(t, id)
// validate the chat table
tab, err = conv.schema.GetTable(conv.getChatTable())
if err != nil {
t.Fatal(err)
}
// Clean up test data
err = store.DeleteChat("test_user", "test_chat")
assert.Nil(t, err)
chatFields := []string{"id", "chat_id", "title", "sid", "created_at", "updated_at"}
for _, field := range chatFields {
assert.Equal(t, true, tab.HasColumn(field))
}
// validate the assistant table
tab, err = conv.schema.GetTable(conv.getAssistantTable())
if err != nil {
t.Fatal(err)
}
assistantFields := []string{"id", "assistant_id", "type", "name", "avatar", "connector", "description", "options", "prompts", "flows", "files", "functions", "tags", "readonly", "permissions", "automated", "mentionable", "created_at", "updated_at"}
for _, field := range assistantFields {
assert.Equal(t, true, tab.HasColumn(field))
}
err = store.DeleteAssistant(id.(string))
assert.Nil(t, err)
}
func TestNewXunConnector(t *testing.T) {
@ -128,7 +137,7 @@ func TestNewXunConnector(t *testing.T) {
// Add a small delay to ensure table is created
time.Sleep(100 * time.Millisecond)
conv, err := NewXun(Setting{
store, err := NewXun(Setting{
Connector: "mysql",
Table: "__unit_test_conversation",
})
@ -159,38 +168,19 @@ func TestNewXunConnector(t *testing.T) {
}
assert.Equal(t, true, has)
// validate the history table
tab, err := conv.schema.GetTable(conv.getHistoryTable())
if err != nil {
t.Fatal(err)
// Test basic operations
messages := []map[string]interface{}{
{"role": "user", "content": "test message"},
}
err = store.SaveHistory("test_user", messages, "test_chat", nil)
assert.Nil(t, err)
fields := []string{"id", "sid", "cid", "uid", "role", "name", "content", "context", "created_at", "updated_at", "expired_at"}
for _, field := range fields {
assert.Equal(t, true, tab.HasColumn(field))
}
history, err := store.GetHistory("test_user", "test_chat")
assert.Nil(t, err)
assert.NotEmpty(t, history)
// validate the chat table
tab, err = conv.schema.GetTable(conv.getChatTable())
if err != nil {
t.Fatal(err)
}
chatFields := []string{"id", "chat_id", "title", "sid", "created_at", "updated_at"}
for _, field := range chatFields {
assert.Equal(t, true, tab.HasColumn(field))
}
// validate the assistant table
tab, err = conv.schema.GetTable(conv.getAssistantTable())
if err != nil {
t.Fatal(err)
}
assistantFields := []string{"id", "assistant_id", "type", "name", "avatar", "connector", "description", "options", "prompts", "flows", "files", "functions", "tags", "readonly", "permissions", "automated", "mentionable", "created_at", "updated_at"}
for _, field := range assistantFields {
assert.Equal(t, true, tab.HasColumn(field))
}
err = store.DeleteChat("test_user", "test_chat")
assert.Nil(t, err)
}
func TestXunSaveAndGetHistory(t *testing.T) {
@ -209,7 +199,7 @@ func TestXunSaveAndGetHistory(t *testing.T) {
t.Fatal(err)
}
conv, err := NewXun(Setting{
store, err := NewXun(Setting{
Connector: "default",
Table: "__unit_test_conversation",
TTL: 3600,
@ -217,14 +207,14 @@ func TestXunSaveAndGetHistory(t *testing.T) {
// save the history
cid := "123456"
err = conv.SaveHistory("123456", []map[string]interface{}{
err = store.SaveHistory("123456", []map[string]interface{}{
{"role": "user", "name": "user1", "content": "hello"},
{"role": "assistant", "name": "user1", "content": "Hello there, how"},
}, cid, nil)
assert.Nil(t, err)
// get the history
data, err := conv.GetHistory("123456", cid)
data, err := store.GetHistory("123456", cid)
if err != nil {
t.Fatal(err)
}
@ -247,7 +237,7 @@ func TestXunSaveAndGetHistoryWithCID(t *testing.T) {
t.Fatal(err)
}
conv, err := NewXun(Setting{
store, err := NewXun(Setting{
Connector: "default",
Table: "__unit_test_conversation",
TTL: 3600,
@ -260,11 +250,11 @@ func TestXunSaveAndGetHistoryWithCID(t *testing.T) {
{"role": "user", "name": "user1", "content": "hello"},
{"role": "assistant", "name": "assistant1", "content": "Hi! How can I help you?"},
}
err = conv.SaveHistory(sid, messages, cid, nil)
err = store.SaveHistory(sid, messages, cid, nil)
assert.Nil(t, err)
// get the history for specific cid
data, err := conv.GetHistory(sid, cid)
data, err := store.GetHistory(sid, cid)
if err != nil {
t.Fatal(err)
}
@ -275,25 +265,25 @@ func TestXunSaveAndGetHistoryWithCID(t *testing.T) {
moreMessages := []map[string]interface{}{
{"role": "user", "name": "user1", "content": "another message"},
}
err = conv.SaveHistory(sid, moreMessages, anotherCID, nil)
err = store.SaveHistory(sid, moreMessages, anotherCID, nil)
assert.Nil(t, err)
// get history for the first cid - should still be 2 messages
data, err = conv.GetHistory(sid, cid)
data, err = store.GetHistory(sid, cid)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, 2, len(data))
// get history for the second cid - should be 1 message
data, err = conv.GetHistory(sid, anotherCID)
data, err = store.GetHistory(sid, anotherCID)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, 1, len(data))
// get all history for the sid without specifying cid
allData, err := conv.GetHistory(sid, cid)
allData, err := store.GetHistory(sid, cid)
if err != nil {
t.Fatal(err)
}
@ -316,7 +306,7 @@ func TestXunGetChats(t *testing.T) {
t.Fatal(err)
}
conv, err := NewXun(Setting{
store, err := NewXun(Setting{
Connector: "default",
Table: "__unit_test_conversation",
})
@ -333,22 +323,15 @@ func TestXunGetChats(t *testing.T) {
// Create chats with different dates
for i := 0; i < 5; i++ {
chatID := fmt.Sprintf("chat_%d", i)
// First create the chat with a title
err = conv.newQueryChat().Insert(map[string]interface{}{
"chat_id": chatID,
"title": fmt.Sprintf("Test Chat %d", i),
"sid": sid,
"created_at": time.Now(),
})
if err != nil {
t.Fatal(err)
}
title := fmt.Sprintf("Test Chat %d", i)
// Then save the history
err = conv.SaveHistory(sid, messages, chatID, nil)
if err != nil {
t.Fatal(err)
}
// Save history first to create the chat
err = store.SaveHistory(sid, messages, chatID, nil)
assert.Nil(t, err)
// Update the chat title
err = store.UpdateChatTitle(sid, chatID, title)
assert.Nil(t, err)
}
// Test getting chats with default filter
@ -356,7 +339,7 @@ func TestXunGetChats(t *testing.T) {
PageSize: 10,
Order: "desc",
}
groups, err := conv.GetChats(sid, filter)
groups, err := store.GetChats(sid, filter)
if err != nil {
t.Fatal(err)
}
@ -365,7 +348,7 @@ func TestXunGetChats(t *testing.T) {
// Test with keywords
filter.Keywords = "test"
groups, err = conv.GetChats(sid, filter)
groups, err = store.GetChats(sid, filter)
if err != nil {
t.Fatal(err)
}
@ -379,7 +362,7 @@ func TestXunDeleteChat(t *testing.T) {
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
conv, err := NewXun(Setting{
store, err := NewXun(Setting{
Connector: "default",
Table: "__unit_test_conversation",
})
@ -395,20 +378,20 @@ func TestXunDeleteChat(t *testing.T) {
}
// Save the chat and history
err = conv.SaveHistory(sid, messages, cid, nil)
err = store.SaveHistory(sid, messages, cid, nil)
assert.Nil(t, err)
// Verify chat exists
chat, err := conv.GetChat(sid, cid)
chat, err := store.GetChat(sid, cid)
assert.Nil(t, err)
assert.NotNil(t, chat)
// Delete the chat
err = conv.DeleteChat(sid, cid)
err = store.DeleteChat(sid, cid)
assert.Nil(t, err)
// Verify chat is deleted
chat, err = conv.GetChat(sid, cid)
chat, err = store.GetChat(sid, cid)
assert.Nil(t, err)
assert.Equal(t, (*ChatInfo)(nil), chat)
}
@ -419,7 +402,7 @@ func TestXunDeleteAllChats(t *testing.T) {
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_history")
defer capsule.Schema().DropTableIfExists("__unit_test_conversation_chat")
conv, err := NewXun(Setting{
store, err := NewXun(Setting{
Connector: "default",
Table: "__unit_test_conversation",
})
@ -436,21 +419,21 @@ func TestXunDeleteAllChats(t *testing.T) {
// Save multiple chats
for i := 0; i < 3; i++ {
cid := fmt.Sprintf("test_chat_%d", i)
err = conv.SaveHistory(sid, messages, cid, nil)
err = store.SaveHistory(sid, messages, cid, nil)
assert.Nil(t, err)
}
// Verify chats exist
response, err := conv.GetChats(sid, ChatFilter{})
response, err := store.GetChats(sid, ChatFilter{})
assert.Nil(t, err)
assert.Greater(t, response.Total, int64(0))
// Delete all chats
err = conv.DeleteAllChats(sid)
err = store.DeleteAllChats(sid)
assert.Nil(t, err)
// Verify all chats are deleted
response, err = conv.GetChats(sid, ChatFilter{})
response, err = store.GetChats(sid, ChatFilter{})
assert.Nil(t, err)
assert.Equal(t, int64(0), response.Total)
}
@ -470,7 +453,7 @@ func TestXunAssistantCRUD(t *testing.T) {
// Add a small delay to ensure table is created
time.Sleep(100 * time.Millisecond)
conv, err := NewXun(Setting{
store, err := NewXun(Setting{
Connector: "default",
Table: "__unit_test_conversation",
})
@ -495,7 +478,7 @@ func TestXunAssistantCRUD(t *testing.T) {
}
// Test SaveAssistant (Create) with string JSON
v, err := conv.SaveAssistant(assistant)
v, err := store.SaveAssistant(assistant)
assert.Nil(t, err)
assistantID := v.(string)
assert.NotEmpty(t, assistantID)
@ -519,7 +502,7 @@ func TestXunAssistantCRUD(t *testing.T) {
}
// Test SaveAssistant (Create) with native types
v, err = conv.SaveAssistant(assistant2)
v, err = store.SaveAssistant(assistant2)
assert.Nil(t, err)
assistant2ID := v.(string)
assert.NotEmpty(t, assistant2ID)
@ -542,13 +525,13 @@ func TestXunAssistantCRUD(t *testing.T) {
}
// Test SaveAssistant (Create) with nil fields
v, err = conv.SaveAssistant(assistant3)
v, err = store.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{})
resp, err := store.GetAssistants(AssistantFilter{})
assert.Nil(t, err)
assert.Equal(t, 3, len(resp.Data))
@ -614,11 +597,11 @@ func TestXunAssistantCRUD(t *testing.T) {
// Test updating with mixed JSON formats
assistant2["assistant_id"] = assistant2ID
_, err = conv.SaveAssistant(assistant2)
_, err = store.SaveAssistant(assistant2)
assert.Nil(t, err)
// Verify update
resp, err = conv.GetAssistants(AssistantFilter{})
resp, err = store.GetAssistants(AssistantFilter{})
assert.Nil(t, err)
for _, item := range resp.Data {
if item["assistant_id"].(string) == assistant2ID {
@ -630,14 +613,14 @@ func TestXunAssistantCRUD(t *testing.T) {
}
// Test DeleteAssistant
err = conv.DeleteAssistant(assistantID)
err = store.DeleteAssistant(assistantID)
assert.Nil(t, err)
err = conv.DeleteAssistant(assistant2ID)
err = store.DeleteAssistant(assistant2ID)
assert.Nil(t, err)
err = conv.DeleteAssistant(assistant3ID)
err = store.DeleteAssistant(assistant3ID)
assert.Nil(t, err)
resp, err = conv.GetAssistants(AssistantFilter{})
resp, err = store.GetAssistants(AssistantFilter{})
assert.Nil(t, err)
assert.Equal(t, 0, len(resp.Data))
}
@ -658,7 +641,7 @@ func TestXunAssistantPagination(t *testing.T) {
// Add a small delay to ensure table is created
time.Sleep(100 * time.Millisecond)
conv, err := NewXun(Setting{
store, err := NewXun(Setting{
Connector: "default",
Table: "__unit_test_conversation",
})
@ -692,12 +675,12 @@ func TestXunAssistantPagination(t *testing.T) {
"mentionable": mentionable,
"automated": automated,
}
_, err = conv.SaveAssistant(assistant)
_, err = store.SaveAssistant(assistant)
assert.Nil(t, err)
}
// Test first page
resp, err := conv.GetAssistants(AssistantFilter{
resp, err := store.GetAssistants(AssistantFilter{
Page: 1,
PageSize: 10,
})
@ -709,7 +692,7 @@ func TestXunAssistantPagination(t *testing.T) {
assert.Equal(t, 0, resp.Prev)
// Test second page
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
Page: 2,
PageSize: 10,
})
@ -719,7 +702,7 @@ func TestXunAssistantPagination(t *testing.T) {
assert.Equal(t, 1, resp.Prev)
// Test last page
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
Page: 3,
PageSize: 10,
})
@ -729,7 +712,7 @@ func TestXunAssistantPagination(t *testing.T) {
assert.Equal(t, 2, resp.Prev)
// Test filtering with tags
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
Tags: []string{"tag0"},
Page: 1,
PageSize: 10,
@ -738,7 +721,7 @@ func TestXunAssistantPagination(t *testing.T) {
assert.Equal(t, 5, len(resp.Data))
// Test filtering with keywords
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
Keywords: "Assistant 1",
Page: 1,
PageSize: 10,
@ -747,7 +730,7 @@ func TestXunAssistantPagination(t *testing.T) {
assert.Greater(t, len(resp.Data), 0)
// Test filtering with connector
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
Connector: "connector0",
Page: 1,
PageSize: 10,
@ -757,7 +740,7 @@ func TestXunAssistantPagination(t *testing.T) {
// Test filtering with mentionable
mentionableTrue := true
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
Mentionable: &mentionableTrue,
Page: 1,
PageSize: 10,
@ -767,7 +750,7 @@ func TestXunAssistantPagination(t *testing.T) {
// Test filtering with automated
automatedTrue := true
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
Automated: &automatedTrue,
Page: 1,
PageSize: 10,
@ -780,7 +763,7 @@ func TestXunAssistantPagination(t *testing.T) {
firstAssistantID := resp.Data[0]["assistant_id"].(string)
// Test exact match with assistant_id
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
AssistantID: firstAssistantID,
Page: 1,
PageSize: 10,
@ -790,7 +773,7 @@ func TestXunAssistantPagination(t *testing.T) {
assert.Equal(t, firstAssistantID, resp.Data[0]["assistant_id"])
// Test assistant_id with other filters
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
AssistantID: firstAssistantID,
Select: []string{"name", "assistant_id", "description"},
Page: 1,
@ -807,7 +790,7 @@ func TestXunAssistantPagination(t *testing.T) {
assert.NotContains(t, resp.Data[0], "options")
// Test non-existent assistant_id
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
AssistantID: "non-existent-id",
Page: 1,
PageSize: 10,
@ -816,7 +799,7 @@ func TestXunAssistantPagination(t *testing.T) {
assert.Equal(t, 0, len(resp.Data))
// Test combined filters
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
Tags: []string{"tag0"},
Keywords: "Assistant",
Connector: "connector0",
@ -828,7 +811,7 @@ func TestXunAssistantPagination(t *testing.T) {
assert.Nil(t, err)
// Test filtering with select fields
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
Select: []string{"name", "description", "tags"},
Page: 1,
PageSize: 10,
@ -851,7 +834,7 @@ func TestXunAssistantPagination(t *testing.T) {
}
// Test filtering with select fields and other filters combined
resp, err = conv.GetAssistants(AssistantFilter{
resp, err = store.GetAssistants(AssistantFilter{
Tags: []string{"tag0"},
Keywords: "Assistant",
Select: []string{"name", "tags"},

View file

@ -5,30 +5,30 @@ import (
"github.com/gin-gonic/gin"
"github.com/yaoapp/yao/neo/assistant"
"github.com/yaoapp/yao/neo/conversation"
"github.com/yaoapp/yao/neo/store"
)
// DSL AI assistant
type DSL struct {
ID string `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Use string `json:"use,omitempty" yaml:"use,omitempty"` // Which assistant to use default
Guard string `json:"guard,omitempty" yaml:"guard,omitempty"`
Connector string `json:"connector" yaml:"connector"`
ConversationSetting conversation.Setting `json:"conversation" yaml:"conversation"`
Option map[string]interface{} `json:"option" yaml:"option"`
Prepare string `json:"prepare,omitempty" yaml:"prepare,omitempty"`
Create string `json:"create,omitempty" yaml:"create,omitempty"`
Write string `json:"write,omitempty" yaml:"write,omitempty"`
AssistantListHook string `json:"assistants,omitempty" yaml:"assistants,omitempty"` // Get the assistant list from the hook
MentionHook string `json:"mentions,omitempty"` // Get the mention list from the hook
Prompts []assistant.Prompt `json:"prompts,omitempty" yaml:"prompts,omitempty"`
Allows []string `json:"allows,omitempty" yaml:"allows,omitempty"`
Assistant assistant.API `json:"-" yaml:"-"` // The default assistant
Conversation conversation.Conversation `json:"-" yaml:"-"`
GuardHandlers []gin.HandlerFunc `json:"-" yaml:"-"`
AssistantList []assistant.Assistant `json:"-" yaml:"-"`
AssistantMaps map[string]assistant.Assistant `json:"-" yaml:"-"`
ID string `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Use string `json:"use,omitempty" yaml:"use,omitempty"` // Which assistant to use default
Guard string `json:"guard,omitempty" yaml:"guard,omitempty"`
Connector string `json:"connector" yaml:"connector"`
StoreSetting store.Setting `json:"store" yaml:"store"`
Option map[string]interface{} `json:"option" yaml:"option"`
Prepare string `json:"prepare,omitempty" yaml:"prepare,omitempty"`
Create string `json:"create,omitempty" yaml:"create,omitempty"`
Write string `json:"write,omitempty" yaml:"write,omitempty"`
AssistantListHook string `json:"assistants,omitempty" yaml:"assistants,omitempty"` // Get the assistant list from the hook
MentionHook string `json:"mentions,omitempty"` // Get the mention list from the hook
Prompts []assistant.Prompt `json:"prompts,omitempty" yaml:"prompts,omitempty"`
Allows []string `json:"allows,omitempty" yaml:"allows,omitempty"`
Assistant assistant.API `json:"-" yaml:"-"` // The default assistant
Store store.Store `json:"-" yaml:"-"`
GuardHandlers []gin.HandlerFunc `json:"-" yaml:"-"`
AssistantList []assistant.Assistant `json:"-" yaml:"-"`
AssistantMaps map[string]assistant.Assistant `json:"-" yaml:"-"`
}
// Mention list