Skip to content

Commit bdd2d58

Browse files
temporal-spring-ai: plan — media byte[] size guard
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d3c157c commit bdd2d58

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

temporal-spring-ai/PLAN.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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

Comments
 (0)