EDDI's attachment pipeline enables multimodal conversations — users can send images, files, and documents alongside text input. Attachments flow through the lifecycle pipeline and are automatically forwarded to vision-capable LLMs.
POST /agents/{conversationId}/say?message=What%20is%20in%20this%20image?
Content-Type: application/json
{
"attachment_0": {
"type": "object",
"value": {
"mimeType": "image/png",
"url": "https://example.com/photo.png",
"fileName": "photo.png"
}
}
}POST /agents/{conversationId}/say?message=Describe%20this%20icon
Content-Type: application/json
{
"attachment_0": {
"type": "object",
"value": {
"mimeType": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAE...",
"fileName": "icon.png"
}
}
}The image is automatically forwarded to the LLM as multimodal content. The LLM "sees" the image alongside the text message.
Client sends context with attachment_* keys
│
▼
┌──────────────────────────────────┐
│ Conversation.prepareLifecycleData() │
│ │
│ AttachmentContextExtractor parses │
│ attachment_0, attachment_1, ... │
│ into List<Attachment> objects │
│ │
│ Stored in memory: "attachments" │
└──────────────┬───────────────────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────────┐
│BehaviorRules│ │LlmTask│ │Other Tasks │
│ │ │ │ │ │
│ContentType- │ │Multi- │ │Read from │
│Matcher │ │modal │ │memory key │
│condition │ │Message│ │"attachments"│
│ │ │Enhancer││ │
└────────┘ └────────┘ └────────────┘
- Context Extraction —
AttachmentContextExtractorparsesattachment_*context keys intoAttachmentobjects - Memory Storage — Attachments are stored as
List<Attachment>in theattachmentsmemory key - Rule Matching —
ContentTypeMatchercondition matches on MIME types for routing - LLM Forwarding —
MultimodalMessageEnhancerconverts attachments to langchain4jImageContentand enhances the user message
Best for images already hosted somewhere. The LLM provider fetches the image directly from the URL.
{
"attachment_0": {
"type": "object",
"value": {
"mimeType": "image/jpeg",
"url": "https://cdn.example.com/photos/sunset.jpg",
"fileName": "sunset.jpg"
}
}
}Best for small images (< 5MB). Data is sent inline as a base64-encoded string.
{
"attachment_0": {
"type": "object",
"value": {
"mimeType": "image/png",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk...",
"fileName": "icon.png"
}
}
}Note:
base64Datais transient — it's never persisted to MongoDB. For large files, use the upload endpoint (Path C below) or URL references.
For large files, upload them to the storage backend and receive a storage reference:
POST /conversations/{conversationId}/attachments
Content-Type: multipart/form-data
# Form field: file (the binary file)Response (201):
{
"storageRef": "gridfs://68abc123def456",
"fileName": "report.pdf",
"mimeType": "application/pdf",
"sizeBytes": 524288
}The returned storageRef can then be used in subsequent conversation turns by setting it as the url in an attachment context key. The storage backend (GridFS or PostgreSQL) is selected automatically based on the configured datastore.
| Response Code | Meaning |
|---|---|
201 |
File stored successfully |
400 |
No file provided |
503 |
No attachment storage configured |
Attachment context keys must match the pattern attachment_*:
| Key | Valid? |
|---|---|
attachment_0 |
✅ |
attachment_screenshot |
✅ |
attachment_ |
✅ |
image_0 |
❌ (wrong prefix) |
attachment |
❌ (no suffix) |
| Field | Required | Description |
|---|---|---|
mimeType |
Yes | MIME type (e.g., image/png, application/pdf) |
url |
One of url/data | External URL reference |
data |
One of url/data | Base64-encoded content |
fileName |
No | Original filename (for metadata/logging) |
If both url and data are present, url takes precedence.
Send multiple attachments by incrementing the key index:
{
"attachment_0": {
"type": "object",
"value": { "mimeType": "image/png", "url": "https://example.com/page1.png" }
},
"attachment_1": {
"type": "object",
"value": { "mimeType": "image/png", "url": "https://example.com/page2.png" }
}
}All attachments are forwarded to the LLM in a single multimodal user message.
The MultimodalMessageEnhancer automatically converts attachments to the appropriate langchain4j content type:
| MIME Type | langchain4j Content | Provider Support |
|---|---|---|
image/* |
ImageContent |
OpenAI GPT-4o, Gemini, Claude 3, Ollama (LLaVA) |
application/pdf |
Metadata text (future: PdfFileContent) |
Gemini |
audio/* |
Metadata text (future: AudioContent) |
Gemini |
| Other | Metadata text description | All (text-only) |
For unsupported MIME types, a text description is injected so the LLM knows an attachment was present:
[Attachment: report.csv (text/csv, 15240 bytes)]
Use contentTypeMatcher to create different workflows based on attachment type:
{
"name": "Image received",
"actions": ["analyze_image"],
"conditions": [
{
"type": "contentTypeMatcher",
"configs": {
"mimeType": "image/*",
"minCount": "1"
}
}
]
}{
"name": "Document received",
"actions": ["process_document"],
"conditions": [
{
"type": "contentTypeMatcher",
"configs": {
"mimeType": "application/pdf",
"minCount": "1"
}
}
]
}{
"name": "Comparison ready",
"actions": ["compare_images"],
"conditions": [
{
"type": "contentTypeMatcher",
"configs": {
"mimeType": "image/*",
"minCount": "2"
}
}
]
}Attachments are available in templates via the memory namespace:
Current step attachments: {memory.current.attachments}
This can be useful for logging, debugging, or constructing custom prompts that reference attachment metadata.
- No inline storage: Attachment payloads are never stored inline in conversation memory documents. Only metadata references are persisted.
- Transient base64: The
base64Datafield istransient— it exists only during the pipeline turn. For persistence, use the upload endpoint withIAttachmentStorage. - DB-agnostic: The
IAttachmentStorageSPI supports MongoDB (GridFS) and PostgreSQL (bytea) implementations. - GDPR cleanup:
IAttachmentStorage.deleteByConversation()removes all attachments when a conversation is deleted.