Teleflow Package Guide for LLMs
August 27, 2026 ยท View on GitHub
This document explains how to use the Teleflow Go package to build Telegram bots. It's designed to help an LLM understand the core concepts and common usage patterns for accomplishing coding tasks with Teleflow.
๐ฏ Core Goal of Teleflow
Teleflow is a Go framework for building Telegram bots. Its main goals are to:
- Simplify Conversational Flows: Provide a structured way to define multi-step interactions with users.
- Automate State Management: Handle user session data and flow progress automatically.
- Enable Type-Safe Interactions: Use Go generics for compile-time safety.
- Offer Clean API: Simple, consistent interface for all common bot operations.
๐งฉ Key Components
1. Bot
-
Purpose: The central object representing your Telegram bot. It manages connections and routes updates to handlers.
-
Initialization:
import ( "log" "os" teleflow "github.com/kslamph/teleflow/core" ) // Set up the router with your handlers router := teleflow.NewRouter() router.Handle("start", teleflow.Func(func(ctx *teleflow.Context) error { return ctx.ReplyText("Welcome!") })) // Register more handlers or flows as needed... // Create the bot with token and router bot, err := teleflow.NewBot(os.Getenv("TELEGRAM_BOT_TOKEN"), router) if err != nil { log.Fatal(err) } -
Starting the Bot:
log.Fatal(bot.Start()) // Starts the long polling loop to receive updates.
2. Context (teleflow.Context)
-
Purpose: Represents the current interaction state with a user. It's passed to all handlers and flow processing functions.
-
Key Functions:
ctx.Text(): Get the text of a message or the data of a callback query.ctx.UserID(): Get the ID of the user.ctx.ChatID(): Get the ID of the chat.ctx.ReplyText(message string): Send a simple text message.ctx.ReplyWithKeyboard(text string, markup interface{}): Send a message with a keyboard.ctx.ReplyTextWithParseMode(text string, parseMode ParseMode): Send a formatted text message.ctx.ReplyWithKeyboardAndParseMode(text string, markup interface{}, parseMode ParseMode): Send a formatted message with a keyboard.ctx.EditMessageText(messageID int, text string): Edit an existing message.ctx.EditMessageTextWithParseMode(messageID int, text string, parseMode ParseMode): Edit an existing message with formatting.ctx.EditMessageReplyMarkup(messageID int, markup tgbotapi.InlineKeyboardMarkup): Edit the keyboard of a message.ctx.EditMessageMedia(messageID int, media interface{}): Edit the media (e.g. photo) of a message.ctx.DeleteMessage(messageID int): Delete a message.ctx.AnswerCallback(): Acknowledge a callback query; must be called from your handler to stop the client's loading spinner (the framework never auto-answers).ctx.RegisterCallback(handler): Register a one-time handler for a callback query; auto-removed after execution.ctx.RegisterData(data): Store complex data under a unique ID for callback buttons (bypasses the 64-byte callback limit).ctx.GetData(): Retrieve and delete data stored byRegisterData.ctx.Set(key, value)/ctx.Get(key): Store and retrieve request-scoped data.ctx.Update(): Access the rawtgbotapi.Updateobject.ctx.API(): Access the underlyingtgbotapi.BotAPIinstance.
-
Parse Mode Constants: Teleflow provides constants for message formatting:
teleflow.ParseModeHTML: HTML formattingteleflow.ParseModeMarkdown: Markdown formattingteleflow.ParseModeMarkdownV2: MarkdownV2 formatting (recommended)
3. Handlers
-
Purpose: Functions that process incoming messages or commands.
-
Using Router:
router := teleflow.NewRouter() // Register a simple handler router.Handle("start", teleflow.Func(func(ctx *teleflow.Context) error { return ctx.ReplyText("Welcome!") })) // Register a flow router.Handle("register", registrationFlow) -
Default Handler:
router.Default(teleflow.Func(func(ctx *teleflow.Context) error { return ctx.ReplyText("Unknown command. Use /start to begin.") })) -
Important โ Inline-Button Routing: Inline-keyboard button presses arrive as
CallbackQueryupdates, not commands.Router.HandleUpdateonly routesmsg.IsCommand()messages. To route button presses to aFlow, use therouter.Defaultforwarding pattern:router.Default(teleflow.Func(func(ctx *teleflow.Context) error { if flow.IsUserInFlow(ctx.ChatID(), ctx.UserID()) { return flow.HandleUpdate(ctx) } return ctx.ReplyText("Unknown command. Use /start.") }))Alternatively, register a one-time callback handler via
ctx.RegisterCallback(handler), which auto-removes after execution.Important โ callbacks are never auto-answered. The framework does not call
AnswerCallbackfor you. Your handler (or the flowProcessthat handles the button press) must callctx.AnswerCallback()to stop the Telegram client's loading spinner and acknowledge the press. A stale button whose handler/data has already been consumed will silently fall through to the router, so always callAnswerCallback()before doing any work.
4. Flows (teleflow.Flow[T])
-
Purpose: Define multi-step conversational interactions with automatic state management.
-
Building a Flow:
// 1. Define a struct for your flow's data. type RegistrationData struct { Name string Age int } // 2. Create a new Flow with your data type. registrationFlow := teleflow.NewFlow[RegistrationData]() // 3. Define steps with clean, type-safe functions. // (Requires imports: "fmt", "strconv", "log", teleflow "github.com/kslamph/teleflow/core") welcomeStep := teleflow.Step[RegistrationData]{ Name: "Welcome", Prompt: func(ctx *teleflow.Context, data *RegistrationData) error { return ctx.ReplyText("Welcome! What is your name?") }, Process: func(ctx *teleflow.Context, data *RegistrationData) (teleflow.FlowAction, error) { data.Name = ctx.Text() return teleflow.Next(), nil }, } ageStep := teleflow.Step[RegistrationData]{ Name: "Age", Prompt: func(ctx *teleflow.Context, data *RegistrationData) error { return ctx.ReplyText(fmt.Sprintf("Nice to meet you, %s! How old are you?", data.Name)) }, Process: func(ctx *teleflow.Context, data *RegistrationData) (teleflow.FlowAction, error) { age, err := strconv.Atoi(ctx.Text()) if err != nil { ctx.ReplyText("Please enter a valid number.") return teleflow.Retry(), nil } data.Age = age return teleflow.End(), nil }, } // 4. Add steps to the flow. if _, err := registrationFlow.AddStep(welcomeStep); err != nil { log.Fatal(err) } if _, err := registrationFlow.AddStep(ageStep); err != nil { log.Fatal(err) } // 5. Set a completion handler. registrationFlow.OnComplete(func(ctx *teleflow.Context, data RegistrationData) error { // The final `data` is type-safe. return ctx.ReplyText(fmt.Sprintf("Thanks, %s! Registration complete. Age: %d", data.Name, data.Age)) }) -
Flow Control Functions:
teleflow.Next(): Move to the next step in sequence.teleflow.Retry(): Re-prompt the current step.teleflow.End(): Successfully end the flow and triggerOnComplete.teleflow.GoTo("step_name"): Jump to a specific named step.
5. Keyboards
-
Inline Keyboards (
teleflow.InlineKeyboard): Attached to messages.keyboard := teleflow.NewInlineKeyboard().AddRow( teleflow.ButtonCallback("Option 1", "opt1"), teleflow.ButtonCallback("Option 2", "opt2"), ).Build() ctx.ReplyWithKeyboard("Choose an option:", keyboard) -
Complex Callback Data:
// Register complex data for callbacks complexData := MyStruct{Field: "value"} dataID := ctx.RegisterData(complexData) keyboard := teleflow.NewInlineKeyboard().AddRow( teleflow.ButtonCallback("Click me", dataID), ).Build() // Later, retrieve the data retrievedData, ok := ctx.GetData() if ok { myData := retrievedData.(MyStruct) // Use myData }
6. Middleware
-
Purpose: Intercept and process updates before they reach handlers.
-
Structure:
func LoggingMiddleware(next teleflow.Handler) teleflow.Handler { return teleflow.Func(func(ctx *teleflow.Context) error { // Code before handler log.Printf("User %d accessing: %s", ctx.UserID(), ctx.Text()) err := next.HandleUpdate(ctx) // Call the next handler // Code after handler return err }) } -
Applying Middleware:
// Apply middleware when registering handlers router.Handle("admin", LoggingMiddleware(adminFlow))
โจ Key Teleflow Features for LLMs
-
Type-Safe Flows:
- Use Go generics (
teleflow.Flow[T]) for compile-time safety. - No more type assertions for flow data.
- Use Go generics (
-
Automatic State Management:
- Teleflow automatically manages user state within a flow.
- Each user has their own isolated data instance.
-
Non-Linear Conversations:
- Named steps and
GoToactions enable complex flow control. - Easy to create loops, branches, and restartable flows.
- Named steps and
-
Large Callback Data:
- Overcome Telegram's 64-byte callback data limit with
ctx.RegisterData()andctx.GetData().
- Overcome Telegram's 64-byte callback data limit with
๐ Common Tasks for LLMs using Teleflow
-
Creating a new command handler:
- Use
router.Handle("command", teleflow.Func(...)) - Inside the handler, use
ctx.ReplyText()orctx.ReplyWithKeyboard().
- Use
-
Defining a new conversational flow:
- Define a data struct
T. - Create a flow with
teleflow.NewFlow[T](). - Define
teleflow.Step[T]withPromptandProcessfunctions. - Add steps with
flow.AddStep(). - Register with
router.Handle("command", flow).
- Define a data struct
-
Asking a question and getting a response in a flow:
- Define a
Stepwith aPromptfunction that sends a message. - In the
Processfunction, usectx.Text()to get the user's response.
- Define a
-
Adding buttons to a message:
- In a
Promptor handler, create a keyboard withteleflow.NewInlineKeyboard(). - Add buttons with
ButtonCallback(). - Send with
ctx.ReplyWithKeyboard().
- In a
-
Storing and retrieving data during a flow:
- Use the
data *Tparameter inPromptandProcessfunctions. - Modify the struct fields directly.
- Use the
-
Handling complex callback data:
- Use
ctx.RegisterData(complexData)to store data. - Use
ctx.GetData()to retrieve data in the next step.
- Use
This guide should provide a solid foundation for an LLM to understand and generate Go code using the Teleflow package. Refer to the README.md and specific Go files in the core directory for more detailed examples and advanced features.