telegram: strip think blocks and split long outbound messages
This commit is contained in:
parent
0617e31647
commit
82aae32fe7
2 changed files with 65 additions and 12 deletions
|
|
@ -45,6 +45,10 @@ func (c *thinkingCancel) Cancel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const telegramMaxMessageChars = 3900
|
||||||
|
|
||||||
|
var thinkBlockPattern = regexp.MustCompile(`(?is)<think>.*?</think>`)
|
||||||
|
|
||||||
func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChannel, error) {
|
func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChannel, error) {
|
||||||
var opts []telego.BotOption
|
var opts []telego.BotOption
|
||||||
telegramCfg := cfg.Channels.Telegram
|
telegramCfg := cfg.Channels.Telegram
|
||||||
|
|
@ -157,35 +161,62 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||||
c.stopThinking.Delete(msg.ChatID)
|
c.stopThinking.Delete(msg.ChatID)
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlContent := markdownToTelegramHTML(msg.Content)
|
cleanContent := sanitizeTelegramOutgoingContent(msg.Content)
|
||||||
|
chunks := utils.SplitMessage(cleanContent, telegramMaxMessageChars)
|
||||||
|
if len(chunks) == 0 {
|
||||||
|
chunks = []string{cleanContent}
|
||||||
|
}
|
||||||
|
|
||||||
// Try to edit placeholder
|
// Try to edit placeholder
|
||||||
|
firstChunkSent := false
|
||||||
if pID, ok := c.placeholders.Load(msg.ChatID); ok {
|
if pID, ok := c.placeholders.Load(msg.ChatID); ok {
|
||||||
c.placeholders.Delete(msg.ChatID)
|
c.placeholders.Delete(msg.ChatID)
|
||||||
editMsg := tu.EditMessageText(tu.ID(chatID), pID.(int), htmlContent)
|
editMsg := tu.EditMessageText(tu.ID(chatID), pID.(int), markdownToTelegramHTML(chunks[0]))
|
||||||
editMsg.ParseMode = telego.ModeHTML
|
editMsg.ParseMode = telego.ModeHTML
|
||||||
|
|
||||||
if _, err = c.bot.EditMessageText(ctx, editMsg); err == nil {
|
if _, err = c.bot.EditMessageText(ctx, editMsg); err == nil {
|
||||||
return nil
|
firstChunkSent = true
|
||||||
}
|
}
|
||||||
// Fallback to new message if edit fails
|
// Fallback to new message if edit fails
|
||||||
}
|
}
|
||||||
|
|
||||||
tgMsg := tu.Message(tu.ID(chatID), htmlContent)
|
sendChunk := func(text string) error {
|
||||||
|
tgMsg := tu.Message(tu.ID(chatID), markdownToTelegramHTML(text))
|
||||||
tgMsg.ParseMode = telego.ModeHTML
|
tgMsg.ParseMode = telego.ModeHTML
|
||||||
|
if _, sendErr := c.bot.SendMessage(ctx, tgMsg); sendErr != nil {
|
||||||
if _, err = c.bot.SendMessage(ctx, tgMsg); err != nil {
|
|
||||||
logger.ErrorCF("telegram", "HTML parse failed, falling back to plain text", map[string]interface{}{
|
logger.ErrorCF("telegram", "HTML parse failed, falling back to plain text", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": sendErr.Error(),
|
||||||
})
|
})
|
||||||
tgMsg.ParseMode = ""
|
fallbackMsg := tu.Message(tu.ID(chatID), text)
|
||||||
_, err = c.bot.SendMessage(ctx, tgMsg)
|
fallbackMsg.ParseMode = ""
|
||||||
|
_, sendErr = c.bot.SendMessage(ctx, fallbackMsg)
|
||||||
|
return sendErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
startIdx := 0
|
||||||
|
if firstChunkSent {
|
||||||
|
startIdx = 1
|
||||||
|
}
|
||||||
|
for i := startIdx; i < len(chunks); i++ {
|
||||||
|
if err = sendChunk(chunks[i]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sanitizeTelegramOutgoingContent(content string) string {
|
||||||
|
cleaned := thinkBlockPattern.ReplaceAllString(content, "")
|
||||||
|
cleaned = strings.TrimSpace(cleaned)
|
||||||
|
if cleaned == "" {
|
||||||
|
return "(empty response)"
|
||||||
|
}
|
||||||
|
return cleaned
|
||||||
|
}
|
||||||
|
|
||||||
func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Message) error {
|
func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Message) error {
|
||||||
if message == nil {
|
if message == nil {
|
||||||
return fmt.Errorf("message is nil")
|
return fmt.Errorf("message is nil")
|
||||||
|
|
|
||||||
22
pkg/channels/telegram_test.go
Normal file
22
pkg/channels/telegram_test.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package channels
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSanitizeTelegramOutgoingContent_RemovesThinkBlock(t *testing.T) {
|
||||||
|
in := "<think>\nsecret reasoning\n</think>\n\nユーザー向け本文"
|
||||||
|
got := sanitizeTelegramOutgoingContent(in)
|
||||||
|
want := "ユーザー向け本文"
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("sanitizeTelegramOutgoingContent() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSanitizeTelegramOutgoingContent_EmptyAfterThink(t *testing.T) {
|
||||||
|
in := "<think>only reasoning</think>"
|
||||||
|
got := sanitizeTelegramOutgoingContent(in)
|
||||||
|
want := "(empty response)"
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("sanitizeTelegramOutgoingContent() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue