diff options
| author | Grail Finder <wohilas@gmail.com> | 2025-12-07 14:17:33 +0300 |
|---|---|---|
| committer | Grail Finder <wohilas@gmail.com> | 2025-12-07 14:17:33 +0300 |
| commit | 02bf308452aa127e9f3d2ce5b4821ba426c4c94a (patch) | |
| tree | f11158f283b1eacf061f4f869e8d42b5f47cc2bb | |
| parent | 4d18d6e7308ebc3741abf0b933841600a7f1e5bb (diff) | |
Enha: address template issues
| -rw-r--r-- | bot.go | 87 | ||||
| -rw-r--r-- | llm.go | 4 | ||||
| -rw-r--r-- | models/models.go | 32 |
3 files changed, 123 insertions, 0 deletions
@@ -67,6 +67,80 @@ var ( } ) +// cleanNullMessages removes messages with null or empty content to prevent API issues +func cleanNullMessages(messages []models.RoleMsg) []models.RoleMsg { + cleaned := make([]models.RoleMsg, 0, len(messages)) + for _, msg := range messages { + // Include message if it has content or if it's a tool response (which might have tool_call_id) + if msg.HasContent() || msg.ToolCallID != "" { + cleaned = append(cleaned, msg) + } + } + return consolidateConsecutiveAssistantMessages(cleaned) +} + +// consolidateConsecutiveAssistantMessages merges consecutive assistant messages into a single message +func consolidateConsecutiveAssistantMessages(messages []models.RoleMsg) []models.RoleMsg { + if len(messages) == 0 { + return messages + } + + consolidated := make([]models.RoleMsg, 0, len(messages)) + currentAssistantMsg := models.RoleMsg{} + isBuildingAssistantMsg := false + + for i := 0; i < len(messages); i++ { + msg := messages[i] + + if msg.Role == cfg.AssistantRole || msg.Role == cfg.WriteNextMsgAsCompletionAgent { + // If this is an assistant message, start or continue building + if !isBuildingAssistantMsg { + // Start accumulating assistant message + currentAssistantMsg = msg.Copy() + isBuildingAssistantMsg = true + } else { + // Continue accumulating - append content to the current assistant message + if currentAssistantMsg.IsContentParts() || msg.IsContentParts() { + // Handle structured content + if !currentAssistantMsg.IsContentParts() { + // Convert existing content to content parts + currentAssistantMsg = models.NewMultimodalMsg(currentAssistantMsg.Role, []interface{}{models.TextContentPart{Type: "text", Text: currentAssistantMsg.Content}}) + currentAssistantMsg.ToolCallID = msg.ToolCallID + } + if msg.IsContentParts() { + currentAssistantMsg.ContentParts = append(currentAssistantMsg.ContentParts, msg.GetContentParts()...) + } else if msg.Content != "" { + currentAssistantMsg.AddTextPart(msg.Content) + } + } else { + // Simple string content + if currentAssistantMsg.Content != "" { + currentAssistantMsg.Content += "\n" + msg.Content + } else { + currentAssistantMsg.Content = msg.Content + } + } + } + } else { + // This is not an assistant message + // If we were building an assistant message, add it to the result + if isBuildingAssistantMsg { + consolidated = append(consolidated, currentAssistantMsg) + isBuildingAssistantMsg = false + } + // Add the non-assistant message + consolidated = append(consolidated, msg) + } + } + + // Don't forget the last assistant message if we were building one + if isBuildingAssistantMsg { + consolidated = append(consolidated, currentAssistantMsg) + } + + return consolidated +} + // GetLogLevel returns the current log level as a string func GetLogLevel() string { level := logLevel.Level() @@ -481,6 +555,10 @@ out: Role: botPersona, Content: respText.String(), }) } + + // Clean null/empty messages to prevent API issues with endpoints like llama.cpp jinja template + cleanChatBody() + colorText() updateStatusLine() // bot msg is done; @@ -492,6 +570,15 @@ out: findCall(respText.String(), toolResp.String(), tv) } +// cleanChatBody removes messages with null or empty content to prevent API issues +func cleanChatBody() { + if chatBody != nil && chatBody.Messages != nil { + originalLen := len(chatBody.Messages) + chatBody.Messages = cleanNullMessages(chatBody.Messages) + logger.Debug("cleaned chat body", "original_len", originalLen, "new_len", len(chatBody.Messages)) + } +} + func findCall(msg, toolCall string, tv *tview.TextView) { fc := &models.FuncCall{} if toolCall != "" { @@ -236,6 +236,8 @@ func (op LCPChat) FormMsg(msg, role string, resume bool) (io.Reader, error) { bodyCopy.Messages[i] = msg } } + // Clean null/empty messages to prevent API issues + bodyCopy.Messages = cleanNullMessages(bodyCopy.Messages) req := models.OpenAIReq{ ChatBody: bodyCopy, Tools: nil, @@ -385,6 +387,8 @@ func (ds DeepSeekerChat) FormMsg(msg, role string, resume bool) (io.Reader, erro bodyCopy.Messages[i] = msg } } + // Clean null/empty messages to prevent API issues + bodyCopy.Messages = cleanNullMessages(bodyCopy.Messages) dsBody := models.NewDSChatReq(*bodyCopy) data, err := json.Marshal(dsBody) if err != nil { diff --git a/models/models.go b/models/models.go index 798ea35..b4e7113 100644 --- a/models/models.go +++ b/models/models.go @@ -230,6 +230,38 @@ func NewMultimodalMsg(role string, contentParts []interface{}) RoleMsg { } } +// HasContent returns true if the message has either string content or structured content parts +func (m RoleMsg) HasContent() bool { + if m.Content != "" { + return true + } + if m.hasContentParts && len(m.ContentParts) > 0 { + return true + } + return false +} + +// IsContentParts returns true if the message uses structured content parts +func (m RoleMsg) IsContentParts() bool { + return m.hasContentParts +} + +// GetContentParts returns the content parts of the message +func (m RoleMsg) GetContentParts() []interface{} { + return m.ContentParts +} + +// Copy creates a copy of the RoleMsg with all fields +func (m RoleMsg) Copy() RoleMsg { + return RoleMsg{ + Role: m.Role, + Content: m.Content, + ContentParts: m.ContentParts, + ToolCallID: m.ToolCallID, + hasContentParts: m.hasContentParts, + } +} + // AddTextPart adds a text content part to the message func (m *RoleMsg) AddTextPart(text string) { if !m.hasContentParts { |
