feat(telegram): inject bot self-identity into agent system prompt

After stripBotMention removes the bot's own @mention from content, the LLM
still lacks knowledge of its own platform handle. In multi-bot groups (e.g.
"@botA @botB do X") the other bot's mention remains in the content and the
agent incorrectly treats it as the intended target, replying NO_REPLY.

Capture the bot's first_name from GetMe during channel Start and expose it
alongside the username via a new MetaChannelSelfIdentity metadata key. The
consumer appends the formatted hint ("You are @{username} ({display_name})
on this Telegram channel.") to the agent's extraSystemPrompt so the LLM can
reliably identify itself across single- and multi-bot scenarios.

Falls back to "You are @{username} on this Telegram channel." when the
display name is not available, and no-ops when the username has not been
resolved yet (startup race).
This commit is contained in:
viettranx committed 2026-04-23 08:31:53 +07:00
1 parent e392fca14d
commit 4d6ebe9c58
6 files changed
+63

No files matched your search

+10
View File
@@ -255,6 +255,16 @@ func processNormalMessage(
extraPrompt += tsp
}
// Append channel-provided self-identity hint (e.g. "You are @bot (Name) on Telegram").
// Prevents the LLM from treating its own platform handle as another bot when users
// @mention it directly or reference it alongside another bot in multi-bot groups.
if identity := msg.Metadata[tools.MetaChannelSelfIdentity]; identity != "" {
if extraPrompt != "" {
extraPrompt += "\n\n"
}
extraPrompt += identity
}
// Per-topic skill filter override (from group/topic config hierarchy).
var skillFilter []string
if ts := msg.Metadata[tools.MetaTopicSkills]; ts != "" {
+2
View File
@@ -39,6 +39,7 @@ type Channel struct {
reactions sync.Map // localKey string → *StatusReactionController
threadIDs sync.Map // localKey string → messageThreadID int (for forum topic routing)
mentionMode string // "strict" (default) or "yield"
botDisplayName string // bot's first_name from GetMe (e.g. "ViệtBot"); captured once at Start
pollCancel context.CancelFunc // cancels the long polling context
pollDone chan struct{} // closed when polling goroutine exits
handlerWg sync.WaitGroup // tracks in-flight handler goroutines for graceful shutdown
@@ -189,6 +190,7 @@ func (c *Channel) Start(ctx context.Context) error {
username := ""
if me != nil {
username = me.Username
c.botDisplayName = me.FirstName
}
// Create a cancellable context for the polling goroutine.
+6
View File
@@ -595,6 +595,12 @@ func (c *Channel) handleMessage(ctx context.Context, update telego.Update) {
metadata[tools.MetaDMThreadID] = fmt.Sprintf("%d", dmThreadID)
metadata[tools.MetaMessageThreadID] = fmt.Sprintf("%d", dmThreadID)
}
// Self-identity hint so the LLM knows its own Telegram handle and does not
// confuse other bots' @mentions (preserved after stripBotMention) for its own.
if identity := buildSelfIdentityPrompt(c.bot.Username(), c.botDisplayName); identity != "" {
metadata[tools.MetaChannelSelfIdentity] = identity
}
if topicCfg.systemPrompt != "" {
metadata[tools.MetaTopicSystemPrompt] = topicCfg.systemPrompt
}
@@ -1,12 +1,28 @@
package telegram
import (
"fmt"
"regexp"
"strings"
"github.com/mymmrac/telego"
)
// buildSelfIdentityPrompt returns a short system-prompt snippet telling the LLM
// which Telegram handle represents itself, so it does not confuse its own
// @mention for a different bot — especially useful in multi-bot groups where
// other bots' mentions remain in the content after stripBotMention.
// Returns empty string when the bot username has not been resolved yet.
func buildSelfIdentityPrompt(botUsername, displayName string) string {
if botUsername == "" {
return ""
}
if displayName != "" {
return fmt.Sprintf("You are @%s (%s) on this Telegram channel.", botUsername, displayName)
}
return fmt.Sprintf("You are @%s on this Telegram channel.", botUsername)
}
// stripBotMention removes @botUsername tokens from text (case-insensitive).
// Applied after the mention gate passes so the LLM does not see its own Telegram handle
// and mistake itself for another bot (e.g. persona "Tiểu Hổ" receiving "@viet_super_bot vẽ...").
@@ -272,6 +272,30 @@ func TestStripBotMention_OnlyMentionBecomesEmpty(t *testing.T) {
}
}
// --- buildSelfIdentityPrompt ---
func TestBuildSelfIdentityPrompt_WithDisplayName(t *testing.T) {
got := buildSelfIdentityPrompt("viet_super_bot", "ViệtBot")
want := "You are @viet_super_bot (ViệtBot) on this Telegram channel."
if got != want {
t.Errorf("buildSelfIdentityPrompt = %q, want %q", got, want)
}
}
func TestBuildSelfIdentityPrompt_NoDisplayName(t *testing.T) {
got := buildSelfIdentityPrompt("viet_super_bot", "")
want := "You are @viet_super_bot on this Telegram channel."
if got != want {
t.Errorf("buildSelfIdentityPrompt = %q, want %q", got, want)
}
}
func TestBuildSelfIdentityPrompt_EmptyUsername(t *testing.T) {
if got := buildSelfIdentityPrompt("", "Name"); got != "" {
t.Errorf("buildSelfIdentityPrompt with empty username should return empty; got %q", got)
}
}
// --- isServiceMessage ---
func TestIsServiceMessage_WithText(t *testing.T) {
+5
View File
@@ -44,6 +44,11 @@ const (
MetaUserName = "user_name"
MetaTopicSystemPrompt = "topic_system_prompt"
MetaTopicSkills = "topic_skills"
// MetaChannelSelfIdentity carries a channel-provided self-identity hint
// (e.g. "You are @viet_super_bot (ViệtBot) on this Telegram channel.")
// appended to the agent's system prompt so the LLM does not confuse its own
// platform handle for a different bot when users @mention it.
MetaChannelSelfIdentity = "channel_self_identity"
)
// Task metadata keys stored in store.TeamTaskData.Metadata.