Inbound Media

Forward user photos and documents to your agent's vision capability across WhatsApp and Telegram

Bot adapters forward inbound photos and documents to your agent’s vision capability — validated, size-capped, and SSRF-safe.

python
from praisonaiagents import Agent

agent = Agent(
    name="Assistant",
    instructions="Describe photos and documents users send in chat.",
)

agent.start("What is in this image?")

The user sends a photo or document in WhatsApp or Telegram; the bot validates the file and passes it to the agent vision model.

Diagram

Inbound Media Flow

👤 User sends photo

📱 Platform

🤖 Bot Adapter

🛡️ Validator

🧠 Agent Vision

💬 Reply

Quick Start

  1. Simple — works with any vision model

    No configuration needed. Point a vision-capable agent at your bot.

    python
    from praisonaiagents import Agent
    from praisonai.bots import TelegramBot
    
    agent = Agent(
        name="vision-assistant",
        instructions="Describe images and answer questions about them.",
        llm="gpt-4o",
    )
    
    bot = TelegramBot(token="your-telegram-bot-token", agent=agent)
    
    import asyncio
    asyncio.run(bot.start())

    Send the bot a photo (with or without a caption). The agent sees the image and responds.

  2. With size limit

    Cap media size for public-facing bots to avoid processing large files.

    python
    from praisonaiagents import Agent
    from praisonai.bots import WhatsAppBot
    from praisonaiagents import BotConfig
    
    agent = Agent(
        name="assistant",
        instructions="Help users with their images and questions.",
        llm="gpt-4o",
    )
    
    bot = WhatsAppBot(
        agent=agent,
        config=BotConfig(
            max_inbound_media_bytes=5 * 1024 * 1024,  # 5 MiB cap
        ),
    )
    
    import asyncio
    asyncio.run(bot.start())
  3. Disable inbound media

    Set max_inbound_media_bytes=0 for text-only agents.

    python
    from praisonaiagents import Agent
    from praisonai.bots import TelegramBot
    from praisonaiagents import BotConfig
    
    agent = Agent(name="text-only", instructions="Answer text questions only.")
    
    bot = TelegramBot(
        token="your-token",
        agent=agent,
        config=BotConfig(max_inbound_media_bytes=0),
    )
    
    import asyncio
    asyncio.run(bot.start())

How It Works

Diagram
🧠 Agent Vision🛡️ cache_inbound_media()🤖 Bot Adapter📱 WhatsApp / Telegram👤 User🧠 Agent Vision🛡️ cache_inbound_media()🤖 Bot Adapter📱 WhatsApp / Telegram👤 User1. Size ≤ max_inbound_media_bytes2. Magic-byte content-type check3. SSRF-safe URL (no private IPs)alt[Validation passes][File too large or invalid]Photo + “What is this?”Media id / file referenceDownload raw bytesFile dataValidate fileCached local pathchat(“What is this?”, attachments=[path])Vision response📨 ReplyReject(caption only, no attachment)
StepWhat happens
DownloadWhatsApp: fetches via Graph API by media id. Telegram: uses get_file() → download_to_drive()
Size capFile rejected if size exceeds max_inbound_media_bytes. Default: 20 MiB
Magic-byte checkFile header bytes checked against declared content-type — prevents type confusion
SSRF guardURL must use http/https and resolve to a public IP — rejects loopback, link-local, and RFC-1918 ranges
ForwardValidated path passed to agent.chat(attachments=[path])

Platform Coverage

PlatformMedia typesCaption support
WhatsApp (Cloud API)Photos, documents✅ Caption forwarded as prompt
TelegramPhotos, all documents✅ Caption forwarded as prompt

Media Albums

Coalesce a burst of updates sharing one media_group_id into a single multimodal turn — the agent sees the whole album at once instead of N separate turns.

Diagram
🧠 Agent🧺 AlbumCoalescer🤖 TelegramBot📱 Telegram👤 User🧠 Agent🧺 AlbumCoalescer🤖 TelegramBot📱 Telegram👤 Userbuffer for window_ms of silenceOR until max_items reachedsend 3-photo album(1 caption on first)updateupdateupdatecollect(X, [A], caption)collect(X, [B], “”)collect(X, [C], “”)MergedAlbum(attachments=[A,B,C], caption)chat(caption, attachments=[A,B,C])single reply covering all 3

Opt in via BotConfig.metadata — disabled by default so existing deployments are unchanged.

python
from praisonaiagents import Agent, BotConfig
from praisonai.bots import TelegramBot

agent = Agent(
    name="album-assistant",
    instructions="Compare and describe the photos the user sends.",
    llm="gpt-4o",
)

bot = TelegramBot(
    token="your-telegram-bot-token",
    agent=agent,
    config=BotConfig(
        metadata={
            "media_group_window_ms": 1200,   # silence window (0 = disabled)
            "media_group_max": 10,           # cap per album (default: 10)
        },
    ),
)

