Merge pull request #820 from trheyi/main
Enhance Neo API assistant message storage and structure
This commit is contained in:
commit
2726753f54
3 changed files with 128 additions and 50 deletions
|
|
@ -10,7 +10,6 @@ import (
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/yaoapp/gou/fs"
|
"github.com/yaoapp/gou/fs"
|
||||||
"github.com/yaoapp/gou/process"
|
"github.com/yaoapp/gou/process"
|
||||||
"github.com/yaoapp/kun/utils"
|
|
||||||
chatctx "github.com/yaoapp/yao/neo/context"
|
chatctx "github.com/yaoapp/yao/neo/context"
|
||||||
chatMessage "github.com/yaoapp/yao/neo/message"
|
chatMessage "github.com/yaoapp/yao/neo/message"
|
||||||
)
|
)
|
||||||
|
|
@ -61,7 +60,7 @@ func (ast *Assistant) Execute(c *gin.Context, ctx chatctx.Context, input string,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch to the new assistant if necessary
|
// Switch to the new assistant if necessary
|
||||||
if res.AssistantID != ctx.AssistantID {
|
if res != nil && res.AssistantID != ctx.AssistantID {
|
||||||
newAst, err := Get(res.AssistantID)
|
newAst, err := Get(res.AssistantID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -70,17 +69,17 @@ func (ast *Assistant) Execute(c *gin.Context, ctx chatctx.Context, input string,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle next action
|
// Handle next action
|
||||||
if res.Next != nil {
|
if res != nil && res.Next != nil {
|
||||||
return res.Next.Execute(c, ctx)
|
return res.Next.Execute(c, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update options if provided
|
// Update options if provided
|
||||||
if res.Options != nil {
|
if res != nil && res.Options != nil {
|
||||||
options = res.Options
|
options = res.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
// messages
|
// messages
|
||||||
if res.Input != nil {
|
if res != nil && res.Input != nil {
|
||||||
messages = res.Input
|
messages = res.Input
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,17 +263,20 @@ func (ast *Assistant) streamChat(
|
||||||
|
|
||||||
chatMessage.New().
|
chatMessage.New().
|
||||||
Map(map[string]interface{}{
|
Map(map[string]interface{}{
|
||||||
"text": value,
|
"assistant_id": ast.ID,
|
||||||
"done": msg.IsDone,
|
"assistant_name": ast.Name,
|
||||||
|
"assistant_avatar": ast.Avatar,
|
||||||
|
"text": value,
|
||||||
|
"done": msg.IsDone,
|
||||||
}).
|
}).
|
||||||
Write(c.Writer)
|
Write(c.Writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete the stream
|
// Complete the stream
|
||||||
if msg.IsDone {
|
if msg.IsDone {
|
||||||
// if value == "" {
|
if value == "" {
|
||||||
// msg.Write(c.Writer)
|
msg.Write(c.Writer)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Call HookDone
|
// Call HookDone
|
||||||
content.SetStatus(chatMessage.ContentStatusDone)
|
content.SetStatus(chatMessage.ContentStatusDone)
|
||||||
|
|
@ -301,8 +303,11 @@ func (ast *Assistant) streamChat(
|
||||||
} else if value != "" {
|
} else if value != "" {
|
||||||
chatMessage.New().
|
chatMessage.New().
|
||||||
Map(map[string]interface{}{
|
Map(map[string]interface{}{
|
||||||
"text": value,
|
"assistant_id": ast.ID,
|
||||||
"done": true,
|
"assistant_name": ast.Name,
|
||||||
|
"assistant_avatar": ast.Avatar,
|
||||||
|
"text": value,
|
||||||
|
"done": true,
|
||||||
}).
|
}).
|
||||||
Write(c.Writer)
|
Write(c.Writer)
|
||||||
}
|
}
|
||||||
|
|
@ -319,15 +324,29 @@ func (ast *Assistant) streamChat(
|
||||||
// saveChatHistory saves the chat history if storage is available
|
// saveChatHistory saves the chat history if storage is available
|
||||||
func (ast *Assistant) saveChatHistory(ctx chatctx.Context, messages []chatMessage.Message, content *chatMessage.Content) {
|
func (ast *Assistant) saveChatHistory(ctx chatctx.Context, messages []chatMessage.Message, content *chatMessage.Content) {
|
||||||
if len(content.Bytes) > 0 && ctx.Sid != "" && len(messages) > 0 {
|
if len(content.Bytes) > 0 && ctx.Sid != "" && len(messages) > 0 {
|
||||||
storage.SaveHistory(
|
userMessage := messages[len(messages)-1]
|
||||||
ctx.Sid,
|
data := []map[string]interface{}{
|
||||||
[]map[string]interface{}{
|
{
|
||||||
{"role": "user", "content": messages[len(messages)-1].Content(), "name": ctx.Sid},
|
"role": "user",
|
||||||
{"role": "assistant", "content": content.String(), "name": ctx.Sid},
|
"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 +540,6 @@ func (ast *Assistant) withAttachments(ctx context.Context, msg *chatMessage.Mess
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Dump(contents)
|
|
||||||
return contents, nil
|
return contents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,27 @@ import (
|
||||||
|
|
||||||
// Message the message
|
// Message the message
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Text string `json:"text,omitempty"` // text content
|
Text string `json:"text,omitempty"` // text content
|
||||||
Type string `json:"type,omitempty"` // error, text, plan, table, form, page, file, video, audio, image, markdown, json ...
|
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
|
Props map[string]interface{} `json:"props,omitempty"` // props for the types
|
||||||
IsDone bool `json:"done,omitempty"`
|
IsDone bool `json:"done,omitempty"` // Mark as a done message from neo
|
||||||
Actions []Action `json:"actions,omitempty"` // Conversation Actions for frontend
|
IsNew bool `json:"is_new,omitempty"` // Mark as a new message from neo
|
||||||
Attachments []Attachment `json:"attachments,omitempty"` // File attachments
|
Actions []Action `json:"actions,omitempty"` // Conversation Actions for frontend
|
||||||
Role string `json:"role,omitempty"` // user, assistant, system ...
|
Attachments []Attachment `json:"attachments,omitempty"` // File attachments
|
||||||
Name string `json:"name,omitempty"` // name for the message
|
Role string `json:"role,omitempty"` // user, assistant, system ...
|
||||||
Data map[string]interface{} `json:"-"`
|
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
|
// Attachment represents a file attachment
|
||||||
|
|
@ -233,6 +245,23 @@ func (m *Message) Map(msg map[string]interface{}) *Message {
|
||||||
if done, ok := msg["done"].(bool); ok {
|
if done, ok := msg["done"].(bool); ok {
|
||||||
m.IsDone = done
|
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 {
|
if actions, ok := msg["actions"].([]interface{}); ok {
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
if v, ok := action.(map[string]interface{}); ok {
|
if v, ok := action.(map[string]interface{}); ok {
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,10 @@ func (conv *Xun) initHistoryTable() error {
|
||||||
table.String("name", 200).Null().Index()
|
table.String("name", 200).Null().Index()
|
||||||
table.Text("content").Null()
|
table.Text("content").Null()
|
||||||
table.JSON("context").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("created_at").SetDefaultRaw("NOW()").Index()
|
||||||
table.TimestampTz("updated_at").Null().Index()
|
table.TimestampTz("updated_at").Null().Index()
|
||||||
table.TimestampTz("expired_at").Null().Index()
|
table.TimestampTz("expired_at").Null().Index()
|
||||||
|
|
@ -158,7 +162,7 @@ func (conv *Xun) initHistoryTable() error {
|
||||||
return err
|
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 {
|
for _, field := range fields {
|
||||||
if !tab.HasColumn(field) {
|
if !tab.HasColumn(field) {
|
||||||
return fmt.Errorf("%s is required", 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().
|
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("sid", userID).
|
||||||
Where("cid", cid).
|
Where("cid", cid).
|
||||||
OrderBy("id", "desc")
|
OrderBy("id", "desc")
|
||||||
|
|
@ -472,13 +476,17 @@ func (conv *Xun) GetHistory(sid string, cid string) ([]map[string]interface{}, e
|
||||||
res := []map[string]interface{}{}
|
res := []map[string]interface{}{}
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
message := map[string]interface{}{
|
message := map[string]interface{}{
|
||||||
"role": row.Get("role"),
|
"role": row.Get("role"),
|
||||||
"name": row.Get("name"),
|
"name": row.Get("name"),
|
||||||
"content": row.Get("content"),
|
"content": row.Get("content"),
|
||||||
"context": row.Get("context"),
|
"context": row.Get("context"),
|
||||||
"uid": row.Get("uid"),
|
"assistant_id": row.Get("assistant_id"),
|
||||||
"created_at": row.Get("created_at"),
|
"assistant_name": row.Get("assistant_name"),
|
||||||
"updated_at": row.Get("updated_at"),
|
"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...)
|
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{}{
|
value := map[string]interface{}{
|
||||||
"role": role,
|
"role": role,
|
||||||
"name": "",
|
"name": "",
|
||||||
"content": content,
|
"content": content,
|
||||||
"sid": userID,
|
"sid": userID,
|
||||||
"cid": cid,
|
"cid": cid,
|
||||||
"uid": userID,
|
"uid": userID,
|
||||||
"context": contextRaw,
|
"context": contextRaw,
|
||||||
"created_at": now,
|
"mentions": mentionsRaw,
|
||||||
"updated_at": nil,
|
"assistant_id": nil,
|
||||||
"expired_at": expiredAt,
|
"assistant_name": nil,
|
||||||
|
"assistant_avatar": nil,
|
||||||
|
"created_at": now,
|
||||||
|
"updated_at": nil,
|
||||||
|
"expired_at": expiredAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
if name, ok := message["name"].(string); ok {
|
if name, ok := message["name"].(string); ok {
|
||||||
value["name"] = name
|
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)
|
values = append(values, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue