summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrail Finder <wohilas@gmail.com>2025-12-07 14:17:33 +0300
committerGrail Finder <wohilas@gmail.com>2025-12-07 14:17:33 +0300
commit02bf308452aa127e9f3d2ce5b4821ba426c4c94a (patch)
treef11158f283b1eacf061f4f869e8d42b5f47cc2bb
parent4d18d6e7308ebc3741abf0b933841600a7f1e5bb (diff)
Enha: address template issues
-rw-r--r--bot.go87
-rw-r--r--llm.go4
-rw-r--r--models/models.go32
3 files changed, 123 insertions, 0 deletions
diff --git a/bot.go b/bot.go
index 3a78011..6c1098e 100644
--- a/bot.go
+++ b/bot.go
@@ -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 != "" {
diff --git a/llm.go b/llm.go
index 2248620..38d6c22 100644
--- a/llm.go
+++ b/llm.go
@@ -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 {