Enha: do not remove tag
This commit is contained in:
26
bot.go
26
bot.go
@@ -71,9 +71,9 @@ var (
|
|||||||
|
|
||||||
// parseKnownToTag extracts known_to list from content using configured tag.
|
// parseKnownToTag extracts known_to list from content using configured tag.
|
||||||
// Returns cleaned content and list of character names.
|
// Returns cleaned content and list of character names.
|
||||||
func parseKnownToTag(content string) (string, []string) {
|
func parseKnownToTag(content string) []string {
|
||||||
if cfg == nil || !cfg.CharSpecificContextEnabled {
|
if cfg == nil || !cfg.CharSpecificContextEnabled {
|
||||||
return content, nil
|
return nil
|
||||||
}
|
}
|
||||||
tag := cfg.CharSpecificContextTag
|
tag := cfg.CharSpecificContextTag
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
@@ -84,17 +84,15 @@ func parseKnownToTag(content string) (string, []string) {
|
|||||||
re := regexp.MustCompile(pattern)
|
re := regexp.MustCompile(pattern)
|
||||||
matches := re.FindAllStringSubmatch(content, -1)
|
matches := re.FindAllStringSubmatch(content, -1)
|
||||||
if len(matches) == 0 {
|
if len(matches) == 0 {
|
||||||
return content, nil
|
return nil
|
||||||
}
|
}
|
||||||
// There may be multiple tags; we combine all.
|
// There may be multiple tags; we combine all.
|
||||||
var knownTo []string
|
var knownTo []string
|
||||||
cleaned := content
|
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
if len(match) < 2 {
|
if len(match) < 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Remove the entire matched tag from content
|
// Remove the entire matched tag from content
|
||||||
cleaned = strings.Replace(cleaned, match[0], "", 1)
|
|
||||||
list := strings.TrimSpace(match[1])
|
list := strings.TrimSpace(match[1])
|
||||||
if list == "" {
|
if list == "" {
|
||||||
continue
|
continue
|
||||||
@@ -108,7 +106,7 @@ func parseKnownToTag(content string) (string, []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Also remove any leftover trailing "__" that might be orphaned? Not needed.
|
// Also remove any leftover trailing "__" that might be orphaned? Not needed.
|
||||||
return strings.TrimSpace(cleaned), knownTo
|
return knownTo
|
||||||
}
|
}
|
||||||
|
|
||||||
// processMessageTag processes a message for known_to tag and sets KnownTo field.
|
// processMessageTag processes a message for known_to tag and sets KnownTo field.
|
||||||
@@ -120,11 +118,8 @@ func processMessageTag(msg models.RoleMsg) models.RoleMsg {
|
|||||||
}
|
}
|
||||||
// If KnownTo already set, assume tag already processed (content cleaned).
|
// If KnownTo already set, assume tag already processed (content cleaned).
|
||||||
// However, we still check for new tags (maybe added later).
|
// However, we still check for new tags (maybe added later).
|
||||||
cleaned, knownTo := parseKnownToTag(msg.Content)
|
knownTo := parseKnownToTag(msg.Content)
|
||||||
logger.Info("processing tags", "msg", msg.Content, "known_to", knownTo)
|
logger.Info("processing tags", "msg", msg.Content, "known_to", knownTo)
|
||||||
if cleaned != msg.Content {
|
|
||||||
msg.Content = cleaned
|
|
||||||
}
|
|
||||||
// If tag found, replace KnownTo with new list (merge with existing?)
|
// If tag found, replace KnownTo with new list (merge with existing?)
|
||||||
// For simplicity, if knownTo is not nil, replace.
|
// For simplicity, if knownTo is not nil, replace.
|
||||||
if knownTo != nil {
|
if knownTo != nil {
|
||||||
@@ -789,10 +784,17 @@ out:
|
|||||||
if resume {
|
if resume {
|
||||||
chatBody.Messages[len(chatBody.Messages)-1].Content += respText.String()
|
chatBody.Messages[len(chatBody.Messages)-1].Content += respText.String()
|
||||||
// lastM.Content = lastM.Content + respText.String()
|
// lastM.Content = lastM.Content + respText.String()
|
||||||
|
// Process the updated message to check for known_to tags in resumed response
|
||||||
|
updatedMsg := chatBody.Messages[len(chatBody.Messages)-1]
|
||||||
|
processedMsg := processMessageTag(updatedMsg)
|
||||||
|
chatBody.Messages[len(chatBody.Messages)-1] = processedMsg
|
||||||
} else {
|
} else {
|
||||||
chatBody.Messages = append(chatBody.Messages, models.RoleMsg{
|
newMsg := models.RoleMsg{
|
||||||
Role: botPersona, Content: respText.String(),
|
Role: botPersona, Content: respText.String(),
|
||||||
})
|
}
|
||||||
|
// Process the new message to check for known_to tags in LLM response
|
||||||
|
newMsg = processMessageTag(newMsg)
|
||||||
|
chatBody.Messages = append(chatBody.Messages, newMsg)
|
||||||
}
|
}
|
||||||
logger.Debug("chatRound: before cleanChatBody", "messages_before_clean", len(chatBody.Messages))
|
logger.Debug("chatRound: before cleanChatBody", "messages_before_clean", len(chatBody.Messages))
|
||||||
for i, msg := range chatBody.Messages {
|
for i, msg := range chatBody.Messages {
|
||||||
|
|||||||
241
bot_test.go
241
bot_test.go
@@ -118,34 +118,34 @@ func TestConsolidateConsecutiveAssistantMessages(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := consolidateConsecutiveAssistantMessages(tt.input)
|
result := consolidateConsecutiveAssistantMessages(tt.input)
|
||||||
|
|
||||||
if len(result) != len(tt.expected) {
|
if len(result) != len(tt.expected) {
|
||||||
t.Errorf("Expected %d messages, got %d", len(tt.expected), len(result))
|
t.Errorf("Expected %d messages, got %d", len(tt.expected), len(result))
|
||||||
t.Logf("Result: %+v", result)
|
t.Logf("Result: %+v", result)
|
||||||
t.Logf("Expected: %+v", tt.expected)
|
t.Logf("Expected: %+v", tt.expected)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, expectedMsg := range tt.expected {
|
for i, expectedMsg := range tt.expected {
|
||||||
if i >= len(result) {
|
if i >= len(result) {
|
||||||
t.Errorf("Result has fewer messages than expected at index %d", i)
|
t.Errorf("Result has fewer messages than expected at index %d", i)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
actualMsg := result[i]
|
actualMsg := result[i]
|
||||||
if actualMsg.Role != expectedMsg.Role {
|
if actualMsg.Role != expectedMsg.Role {
|
||||||
t.Errorf("Message %d: expected role '%s', got '%s'", i, expectedMsg.Role, actualMsg.Role)
|
t.Errorf("Message %d: expected role '%s', got '%s'", i, expectedMsg.Role, actualMsg.Role)
|
||||||
}
|
}
|
||||||
|
|
||||||
if actualMsg.Content != expectedMsg.Content {
|
if actualMsg.Content != expectedMsg.Content {
|
||||||
t.Errorf("Message %d: expected content '%s', got '%s'", i, expectedMsg.Content, actualMsg.Content)
|
t.Errorf("Message %d: expected content '%s', got '%s'", i, expectedMsg.Content, actualMsg.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
if actualMsg.ToolCallID != expectedMsg.ToolCallID {
|
if actualMsg.ToolCallID != expectedMsg.ToolCallID {
|
||||||
t.Errorf("Message %d: expected ToolCallID '%s', got '%s'", i, expectedMsg.ToolCallID, actualMsg.ToolCallID)
|
t.Errorf("Message %d: expected ToolCallID '%s', got '%s'", i, expectedMsg.ToolCallID, actualMsg.ToolCallID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional check: ensure no messages were lost
|
// Additional check: ensure no messages were lost
|
||||||
if !reflect.DeepEqual(result, tt.expected) {
|
if !reflect.DeepEqual(result, tt.expected) {
|
||||||
t.Errorf("Result does not match expected:\nResult: %+v\nExpected: %+v", result, tt.expected)
|
t.Errorf("Result does not match expected:\nResult: %+v\nExpected: %+v", result, tt.expected)
|
||||||
@@ -290,92 +290,92 @@ func TestConvertJSONToMapStringString(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseKnownToTag(t *testing.T) {
|
func TestParseKnownToTag(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
content string
|
content string
|
||||||
enabled bool
|
enabled bool
|
||||||
tag string
|
tag string
|
||||||
wantCleaned string
|
wantCleaned string
|
||||||
wantKnownTo []string
|
wantKnownTo []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "feature disabled returns original",
|
name: "feature disabled returns original",
|
||||||
content: "Hello __known_to_chars__Alice__",
|
content: "Hello __known_to_chars__Alice__",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
tag: "__known_to_chars__",
|
tag: "__known_to_chars__",
|
||||||
wantCleaned: "Hello __known_to_chars__Alice__",
|
wantCleaned: "Hello __known_to_chars__Alice__",
|
||||||
wantKnownTo: nil,
|
wantKnownTo: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no tag returns original",
|
name: "no tag returns original",
|
||||||
content: "Hello Alice",
|
content: "Hello Alice",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
tag: "__known_to_chars__",
|
tag: "__known_to_chars__",
|
||||||
wantCleaned: "Hello Alice",
|
wantCleaned: "Hello Alice",
|
||||||
wantKnownTo: nil,
|
wantKnownTo: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single tag with one char",
|
name: "single tag with one char",
|
||||||
content: "Hello __known_to_chars__Alice__",
|
content: "Hello __known_to_chars__Alice__",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
tag: "__known_to_chars__",
|
tag: "__known_to_chars__",
|
||||||
wantCleaned: "Hello",
|
wantCleaned: "Hello",
|
||||||
wantKnownTo: []string{"Alice"},
|
wantKnownTo: []string{"Alice"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "single tag with two chars",
|
name: "single tag with two chars",
|
||||||
content: "Secret __known_to_chars__Alice,Bob__ message",
|
content: "Secret __known_to_chars__Alice,Bob__ message",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
tag: "__known_to_chars__",
|
tag: "__known_to_chars__",
|
||||||
wantCleaned: "Secret message",
|
wantCleaned: "Secret message",
|
||||||
wantKnownTo: []string{"Alice", "Bob"},
|
wantKnownTo: []string{"Alice", "Bob"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tag at beginning",
|
name: "tag at beginning",
|
||||||
content: "__known_to_chars__Alice__ Hello",
|
content: "__known_to_chars__Alice__ Hello",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
tag: "__known_to_chars__",
|
tag: "__known_to_chars__",
|
||||||
wantCleaned: "Hello",
|
wantCleaned: "Hello",
|
||||||
wantKnownTo: []string{"Alice"},
|
wantKnownTo: []string{"Alice"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tag at end",
|
name: "tag at end",
|
||||||
content: "Hello __known_to_chars__Alice__",
|
content: "Hello __known_to_chars__Alice__",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
tag: "__known_to_chars__",
|
tag: "__known_to_chars__",
|
||||||
wantCleaned: "Hello",
|
wantCleaned: "Hello",
|
||||||
wantKnownTo: []string{"Alice"},
|
wantKnownTo: []string{"Alice"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple tags",
|
name: "multiple tags",
|
||||||
content: "First __known_to_chars__Alice__ then __known_to_chars__Bob__",
|
content: "First __known_to_chars__Alice__ then __known_to_chars__Bob__",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
tag: "__known_to_chars__",
|
tag: "__known_to_chars__",
|
||||||
wantCleaned: "First then",
|
wantCleaned: "First then",
|
||||||
wantKnownTo: []string{"Alice", "Bob"},
|
wantKnownTo: []string{"Alice", "Bob"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "custom tag",
|
name: "custom tag",
|
||||||
content: "Secret __secret__Alice,Bob__ message",
|
content: "Secret __secret__Alice,Bob__ message",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
tag: "__secret__",
|
tag: "__secret__",
|
||||||
wantCleaned: "Secret message",
|
wantCleaned: "Secret message",
|
||||||
wantKnownTo: []string{"Alice", "Bob"},
|
wantKnownTo: []string{"Alice", "Bob"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty list",
|
name: "empty list",
|
||||||
content: "Secret __known_to_chars____",
|
content: "Secret __known_to_chars____",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
tag: "__known_to_chars__",
|
tag: "__known_to_chars__",
|
||||||
wantCleaned: "Secret",
|
wantCleaned: "Secret",
|
||||||
wantKnownTo: nil,
|
wantKnownTo: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "whitespace around commas",
|
name: "whitespace around commas",
|
||||||
content: "__known_to_chars__ Alice , Bob , Carl __",
|
content: "__known_to_chars__ Alice , Bob , Carl __",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
tag: "__known_to_chars__",
|
tag: "__known_to_chars__",
|
||||||
wantCleaned: "",
|
wantCleaned: "",
|
||||||
wantKnownTo: []string{"Alice", "Bob", "Carl"},
|
wantKnownTo: []string{"Alice", "Bob", "Carl"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,13 +387,7 @@ func TestParseKnownToTag(t *testing.T) {
|
|||||||
CharSpecificContextTag: tt.tag,
|
CharSpecificContextTag: tt.tag,
|
||||||
}
|
}
|
||||||
cfg = testCfg
|
cfg = testCfg
|
||||||
|
knownTo := parseKnownToTag(tt.content)
|
||||||
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) {
|
if len(knownTo) != len(tt.wantKnownTo) {
|
||||||
t.Errorf("parseKnownToTag() knownTo length = %v, want %v", 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("got: %v", knownTo)
|
||||||
@@ -411,11 +405,11 @@ func TestParseKnownToTag(t *testing.T) {
|
|||||||
|
|
||||||
func TestProcessMessageTag(t *testing.T) {
|
func TestProcessMessageTag(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
msg models.RoleMsg
|
msg models.RoleMsg
|
||||||
enabled bool
|
enabled bool
|
||||||
tag string
|
tag string
|
||||||
wantMsg models.RoleMsg
|
wantMsg models.RoleMsg
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "feature disabled returns unchanged",
|
name: "feature disabled returns unchanged",
|
||||||
@@ -488,6 +482,21 @@ func TestProcessMessageTag(t *testing.T) {
|
|||||||
KnownTo: []string{"Bob", "Alice"},
|
KnownTo: []string{"Bob", "Alice"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "example from real use",
|
||||||
|
msg: models.RoleMsg{
|
||||||
|
Role: "Alice",
|
||||||
|
Content: "I'll start with a simple one! The word is 'banana'. (ooc: __known_to_chars__Bob__)",
|
||||||
|
KnownTo: []string{"Alice"}, // from previous processing
|
||||||
|
},
|
||||||
|
enabled: true,
|
||||||
|
tag: "__known_to_chars__",
|
||||||
|
wantMsg: models.RoleMsg{
|
||||||
|
Role: "Alice",
|
||||||
|
Content: "I'll start with a simple one! The word is 'banana'. (ooc: __known_to_chars__Bob__)",
|
||||||
|
KnownTo: []string{"Bob", "Alice"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -497,13 +506,7 @@ func TestProcessMessageTag(t *testing.T) {
|
|||||||
CharSpecificContextTag: tt.tag,
|
CharSpecificContextTag: tt.tag,
|
||||||
}
|
}
|
||||||
cfg = testCfg
|
cfg = testCfg
|
||||||
|
|
||||||
got := processMessageTag(tt.msg)
|
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) {
|
if len(got.KnownTo) != len(tt.wantMsg.KnownTo) {
|
||||||
t.Errorf("processMessageTag() KnownTo length = %v, want %v", 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("got: %v", got.KnownTo)
|
||||||
@@ -530,7 +533,7 @@ func TestProcessMessageTag(t *testing.T) {
|
|||||||
func TestFilterMessagesForCharacter(t *testing.T) {
|
func TestFilterMessagesForCharacter(t *testing.T) {
|
||||||
messages := []models.RoleMsg{
|
messages := []models.RoleMsg{
|
||||||
{Role: "system", Content: "System message", KnownTo: nil}, // visible to all
|
{Role: "system", Content: "System message", KnownTo: nil}, // visible to all
|
||||||
{Role: "Alice", Content: "Hello everyone", 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: "Alice", Content: "Secret for Bob", KnownTo: []string{"Alice", "Bob"}},
|
||||||
{Role: "Bob", Content: "Reply to Alice", 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: "Alice", Content: "Private to Carl", KnownTo: []string{"Alice", "Carl"}},
|
||||||
@@ -538,46 +541,46 @@ func TestFilterMessagesForCharacter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
enabled bool
|
enabled bool
|
||||||
character string
|
character string
|
||||||
wantIndices []int // indices from original messages that should be included
|
wantIndices []int // indices from original messages that should be included
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "feature disabled returns all",
|
name: "feature disabled returns all",
|
||||||
enabled: false,
|
enabled: false,
|
||||||
character: "Alice",
|
character: "Alice",
|
||||||
wantIndices: []int{0,1,2,3,4,5},
|
wantIndices: []int{0, 1, 2, 3, 4, 5},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "character empty returns all",
|
name: "character empty returns all",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
character: "",
|
character: "",
|
||||||
wantIndices: []int{0,1,2,3,4,5},
|
wantIndices: []int{0, 1, 2, 3, 4, 5},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Alice sees all including Carl-private",
|
name: "Alice sees all including Carl-private",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
character: "Alice",
|
character: "Alice",
|
||||||
wantIndices: []int{0,1,2,3,4,5},
|
wantIndices: []int{0, 1, 2, 3, 4, 5},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Bob sees Alice-Bob secrets and all public",
|
name: "Bob sees Alice-Bob secrets and all public",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
character: "Bob",
|
character: "Bob",
|
||||||
wantIndices: []int{0,1,2,3,5},
|
wantIndices: []int{0, 1, 2, 3, 5},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Carl sees Alice-Carl secret and public",
|
name: "Carl sees Alice-Carl secret and public",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
character: "Carl",
|
character: "Carl",
|
||||||
wantIndices: []int{0,1,4,5},
|
wantIndices: []int{0, 1, 4, 5},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "David sees only public messages",
|
name: "David sees only public messages",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
character: "David",
|
character: "David",
|
||||||
wantIndices: []int{0,1,5},
|
wantIndices: []int{0, 1, 5},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,4 +607,4 @@ func TestFilterMessagesForCharacter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user