Media

In shortMediaOffloadPolicy, ensure_media_offloaded, BaseMediaStore, MediaRefResolver — handling large binary content in agent messages.

  • 5 min read
  • 12 sections
  • Updated
  • v0.9.2
  • Markdown

When to use this

Use the media layer when your agent processes images, audio, video, or documents. Large inline data_base64 blobs inside messages are expensive to serialise, checkpoint, and send over the network. The media layer automatically offloads them to a dedicated store and replaces the blob with a lightweight URI reference.

Import path

Python
from tenxgraph.storage.media import MediaOffloadPolicy, ensure_media_offloaded

MediaOffloadPolicy

An enum that controls when inline base64 data is offloaded to a BaseMediaStore.

Python
from tenxgraph.storage.media import MediaOffloadPolicy
Value Description
NEVER Never offload. All data_base64 content stays inline. Use for unit tests.
THRESHOLD Offload only when the decoded blob exceeds max_inline_bytes. Default policy.
ALWAYS Always offload every data_base64 blob, regardless of size.

ensure_media_offloaded

Python
message = await ensure_media_offloaded(
    message=my_message,
    store=media_store,
    policy=MediaOffloadPolicy.THRESHOLD,
    max_inline_bytes=50_000,
)

Inspects all ImageBlock, AudioBlock, VideoBlock, and DocumentBlock entries in a message. For any block whose media.kind == "data" and media.data_base64 is present, the function:

  1. Decodes the base64 to bytes.
  2. Uploads the bytes to store.
  3. Replaces block.media with a new MediaRef(kind="url", url="graph://media/{key}").

The message is mutated in place and also returned. Blocks without inline data (URL or file_id references) are left unchanged.

Parameters:

Parameter Type Default Description
message Message required Message to inspect and potentially modify.
store BaseMediaStore required Media store to upload blobs into.
policy MediaOffloadPolicy THRESHOLD When to offload.
max_inline_bytes int 50_000 Decoded byte threshold for the THRESHOLD policy.

BaseMediaStore

Abstract interface for media storage backends. Concrete stores implement store(), retrieve(), delete(), exists(), get_metadata(), get_direct_url(), and to_media_ref().

Python
from tenxgraph.storage.media import BaseMediaStore

Implementations

Class Backend Notes
InMemoryMediaStore Process memory Development and tests. Data is lost on restart.
LocalFileMediaStore Local filesystem Single-server setups. LocalFileMediaStore(base_dir="./agentflow_media").
CloudMediaStore S3 / GCS / Azure via cloud-storage-manager Requires the cloud-storage extra. Supports signed URLs.

All three are re-exported from tenxgraph.storage.media (and from tenxgraph.storage.media.storage).


Wiring media storage into the graph

Python
from tenxgraph.storage.media import LocalFileMediaStore

media_store = LocalFileMediaStore(base_dir="./media_uploads")

app = graph.compile(
    checkpointer=my_checkpointer,
    media_store=media_store,
)

When a media_store is configured:

  1. The framework calls ensure_media_offloaded() on incoming messages before they enter the graph.
  2. Any blob that exceeds max_inline_bytes (default 50 KB) is uploaded and replaced with an graph://media/{key} reference URI.
  3. The checkpointer stores only the lightweight URI, not the binary blob.
  4. When the API serves the message back to a client, it resolves graph://media/{key} to a signed access URL.

Multimodal message construction

When sending a multimodal message to the graph, use MediaRef to reference the media:

Python
from tenxgraph.core.state import ImageBlock, MediaRef, Message, TextBlock

# From a URL
msg = Message(
    role="user",
    content=[
        TextBlock(text="What's in this image?"),
        ImageBlock(
            media=MediaRef(kind="url", url="https://example.com/photo.jpg", mime_type="image/jpeg")
        ),
    ],
)

# From base64 (will be offloaded if media_store is configured and blob is large)
import base64
with open("chart.png", "rb") as f:
    b64 = base64.b64encode(f.read()).decode()

msg = Message(
    role="user",
    content=[
        TextBlock(text="Describe this chart"),
        ImageBlock(
            media=MediaRef(kind="data", data_base64=b64, mime_type="image/png")
        ),
    ],
)

MultimodalConfig

Per-agent configuration for how media is validated and delivered to the provider:

Python
from tenxgraph.storage.media import DocumentHandling, ImageHandling, MultimodalConfig

agent = Agent(
    model="gpt-4o",
    multimodal_config=MultimodalConfig(
        image_handling=ImageHandling.BASE64,
        document_handling=DocumentHandling.EXTRACT_TEXT,
        max_image_dimension=2048,
    ),
)
Field Type Default Description
image_handling ImageHandling BASE64 How images are sent to the provider.
document_handling DocumentHandling EXTRACT_TEXT How documents are processed before sending.
max_image_size_mb float 10.0 Maximum accepted image size in megabytes.
max_image_dimension int 2048 Images are resized when either dimension exceeds this.
supported_image_types set[str] jpeg, png, webp, gif Allowed image MIME types.
supported_doc_types set[str] pdf, docx Allowed document MIME types.

Offload behaviour is not configured here. It is driven by MediaOffloadPolicy and the media_store passed to graph.compile().


MediaRefResolver

Resolves a MediaRef into the concrete content part a provider expects: it fetches graph://media/{key} references out of the media store, and can hand out signed direct URLs instead of re-uploading bytes on every turn.

Python
from tenxgraph.storage.media import MediaRefResolver

resolver = MediaRefResolver(media_store=media_store)

# Optional: share signed URLs across processes through a cache backend
resolver = resolver.with_cache(
    cache_backend=my_redis_cache,
    expiration_seconds=3600,
    refresh_buffer_seconds=60,
)
Parameter Type Default Description
media_store BaseMediaStore | None None Store used to resolve internal graph://media/{key} references. Internal references raise when omitted.
cache_backend Any | None None Optional cache for generated signed URLs.
direct_url_expiration_seconds int 3600 Lifetime of a generated signed URL.
direct_url_refresh_buffer_seconds int 60 Regenerate a cached URL this many seconds before it expires.

OpenAI file helpers

For documents already uploaded through the OpenAI Files API, two helpers build the request fragments OpenAI expects:

Python
from tenxgraph.storage.media import (
    create_openai_file_attachment,
    create_openai_file_search_tool,
)

tool = create_openai_file_search_tool(["file-abc123"])
# -> {"type": "file_search", "file_search": {...}}  — pass in the `tools` list

attachment = create_openai_file_attachment("file-abc123", tools=["file_search"])
# -> {"file_id": "file-abc123", "tools": [{"type": "file_search"}]}

create_openai_file_search_tool(file_ids) returns a tool dict for the tools parameter. create_openai_file_attachment(file_id, tools=None) returns a message attachment dict; tools defaults to ["file_search"].


Security note

ensure_media_offloaded uses base64.b64decode() with no URL-safe alphabet option. Ensure incoming data_base64 values are standard base64-encoded. The media store should validate MIME types before accepting uploads to prevent arbitrary file storage.


Common errors

Error Cause Fix
graph://media/... reference not resolved Client receives an internal URI instead of an accessible URL. Ensure the API server is configured with a media_store that supports signed URL generation.
No offloading happening media_store not passed to graph.compile(). Add media_store=your_store to the compile() call.
Blob stays inline despite ALWAYS policy ensure_media_offloaded is not called on the message. Confirm the graph is compiled with a media_store. The framework calls offload automatically on ingestion.
Last updated for v0.9.2Edit this page on GitHubReport an issue