import asyncio
asyncio.run(bot.start())

Each inbound update flows through this decision path:

Diagram

single photo/doc

album, media_group_id set

no

yes

yes

no

yes

no

User sends media?

Standalone message
→ returned immediately

window_ms > 0?

Legacy: N separate turns
backward compatible default

Buffer parts

Reached max_items?

⚡ Flush immediately

window_ms of silence?

Behaviour

BehaviourDetail
Disabled by defaultmedia_group_window_ms=0 keeps every existing deployment byte-for-byte unchanged.
First non-empty caption winsThe album’s prompt is the caption on whichever part carried one first (Telegram typically puts it on part 1).
Sibling updates fold inOnly one caller owns the merged turn; siblings’ media folds into it and their collect() returns None internally.
Cancelled-owner cleanupIf the owning update is cancelled mid-wait (e.g. during shutdown), buffered temp files are reclaimed via the shared _remove_inbound_media hook.
Shutdown flushbot.stop() calls AlbumCoalescer.cancel_all(), flushing pending buffers.
Telegram-onlySlack and Discord adapters do not wire the coalescer — they don’t have Telegram’s split-update album model.

Configuration

OptionTypeDefaultDescription
max_inbound_media_bytesint20971520Maximum file size in bytes. 0 = disable inbound media.
metadata["media_group_window_ms"]int (ms)0 (disabled)Silence window before an album flushes as one turn. Set > 0 (e.g. 1200) to enable coalescing; 0 keeps legacy per-update behaviour.
metadata["media_group_max"]int10Upper bound on attachments buffered per album — a full Telegram album is 10, so the default splits nothing. Lower it to bound memory/latency; reaching it forces an immediate flush.

Both album options live under BotConfig.metadata (there are no top-level BotConfig fields for them — resolve_album_window_ms / resolve_album_max_items read metadata first, then a direct attribute). Values that fail int() coercion silently fall back to the default.

Set via BotConfig:

python
from praisonaiagents import BotConfig

config = BotConfig(
    max_inbound_media_bytes=10 * 1024 * 1024,  # 10 MiB
)

Or via YAML:

yaml
agent:
  name: vision-bot
  instructions: Describe images.
  llm: gpt-4o

platforms:
  telegram:
    token: ${TELEGRAM_BOT_TOKEN}
    max_inbound_media_bytes: 10485760       # 10 MiB
    metadata:
      media_group_window_ms: 1200           # coalesce albums (0 disables)
      media_group_max: 10

Common Patterns

Photo + question (most common)

The user sends a photo with a caption asking a question. The caption becomes the prompt; the image is the attachment.

User: [photo of a chart] → "What does this chart show?"
Agent: "The chart shows quarterly revenue growth from Q1 to Q4..."

Document analysis

Send a PDF or Word document. The agent reads the content via vision.

User: [report.pdf] → "Summarize the key findings"
Agent: "The report covers three main findings..."

Oversized file rejected

Files above max_inbound_media_bytes are silently dropped. Only the caption text is forwarded.

User: [50 MB video] → "Describe this"
Agent: (receives only "Describe this", no attachment)

Non-vision agent (backward compatible)

If agent.chat() does not accept attachments, the adapter skips the file gracefully.

python
agent = Agent(name="text-only", instructions="Answer text questions.")
# Agent chat() has no attachments parameter — attachments are skipped automatically

Best Practices

Set a tighter cap for public bots

The default 20 MiB is generous. For public-facing bots, set 2–5 MiB to reduce processing time and storage use.

python
config = BotConfig(max_inbound_media_bytes=2 * 1024 * 1024)
Disable with 0 for text-only agents

If your agent doesn’t use vision, set max_inbound_media_bytes=0. Users still get a response — just from the caption text alone.

python
config = BotConfig(max_inbound_media_bytes=0)
Watch logs for SSRF rejects

The SSRF guard logs rejections at WARNING level. If you see unexpected rejections in production, check that the platform’s media CDN URLs resolve to public IPs.

WARNING: SSRF guard rejected URL: https://internal-cdn.example.com/...
Enable album coalescing on public Telegram bots

Set metadata["media_group_window_ms"] to 1000–1500 ms so a user sending “compare these 3 photos” reaches the agent as one multimodal turn instead of three. Leave media_group_max at 10 (a full Telegram album) unless you want to cap memory further.

python
config = BotConfig(metadata={"media_group_window_ms": 1200})
Use gpt-4o or claude-3 for vision

Not all models support vision. Pass a vision-capable model to see image attachments.

python
agent = Agent(llm="gpt-4o")          # OpenAI vision
agent = Agent(llm="claude-3-7-sonnet-20250219")  # Anthropic vision
agent = Agent(llm="gemini/gemini-2.0-flash")      # Google vision

Related