Skip to content

Multi-Modal Nodes

trails.multimodal provides content-addressed binary storage with graph linking, making binary attachments (images, PDFs, DICOM scans, etc.) first-class knowledge graph citizens. Binaries are never stored inside Oxigraph -- the graph holds metadata (SHA-256 hash, MIME type, size, storage path) while actual bytes live in a content-addressed file tree under data/attachments/.

See ADR-0040 for the full design rationale.

Quick start

from trails.multimodal import attach, get_attachment, get_attachment_data, detach, binary_field
from trails import node_type

# Declare a node type with a binary field
@node_type("Scan", fields={
    "label": str,
    "image": binary_field(mime_types=["image/png", "image/dicom"]),
})
class Scan: pass

# Attach binary data to a KG node
with open("scan.png", "rb") as f:
    att = attach(ctx, node_iri, f.read(), "image/png")
print(att.content_hash)  # SHA-256 hex digest
print(att.size_bytes)    # file size

# Retrieve attachment metadata
att = get_attachment(ctx, node_iri, mime_type="image/png")

# Retrieve actual bytes
data = get_attachment_data(ctx, node_iri)

# Detach (removes graph triples and optionally the file)
detach(ctx, node_iri, delete_file=True)

Key types

Type Description
BinaryAttachment Frozen dataclass: content_hash, mime_type, size_bytes, storage_path, iri
AttachmentStore Content-addressed file store: store(data, mime_type), retrieve(hash), exists(hash), delete(hash)
BinaryFieldDescriptor Marker returned by binary_field() for ORM integration

API

Function Signature Description
binary_field (*, mime_types=None) -> BinaryFieldDescriptor Declare a binary attachment slot on a @node_type
attach (ctx, node_iri, data, mime_type, *, store=None, binary_descriptor=None) -> BinaryAttachment Attach binary data to a KG node; stores file, inserts graph triples, emits PROV
get_attachment (ctx, node_iri, *, mime_type=None) -> BinaryAttachment \| None Retrieve attachment metadata for a node
get_attachment_data (ctx, node_iri, *, store=None, mime_type=None) -> bytes Retrieve the actual binary content
detach (ctx, node_iri, *, store=None, delete_file=True) -> bool Remove attachment from a node
multimodal_retrieve (ctx, query, *, mode="text", top_k=10) -> list[dict] Hybrid search across text and binary-attached nodes