|
| 1 | +# Plan: Media byte[] size guard |
| 2 | + |
| 3 | +## Scope |
| 4 | + |
| 5 | +`ChatModelTypes.MediaContent` has a `byte[] data` field. If a user attaches |
| 6 | +an image-as-bytes to a `UserMessage`, those bytes end up serialized into: |
| 7 | + |
| 8 | +1. The activity's input payload (ActivityTaskScheduled event). |
| 9 | +2. The activity's result payload, if the assistant echoes media back. |
| 10 | +3. Every `ToolResponseMessage` carrying media, on every tool iteration. |
| 11 | + |
| 12 | +Temporal's default per-event payload size limit is 2 MiB, and total history |
| 13 | +is bounded too. A single 5 MB PNG silently breaks the workflow at runtime |
| 14 | +with a cryptic serialization error. The integration guide's |
| 15 | +"arguments and return values must be serializable" point generalizes to |
| 16 | +"don't stuff giant blobs into history." |
| 17 | + |
| 18 | +This branch adds a defensive size check with a clear error message, plus |
| 19 | +documentation steering users to URI-based media. |
| 20 | + |
| 21 | +## Files to change |
| 22 | + |
| 23 | +- `src/main/java/io/temporal/springai/model/ActivityChatModel.java` |
| 24 | + - In `toMediaContent(Media)`, when `media.getData() instanceof byte[]`, |
| 25 | + check the length against a threshold (default **1 MiB**, configurable). |
| 26 | + If exceeded, throw `IllegalArgumentException` with a message pointing |
| 27 | + the user at the URI-based `Media` constructor and the threshold. |
| 28 | + |
| 29 | +- `src/main/java/io/temporal/springai/activity/ChatModelActivityImpl.java` |
| 30 | + - Same guard on the return path (`fromMedia` / `toOutput`). |
| 31 | + |
| 32 | +- New constant `MAX_MEDIA_BYTES_IN_HISTORY = 1 * 1024 * 1024` on |
| 33 | + `ChatModelTypes` or `ActivityChatModel`. |
| 34 | + |
| 35 | +- `README.md` |
| 36 | + - Add a "Media in messages" section that: |
| 37 | + - States raw `byte[]` media is size-limited. |
| 38 | + - Recommends passing `Media` via URI (`new Media(mimeType, URI)`) or |
| 39 | + via a binary store your activity writes to before the chat call, so |
| 40 | + only the URI crosses the history boundary. |
| 41 | + |
| 42 | +## Threshold rationale |
| 43 | + |
| 44 | +- Temporal default history-event payload limit is 2 MiB. |
| 45 | +- A chat activity carries messages + tool definitions + options + media, |
| 46 | + and the result carries messages back; both events separately must fit. |
| 47 | +- 1 MiB leaves headroom for everything else. Users who want to raise it |
| 48 | + can override via a system property or config — document that. |
| 49 | + |
| 50 | +## Test plan |
| 51 | + |
| 52 | +- Unit test: `UserMessage` with 2 MiB media → `IllegalArgumentException` |
| 53 | + with a message mentioning the limit and the URI alternative. |
| 54 | +- Unit test: `UserMessage` with 500 KiB media → passes through. |
| 55 | +- Unit test: assistant echoes media back → same guard applies on the |
| 56 | + result path. |
| 57 | + |
| 58 | +## PR |
| 59 | + |
| 60 | +**Title:** `temporal-spring-ai: guard large media byte[] from entering workflow history` |
| 61 | + |
| 62 | +**Body:** |
| 63 | + |
| 64 | +``` |
| 65 | +## What was changed |
| 66 | +- `ActivityChatModel` and `ChatModelActivityImpl` now reject |
| 67 | + `Media.data` byte arrays larger than 1 MiB with a clear |
| 68 | + `IllegalArgumentException` pointing users to URI-based media. |
| 69 | +- The threshold is a single named constant so teams can override it. |
| 70 | +- README gains a short "Media in messages" section documenting the |
| 71 | + limit and the URI-based workaround. |
| 72 | +
|
| 73 | +## Why? |
| 74 | +Raw media bytes in messages get serialized into every relevant history |
| 75 | +event. Without a guard, a single large image silently produces a |
| 76 | +`gRPC message exceeds maximum size` failure at workflow runtime, |
| 77 | +which is hard for users to diagnose. Failing fast at the serialization |
| 78 | +edge, with an actionable message, turns a mystery-failure into a |
| 79 | +clear "use a URI" hint. |
| 80 | +``` |
0 commit comments