From 1404c549a78a75d0244d1904ad4d4b1b82342893 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 22 Jan 2025 16:09:01 +0800 Subject: [PATCH] Refactor Neo API assistant message handling and content processing - Enhanced message generation by introducing NewHistory and NewContent methods for improved handling of message content and history. - Updated the withHistory method to utilize the new NewHistory method, streamlining the addition of historical messages. - Improved the call method to conditionally save message history based on input parameters, enhancing flexibility in message management. - Refactored the String method in the Message struct to better represent message types and properties, improving serialization consistency. These changes enhance the maintainability and robustness of the Neo API assistant, paving the way for improved message handling and content processing. --- neo/assistant/api.go | 9 ++++- neo/assistant/hooks.go | 14 ++++++- neo/message/message.go | 90 ++++++++++++++++++++++++++++++++++++++---- neo/neo.go | 21 ++++++++-- 4 files changed, 120 insertions(+), 14 deletions(-) diff --git a/neo/assistant/api.go b/neo/assistant/api.go index e6fa029854..142e86eef7 100644 --- a/neo/assistant/api.go +++ b/neo/assistant/api.go @@ -387,7 +387,11 @@ func (ast *Assistant) withHistory(ctx chatctx.Context, input string) ([]chatMess // Add history messages for _, h := range history { - messages = append(messages, *chatMessage.New().Map(h)) + msgs, err := chatMessage.NewHistory(h) + if err != nil { + return nil, err + } + messages = append(messages, msgs...) } } @@ -416,6 +420,7 @@ func (ast *Assistant) Chat(ctx context.Context, messages []chatMessage.Message, } func (ast *Assistant) requestMessages(ctx context.Context, messages []chatMessage.Message) ([]map[string]interface{}, error) { + newMessages := []map[string]interface{}{} length := len(messages) @@ -425,7 +430,7 @@ func (ast *Assistant) requestMessages(ctx context.Context, messages []chatMessag return nil, fmt.Errorf("role must be string") } - content := message.Text + content := message.String() if content == "" { return nil, fmt.Errorf("content must be string") } diff --git a/neo/assistant/hooks.go b/neo/assistant/hooks.go index dcfa2c6714..fa8171cdef 100644 --- a/neo/assistant/hooks.go +++ b/neo/assistant/hooks.go @@ -276,6 +276,12 @@ func (ast *Assistant) call(ctx context.Context, method string, c *gin.Context, c return bridge.JsException(info.Context(), err.Error()) } + // Save history by default + saveHistory := true + if len(args) > 1 && args[1].IsBoolean() { + saveHistory = args[1].Boolean() + } + switch v := input.(type) { case string: // Check if the message is json @@ -285,13 +291,17 @@ func (ast *Assistant) call(ctx context.Context, method string, c *gin.Context, c } // Append the message to the contents - msg.AppendTo(contents) + if saveHistory { + msg.AppendTo(contents) + } msg.Write(c.Writer) return nil case map[string]interface{}: msg := message.New().Map(v) - msg.AppendTo(contents) + if saveHistory { + msg.AppendTo(contents) + } msg.Write(c.Writer) return nil diff --git a/neo/message/message.go b/neo/message/message.go index a659d7a6a8..a820f65f04 100644 --- a/neo/message/message.go +++ b/neo/message/message.go @@ -65,6 +65,73 @@ func New() *Message { return &Message{Actions: []Action{}, Props: map[string]interface{}{}} } +// NewHistory create a new message from history +func NewHistory(history map[string]interface{}) ([]Message, error) { + if history == nil { + return []Message{}, nil + } + + var copy map[string]interface{} = map[string]interface{}{} + for key, value := range history { + if key != "content" { + copy[key] = value + } + } + + globalMessage := New().Map(copy) + messages := []Message{} + if content, ok := history["content"].(string); ok { + if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") { + var msg Message = *globalMessage + if err := jsoniter.UnmarshalFromString(content, &msg); err != nil { + return nil, err + } + messages = append(messages, msg) + } else if strings.HasPrefix(content, "[") && strings.HasSuffix(content, "]") { + var msgs []Message + if err := jsoniter.UnmarshalFromString(content, &msgs); err != nil { + return nil, err + } + for _, msg := range msgs { + msg.AssistantID = globalMessage.AssistantID + msg.AssistantName = globalMessage.AssistantName + msg.AssistantAvatar = globalMessage.AssistantAvatar + msg.Role = globalMessage.Role + msg.Name = globalMessage.Name + msg.Mentions = globalMessage.Mentions + messages = append(messages, msg) + } + } else { + messages = append(messages, Message{Text: content}) + } + } + + return messages, nil +} + +// NewContent create a new message from content +func NewContent(content string) ([]Message, error) { + messages := []Message{} + if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") { + var msg Message + if err := jsoniter.UnmarshalFromString(content, &msg); err != nil { + return nil, err + } + messages = append(messages, msg) + } else if strings.HasPrefix(content, "[") && strings.HasSuffix(content, "]") { + var msgs []Message + if err := jsoniter.UnmarshalFromString(content, &msgs); err != nil { + return nil, err + } + for _, msg := range msgs { + messages = append(messages, msg) + } + } else { + messages = append(messages, Message{Text: content}) + } + return messages, nil +} + // NewString create a new message from string func NewString(content string) (*Message, error) { if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") { @@ -86,10 +153,6 @@ func NewOpenAI(data []byte) *Message { msg := New() text := string(data) data = []byte(strings.TrimPrefix(text, "data: ")) - // fmt.Println("--------------------------------") - // fmt.Println(string(data)) - // fmt.Println("--------------------------------") - switch { case strings.Contains(text, `"delta":{`) && strings.Contains(text, `"tool_calls"`): @@ -138,10 +201,18 @@ func NewOpenAI(data []byte) *Message { // String returns the string representation func (m *Message) String() string { - if m.Text != "" { + typ := m.Type + if typ == "" { + typ = "text" + } + + switch typ { + case "text": return m.Text + default: + raw, _ := jsoniter.MarshalToString(map[string]interface{}{"type": m.Type, "props": m.Props}) + return raw } - return "" } // SetText set the text @@ -222,7 +293,7 @@ func (m *Message) AppendTo(contents *Contents) *Message { contents.AppendFunction([]byte(m.Text)) return m - case "loading": + case "loading", "error": // Ignore loading and error messages return m default: @@ -307,6 +378,11 @@ func (m *Message) Map(msg map[string]interface{}) *Message { if assistantID, ok := msg["assistant_id"].(string); ok { m.AssistantID = assistantID + + // Set name + if m.Role == "assistant" { + m.Name = m.AssistantID + } } if assistantName, ok := msg["assistant_name"].(string); ok { diff --git a/neo/neo.go b/neo/neo.go index 89cae3361b..5d64ffa639 100644 --- a/neo/neo.go +++ b/neo/neo.go @@ -104,10 +104,25 @@ func (neo *DSL) GenerateWithAI(ctx chatctx.Context, input string, messageType st // Chat with AI in background go func() { - msgList := make([]message.Message, len(messages)) - for i, msg := range messages { - msgList[i] = *message.New().Map(msg) + msgList := []message.Message{} + for _, vv := range messages { + msg := message.New().Map(vv) + if content, ok := vv["content"].(string); ok { + msgs, err := message.NewContent(content) + if err == nil { + for _, v := range msgs { + v.AssistantID = msg.AssistantID + v.AssistantName = msg.AssistantName + v.AssistantAvatar = msg.AssistantAvatar + v.Role = msg.Role + v.Name = msg.Name + v.Mentions = msg.Mentions + msgList = append(msgList, v) + } + } + } } + err := ast.Chat(c.Request.Context(), msgList, neo.Option, func(data []byte) int { select { case <-clientBreak: