Add unique ID tracking for message contents and streaming

- Introduced UUID generation for message contents during token scanning
- Updated message and contents methods to support optional ID parameter
- Modified streaming chat processing to include message ID tracking
- Enhanced message parsing and appending with ID preservation
This commit is contained in:
Max 2025-02-04 17:17:24 +08:00
parent 991c884d78
commit 75d55ae13b
3 changed files with 55 additions and 28 deletions

View file

@ -321,7 +321,8 @@ func (ast *Assistant) streamChat(
msg.AppendTo(contents) // Append content and send message msg.AppendTo(contents) // Append content and send message
// Scan the tokens // Scan the tokens
contents.ScanTokens(func(token string, begin bool, text string, tails string) { contents.ScanTokens(func(token string, id string, begin bool, text string, tails string) {
msg.ID = id
msg.Type = token msg.Type = token
msg.Text = "" // clear the text msg.Text = "" // clear the text
msg.Props = map[string]interface{}{"text": text} // Update props msg.Props = map[string]interface{}{"text": text} // Update props
@ -332,7 +333,7 @@ func (ast *Assistant) streamChat(
} }
// New message with the tails // New message with the tails
newMsg, err := chatMessage.NewString(tails) newMsg, err := chatMessage.NewString(tails, id)
if err != nil { if err != nil {
return return
} }

View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/google/uuid"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
) )
@ -26,6 +27,7 @@ type Contents struct {
Current int `json:"current"` // the current content index Current int `json:"current"` // the current content index
Data []Data `json:"data"` // the data Data []Data `json:"data"` // the data
token string // the current token token string // the current token
id string // the id of the contents
} }
// Data the data of the content // Data the data of the content
@ -47,7 +49,7 @@ func NewContents() *Contents {
} }
// ScanTokens scan the tokens // ScanTokens scan the tokens
func (c *Contents) ScanTokens(cb func(token string, begin bool, text string, tails string)) { func (c *Contents) ScanTokens(cb func(token string, id string, begin bool, text string, tails string)) {
text := strings.TrimSpace(c.Text()) text := strings.TrimSpace(c.Text())
@ -61,15 +63,15 @@ func (c *Contents) ScanTokens(cb func(token string, begin bool, text string, tai
if index > 0 { if index > 0 {
tails = text[index+len(token[1]):] tails = text[index+len(token[1]):]
} }
c.UpdateType(c.token, map[string]interface{}{"text": text}) c.UpdateType(c.token, map[string]interface{}{"text": text}, c.id)
c.NewText([]byte(tails)) // Create new text with the tails c.NewText([]byte(tails), c.id) // Create new text with the tails
cb(c.token, false, text, tails) cb(c.token, c.id, false, text, tails)
c.token = "" // clear the token c.token = "" // clear the token
return return
} }
// call the callback for the begin of the token // call the callback for the begin of the token
cb(c.token, true, text, "") cb(c.token, c.id, true, text, "")
return return
} }
@ -77,7 +79,8 @@ func (c *Contents) ScanTokens(cb func(token string, begin bool, text string, tai
for name, token := range tokens { for name, token := range tokens {
if index := strings.Index(text, token[0]); index >= 0 { if index := strings.Index(text, token[0]); index >= 0 {
c.token = name c.token = name
cb(name, true, text, "") // call the callback c.id = uuid.New().String()
cb(name, c.id, true, text, "") // call the callback
} }
} }
} }
@ -96,43 +99,58 @@ func (c *Contents) RemoveLastEmpty() {
} }
// NewText create a new text data and append to the contents // NewText create a new text data and append to the contents
func (c *Contents) NewText(bytes []byte) *Contents { func (c *Contents) NewText(bytes []byte, id ...string) *Contents {
c.Data = append(c.Data, Data{
Type: "text", data := Data{Type: "text", Bytes: bytes}
Bytes: bytes, if len(id) > 0 && id[0] != "" {
}) data.ID = id[0]
}
c.Data = append(c.Data, data)
c.Current++ c.Current++
return c return c
} }
// NewTool create a new tool data and append to the contents // NewTool create a new tool data and append to the contents
func (c *Contents) NewTool(function string, arguments []byte) *Contents { func (c *Contents) NewTool(function string, arguments []byte, id ...string) *Contents {
c.Data = append(c.Data, Data{
data := Data{
Type: "tool", Type: "tool",
Function: function, Function: function,
Arguments: arguments, Arguments: arguments,
}) }
if len(id) > 0 && id[0] != "" {
data.ID = id[0]
}
c.Data = append(c.Data, data)
c.Current++ c.Current++
return c return c
} }
// NewType create a new type data and append to the contents // NewType create a new type data and append to the contents
func (c *Contents) NewType(typ string, props map[string]interface{}) *Contents { func (c *Contents) NewType(typ string, props map[string]interface{}, id ...string) *Contents {
c.Data = append(c.Data, Data{
data := Data{
Type: typ, Type: typ,
Props: props, Props: props,
}) }
if len(id) > 0 && id[0] != "" {
data.ID = id[0]
}
c.Data = append(c.Data, data)
c.Current++ c.Current++
return c return c
} }
// UpdateType update the type of the current content // UpdateType update the type of the current content
func (c *Contents) UpdateType(typ string, props map[string]interface{}) *Contents { func (c *Contents) UpdateType(typ string, props map[string]interface{}, id ...string) *Contents {
if c.Current == -1 { if c.Current == -1 {
c.NewType(typ, props) c.NewType(typ, props, id...)
return c return c
} }
if len(id) > 0 && id[0] != "" {
c.Data[c.Current].ID = id[0]
}
c.Data[c.Current].Type = typ c.Data[c.Current].Type = typ
c.Data[c.Current].Props = props c.Data[c.Current].Props = props
return c return c
@ -158,19 +176,23 @@ func (c *Contents) NewError(err []byte) *Contents {
} }
// AppendText append the text to the current content // AppendText append the text to the current content
func (c *Contents) AppendText(bytes []byte) *Contents { func (c *Contents) AppendText(bytes []byte, id ...string) *Contents {
if c.Current == -1 { if c.Current == -1 {
c.NewText(bytes) c.NewText(bytes, id...)
return c return c
} }
if len(id) > 0 && id[0] != "" {
c.Data[c.Current].ID = id[0]
}
c.Data[c.Current].Bytes = append(c.Data[c.Current].Bytes, bytes...) c.Data[c.Current].Bytes = append(c.Data[c.Current].Bytes, bytes...)
return c return c
} }
// AppendTool append the tool to the current content // AppendTool append the tool to the current content
func (c *Contents) AppendTool(arguments []byte) *Contents { func (c *Contents) AppendTool(arguments []byte, id ...string) *Contents {
if c.Current == -1 { if c.Current == -1 {
c.NewTool("", arguments) c.NewTool("", arguments, id...)
return c return c
} }
c.Data[c.Current].Arguments = append(c.Data[c.Current].Arguments, arguments...) c.Data[c.Current].Arguments = append(c.Data[c.Current].Arguments, arguments...)

View file

@ -16,6 +16,7 @@ import (
// Message the message // Message the message
type Message struct { type Message struct {
ID string `json:"id,omitempty"` // id for the message
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
@ -135,7 +136,7 @@ func NewContent(content string) ([]Message, error) {
} }
// NewString create a new message from string // NewString create a new message from string
func NewString(content string) (*Message, error) { func NewString(content string, id ...string) (*Message, error) {
if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") { if strings.HasPrefix(content, "{") && strings.HasSuffix(content, "}") {
var msg Message var msg Message
if err := jsoniter.UnmarshalFromString(content, &msg); err != nil { if err := jsoniter.UnmarshalFromString(content, &msg); err != nil {
@ -143,6 +144,9 @@ func NewString(content string) (*Message, error) {
} }
return &msg, nil return &msg, nil
} }
if len(id) > 0 {
return &Message{ID: id[0], Text: content}, nil
}
return &Message{Text: content}, nil return &Message{Text: content}, nil
} }
@ -357,10 +361,10 @@ func (m *Message) AppendTo(contents *Contents) *Message {
case "text", "think", "tool": case "text", "think", "tool":
if m.Text != "" { if m.Text != "" {
if m.IsNew { if m.IsNew {
contents.NewText([]byte(m.Text)) contents.NewText([]byte(m.Text), m.ID)
return m return m
} }
contents.AppendText([]byte(m.Text)) contents.AppendText([]byte(m.Text), m.ID)
return m return m
} }
return m return m