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 |