Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Neo API assistant message handling and content processing #826

Merged
merged 1 commit into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions neo/assistant/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
}
}

Expand Down Expand Up @@ -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)

Expand All @@ -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")
}
Expand Down
14 changes: 12 additions & 2 deletions neo/assistant/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
90 changes: 83 additions & 7 deletions neo/message/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, "}") {
Expand All @@ -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"`):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 {
Expand Down
21 changes: 18 additions & 3 deletions neo/neo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading