Fix (race): mutex chatbody
This commit is contained in:
116
bot.go
116
bot.go
@@ -37,7 +37,7 @@ var (
|
||||
chunkChan = make(chan string, 10)
|
||||
openAIToolChan = make(chan string, 10)
|
||||
streamDone = make(chan bool, 1)
|
||||
chatBody *models.ChatBody
|
||||
chatBody *models.SafeChatBody
|
||||
store storage.FullRepo
|
||||
defaultFirstMsg = "Hello! What can I do for you?"
|
||||
defaultStarter = []models.RoleMsg{}
|
||||
@@ -262,13 +262,13 @@ func warmUpModel() {
|
||||
return
|
||||
}
|
||||
// Check if model is already loaded
|
||||
loaded, err := isModelLoaded(chatBody.Model)
|
||||
loaded, err := isModelLoaded(chatBody.GetModel())
|
||||
if err != nil {
|
||||
logger.Debug("failed to check model status", "model", chatBody.Model, "error", err)
|
||||
logger.Debug("failed to check model status", "model", chatBody.GetModel(), "error", err)
|
||||
// Continue with warmup attempt anyway
|
||||
}
|
||||
if loaded {
|
||||
showToast("model already loaded", "Model "+chatBody.Model+" is already loaded.")
|
||||
showToast("model already loaded", "Model "+chatBody.GetModel()+" is already loaded.")
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
@@ -277,7 +277,7 @@ func warmUpModel() {
|
||||
switch {
|
||||
case strings.HasSuffix(cfg.CurrentAPI, "/completion"):
|
||||
// Old completion endpoint
|
||||
req := models.NewLCPReq(".", chatBody.Model, nil, map[string]float32{
|
||||
req := models.NewLCPReq(".", chatBody.GetModel(), nil, map[string]float32{
|
||||
"temperature": 0.8,
|
||||
"dry_multiplier": 0.0,
|
||||
"min_p": 0.05,
|
||||
@@ -289,7 +289,7 @@ func warmUpModel() {
|
||||
// OpenAI-compatible chat endpoint
|
||||
req := models.OpenAIReq{
|
||||
ChatBody: &models.ChatBody{
|
||||
Model: chatBody.Model,
|
||||
Model: chatBody.GetModel(),
|
||||
Messages: []models.RoleMsg{
|
||||
{Role: "system", Content: "."},
|
||||
},
|
||||
@@ -313,7 +313,7 @@ func warmUpModel() {
|
||||
}
|
||||
resp.Body.Close()
|
||||
// Start monitoring for model load completion
|
||||
monitorModelLoad(chatBody.Model)
|
||||
monitorModelLoad(chatBody.GetModel())
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -418,7 +418,9 @@ func fetchLCPModelsWithStatus() (*models.LCPModels, error) {
|
||||
if err := json.NewDecoder(resp.Body).Decode(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
localModelsMu.Lock()
|
||||
localModelsData = data
|
||||
localModelsMu.Unlock()
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -821,10 +823,10 @@ func chatRound(r *models.ChatRoundReq) error {
|
||||
}
|
||||
go sendMsgToLLM(reader)
|
||||
logger.Debug("looking at vars in chatRound", "msg", r.UserMsg, "regen", r.Regen, "resume", r.Resume)
|
||||
msgIdx := len(chatBody.Messages)
|
||||
msgIdx := chatBody.GetMessageCount()
|
||||
if !r.Resume {
|
||||
// Add empty message to chatBody immediately so it persists during Alt+T toggle
|
||||
chatBody.Messages = append(chatBody.Messages, models.RoleMsg{
|
||||
chatBody.AppendMessage(models.RoleMsg{
|
||||
Role: botPersona, Content: "",
|
||||
})
|
||||
nl := "\n\n"
|
||||
@@ -836,7 +838,7 @@ func chatRound(r *models.ChatRoundReq) error {
|
||||
}
|
||||
fmt.Fprintf(textView, "%s[-:-:b](%d) %s[-:-:-]\n", nl, msgIdx, roleToIcon(botPersona))
|
||||
} else {
|
||||
msgIdx = len(chatBody.Messages) - 1
|
||||
msgIdx = chatBody.GetMessageCount() - 1
|
||||
}
|
||||
respText := strings.Builder{}
|
||||
toolResp := strings.Builder{}
|
||||
@@ -893,7 +895,10 @@ out:
|
||||
fmt.Fprint(textView, chunk)
|
||||
respText.WriteString(chunk)
|
||||
// Update the message in chatBody.Messages so it persists during Alt+T
|
||||
chatBody.Messages[msgIdx].Content = respText.String()
|
||||
chatBody.UpdateMessageFunc(msgIdx, func(msg models.RoleMsg) models.RoleMsg {
|
||||
msg.Content = respText.String()
|
||||
return msg
|
||||
})
|
||||
if scrollToEndEnabled {
|
||||
textView.ScrollToEnd()
|
||||
}
|
||||
@@ -936,29 +941,32 @@ out:
|
||||
}
|
||||
botRespMode = false
|
||||
if r.Resume {
|
||||
chatBody.Messages[len(chatBody.Messages)-1].Content += respText.String()
|
||||
updatedMsg := chatBody.Messages[len(chatBody.Messages)-1]
|
||||
processedMsg := processMessageTag(&updatedMsg)
|
||||
chatBody.Messages[len(chatBody.Messages)-1] = *processedMsg
|
||||
if msgStats != nil && chatBody.Messages[len(chatBody.Messages)-1].Role != cfg.ToolRole {
|
||||
chatBody.Messages[len(chatBody.Messages)-1].Stats = msgStats
|
||||
}
|
||||
chatBody.UpdateMessageFunc(chatBody.GetMessageCount()-1, func(msg models.RoleMsg) models.RoleMsg {
|
||||
msg.Content += respText.String()
|
||||
processedMsg := processMessageTag(&msg)
|
||||
if msgStats != nil && processedMsg.Role != cfg.ToolRole {
|
||||
processedMsg.Stats = msgStats
|
||||
}
|
||||
return *processedMsg
|
||||
})
|
||||
} else {
|
||||
chatBody.Messages[msgIdx].Content = respText.String()
|
||||
processedMsg := processMessageTag(&chatBody.Messages[msgIdx])
|
||||
chatBody.Messages[msgIdx] = *processedMsg
|
||||
if msgStats != nil && chatBody.Messages[msgIdx].Role != cfg.ToolRole {
|
||||
chatBody.Messages[msgIdx].Stats = msgStats
|
||||
}
|
||||
stopTTSIfNotForUser(&chatBody.Messages[msgIdx])
|
||||
chatBody.UpdateMessageFunc(msgIdx, func(msg models.RoleMsg) models.RoleMsg {
|
||||
msg.Content = respText.String()
|
||||
processedMsg := processMessageTag(&msg)
|
||||
if msgStats != nil && processedMsg.Role != cfg.ToolRole {
|
||||
processedMsg.Stats = msgStats
|
||||
}
|
||||
return *processedMsg
|
||||
})
|
||||
stopTTSIfNotForUser(&chatBody.GetMessages()[msgIdx])
|
||||
}
|
||||
cleanChatBody()
|
||||
refreshChatDisplay()
|
||||
updateStatusLine()
|
||||
// bot msg is done;
|
||||
// now check it for func call
|
||||
// logChat(activeChatName, chatBody.Messages)
|
||||
if err := updateStorageChat(activeChatName, chatBody.Messages); err != nil {
|
||||
// logChat(activeChatName, chatBody.GetMessages())
|
||||
if err := updateStorageChat(activeChatName, chatBody.GetMessages()); err != nil {
|
||||
logger.Warn("failed to update storage", "error", err, "name", activeChatName)
|
||||
}
|
||||
// Strip think blocks before parsing for tool calls
|
||||
@@ -973,8 +981,8 @@ out:
|
||||
// If so, trigger those characters to respond if that char is not controlled by user
|
||||
// perhaps we should have narrator role to determine which char is next to act
|
||||
if cfg.AutoTurn {
|
||||
lastMsg := chatBody.Messages[len(chatBody.Messages)-1]
|
||||
if len(lastMsg.KnownTo) > 0 {
|
||||
lastMsg, ok := chatBody.GetLastMessage()
|
||||
if ok && len(lastMsg.KnownTo) > 0 {
|
||||
triggerPrivateMessageResponses(&lastMsg)
|
||||
}
|
||||
}
|
||||
@@ -983,13 +991,15 @@ out:
|
||||
|
||||
// cleanChatBody removes messages with null or empty content to prevent API issues
|
||||
func cleanChatBody() {
|
||||
if chatBody == nil || chatBody.Messages == nil {
|
||||
if chatBody == nil || chatBody.GetMessageCount() == 0 {
|
||||
return
|
||||
}
|
||||
// Tool request cleaning is now configurable via AutoCleanToolCallsFromCtx (default false)
|
||||
// /completion msg where part meant for user and other part tool call
|
||||
// chatBody.Messages = cleanToolCalls(chatBody.Messages)
|
||||
chatBody.Messages = consolidateAssistantMessages(chatBody.Messages)
|
||||
chatBody.WithLock(func(cb *models.ChatBody) {
|
||||
cb.Messages = consolidateAssistantMessages(cb.Messages)
|
||||
})
|
||||
}
|
||||
|
||||
// convertJSONToMapStringString unmarshals JSON into map[string]interface{} and converts all values to strings.
|
||||
@@ -1089,7 +1099,7 @@ func findCall(msg, toolCall string) bool {
|
||||
Content: fmt.Sprintf("Error processing tool call: %v. Please check the JSON format and try again.", err),
|
||||
ToolCallID: lastToolCall.ID, // Use the stored tool call ID
|
||||
}
|
||||
chatBody.Messages = append(chatBody.Messages, toolResponseMsg)
|
||||
chatBody.AppendMessage(toolResponseMsg)
|
||||
// Clear the stored tool call ID after using it (no longer needed)
|
||||
// Trigger the assistant to continue processing with the error message
|
||||
crr := &models.ChatRoundReq{
|
||||
@@ -1126,7 +1136,7 @@ func findCall(msg, toolCall string) bool {
|
||||
Role: cfg.ToolRole,
|
||||
Content: "Error processing tool call: no valid JSON found. Please check the JSON format.",
|
||||
}
|
||||
chatBody.Messages = append(chatBody.Messages, toolResponseMsg)
|
||||
chatBody.AppendMessage(toolResponseMsg)
|
||||
crr := &models.ChatRoundReq{
|
||||
Role: cfg.AssistantRole,
|
||||
}
|
||||
@@ -1143,8 +1153,8 @@ func findCall(msg, toolCall string) bool {
|
||||
Role: cfg.ToolRole,
|
||||
Content: fmt.Sprintf("Error processing tool call: %v. Please check the JSON format and try again.", err),
|
||||
}
|
||||
chatBody.Messages = append(chatBody.Messages, toolResponseMsg)
|
||||
logger.Debug("findCall: added tool error response", "role", toolResponseMsg.Role, "content_len", len(toolResponseMsg.Content), "message_count_after_add", len(chatBody.Messages))
|
||||
chatBody.AppendMessage(toolResponseMsg)
|
||||
logger.Debug("findCall: added tool error response", "role", toolResponseMsg.Role, "content_len", len(toolResponseMsg.Content), "message_count_after_add", chatBody.GetMessageCount())
|
||||
// Trigger the assistant to continue processing with the error message
|
||||
// chatRound("", cfg.AssistantRole, tv, false, false)
|
||||
crr := &models.ChatRoundReq{
|
||||
@@ -1162,17 +1172,23 @@ func findCall(msg, toolCall string) bool {
|
||||
// we got here => last msg recognized as a tool call (correct or not)
|
||||
// Use the tool call ID from streaming response (lastToolCall.ID)
|
||||
// Don't generate random ID - the ID should match between assistant message and tool response
|
||||
lastMsgIdx := len(chatBody.Messages) - 1
|
||||
lastMsgIdx := chatBody.GetMessageCount() - 1
|
||||
if lastToolCall.ID != "" {
|
||||
chatBody.Messages[lastMsgIdx].ToolCallID = lastToolCall.ID
|
||||
chatBody.UpdateMessageFunc(lastMsgIdx, func(msg models.RoleMsg) models.RoleMsg {
|
||||
msg.ToolCallID = lastToolCall.ID
|
||||
return msg
|
||||
})
|
||||
}
|
||||
// Store tool call info in the assistant message
|
||||
// Convert Args map to JSON string for storage
|
||||
chatBody.Messages[lastMsgIdx].ToolCall = &models.ToolCall{
|
||||
ID: lastToolCall.ID,
|
||||
Name: lastToolCall.Name,
|
||||
Args: mapToString(lastToolCall.Args),
|
||||
}
|
||||
chatBody.UpdateMessageFunc(lastMsgIdx, func(msg models.RoleMsg) models.RoleMsg {
|
||||
msg.ToolCall = &models.ToolCall{
|
||||
ID: lastToolCall.ID,
|
||||
Name: lastToolCall.Name,
|
||||
Args: mapToString(lastToolCall.Args),
|
||||
}
|
||||
return msg
|
||||
})
|
||||
// call a func
|
||||
_, ok := fnMap[fc.Name]
|
||||
if !ok {
|
||||
@@ -1183,8 +1199,8 @@ func findCall(msg, toolCall string) bool {
|
||||
Content: m,
|
||||
ToolCallID: lastToolCall.ID, // Use the stored tool call ID
|
||||
}
|
||||
chatBody.Messages = append(chatBody.Messages, toolResponseMsg)
|
||||
logger.Debug("findCall: added tool not implemented response", "role", toolResponseMsg.Role, "content_len", len(toolResponseMsg.Content), "tool_call_id", toolResponseMsg.ToolCallID, "message_count_after_add", len(chatBody.Messages))
|
||||
chatBody.AppendMessage(toolResponseMsg)
|
||||
logger.Debug("findCall: added tool not implemented response", "role", toolResponseMsg.Role, "content_len", len(toolResponseMsg.Content), "tool_call_id", toolResponseMsg.ToolCallID, "message_count_after_add", chatBody.GetMessageCount())
|
||||
// Clear the stored tool call ID after using it
|
||||
lastToolCall.ID = ""
|
||||
// Trigger the assistant to continue processing with the new tool response
|
||||
@@ -1255,9 +1271,9 @@ func findCall(msg, toolCall string) bool {
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(textView, "%s[-:-:b](%d) <%s>: [-:-:-]\n%s\n",
|
||||
"\n\n", len(chatBody.Messages), cfg.ToolRole, toolResponseMsg.GetText())
|
||||
chatBody.Messages = append(chatBody.Messages, toolResponseMsg)
|
||||
logger.Debug("findCall: added actual tool response", "role", toolResponseMsg.Role, "content_len", len(toolResponseMsg.Content), "tool_call_id", toolResponseMsg.ToolCallID, "message_count_after_add", len(chatBody.Messages))
|
||||
"\n\n", chatBody.GetMessageCount(), cfg.ToolRole, toolResponseMsg.GetText())
|
||||
chatBody.AppendMessage(toolResponseMsg)
|
||||
logger.Debug("findCall: added actual tool response", "role", toolResponseMsg.Role, "content_len", len(toolResponseMsg.Content), "tool_call_id", toolResponseMsg.ToolCallID, "message_count_after_add", chatBody.GetMessageCount())
|
||||
// Clear the stored tool call ID after using it
|
||||
lastToolCall.ID = ""
|
||||
// Trigger the assistant to continue processing with the new tool response
|
||||
@@ -1497,7 +1513,7 @@ func init() {
|
||||
// load cards
|
||||
basicCard.Role = cfg.AssistantRole
|
||||
logLevel.Set(slog.LevelInfo)
|
||||
logger = slog.New(slog.NewTextHandler(logfile, &slog.HandlerOptions{Level: logLevel}))
|
||||
logger = slog.New(slog.NewTextHandler(logfile, &slog.HandlerOptions{Level: logLevel, AddSource: true}))
|
||||
store = storage.NewProviderSQL(cfg.DBPATH, logger)
|
||||
if store == nil {
|
||||
cancel()
|
||||
@@ -1521,11 +1537,11 @@ func init() {
|
||||
}
|
||||
lastToolCall = &models.FuncCall{}
|
||||
lastChat := loadOldChatOrGetNew()
|
||||
chatBody = &models.ChatBody{
|
||||
chatBody = models.NewSafeChatBody(&models.ChatBody{
|
||||
Model: "modelname",
|
||||
Stream: true,
|
||||
Messages: lastChat,
|
||||
}
|
||||
})
|
||||
choseChunkParser()
|
||||
httpClient = createClient(time.Second * 90)
|
||||
if cfg.TTS_ENABLED {
|
||||
|
||||
Reference in New Issue
Block a user