From d43b7b89277610a5f3c5a8ca631e3deab56155af Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 18 Jan 2025 10:19:17 +0800 Subject: [PATCH 1/2] Enhance Neo API assistant message storage and structure - Removed unused utility imports and streamlined the saveChatHistory method to improve message storage efficiency. - Updated the Message struct to include new fields for assistant identification and mentions, enhancing message context and traceability. - Modified the history table structure to accommodate new assistant-related fields and mentions, ensuring comprehensive data storage. - Improved the SaveHistory method to process and store mentions alongside messages, providing better context for user interactions. These changes enhance the robustness and maintainability of the Neo API, paving the way for improved message handling and assistant functionalities. --- neo/assistant/api.go | 33 +++++++++++++------- neo/message/message.go | 29 +++++++++++------ neo/store/xun.go | 70 ++++++++++++++++++++++++++++++------------ 3 files changed, 93 insertions(+), 39 deletions(-) diff --git a/neo/assistant/api.go b/neo/assistant/api.go index 6ce85c89..6447f82c 100644 --- a/neo/assistant/api.go +++ b/neo/assistant/api.go @@ -10,7 +10,6 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/fs" "github.com/yaoapp/gou/process" - "github.com/yaoapp/kun/utils" chatctx "github.com/yaoapp/yao/neo/context" chatMessage "github.com/yaoapp/yao/neo/message" ) @@ -319,15 +318,29 @@ func (ast *Assistant) streamChat( // saveChatHistory saves the chat history if storage is available func (ast *Assistant) saveChatHistory(ctx chatctx.Context, messages []chatMessage.Message, content *chatMessage.Content) { if len(content.Bytes) > 0 && ctx.Sid != "" && len(messages) > 0 { - storage.SaveHistory( - ctx.Sid, - []map[string]interface{}{ - {"role": "user", "content": messages[len(messages)-1].Content(), "name": ctx.Sid}, - {"role": "assistant", "content": content.String(), "name": ctx.Sid}, + userMessage := messages[len(messages)-1] + data := []map[string]interface{}{ + { + "role": "user", + "content": userMessage.Content(), + "name": ctx.Sid, }, - ctx.ChatID, - nil, - ) + { + "role": "assistant", + "content": content.String(), + "name": ctx.Sid, + "assistant_id": ast.ID, + "assistant_name": ast.Name, + "assistant_avatar": ast.Avatar, + }, + } + + // Add mentions + if userMessage.Mentions != nil { + data[0]["mentions"] = userMessage.Mentions + } + + storage.SaveHistory(ctx.Sid, data, ctx.ChatID, ctx.Map()) } } @@ -521,8 +534,6 @@ func (ast *Assistant) withAttachments(ctx context.Context, msg *chatMessage.Mess }, }) } - - utils.Dump(contents) return contents, nil } diff --git a/neo/message/message.go b/neo/message/message.go index e9ce0cea..6f559bc9 100644 --- a/neo/message/message.go +++ b/neo/message/message.go @@ -16,15 +16,26 @@ import ( // Message the message type Message struct { - Text string `json:"text,omitempty"` // text content - Type string `json:"type,omitempty"` // error, text, plan, table, form, page, file, video, audio, image, markdown, json ... - Props map[string]interface{} `json:"props,omitempty"` // props for the types - IsDone bool `json:"done,omitempty"` - Actions []Action `json:"actions,omitempty"` // Conversation Actions for frontend - Attachments []Attachment `json:"attachments,omitempty"` // File attachments - Role string `json:"role,omitempty"` // user, assistant, system ... - Name string `json:"name,omitempty"` // name for the message - Data map[string]interface{} `json:"-"` + Text string `json:"text,omitempty"` // text content + Type string `json:"type,omitempty"` // error, text, plan, table, form, page, file, video, audio, image, markdown, json ... + Props map[string]interface{} `json:"props,omitempty"` // props for the types + IsDone bool `json:"done,omitempty"` + Actions []Action `json:"actions,omitempty"` // Conversation Actions for frontend + Attachments []Attachment `json:"attachments,omitempty"` // File attachments + Role string `json:"role,omitempty"` // user, assistant, system ... + Name string `json:"name,omitempty"` // name for the message + AssistantID string `json:"assistant_id,omitempty"` // assistant_id (for assistant role = assistant ) + AssistantName string `json:"assistant_name,omitempty"` // assistant_name (for assistant role = assistant ) + AssistantAvatar string `json:"assistant_avatar,omitempty"` // assistant_avatar (for assistant role = assistant ) + Mentions []Mention `json:"menions,omitempty"` // Mentions for the message ( for user role = user ) + Data map[string]interface{} `json:"-"` +} + +// Mention represents a mention +type Mention struct { + ID string `json:"assistant_id"` // assistant_id + Name string `json:"name"` // name + Avatar string `json:"avatar,omitempty"` // avatar } // Attachment represents a file attachment diff --git a/neo/store/xun.go b/neo/store/xun.go index aadb4583..1bf8a94b 100644 --- a/neo/store/xun.go +++ b/neo/store/xun.go @@ -141,6 +141,10 @@ func (conv *Xun) initHistoryTable() error { table.String("name", 200).Null().Index() table.Text("content").Null() table.JSON("context").Null() + table.String("assistant_id", 200).Null().Index() + table.String("assistant_name", 200).Null() + table.String("assistant_avatar", 200).Null() + table.JSON("mentions").Null() table.TimestampTz("created_at").SetDefaultRaw("NOW()").Index() table.TimestampTz("updated_at").Null().Index() table.TimestampTz("expired_at").Null().Index() @@ -158,7 +162,7 @@ func (conv *Xun) initHistoryTable() error { return err } - fields := []string{"id", "sid", "cid", "uid", "role", "name", "content", "context", "created_at", "updated_at", "expired_at"} + fields := []string{"id", "sid", "cid", "uid", "role", "name", "content", "context", "assistant_id", "assistant_name", "assistant_avatar", "mentions", "created_at", "updated_at", "expired_at"} for _, field := range fields { if !tab.HasColumn(field) { return fmt.Errorf("%s is required", field) @@ -450,7 +454,7 @@ func (conv *Xun) GetHistory(sid string, cid string) ([]map[string]interface{}, e } qb := conv.newQuery(). - Select("role", "name", "content", "context", "uid", "created_at", "updated_at"). + Select("role", "name", "content", "context", "assistant_id", "assistant_name", "assistant_avatar", "mentions", "uid", "created_at", "updated_at"). Where("sid", userID). Where("cid", cid). OrderBy("id", "desc") @@ -472,13 +476,17 @@ func (conv *Xun) GetHistory(sid string, cid string) ([]map[string]interface{}, e res := []map[string]interface{}{} for _, row := range rows { message := map[string]interface{}{ - "role": row.Get("role"), - "name": row.Get("name"), - "content": row.Get("content"), - "context": row.Get("context"), - "uid": row.Get("uid"), - "created_at": row.Get("created_at"), - "updated_at": row.Get("updated_at"), + "role": row.Get("role"), + "name": row.Get("name"), + "content": row.Get("content"), + "context": row.Get("context"), + "assistant_id": row.Get("assistant_id"), + "assistant_name": row.Get("assistant_name"), + "assistant_avatar": row.Get("assistant_avatar"), + "mentions": row.Get("mentions"), + "uid": row.Get("uid"), + "created_at": row.Get("created_at"), + "updated_at": row.Get("updated_at"), } res = append([]map[string]interface{}{message}, res...) } @@ -551,23 +559,47 @@ func (conv *Xun) SaveHistory(sid string, messages []map[string]interface{}, cid } } + // Process mentions if present + var mentionsRaw interface{} = nil + if mentions, ok := message["mentions"].([]interface{}); ok && len(mentions) > 0 { + mentionsRaw, err = jsoniter.MarshalToString(mentions) + if err != nil { + return err + } + } + value := map[string]interface{}{ - "role": role, - "name": "", - "content": content, - "sid": userID, - "cid": cid, - "uid": userID, - "context": contextRaw, - "created_at": now, - "updated_at": nil, - "expired_at": expiredAt, + "role": role, + "name": "", + "content": content, + "sid": userID, + "cid": cid, + "uid": userID, + "context": contextRaw, + "mentions": mentionsRaw, + "assistant_id": nil, + "assistant_name": nil, + "assistant_avatar": nil, + "created_at": now, + "updated_at": nil, + "expired_at": expiredAt, } if name, ok := message["name"].(string); ok { value["name"] = name } + // Add assistant fields if present + if assistantID, ok := message["assistant_id"].(string); ok { + value["assistant_id"] = assistantID + } + if assistantName, ok := message["assistant_name"].(string); ok { + value["assistant_name"] = assistantName + } + if assistantAvatar, ok := message["assistant_avatar"].(string); ok { + value["assistant_avatar"] = assistantAvatar + } + values = append(values, value) } From 228e8cf482b31937a81d2a6339efd8e06f949e3d Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 18 Jan 2025 11:20:55 +0800 Subject: [PATCH 2/2] Enhance Neo API assistant message handling and response structure - Added nil checks for response objects in the Execute method to prevent potential nil pointer dereferences. - Updated the streamChat method to include assistant identification details (ID, name, avatar) in the message structure, improving context for messages. - Introduced a new field in the Message struct to mark messages as new, enhancing message tracking capabilities. These changes improve the robustness and maintainability of the Neo API, paving the way for better message handling and assistant functionalities. --- neo/assistant/api.go | 28 +++++++++++++++++----------- neo/message/message.go | 26 ++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/neo/assistant/api.go b/neo/assistant/api.go index 6447f82c..41f2b0d4 100644 --- a/neo/assistant/api.go +++ b/neo/assistant/api.go @@ -60,7 +60,7 @@ func (ast *Assistant) Execute(c *gin.Context, ctx chatctx.Context, input string, } // Switch to the new assistant if necessary - if res.AssistantID != ctx.AssistantID { + if res != nil && res.AssistantID != ctx.AssistantID { newAst, err := Get(res.AssistantID) if err != nil { return err @@ -69,17 +69,17 @@ func (ast *Assistant) Execute(c *gin.Context, ctx chatctx.Context, input string, } // Handle next action - if res.Next != nil { + if res != nil && res.Next != nil { return res.Next.Execute(c, ctx) } // Update options if provided - if res.Options != nil { + if res != nil && res.Options != nil { options = res.Options } // messages - if res.Input != nil { + if res != nil && res.Input != nil { messages = res.Input } @@ -263,17 +263,20 @@ func (ast *Assistant) streamChat( chatMessage.New(). Map(map[string]interface{}{ - "text": value, - "done": msg.IsDone, + "assistant_id": ast.ID, + "assistant_name": ast.Name, + "assistant_avatar": ast.Avatar, + "text": value, + "done": msg.IsDone, }). Write(c.Writer) } // Complete the stream if msg.IsDone { - // if value == "" { - // msg.Write(c.Writer) - // } + if value == "" { + msg.Write(c.Writer) + } // Call HookDone content.SetStatus(chatMessage.ContentStatusDone) @@ -300,8 +303,11 @@ func (ast *Assistant) streamChat( } else if value != "" { chatMessage.New(). Map(map[string]interface{}{ - "text": value, - "done": true, + "assistant_id": ast.ID, + "assistant_name": ast.Name, + "assistant_avatar": ast.Avatar, + "text": value, + "done": true, }). Write(c.Writer) } diff --git a/neo/message/message.go b/neo/message/message.go index 6f559bc9..39551102 100644 --- a/neo/message/message.go +++ b/neo/message/message.go @@ -16,10 +16,11 @@ import ( // Message the message type Message struct { - Text string `json:"text,omitempty"` // text content - Type string `json:"type,omitempty"` // error, text, plan, table, form, page, file, video, audio, image, markdown, json ... - Props map[string]interface{} `json:"props,omitempty"` // props for the types - IsDone bool `json:"done,omitempty"` + Text string `json:"text,omitempty"` // text content + Type string `json:"type,omitempty"` // error, text, plan, table, form, page, file, video, audio, image, markdown, json ... + Props map[string]interface{} `json:"props,omitempty"` // props for the types + IsDone bool `json:"done,omitempty"` // Mark as a done message from neo + IsNew bool `json:"is_new,omitempty"` // Mark as a new message from neo Actions []Action `json:"actions,omitempty"` // Conversation Actions for frontend Attachments []Attachment `json:"attachments,omitempty"` // File attachments Role string `json:"role,omitempty"` // user, assistant, system ... @@ -244,6 +245,23 @@ func (m *Message) Map(msg map[string]interface{}) *Message { if done, ok := msg["done"].(bool); ok { m.IsDone = done } + + if isNew, ok := msg["is_new"].(bool); ok { + m.IsNew = isNew + } + + if assistantID, ok := msg["assistant_id"].(string); ok { + m.AssistantID = assistantID + } + + if assistantName, ok := msg["assistant_name"].(string); ok { + m.AssistantName = assistantName + } + + if assistantAvatar, ok := msg["assistant_avatar"].(string); ok { + m.AssistantAvatar = assistantAvatar + } + if actions, ok := msg["actions"].([]interface{}); ok { for _, action := range actions { if v, ok := action.(map[string]interface{}); ok {