diff options
| -rw-r--r-- | bot.go | 106 | ||||
| -rw-r--r-- | bot_test.go | 318 | ||||
| -rw-r--r-- | config.example.toml | 2 | ||||
| -rw-r--r-- | config/config.go | 10 | ||||
| -rw-r--r-- | llm.go | 66 | ||||
| -rw-r--r-- | models/models.go | 14 |
6 files changed, 486 insertions, 30 deletions
@@ -18,6 +18,7 @@ import ( "net/url" "os" "path" + "regexp" "strconv" "strings" "sync" @@ -68,6 +69,111 @@ var ( LocalModels = []string{} ) +// parseKnownToTag extracts known_to list from content using configured tag. +// Returns cleaned content and list of character names. +func parseKnownToTag(content string) (string, []string) { + if cfg == nil || !cfg.CharSpecificContextEnabled { + return content, nil + } + tag := cfg.CharSpecificContextTag + if tag == "" { + tag = "__known_to_chars__" + } + // Pattern: tag + list + "__" + pattern := regexp.QuoteMeta(tag) + `(.*?)__` + re := regexp.MustCompile(pattern) + matches := re.FindAllStringSubmatch(content, -1) + if len(matches) == 0 { + return content, nil + } + // There may be multiple tags; we combine all. + var knownTo []string + cleaned := content + for _, match := range matches { + if len(match) < 2 { + continue + } + // Remove the entire matched tag from content + cleaned = strings.Replace(cleaned, match[0], "", 1) + + list := strings.TrimSpace(match[1]) + if list == "" { + continue + } + parts := strings.Split(list, ",") + for _, p := range parts { + p = strings.TrimSpace(p) + if p != "" { + knownTo = append(knownTo, p) + } + } + } + // Also remove any leftover trailing "__" that might be orphaned? Not needed. + return strings.TrimSpace(cleaned), knownTo +} + +// processMessageTag processes a message for known_to tag and sets KnownTo field. +// It also ensures the sender's role is included in KnownTo. +// If KnownTo already set (e.g., from DB), preserves it unless new tag found. +func processMessageTag(msg models.RoleMsg) models.RoleMsg { + if cfg == nil || !cfg.CharSpecificContextEnabled { + return msg + } + // If KnownTo already set, assume tag already processed (content cleaned). + // However, we still check for new tags (maybe added later). + cleaned, knownTo := parseKnownToTag(msg.Content) + if cleaned != msg.Content { + msg.Content = cleaned + } + // If tag found, replace KnownTo with new list (merge with existing?) + // For simplicity, if knownTo is not nil, replace. + if knownTo != nil { + msg.KnownTo = knownTo + } + // Ensure sender role is in KnownTo + if msg.Role != "" { + senderAdded := false + for _, k := range msg.KnownTo { + if k == msg.Role { + senderAdded = true + break + } + } + if !senderAdded { + msg.KnownTo = append(msg.KnownTo, msg.Role) + } + } + return msg +} + +// filterMessagesForCharacter returns messages visible to the specified character. +// If CharSpecificContextEnabled is false, returns all messages. +func filterMessagesForCharacter(messages []models.RoleMsg, character string) []models.RoleMsg { + if cfg == nil || !cfg.CharSpecificContextEnabled || character == "" { + return messages + } + filtered := make([]models.RoleMsg, 0, len(messages)) + for _, msg := range messages { + // If KnownTo is nil or empty, message is visible to all + if len(msg.KnownTo) == 0 { + filtered = append(filtered, msg) + continue + } + // Check if character is in KnownTo list + found := false + for _, k := range msg.KnownTo { + if k == character { + found = true + break + } + } + if found { + filtered = append(filtered, msg) + } + } + return filtered +} + // cleanNullMessages removes messages with null or empty content to prevent API issues func cleanNullMessages(messages []models.RoleMsg) []models.RoleMsg { // // deletes tool calls which we don't want for now diff --git a/bot_test.go b/bot_test.go index d2956a9..7496175 100644 --- a/bot_test.go +++ b/bot_test.go @@ -286,4 +286,322 @@ func TestConvertJSONToMapStringString(t *testing.T) { } }) } +} + +func TestParseKnownToTag(t *testing.T) { + tests := []struct { + name string + content string + enabled bool + tag string + wantCleaned string + wantKnownTo []string + }{ + { + name: "feature disabled returns original", + content: "Hello __known_to_chars__Alice__", + enabled: false, + tag: "__known_to_chars__", + wantCleaned: "Hello __known_to_chars__Alice__", + wantKnownTo: nil, + }, + { + name: "no tag returns original", + content: "Hello Alice", + enabled: true, + tag: "__known_to_chars__", + wantCleaned: "Hello Alice", + wantKnownTo: nil, + }, + { + name: "single tag with one char", + content: "Hello __known_to_chars__Alice__", + enabled: true, + tag: "__known_to_chars__", + wantCleaned: "Hello", + wantKnownTo: []string{"Alice"}, + }, + { + name: "single tag with two chars", + content: "Secret __known_to_chars__Alice,Bob__ message", + enabled: true, + tag: "__known_to_chars__", + wantCleaned: "Secret message", + wantKnownTo: []string{"Alice", "Bob"}, + }, + { + name: "tag at beginning", + content: "__known_to_chars__Alice__ Hello", + enabled: true, + tag: "__known_to_chars__", + wantCleaned: "Hello", + wantKnownTo: []string{"Alice"}, + }, + { + name: "tag at end", + content: "Hello __known_to_chars__Alice__", + enabled: true, + tag: "__known_to_chars__", + wantCleaned: "Hello", + wantKnownTo: []string{"Alice"}, + }, + { + name: "multiple tags", + content: "First __known_to_chars__Alice__ then __known_to_chars__Bob__", + enabled: true, + tag: "__known_to_chars__", + wantCleaned: "First then", + wantKnownTo: []string{"Alice", "Bob"}, + }, + { + name: "custom tag", + content: "Secret __secret__Alice,Bob__ message", + enabled: true, + tag: "__secret__", + wantCleaned: "Secret message", + wantKnownTo: []string{"Alice", "Bob"}, + }, + { + name: "empty list", + content: "Secret __known_to_chars____", + enabled: true, + tag: "__known_to_chars__", + wantCleaned: "Secret", + wantKnownTo: nil, + }, + { + name: "whitespace around commas", + content: "__known_to_chars__ Alice , Bob , Carl __", + enabled: true, + tag: "__known_to_chars__", + wantCleaned: "", + wantKnownTo: []string{"Alice", "Bob", "Carl"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up config + testCfg := &config.Config{ + CharSpecificContextEnabled: tt.enabled, + CharSpecificContextTag: tt.tag, + } + cfg = testCfg + + cleaned, knownTo := parseKnownToTag(tt.content) + + if cleaned != tt.wantCleaned { + t.Errorf("parseKnownToTag() cleaned = %q, want %q", cleaned, tt.wantCleaned) + } + + if len(knownTo) != len(tt.wantKnownTo) { + t.Errorf("parseKnownToTag() knownTo length = %v, want %v", len(knownTo), len(tt.wantKnownTo)) + t.Logf("got: %v", knownTo) + t.Logf("want: %v", tt.wantKnownTo) + } else { + for i, got := range knownTo { + if got != tt.wantKnownTo[i] { + t.Errorf("parseKnownToTag() knownTo[%d] = %q, want %q", i, got, tt.wantKnownTo[i]) + } + } + } + }) + } +} + +func TestProcessMessageTag(t *testing.T) { + tests := []struct { + name string + msg models.RoleMsg + enabled bool + tag string + wantMsg models.RoleMsg + }{ + { + name: "feature disabled returns unchanged", + msg: models.RoleMsg{ + Role: "Alice", + Content: "Secret __known_to_chars__Bob__", + }, + enabled: false, + tag: "__known_to_chars__", + wantMsg: models.RoleMsg{ + Role: "Alice", + Content: "Secret __known_to_chars__Bob__", + KnownTo: nil, + }, + }, + { + name: "no tag, no knownTo", + msg: models.RoleMsg{ + Role: "Alice", + Content: "Hello everyone", + }, + enabled: true, + tag: "__known_to_chars__", + wantMsg: models.RoleMsg{ + Role: "Alice", + Content: "Hello everyone", + KnownTo: []string{"Alice"}, + }, + }, + { + name: "tag with Bob, adds Alice automatically", + msg: models.RoleMsg{ + Role: "Alice", + Content: "Secret __known_to_chars__Bob__", + }, + enabled: true, + tag: "__known_to_chars__", + wantMsg: models.RoleMsg{ + Role: "Alice", + Content: "Secret", + KnownTo: []string{"Bob", "Alice"}, + }, + }, + { + name: "tag already includes sender", + msg: models.RoleMsg{ + Role: "Alice", + Content: "__known_to_chars__Alice,Bob__", + }, + enabled: true, + tag: "__known_to_chars__", + wantMsg: models.RoleMsg{ + Role: "Alice", + Content: "", + KnownTo: []string{"Alice", "Bob"}, + }, + }, + { + name: "knownTo already set (from DB), tag still processed", + msg: models.RoleMsg{ + Role: "Alice", + Content: "Secret __known_to_chars__Bob__", + KnownTo: []string{"Alice"}, // from previous processing + }, + enabled: true, + tag: "__known_to_chars__", + wantMsg: models.RoleMsg{ + Role: "Alice", + Content: "Secret", + KnownTo: []string{"Bob", "Alice"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testCfg := &config.Config{ + CharSpecificContextEnabled: tt.enabled, + CharSpecificContextTag: tt.tag, + } + cfg = testCfg + + got := processMessageTag(tt.msg) + + if got.Content != tt.wantMsg.Content { + t.Errorf("processMessageTag() content = %q, want %q", got.Content, tt.wantMsg.Content) + } + + if len(got.KnownTo) != len(tt.wantMsg.KnownTo) { + t.Errorf("processMessageTag() KnownTo length = %v, want %v", len(got.KnownTo), len(tt.wantMsg.KnownTo)) + t.Logf("got: %v", got.KnownTo) + t.Logf("want: %v", tt.wantMsg.KnownTo) + } else { + // order may differ; check membership + for _, want := range tt.wantMsg.KnownTo { + found := false + for _, gotVal := range got.KnownTo { + if gotVal == want { + found = true + break + } + } + if !found { + t.Errorf("processMessageTag() missing KnownTo entry %q, got %v", want, got.KnownTo) + } + } + } + }) + } +} + +func TestFilterMessagesForCharacter(t *testing.T) { + messages := []models.RoleMsg{ + {Role: "system", Content: "System message", KnownTo: nil}, // visible to all + {Role: "Alice", Content: "Hello everyone", KnownTo: nil}, // visible to all + {Role: "Alice", Content: "Secret for Bob", KnownTo: []string{"Alice", "Bob"}}, + {Role: "Bob", Content: "Reply to Alice", KnownTo: []string{"Alice", "Bob"}}, + {Role: "Alice", Content: "Private to Carl", KnownTo: []string{"Alice", "Carl"}}, + {Role: "Carl", Content: "Hi all", KnownTo: nil}, // visible to all + } + + tests := []struct { + name string + enabled bool + character string + wantIndices []int // indices from original messages that should be included + }{ + { + name: "feature disabled returns all", + enabled: false, + character: "Alice", + wantIndices: []int{0,1,2,3,4,5}, + }, + { + name: "character empty returns all", + enabled: true, + character: "", + wantIndices: []int{0,1,2,3,4,5}, + }, + { + name: "Alice sees all including Carl-private", + enabled: true, + character: "Alice", + wantIndices: []int{0,1,2,3,4,5}, + }, + { + name: "Bob sees Alice-Bob secrets and all public", + enabled: true, + character: "Bob", + wantIndices: []int{0,1,2,3,5}, + }, + { + name: "Carl sees Alice-Carl secret and public", + enabled: true, + character: "Carl", + wantIndices: []int{0,1,4,5}, + }, + { + name: "David sees only public messages", + enabled: true, + character: "David", + wantIndices: []int{0,1,5}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testCfg := &config.Config{ + CharSpecificContextEnabled: tt.enabled, + CharSpecificContextTag: "__known_to_chars__", + } + cfg = testCfg + + got := filterMessagesForCharacter(messages, tt.character) + + if len(got) != len(tt.wantIndices) { + t.Errorf("filterMessagesForCharacter() returned %d messages, want %d", len(got), len(tt.wantIndices)) + t.Logf("got: %v", got) + return + } + + for i, idx := range tt.wantIndices { + if got[i].Content != messages[idx].Content { + t.Errorf("filterMessagesForCharacter() message %d content = %q, want %q", i, got[i].Content, messages[idx].Content) + } + } + }) + } }
\ No newline at end of file diff --git a/config.example.toml b/config.example.toml index 3a5401b..85b2662 100644 --- a/config.example.toml +++ b/config.example.toml @@ -43,3 +43,5 @@ DBPATH = "gflt.db" FilePickerDir = "." # Directory where file picker should start FilePickerExts = "png,jpg,jpeg,gif,webp" # Comma-separated list of allowed file extensions for file picker EnableMouse = false # Enable mouse support in the UI +CharSpecificContextEnabled = false +CharSpecificContextTag = "__known_to_chars__" diff --git a/config/config.go b/config/config.go index 20935a2..62c8331 100644 --- a/config/config.go +++ b/config/config.go @@ -61,10 +61,12 @@ type Config struct { WhisperBinaryPath string `toml:"WhisperBinaryPath"` WhisperModelPath string `toml:"WhisperModelPath"` STT_LANG string `toml:"STT_LANG"` - DBPATH string `toml:"DBPATH"` - FilePickerDir string `toml:"FilePickerDir"` - FilePickerExts string `toml:"FilePickerExts"` - EnableMouse bool `toml:"EnableMouse"` + DBPATH string `toml:"DBPATH"` + FilePickerDir string `toml:"FilePickerDir"` + FilePickerExts string `toml:"FilePickerExts"` + EnableMouse bool `toml:"EnableMouse"` + CharSpecificContextEnabled bool `toml:"CharSpecificContextEnabled"` + CharSpecificContextTag string `toml:"CharSpecificContextTag"` } func LoadConfig(fn string) (*Config, error) { @@ -34,6 +34,24 @@ func ClearImageAttachment() { imageAttachmentPath = "" } +// filterMessagesForCurrentCharacter filters messages based on char-specific context. +// Returns filtered messages and the bot persona role (target character). +func filterMessagesForCurrentCharacter(messages []models.RoleMsg) ([]models.RoleMsg, string) { + if cfg == nil || !cfg.CharSpecificContextEnabled { + botPersona := cfg.AssistantRole + if cfg.WriteNextMsgAsCompletionAgent != "" { + botPersona = cfg.WriteNextMsgAsCompletionAgent + } + return messages, botPersona + } + botPersona := cfg.AssistantRole + if cfg.WriteNextMsgAsCompletionAgent != "" { + botPersona = cfg.WriteNextMsgAsCompletionAgent + } + filtered := filterMessagesForCharacter(messages, botPersona) + return filtered, botPersona +} + type ChunkParser interface { ParseChunk([]byte) (*models.TextChunk, error) FormMsg(msg, role string, cont bool) (io.Reader, error) @@ -113,6 +131,7 @@ func (lcp LCPCompletion) FormMsg(msg, role string, resume bool) (io.Reader, erro } if msg != "" { // otherwise let the bot to continue newMsg := models.RoleMsg{Role: role, Content: msg} + newMsg = processMessageTag(newMsg) chatBody.Messages = append(chatBody.Messages, newMsg) } if !resume { @@ -136,17 +155,14 @@ func (lcp LCPCompletion) FormMsg(msg, role string, resume bool) (io.Reader, erro // add to chat body chatBody.Messages = append(chatBody.Messages, models.RoleMsg{Role: cfg.ToolRole, Content: toolSysMsg}) } - messages := make([]string, len(chatBody.Messages)) - for i, m := range chatBody.Messages { + filteredMessages, botPersona := filterMessagesForCurrentCharacter(chatBody.Messages) + messages := make([]string, len(filteredMessages)) + for i, m := range filteredMessages { messages[i] = m.ToPrompt() } prompt := strings.Join(messages, "\n") // strings builder? if !resume { - botPersona := cfg.AssistantRole - if cfg.WriteNextMsgAsCompletionAgent != "" { - botPersona = cfg.WriteNextMsgAsCompletionAgent - } botMsgStart := "\n" + botPersona + ":\n" prompt += botMsgStart } @@ -270,6 +286,7 @@ func (op LCPChat) FormMsg(msg, role string, resume bool) (io.Reader, error) { // Create a simple text message newMsg = models.NewRoleMsg(role, msg) } + newMsg = processMessageTag(newMsg) chatBody.Messages = append(chatBody.Messages, newMsg) logger.Debug("LCPChat FormMsg: added message to chatBody", "role", newMsg.Role, "content_len", len(newMsg.Content), "message_count_after_add", len(chatBody.Messages)) } @@ -291,12 +308,13 @@ func (op LCPChat) FormMsg(msg, role string, resume bool) (io.Reader, error) { } } // openai /v1/chat does not support custom roles; needs to be user, assistant, system + filteredMessages, _ := filterMessagesForCurrentCharacter(chatBody.Messages) bodyCopy := &models.ChatBody{ - Messages: make([]models.RoleMsg, len(chatBody.Messages)), + Messages: make([]models.RoleMsg, len(filteredMessages)), Model: chatBody.Model, Stream: chatBody.Stream, } - for i, msg := range chatBody.Messages { + for i, msg := range filteredMessages { if msg.Role == cfg.UserRole { bodyCopy.Messages[i] = msg bodyCopy.Messages[i].Role = "user" @@ -348,6 +366,7 @@ func (ds DeepSeekerCompletion) FormMsg(msg, role string, resume bool) (io.Reader logger.Debug("formmsg deepseekercompletion", "link", cfg.CurrentAPI) if msg != "" { // otherwise let the bot to continue newMsg := models.RoleMsg{Role: role, Content: msg} + newMsg = processMessageTag(newMsg) chatBody.Messages = append(chatBody.Messages, newMsg) } if !resume { @@ -372,17 +391,14 @@ func (ds DeepSeekerCompletion) FormMsg(msg, role string, resume bool) (io.Reader // add to chat body chatBody.Messages = append(chatBody.Messages, models.RoleMsg{Role: cfg.ToolRole, Content: toolSysMsg}) } - messages := make([]string, len(chatBody.Messages)) - for i, m := range chatBody.Messages { + filteredMessages, botPersona := filterMessagesForCurrentCharacter(chatBody.Messages) + messages := make([]string, len(filteredMessages)) + for i, m := range filteredMessages { messages[i] = m.ToPrompt() } prompt := strings.Join(messages, "\n") // strings builder? if !resume { - botPersona := cfg.AssistantRole - if cfg.WriteNextMsgAsCompletionAgent != "" { - botPersona = cfg.WriteNextMsgAsCompletionAgent - } botMsgStart := "\n" + botPersona + ":\n" prompt += botMsgStart } @@ -432,6 +448,7 @@ func (ds DeepSeekerChat) FormMsg(msg, role string, resume bool) (io.Reader, erro logger.Debug("formmsg deepseekerchat", "link", cfg.CurrentAPI) if msg != "" { // otherwise let the bot continue newMsg := models.RoleMsg{Role: role, Content: msg} + newMsg = processMessageTag(newMsg) chatBody.Messages = append(chatBody.Messages, newMsg) } if !resume { @@ -451,12 +468,13 @@ func (ds DeepSeekerChat) FormMsg(msg, role string, resume bool) (io.Reader, erro logger.Debug("RAG message added to chat body", "message_count", len(chatBody.Messages)) } } + filteredMessages, _ := filterMessagesForCurrentCharacter(chatBody.Messages) bodyCopy := &models.ChatBody{ - Messages: make([]models.RoleMsg, len(chatBody.Messages)), + Messages: make([]models.RoleMsg, len(filteredMessages)), Model: chatBody.Model, Stream: chatBody.Stream, } - for i, msg := range chatBody.Messages { + for i, msg := range filteredMessages { if msg.Role == cfg.UserRole || i == 1 { bodyCopy.Messages[i] = msg bodyCopy.Messages[i].Role = "user" @@ -502,6 +520,7 @@ func (or OpenRouterCompletion) FormMsg(msg, role string, resume bool) (io.Reader logger.Debug("formmsg openroutercompletion", "link", cfg.CurrentAPI) if msg != "" { // otherwise let the bot to continue newMsg := models.RoleMsg{Role: role, Content: msg} + newMsg = processMessageTag(newMsg) chatBody.Messages = append(chatBody.Messages, newMsg) } if !resume { @@ -525,17 +544,14 @@ func (or OpenRouterCompletion) FormMsg(msg, role string, resume bool) (io.Reader // add to chat body chatBody.Messages = append(chatBody.Messages, models.RoleMsg{Role: cfg.ToolRole, Content: toolSysMsg}) } - messages := make([]string, len(chatBody.Messages)) - for i, m := range chatBody.Messages { + filteredMessages, botPersona := filterMessagesForCurrentCharacter(chatBody.Messages) + messages := make([]string, len(filteredMessages)) + for i, m := range filteredMessages { messages[i] = m.ToPrompt() } prompt := strings.Join(messages, "\n") // strings builder? if !resume { - botPersona := cfg.AssistantRole - if cfg.WriteNextMsgAsCompletionAgent != "" { - botPersona = cfg.WriteNextMsgAsCompletionAgent - } botMsgStart := "\n" + botPersona + ":\n" prompt += botMsgStart } @@ -619,6 +635,7 @@ func (or OpenRouterChat) FormMsg(msg, role string, resume bool) (io.Reader, erro // Create a simple text message newMsg = models.NewRoleMsg(role, msg) } + newMsg = processMessageTag(newMsg) chatBody.Messages = append(chatBody.Messages, newMsg) } if !resume { @@ -639,12 +656,13 @@ func (or OpenRouterChat) FormMsg(msg, role string, resume bool) (io.Reader, erro } } // Create copy of chat body with standardized user role + filteredMessages, _ := filterMessagesForCurrentCharacter(chatBody.Messages) bodyCopy := &models.ChatBody{ - Messages: make([]models.RoleMsg, len(chatBody.Messages)), + Messages: make([]models.RoleMsg, len(filteredMessages)), Model: chatBody.Model, Stream: chatBody.Stream, } - for i, msg := range chatBody.Messages { + for i, msg := range filteredMessages { bodyCopy.Messages[i] = msg // Standardize role if it's a user role if bodyCopy.Messages[i].Role == cfg.UserRole { diff --git a/models/models.go b/models/models.go index 912f72b..88ba144 100644 --- a/models/models.go +++ b/models/models.go @@ -93,6 +93,7 @@ type RoleMsg struct { Content string `json:"-"` ContentParts []interface{} `json:"-"` ToolCallID string `json:"tool_call_id,omitempty"` // For tool response messages + KnownTo []string `json:"known_to,omitempty"` hasContentParts bool // Flag to indicate which content type to marshal } @@ -104,10 +105,12 @@ func (m RoleMsg) MarshalJSON() ([]byte, error) { Role string `json:"role"` Content []interface{} `json:"content"` ToolCallID string `json:"tool_call_id,omitempty"` + KnownTo []string `json:"known_to,omitempty"` }{ Role: m.Role, Content: m.ContentParts, ToolCallID: m.ToolCallID, + KnownTo: m.KnownTo, } return json.Marshal(aux) } else { @@ -116,10 +119,12 @@ func (m RoleMsg) MarshalJSON() ([]byte, error) { Role string `json:"role"` Content string `json:"content"` ToolCallID string `json:"tool_call_id,omitempty"` + KnownTo []string `json:"known_to,omitempty"` }{ Role: m.Role, Content: m.Content, ToolCallID: m.ToolCallID, + KnownTo: m.KnownTo, } return json.Marshal(aux) } @@ -132,11 +137,13 @@ func (m *RoleMsg) UnmarshalJSON(data []byte) error { Role string `json:"role"` Content []interface{} `json:"content"` ToolCallID string `json:"tool_call_id,omitempty"` + KnownTo []string `json:"known_to,omitempty"` } if err := json.Unmarshal(data, &structured); err == nil && len(structured.Content) > 0 { m.Role = structured.Role m.ContentParts = structured.Content m.ToolCallID = structured.ToolCallID + m.KnownTo = structured.KnownTo m.hasContentParts = true return nil } @@ -146,6 +153,7 @@ func (m *RoleMsg) UnmarshalJSON(data []byte) error { Role string `json:"role"` Content string `json:"content"` ToolCallID string `json:"tool_call_id,omitempty"` + KnownTo []string `json:"known_to,omitempty"` } if err := json.Unmarshal(data, &simple); err != nil { return err @@ -153,6 +161,7 @@ func (m *RoleMsg) UnmarshalJSON(data []byte) error { m.Role = simple.Role m.Content = simple.Content m.ToolCallID = simple.ToolCallID + m.KnownTo = simple.KnownTo m.hasContentParts = false return nil } @@ -363,7 +372,8 @@ func (cb *ChatBody) MakeStopSlice() []string { for _, m := range cb.Messages { namesMap[m.Role] = struct{}{} } - ss := []string{"<|im_end|>"} + ss := make([]string, 0, 1+len(namesMap)) + ss = append(ss, "<|im_end|>") for k := range namesMap { ss = append(ss, k+":\n") } @@ -523,7 +533,7 @@ type LCPModels struct { } func (lcp *LCPModels) ListModels() []string { - resp := []string{} + resp := make([]string, 0, len(lcp.Data)) for _, model := range lcp.Data { resp = append(resp, model.ID) } |
