Skip to main content
Agents can return a MessagePresentation alongside their reply text β€” Telegram renders it as a native inline keyboard; other platforms fall back to plain text until their renderer is wired.
from praisonaiagents import Agent

agent = Agent(
    name="Survey Bot",
    instructions="Ask one multiple-choice question and attach button choices.",
)
agent.start("Which topic should we cover next: memory, tools, or workflows?")
The user receives a reply with inline buttons; their tap sends the choice back as the next message.

Quick Start

1

Quick-Reply Buttons

The simplest way to add buttons β€” each tapped choice feeds the value back to the agent as the next user message:
from praisonaiagents import Agent
from praisonaiagents.bots import MessagePresentation, PresentationBlock

agent = Agent(
    name="Survey Bot",
    instructions="""
    Ask a single multiple-choice question.
    Return your reply as text, then attach a presentation with buttons
    for the choices using PresentationBlock.quick_replies().
    """,
)

presentation = MessagePresentation(
    blocks=[
        PresentationBlock.make_text("How would you rate your experience?"),
        PresentationBlock.quick_replies([
            ("⭐ Excellent", "excellent"),
            ("πŸ‘ Good", "good"),
            ("πŸ‘Ž Poor", "poor"),
        ]),
    ]
)
2

Inline Keyboard with Callbacks

Full control over button appearance and callback data:
from praisonaiagents.bots import (
    MessagePresentation,
    PresentationBlock,
    PresentationButton,
    PresentationAction,
    ButtonStyle,
)

presentation = MessagePresentation(
    blocks=[
        PresentationBlock.make_text("Choose an action:"),
        PresentationBlock.make_buttons([
            PresentationButton(
                label="βœ… Approve",
                action=PresentationAction.callback("approve:req-42"),
                style=ButtonStyle.PRIMARY,
                priority=10,
            ),
            PresentationButton(
                label="❌ Deny",
                action=PresentationAction.callback("deny:req-42"),
                style=ButtonStyle.DANGER,
                priority=9,
            ),
            PresentationButton(
                label="πŸ”— Open Details",
                action=PresentationAction.open_url("https://example.com/req/42"),
                priority=8,
            ),
        ]),
    ]
)
3

Use with an Agent and Telegram Bot

import asyncio
from praisonaiagents import Agent
from praisonaiagents.bots import (
    MessagePresentation, PresentationBlock, PresentationButton,
    PresentationAction, ButtonStyle,
)
from praisonai.bots import TelegramBot

agent = Agent(
    name="Action Bot",
    instructions="Help users take actions.",
)

bot = TelegramBot(token="YOUR_TOKEN", agent=agent)

presentation = MessagePresentation(
    blocks=[
        PresentationBlock.make_text("What would you like to do?"),
        PresentationBlock.quick_replies([("Start", "start"), ("Help", "help")]),
    ]
)

async def main():
    await bot.start()

asyncio.run(main())

How It Works

The adapt_presentation() function runs a channel-agnostic adaptation pass before rendering: it truncates buttons to platform limits, degrades select menus to button rows when unsupported, and degrades web_app actions to URLs on channels without mini-app support.

Block Types

Block typeMethodDescription
textPresentationBlock.make_text(content)Plain or markdown text
buttonsPresentationBlock.make_buttons(items)Row of clickable buttons
quick_repliesPresentationBlock.quick_replies(choices)Shortcut for reply-action buttons
selectPresentationBlock.make_select(options)Dropdown menu (degraded to buttons on Telegram)
dividerPresentationBlock.make_divider()Visual separator
contextPresentationBlock.make_context(content)Smaller contextual text

Action Types

ActionFactoryDescription
callbackPresentationAction.callback(value)Opaque callback data for your handler
replyPresentationAction.reply(value)Feed value back as next agent input
commandPresentationAction.command("/cmd")Trigger a slash command
urlPresentationAction.open_url(url)Open a URL
web_appPresentationAction(type="web_app", web_app_url=url)Open a mini-app (Telegram only)

Platform Support & Limits

PlatformPresentation supportNotes
Telegramβœ… Native inline keyboardWired in PR #2483
SlackπŸ”œ PlannedFalls back to plain text
DiscordπŸ”œ PlannedFalls back to plain text
WhatsAppβœ… Native interactive (reply buttons + list messages)Delivered via Cloud API interactive payloads (button / list) β€” see Bot Presentations

Telegram Limits

LimitValue
max_buttons per row/message8
max_button_rows100
max_button_label64 characters
callback_data64 UTF-8 bytes
max_text_length4096 characters
Native select menu❌ (degraded to buttons)
Web app (web_app_url)βœ…
Callback data is capped at 64 UTF-8 bytes (Telegram platform limit). callback_data values that exceed this are truncated at the UTF-8 byte boundary. For non-ASCII payloads this means fewer than 64 characters may be kept. Use short, ASCII-safe identifiers as callback values.

WhatsApp Limits

LimitValue
max_buttons (list rows)10
Reply buttons per message3
max_button_rows1
max_button_label (list row)24 characters
Reply-button title20 characters
max_options (list rows)10
max_option_label24 characters
max_text_length (text / list body)4096 characters
Reply-button message body1024 characters
Reply / list-row id256 characters
Native select menuβœ… (rendered as a list message)
Markdown❌
Web app (web_app_url)❌ (degraded via adapt_presentation)
WhatsApp uses split caps: reply-button titles are capped at 20 chars while list-row titles allow 24. Reply-button message bodies cap at 1024 chars (vs 4096 for text and list bodies). Every reply/list-row id is capped at 256 chars (Cloud API limit).

Button Priorities

When more buttons are defined than the platform limit allows, the adaptation pass keeps the highest-priority buttons and drops the rest:
PresentationButton(label="Critical", action=..., priority=10)  # kept first
PresentationButton(label="Secondary", action=..., priority=5)   # dropped if cap hit
PresentationButton(label="Optional", action=..., priority=1)    # dropped first

Best Practices

PresentationAction.reply(value) feeds the chosen value back directly as the user’s next message β€” your agent sees it as normal input without needing any callback handler.
Telegram’s 64-byte callback cap is measured in UTF-8 bytes. Emoji, CJK, or long UUIDs can exhaust the budget quickly. Use compact IDs (e.g. "approve:42") as callback payloads.
The captured presentation is popped after chat() in both streaming and non-streaming paths, so cancelling or returning never leaves stale buttons visible.
Call adapt_presentation(presentation, PresentationLimits.telegram()) before sending to preview which buttons survive the Telegram limit and how select menus are degraded. Use PresentationLimits.whatsapp(), .slack(), or .discord() to preview the other channels.

Approval Secure Backend

Durable approval buttons with actor authorisation

Bot Presentations

Overview of bot presentation capabilities

Interactive Bot Actions

Handling button taps and callbacks

Channel Capabilities

What each platform can render