Quick Start
1
Adapt and report in one call
adapt_presentation_with_report returns the adapted presentation plus a typed report you can append to the last text block:2
Preserve lossless callbacks
Attach a
callback_store so long reply / select values round-trip losslessly — DEGRADE_CALLBACK_DATA_TOO_LONG is then not reported:3
Aggregate by reason code
Count
DEGRADE_* codes across a run to find channels or UIs that consistently degrade. Compare against the module constants, not string literals:adapt_presentation() already downgrades unsupported controls to text, but returned no record of what it dropped — the user and the model never learned that a button vanished or a chart became text. adapt_presentation_with_report() is a drop-in replacement that returns both the adapted presentation and a typed report. The original adapt_presentation() is retained unchanged.Which function?
DegradedDelivery
adapt_presentation_with_report() is a drop-in replacement for adapt_presentation() that also reports what degraded.
- Import:
from praisonaiagents.bots import DegradedDelivery, adapt_presentation_with_report - Signature:
adapt_presentation_with_report(presentation, limits, *, callback_store=None) -> tuple[MessagePresentation, DegradedDelivery | None] Nonewhen nothing degrades — an adapter that always appendsreport.fallback_textshould guard onreport is not None.
Reason codes
All exported frompraisonaiagents.bots. Compare against the DEGRADE_* constants, not string literals.
How It Works
The report is derived from the same conversion and selection decisions asadapt_presentation, so it never disagrees with the adaptation.
The report respects the adaptation order and priority:
select → buttonsis applied first, then buttons truncation runs — so a huge select on Telegram reportsselect_unsupportedand (if it overflows the cap)buttons_truncated.- A
web_app/ lossy-callback on a dropped button is not reported — the user never saw it. - Callback shortening is reported only when the adapter genuinely emitted a lossy payload — a callback that round-trips losslessly via a
callback_storeis not reported. - Plain-callback actions (already channel-safe) are never reported.
DEGRADE_CALLBACK_DATA_TOO_LONG code stops firing.
User Interaction Flow
A user in Telegram asks a status bot for a bar chart of yesterday’s errors. Telegram cannot render charts natively. The bot delivers a compact text summary and appends"(chart rendered as text.)" — so the user knows the numbers are the whole picture. The operator dashboard buckets this event under chart_rendered_as_text.
Common Patterns
Append the fallback to the last text block — a one-liner in the adapter’s send path keeps every channel’s fallback consistent:callback_store on channels with byte-capped callbacks — the built-in InMemoryCallbackPayloadStore (already used by TelegramBot) turns lossy shortening into lossless round-tripping.
Alert on repeated buttons_truncated / options_truncated — signals a UI that consistently over-builds for the target channel.
Best Practices
Prefer adapt_presentation_with_report in new adapters
Prefer adapt_presentation_with_report in new adapters
Silent downgrades hide UX issues from users and operators alike — surface them.
Compare against DEGRADE_* constants, not string literals
Compare against DEGRADE_* constants, not string literals
The reason codes are stable module constants for exactly this reason.
Empty dropped means the whole report is None
Empty dropped means the whole report is None
Guard with
if report is not None: before appending report.fallback_text.Attach a callback store when you can
Attach a callback store when you can
It eliminates the only lossy adaptation (
DEGRADE_CALLBACK_DATA_TOO_LONG) and makes long-value round-trips exact.Related
Interactive Bot Messages
Buttons, selects, tables, and charts across channels
Interactive Callback Store
Lossless round-tripping for long callback payloads
Failure Reply
The failure-path counterpart
Visible-Outcome Guarantee
Every turn ends in a visible outcome
Introduced in PraisonAI commit 892b9fb (fixes #3799).

