Feat: impl attempt
This commit is contained in:
106
bot.go
106
bot.go
@@ -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
|
||||
|
||||
318
bot_test.go
318
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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__"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
66
llm.go
66
llm.go
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user