Feat: impl attempt
This commit is contained in:
106
bot.go
106
bot.go
@@ -18,6 +18,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -68,6 +69,111 @@ var (
|
|||||||
LocalModels = []string{}
|
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
|
// cleanNullMessages removes messages with null or empty content to prevent API issues
|
||||||
func cleanNullMessages(messages []models.RoleMsg) []models.RoleMsg {
|
func cleanNullMessages(messages []models.RoleMsg) []models.RoleMsg {
|
||||||
// // deletes tool calls which we don't want for now
|
// // deletes tool calls which we don't want for now
|
||||||
|
|||||||
318
bot_test.go
318
bot_test.go
@@ -287,3 +287,321 @@ 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
|
FilePickerDir = "." # Directory where file picker should start
|
||||||
FilePickerExts = "png,jpg,jpeg,gif,webp" # Comma-separated list of allowed file extensions for file picker
|
FilePickerExts = "png,jpg,jpeg,gif,webp" # Comma-separated list of allowed file extensions for file picker
|
||||||
EnableMouse = false # Enable mouse support in the UI
|
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"`
|
WhisperBinaryPath string `toml:"WhisperBinaryPath"`
|
||||||
WhisperModelPath string `toml:"WhisperModelPath"`
|
WhisperModelPath string `toml:"WhisperModelPath"`
|
||||||
STT_LANG string `toml:"STT_LANG"`
|
STT_LANG string `toml:"STT_LANG"`
|
||||||
DBPATH string `toml:"DBPATH"`
|
DBPATH string `toml:"DBPATH"`
|
||||||
FilePickerDir string `toml:"FilePickerDir"`
|
FilePickerDir string `toml:"FilePickerDir"`
|
||||||
FilePickerExts string `toml:"FilePickerExts"`
|
FilePickerExts string `toml:"FilePickerExts"`
|
||||||
EnableMouse bool `toml:"EnableMouse"`
|
EnableMouse bool `toml:"EnableMouse"`
|
||||||
|
CharSpecificContextEnabled bool `toml:"CharSpecificContextEnabled"`
|
||||||
|
CharSpecificContextTag string `toml:"CharSpecificContextTag"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig(fn string) (*Config, error) {
|
func LoadConfig(fn string) (*Config, error) {
|
||||||
|
|||||||
66
llm.go
66
llm.go
@@ -34,6 +34,24 @@ func ClearImageAttachment() {
|
|||||||
imageAttachmentPath = ""
|
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 {
|
type ChunkParser interface {
|
||||||
ParseChunk([]byte) (*models.TextChunk, error)
|
ParseChunk([]byte) (*models.TextChunk, error)
|
||||||
FormMsg(msg, role string, cont bool) (io.Reader, 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
|
if msg != "" { // otherwise let the bot to continue
|
||||||
newMsg := models.RoleMsg{Role: role, Content: msg}
|
newMsg := models.RoleMsg{Role: role, Content: msg}
|
||||||
|
newMsg = processMessageTag(newMsg)
|
||||||
chatBody.Messages = append(chatBody.Messages, newMsg)
|
chatBody.Messages = append(chatBody.Messages, newMsg)
|
||||||
}
|
}
|
||||||
if !resume {
|
if !resume {
|
||||||
@@ -136,17 +155,14 @@ func (lcp LCPCompletion) FormMsg(msg, role string, resume bool) (io.Reader, erro
|
|||||||
// add to chat body
|
// add to chat body
|
||||||
chatBody.Messages = append(chatBody.Messages, models.RoleMsg{Role: cfg.ToolRole, Content: toolSysMsg})
|
chatBody.Messages = append(chatBody.Messages, models.RoleMsg{Role: cfg.ToolRole, Content: toolSysMsg})
|
||||||
}
|
}
|
||||||
messages := make([]string, len(chatBody.Messages))
|
filteredMessages, botPersona := filterMessagesForCurrentCharacter(chatBody.Messages)
|
||||||
for i, m := range chatBody.Messages {
|
messages := make([]string, len(filteredMessages))
|
||||||
|
for i, m := range filteredMessages {
|
||||||
messages[i] = m.ToPrompt()
|
messages[i] = m.ToPrompt()
|
||||||
}
|
}
|
||||||
prompt := strings.Join(messages, "\n")
|
prompt := strings.Join(messages, "\n")
|
||||||
// strings builder?
|
// strings builder?
|
||||||
if !resume {
|
if !resume {
|
||||||
botPersona := cfg.AssistantRole
|
|
||||||
if cfg.WriteNextMsgAsCompletionAgent != "" {
|
|
||||||
botPersona = cfg.WriteNextMsgAsCompletionAgent
|
|
||||||
}
|
|
||||||
botMsgStart := "\n" + botPersona + ":\n"
|
botMsgStart := "\n" + botPersona + ":\n"
|
||||||
prompt += botMsgStart
|
prompt += botMsgStart
|
||||||
}
|
}
|
||||||
@@ -270,6 +286,7 @@ func (op LCPChat) FormMsg(msg, role string, resume bool) (io.Reader, error) {
|
|||||||
// Create a simple text message
|
// Create a simple text message
|
||||||
newMsg = models.NewRoleMsg(role, msg)
|
newMsg = models.NewRoleMsg(role, msg)
|
||||||
}
|
}
|
||||||
|
newMsg = processMessageTag(newMsg)
|
||||||
chatBody.Messages = append(chatBody.Messages, 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))
|
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
|
// openai /v1/chat does not support custom roles; needs to be user, assistant, system
|
||||||
|
filteredMessages, _ := filterMessagesForCurrentCharacter(chatBody.Messages)
|
||||||
bodyCopy := &models.ChatBody{
|
bodyCopy := &models.ChatBody{
|
||||||
Messages: make([]models.RoleMsg, len(chatBody.Messages)),
|
Messages: make([]models.RoleMsg, len(filteredMessages)),
|
||||||
Model: chatBody.Model,
|
Model: chatBody.Model,
|
||||||
Stream: chatBody.Stream,
|
Stream: chatBody.Stream,
|
||||||
}
|
}
|
||||||
for i, msg := range chatBody.Messages {
|
for i, msg := range filteredMessages {
|
||||||
if msg.Role == cfg.UserRole {
|
if msg.Role == cfg.UserRole {
|
||||||
bodyCopy.Messages[i] = msg
|
bodyCopy.Messages[i] = msg
|
||||||
bodyCopy.Messages[i].Role = "user"
|
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)
|
logger.Debug("formmsg deepseekercompletion", "link", cfg.CurrentAPI)
|
||||||
if msg != "" { // otherwise let the bot to continue
|
if msg != "" { // otherwise let the bot to continue
|
||||||
newMsg := models.RoleMsg{Role: role, Content: msg}
|
newMsg := models.RoleMsg{Role: role, Content: msg}
|
||||||
|
newMsg = processMessageTag(newMsg)
|
||||||
chatBody.Messages = append(chatBody.Messages, newMsg)
|
chatBody.Messages = append(chatBody.Messages, newMsg)
|
||||||
}
|
}
|
||||||
if !resume {
|
if !resume {
|
||||||
@@ -372,17 +391,14 @@ func (ds DeepSeekerCompletion) FormMsg(msg, role string, resume bool) (io.Reader
|
|||||||
// add to chat body
|
// add to chat body
|
||||||
chatBody.Messages = append(chatBody.Messages, models.RoleMsg{Role: cfg.ToolRole, Content: toolSysMsg})
|
chatBody.Messages = append(chatBody.Messages, models.RoleMsg{Role: cfg.ToolRole, Content: toolSysMsg})
|
||||||
}
|
}
|
||||||
messages := make([]string, len(chatBody.Messages))
|
filteredMessages, botPersona := filterMessagesForCurrentCharacter(chatBody.Messages)
|
||||||
for i, m := range chatBody.Messages {
|
messages := make([]string, len(filteredMessages))
|
||||||
|
for i, m := range filteredMessages {
|
||||||
messages[i] = m.ToPrompt()
|
messages[i] = m.ToPrompt()
|
||||||
}
|
}
|
||||||
prompt := strings.Join(messages, "\n")
|
prompt := strings.Join(messages, "\n")
|
||||||
// strings builder?
|
// strings builder?
|
||||||
if !resume {
|
if !resume {
|
||||||
botPersona := cfg.AssistantRole
|
|
||||||
if cfg.WriteNextMsgAsCompletionAgent != "" {
|
|
||||||
botPersona = cfg.WriteNextMsgAsCompletionAgent
|
|
||||||
}
|
|
||||||
botMsgStart := "\n" + botPersona + ":\n"
|
botMsgStart := "\n" + botPersona + ":\n"
|
||||||
prompt += botMsgStart
|
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)
|
logger.Debug("formmsg deepseekerchat", "link", cfg.CurrentAPI)
|
||||||
if msg != "" { // otherwise let the bot continue
|
if msg != "" { // otherwise let the bot continue
|
||||||
newMsg := models.RoleMsg{Role: role, Content: msg}
|
newMsg := models.RoleMsg{Role: role, Content: msg}
|
||||||
|
newMsg = processMessageTag(newMsg)
|
||||||
chatBody.Messages = append(chatBody.Messages, newMsg)
|
chatBody.Messages = append(chatBody.Messages, newMsg)
|
||||||
}
|
}
|
||||||
if !resume {
|
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))
|
logger.Debug("RAG message added to chat body", "message_count", len(chatBody.Messages))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
filteredMessages, _ := filterMessagesForCurrentCharacter(chatBody.Messages)
|
||||||
bodyCopy := &models.ChatBody{
|
bodyCopy := &models.ChatBody{
|
||||||
Messages: make([]models.RoleMsg, len(chatBody.Messages)),
|
Messages: make([]models.RoleMsg, len(filteredMessages)),
|
||||||
Model: chatBody.Model,
|
Model: chatBody.Model,
|
||||||
Stream: chatBody.Stream,
|
Stream: chatBody.Stream,
|
||||||
}
|
}
|
||||||
for i, msg := range chatBody.Messages {
|
for i, msg := range filteredMessages {
|
||||||
if msg.Role == cfg.UserRole || i == 1 {
|
if msg.Role == cfg.UserRole || i == 1 {
|
||||||
bodyCopy.Messages[i] = msg
|
bodyCopy.Messages[i] = msg
|
||||||
bodyCopy.Messages[i].Role = "user"
|
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)
|
logger.Debug("formmsg openroutercompletion", "link", cfg.CurrentAPI)
|
||||||
if msg != "" { // otherwise let the bot to continue
|
if msg != "" { // otherwise let the bot to continue
|
||||||
newMsg := models.RoleMsg{Role: role, Content: msg}
|
newMsg := models.RoleMsg{Role: role, Content: msg}
|
||||||
|
newMsg = processMessageTag(newMsg)
|
||||||
chatBody.Messages = append(chatBody.Messages, newMsg)
|
chatBody.Messages = append(chatBody.Messages, newMsg)
|
||||||
}
|
}
|
||||||
if !resume {
|
if !resume {
|
||||||
@@ -525,17 +544,14 @@ func (or OpenRouterCompletion) FormMsg(msg, role string, resume bool) (io.Reader
|
|||||||
// add to chat body
|
// add to chat body
|
||||||
chatBody.Messages = append(chatBody.Messages, models.RoleMsg{Role: cfg.ToolRole, Content: toolSysMsg})
|
chatBody.Messages = append(chatBody.Messages, models.RoleMsg{Role: cfg.ToolRole, Content: toolSysMsg})
|
||||||
}
|
}
|
||||||
messages := make([]string, len(chatBody.Messages))
|
filteredMessages, botPersona := filterMessagesForCurrentCharacter(chatBody.Messages)
|
||||||
for i, m := range chatBody.Messages {
|
messages := make([]string, len(filteredMessages))
|
||||||
|
for i, m := range filteredMessages {
|
||||||
messages[i] = m.ToPrompt()
|
messages[i] = m.ToPrompt()
|
||||||
}
|
}
|
||||||
prompt := strings.Join(messages, "\n")
|
prompt := strings.Join(messages, "\n")
|
||||||
// strings builder?
|
// strings builder?
|
||||||
if !resume {
|
if !resume {
|
||||||
botPersona := cfg.AssistantRole
|
|
||||||
if cfg.WriteNextMsgAsCompletionAgent != "" {
|
|
||||||
botPersona = cfg.WriteNextMsgAsCompletionAgent
|
|
||||||
}
|
|
||||||
botMsgStart := "\n" + botPersona + ":\n"
|
botMsgStart := "\n" + botPersona + ":\n"
|
||||||
prompt += botMsgStart
|
prompt += botMsgStart
|
||||||
}
|
}
|
||||||
@@ -619,6 +635,7 @@ func (or OpenRouterChat) FormMsg(msg, role string, resume bool) (io.Reader, erro
|
|||||||
// Create a simple text message
|
// Create a simple text message
|
||||||
newMsg = models.NewRoleMsg(role, msg)
|
newMsg = models.NewRoleMsg(role, msg)
|
||||||
}
|
}
|
||||||
|
newMsg = processMessageTag(newMsg)
|
||||||
chatBody.Messages = append(chatBody.Messages, newMsg)
|
chatBody.Messages = append(chatBody.Messages, newMsg)
|
||||||
}
|
}
|
||||||
if !resume {
|
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
|
// Create copy of chat body with standardized user role
|
||||||
|
filteredMessages, _ := filterMessagesForCurrentCharacter(chatBody.Messages)
|
||||||
bodyCopy := &models.ChatBody{
|
bodyCopy := &models.ChatBody{
|
||||||
Messages: make([]models.RoleMsg, len(chatBody.Messages)),
|
Messages: make([]models.RoleMsg, len(filteredMessages)),
|
||||||
Model: chatBody.Model,
|
Model: chatBody.Model,
|
||||||
Stream: chatBody.Stream,
|
Stream: chatBody.Stream,
|
||||||
}
|
}
|
||||||
for i, msg := range chatBody.Messages {
|
for i, msg := range filteredMessages {
|
||||||
bodyCopy.Messages[i] = msg
|
bodyCopy.Messages[i] = msg
|
||||||
// Standardize role if it's a user role
|
// Standardize role if it's a user role
|
||||||
if bodyCopy.Messages[i].Role == cfg.UserRole {
|
if bodyCopy.Messages[i].Role == cfg.UserRole {
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ type RoleMsg struct {
|
|||||||
Content string `json:"-"`
|
Content string `json:"-"`
|
||||||
ContentParts []interface{} `json:"-"`
|
ContentParts []interface{} `json:"-"`
|
||||||
ToolCallID string `json:"tool_call_id,omitempty"` // For tool response messages
|
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
|
hasContentParts bool // Flag to indicate which content type to marshal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,10 +105,12 @@ func (m RoleMsg) MarshalJSON() ([]byte, error) {
|
|||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content []interface{} `json:"content"`
|
Content []interface{} `json:"content"`
|
||||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||||
|
KnownTo []string `json:"known_to,omitempty"`
|
||||||
}{
|
}{
|
||||||
Role: m.Role,
|
Role: m.Role,
|
||||||
Content: m.ContentParts,
|
Content: m.ContentParts,
|
||||||
ToolCallID: m.ToolCallID,
|
ToolCallID: m.ToolCallID,
|
||||||
|
KnownTo: m.KnownTo,
|
||||||
}
|
}
|
||||||
return json.Marshal(aux)
|
return json.Marshal(aux)
|
||||||
} else {
|
} else {
|
||||||
@@ -116,10 +119,12 @@ func (m RoleMsg) MarshalJSON() ([]byte, error) {
|
|||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||||
|
KnownTo []string `json:"known_to,omitempty"`
|
||||||
}{
|
}{
|
||||||
Role: m.Role,
|
Role: m.Role,
|
||||||
Content: m.Content,
|
Content: m.Content,
|
||||||
ToolCallID: m.ToolCallID,
|
ToolCallID: m.ToolCallID,
|
||||||
|
KnownTo: m.KnownTo,
|
||||||
}
|
}
|
||||||
return json.Marshal(aux)
|
return json.Marshal(aux)
|
||||||
}
|
}
|
||||||
@@ -132,11 +137,13 @@ func (m *RoleMsg) UnmarshalJSON(data []byte) error {
|
|||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content []interface{} `json:"content"`
|
Content []interface{} `json:"content"`
|
||||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
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 {
|
if err := json.Unmarshal(data, &structured); err == nil && len(structured.Content) > 0 {
|
||||||
m.Role = structured.Role
|
m.Role = structured.Role
|
||||||
m.ContentParts = structured.Content
|
m.ContentParts = structured.Content
|
||||||
m.ToolCallID = structured.ToolCallID
|
m.ToolCallID = structured.ToolCallID
|
||||||
|
m.KnownTo = structured.KnownTo
|
||||||
m.hasContentParts = true
|
m.hasContentParts = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -146,6 +153,7 @@ func (m *RoleMsg) UnmarshalJSON(data []byte) error {
|
|||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||||
|
KnownTo []string `json:"known_to,omitempty"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &simple); err != nil {
|
if err := json.Unmarshal(data, &simple); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -153,6 +161,7 @@ func (m *RoleMsg) UnmarshalJSON(data []byte) error {
|
|||||||
m.Role = simple.Role
|
m.Role = simple.Role
|
||||||
m.Content = simple.Content
|
m.Content = simple.Content
|
||||||
m.ToolCallID = simple.ToolCallID
|
m.ToolCallID = simple.ToolCallID
|
||||||
|
m.KnownTo = simple.KnownTo
|
||||||
m.hasContentParts = false
|
m.hasContentParts = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -363,7 +372,8 @@ func (cb *ChatBody) MakeStopSlice() []string {
|
|||||||
for _, m := range cb.Messages {
|
for _, m := range cb.Messages {
|
||||||
namesMap[m.Role] = struct{}{}
|
namesMap[m.Role] = struct{}{}
|
||||||
}
|
}
|
||||||
ss := []string{"<|im_end|>"}
|
ss := make([]string, 0, 1+len(namesMap))
|
||||||
|
ss = append(ss, "<|im_end|>")
|
||||||
for k := range namesMap {
|
for k := range namesMap {
|
||||||
ss = append(ss, k+":\n")
|
ss = append(ss, k+":\n")
|
||||||
}
|
}
|
||||||
@@ -523,7 +533,7 @@ type LCPModels struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (lcp *LCPModels) ListModels() []string {
|
func (lcp *LCPModels) ListModels() []string {
|
||||||
resp := []string{}
|
resp := make([]string, 0, len(lcp.Data))
|
||||||
for _, model := range lcp.Data {
|
for _, model := range lcp.Data {
|
||||||
resp = append(resp, model.ID)
|
resp = append(resp, model.ID)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user