From 5f55cec0e2ae196a95843365563bda22d72d215c Mon Sep 17 00:00:00 2001 From: Alexey Skobkin Date: Tue, 12 Mar 2024 22:06:40 +0300 Subject: [PATCH 1/6] fix #15 slog usage. Also adding more logging. --- bot/bot.go | 42 ++++++++++++++++++++---------------------- extractor/extractor.go | 7 +++++++ llm/llm.go | 12 ++++++------ main.go | 2 +- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index e855eb2..c187d2f 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -38,28 +38,23 @@ func NewBot(api *telego.Bot, llm *llm.LlmConnector, extractor *extractor.Extract func (b *Bot) Run() error { botUser, err := b.api.GetMe() if err != nil { - slog.Error("Cannot retrieve api user", err) + slog.Error("Cannot retrieve api user", "error", err) return ErrGetMe } - slog.Info("Running api as", map[string]any{ - "id": botUser.ID, - "username": botUser.Username, - "name": botUser.FirstName, - "is_bot": botUser.IsBot, - }) + slog.Info("Running api as", "id", botUser.ID, "username", botUser.Username, "name", botUser.FirstName, "is_bot", botUser.IsBot) updates, err := b.api.UpdatesViaLongPolling(nil) if err != nil { - slog.Error("Cannot get update channel", err) + slog.Error("Cannot get update channel", "error", err) return ErrUpdatesChannel } bh, err := th.NewBotHandler(b.api, updates) if err != nil { - slog.Error("Cannot initialize bot handler", err) + slog.Error("Cannot initialize bot handler", "error", err) return ErrHandlerInit } @@ -83,7 +78,7 @@ func (b *Bot) Run() error { } func (b *Bot) heyHandler(bot *telego.Bot, update telego.Update) { - slog.Info("/hey") + slog.Info("/hey", "message-text", update.Message.Text) b.stats.HeyRequest() @@ -111,7 +106,7 @@ func (b *Bot) heyHandler(bot *telego.Bot, update telego.Update) { return } - slog.Debug("Got completion. Going to send.", llmReply) + slog.Debug("Got completion. Going to send.", "llm-reply", llmReply) message := tu.Message( chatID, @@ -119,16 +114,15 @@ func (b *Bot) heyHandler(bot *telego.Bot, update telego.Update) { ).WithParseMode("Markdown") _, err = bot.SendMessage(b.reply(update.Message, message)) - if err != nil { - slog.Error("Can't send reply message", err) + slog.Error("Can't send reply message", "error", err) b.trySendReplyError(update.Message) } } func (b *Bot) summarizeHandler(bot *telego.Bot, update telego.Update) { - slog.Info("/summarize", update.Message.Text) + slog.Info("/summarize", "message-text", update.Message.Text) b.stats.SummarizeRequest() @@ -151,7 +145,7 @@ func (b *Bot) summarizeHandler(bot *telego.Bot, update telego.Update) { _, err := url.ParseRequestURI(args[1]) if err != nil { - slog.Error("Provided URL is not valid", args[1]) + slog.Error("Provided URL is not valid", "url", args[1]) _, _ = b.api.SendMessage(b.reply(update.Message, tu.Message( chatID, @@ -163,7 +157,7 @@ func (b *Bot) summarizeHandler(bot *telego.Bot, update telego.Update) { article, err := b.extractor.GetArticleFromUrl(args[1]) if err != nil { - slog.Error("Cannot retrieve an article using extractor", err) + slog.Error("Cannot retrieve an article using extractor", "error", err) } llmReply, err := b.llm.Summarize(article.Text, llm.ModelMistralUncensored) @@ -178,7 +172,7 @@ func (b *Bot) summarizeHandler(bot *telego.Bot, update telego.Update) { return } - slog.Debug("Got completion. Going to send.", llmReply) + slog.Debug("Got completion. Going to send.", "llm-reply", llmReply) message := tu.Message( chatID, @@ -188,7 +182,7 @@ func (b *Bot) summarizeHandler(bot *telego.Bot, update telego.Update) { _, err = bot.SendMessage(b.reply(update.Message, message)) if err != nil { - slog.Error("Can't send reply message", err) + slog.Error("Can't send reply message", "error", err) b.trySendReplyError(update.Message) } @@ -209,7 +203,7 @@ func (b *Bot) helpHandler(bot *telego.Bot, update telego.Update) { "/help - Show this help", ))) if err != nil { - slog.Error("Cannot send a message", err) + slog.Error("Cannot send a message", "error", err) b.trySendReplyError(update.Message) } @@ -228,7 +222,7 @@ func (b *Bot) startHandler(bot *telego.Bot, update telego.Update) { "Check out /help to learn how to use this bot.", ))) if err != nil { - slog.Error("Cannot send a message", err) + slog.Error("Cannot send a message", "error", err) b.trySendReplyError(update.Message) } @@ -249,7 +243,7 @@ func (b *Bot) statsHandler(bot *telego.Bot, update telego.Update) { "```", )).WithParseMode("Markdown")) if err != nil { - slog.Error("Cannot send a message", err) + slog.Error("Cannot send a message", "error", err) b.trySendReplyError(update.Message) } @@ -261,6 +255,8 @@ func (b *Bot) createLlmRequestContext(update telego.Update) llm.RequestContext { rc := llm.RequestContext{} if message == nil { + slog.Debug("request context creation problem: no message provided. returning empty context.", "request-context", rc) + return rc } @@ -281,6 +277,8 @@ func (b *Bot) createLlmRequestContext(update telego.Update) llm.RequestContext { Type: chat.Type, } + slog.Debug("request context created", "request-context", rc) + return rc } @@ -295,7 +293,7 @@ func (b *Bot) sendTyping(chatId telego.ChatID) { err := b.api.SendChatAction(tu.ChatAction(chatId, "typing")) if err != nil { - slog.Error("Cannot set chat action", err) + slog.Error("Cannot set chat action", "error", err) } } diff --git a/extractor/extractor.go b/extractor/extractor.go index 82f7fdf..47cb1bd 100644 --- a/extractor/extractor.go +++ b/extractor/extractor.go @@ -3,6 +3,7 @@ package extractor import ( "errors" "github.com/advancedlogic/GoOse" + "log/slog" ) var ( @@ -28,12 +29,18 @@ type Article struct { } func (e *Extractor) GetArticleFromUrl(url string) (Article, error) { + slog.Info("extractor: requested extraction from URL ", "url", url) + article, err := e.goose.ExtractFromURL(url) if err != nil { + slog.Error("extractor: failed extracting from URL", "url", url) + return Article{}, ErrExtractFailed } + slog.Debug("extractor: article extracted", "article", article) + return Article{ Title: article.Title, Text: article.CleanedText, diff --git a/llm/llm.go b/llm/llm.go index 4a5084b..9728c8a 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -50,15 +50,15 @@ func (l *LlmConnector) HandleSingleRequest(text string, model string, requestCon resp, err := l.client.CreateChatCompletion(context.Background(), req) if err != nil { - slog.Error("LLM back-end request failed", err) + slog.Error("llm: LLM back-end request failed", "error", err) return "", ErrLlmBackendRequestFailed } - slog.Debug("Received LLM back-end response", resp) + slog.Debug("llm: Received LLM back-end response", "response", resp) if len(resp.Choices) < 1 { - slog.Error("LLM back-end reply has no choices") + slog.Error("llm: LLM back-end reply has no choices") return "", ErrNoChoices } @@ -86,15 +86,15 @@ func (l *LlmConnector) Summarize(text string, model string) (string, error) { resp, err := l.client.CreateChatCompletion(context.Background(), req) if err != nil { - slog.Error("LLM back-end request failed", err) + slog.Error("llm: LLM back-end request failed", "error", err) return "", ErrLlmBackendRequestFailed } - slog.Debug("Received LLM back-end response", resp) + slog.Debug("llm: Received LLM back-end response", resp) if len(resp.Choices) < 1 { - slog.Error("LLM back-end reply has no choices") + slog.Error("llm: LLM back-end reply has no choices") return "", ErrNoChoices } diff --git a/main.go b/main.go index b2f72a6..e184ea3 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,7 @@ func main() { err = botService.Run() if err != nil { - slog.Error("Running bot finished with an error", err) + slog.Error("Running bot finished with an error", "error", err) os.Exit(1) } } From a2a37d025684bc030b1c39796a5faf8339897558 Mon Sep 17 00:00:00 2001 From: Alexey Skobkin Date: Tue, 12 Mar 2024 22:10:34 +0300 Subject: [PATCH 2/6] Trying to fix #13. Adding logging to the middleware. --- bot/middleware.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bot/middleware.go b/bot/middleware.go index 27ee120..a1e2204 100644 --- a/bot/middleware.go +++ b/bot/middleware.go @@ -3,15 +3,22 @@ package bot import ( "github.com/mymmrac/telego" "github.com/mymmrac/telego/telegohandler" + "log/slog" ) func (b *Bot) chatTypeStatsCounter(bot *telego.Bot, update telego.Update, next telegohandler.Handler) { message := update.Message if message == nil { + slog.Info("chat-type-middleware: update has no message. skipping.") + next(bot, update) + + return } + slog.Info("chat-type-middleware: counting message chat type in stats", "type", message.Chat.Type) + switch message.Chat.Type { case telego.ChatTypeGroup, telego.ChatTypeSupergroup: b.stats.GroupRequest() From bfacbb9f982b8ad7cd8d74e2c3efdad90e589bd9 Mon Sep 17 00:00:00 2001 From: Alexey Skobkin Date: Tue, 12 Mar 2024 22:12:58 +0300 Subject: [PATCH 3/6] Tweaking summarizer prompt a bit. --- llm/llm.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/llm/llm.go b/llm/llm.go index 9728c8a..df52d28 100644 --- a/llm/llm.go +++ b/llm/llm.go @@ -72,8 +72,9 @@ func (l *LlmConnector) Summarize(text string, model string) (string, error) { Messages: []openai.ChatCompletionMessage{ { Role: openai.ChatMessageRoleSystem, - Content: "You are a short digest editor. Summarize the text you received " + - "as a list of bullet points with most important facts from the text. " + + Content: "You're a text shortener. Give a very brief summary of the main facts " + + "point by point. Format them as a list of bullet points. " + + "Avoid any commentaries and value judgement on the matter. " + "If possible, use the same language as the original text.", }, }, From 38fcd1a5a9ccb075c59626824fcdad059b1a1b72 Mon Sep 17 00:00:00 2001 From: Alexey Skobkin Date: Tue, 12 Mar 2024 22:13:32 +0300 Subject: [PATCH 4/6] Adding API URL suffix to the README.md. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 82b09c6..d5733f7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ```shell docker run \ -e OLLAMA_TOKEN=123 \ - -e OLLAMA_BASE_URL=http://ollama.tld:11434 \ + -e OLLAMA_BASE_URL=http://ollama.localhost:11434/v1 \ -e TELEGRAM_TOKEN=12345 \ skobkin/telegram-llm-bot ``` From d3c0bc28f10bec9da66eb2b070ea5bf96a3e3b4e Mon Sep 17 00:00:00 2001 From: Alexey Skobkin Date: Tue, 12 Mar 2024 23:01:05 +0300 Subject: [PATCH 5/6] Fix #18. Escaping underscore symbols to avoid Telegram's Bot API "Markdown" (v1) parser errors. --- bot/bot.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index c187d2f..c12c070 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -24,6 +24,8 @@ type Bot struct { llm *llm.LlmConnector extractor *extractor.Extractor stats *stats.Stats + + markdownV1Replacer *strings.Replacer } func NewBot(api *telego.Bot, llm *llm.LlmConnector, extractor *extractor.Extractor) *Bot { @@ -32,6 +34,14 @@ func NewBot(api *telego.Bot, llm *llm.LlmConnector, extractor *extractor.Extract llm: llm, extractor: extractor, stats: stats.NewStats(), + + markdownV1Replacer: strings.NewReplacer( + // https://core.telegram.org/bots/api#markdown-style + "_", "\\_", + //"*", "\\*", + //"`", "\\`", + //"[", "\\[", + ), } } @@ -110,7 +120,7 @@ func (b *Bot) heyHandler(bot *telego.Bot, update telego.Update) { message := tu.Message( chatID, - llmReply, + b.escapeMarkdownV1Symbols(llmReply), ).WithParseMode("Markdown") _, err = bot.SendMessage(b.reply(update.Message, message)) @@ -176,7 +186,7 @@ func (b *Bot) summarizeHandler(bot *telego.Bot, update telego.Update) { message := tu.Message( chatID, - llmReply, + b.escapeMarkdownV1Symbols(llmReply), ).WithParseMode("Markdown") _, err = bot.SendMessage(b.reply(update.Message, message)) @@ -282,6 +292,10 @@ func (b *Bot) createLlmRequestContext(update telego.Update) llm.RequestContext { return rc } +func (b *Bot) escapeMarkdownV1Symbols(input string) string { + return b.markdownV1Replacer.Replace(input) +} + func (b *Bot) reply(originalMessage *telego.Message, newMessage *telego.SendMessageParams) *telego.SendMessageParams { return newMessage.WithReplyParameters(&telego.ReplyParameters{ MessageID: originalMessage.MessageID, From 3fa7c2434fdb26c77857608645109f29e2bc8610 Mon Sep 17 00:00:00 2001 From: Alexey Skobkin Date: Tue, 12 Mar 2024 23:05:52 +0300 Subject: [PATCH 6/6] Fix #17. Implementing slog-based logger for telego and passing it into the library. --- bot/logger.go | 24 ++++++++++++++++++++++++ main.go | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 bot/logger.go diff --git a/bot/logger.go b/bot/logger.go new file mode 100644 index 0000000..7089e8d --- /dev/null +++ b/bot/logger.go @@ -0,0 +1,24 @@ +package bot + +import ( + "fmt" + "log/slog" +) + +type Logger struct { + prefix string +} + +func NewLogger(prefix string) Logger { + return Logger{ + prefix: prefix, + } +} + +func (l Logger) Debugf(format string, args ...any) { + slog.Debug(l.prefix + fmt.Sprint(format, args)) +} + +func (l Logger) Errorf(format string, args ...any) { + slog.Error(l.prefix + fmt.Sprintf(format, args)) +} diff --git a/main.go b/main.go index e184ea3..6611c9a 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,7 @@ func main() { llmc := llm.NewConnector(ollamaBaseUrl, ollamaToken) ext := extractor.NewExtractor() - telegramApi, err := tg.NewBot(telegramToken, tg.WithDefaultLogger(false, true)) + telegramApi, err := tg.NewBot(telegramToken, tg.WithLogger(bot.NewLogger("telego: "))) if err != nil { fmt.Println(err) os.Exit(1)