Skip to content

[BUG] saveInputBlobsAsArtifacts throws on an immutable Content parts list #1377

Description

@svetanis

Please make sure you read the contribution guide and file the issues in the right place.
Contribution guide.

🔴 Required Information

Describe the Bug:

With saveInputBlobsAsArtifacts(true), Runner replaces each inline blob with a placeholder by writing
into the parts list of the Content the caller passed to runAsync:

// Runner.java:376-382
newMessage.parts().get().set(i, Part.fromText("Uploaded file: " + fileName + " ..."));

That list is never copied on the way in, so whether the write succeeds depends on how the message was
built:

Construction Backing list Result
builder().parts(List.of(text, blob)) caller's List.of, uncopied throws
builder().parts(textBuilder, blobBuilder) genai's own ImmutableList throws
Content.fromParts(text, blob) Arrays.asList works

Steps to Reproduce:

The construction is the trigger. Content.fromParts(...) and parts(Part...) wrap
Arrays.asList, whose slots are settable, so they will not reproduce this. (That is also why
RunnerTest.runner_executesSaveArtifactFlow passes today — its helper uses the Part... overload.)

  1. Build a message with an inline blob and an immutable parts list:
    Content.builder().role("user").parts(List.of(Part.fromText("hello"), Part.fromBytes(bytes, "text/plain"))).build()
  2. Run it through any Runner (e.g. InMemoryRunner) with
    RunConfig.builder().saveInputBlobsAsArtifacts(true).build().
  3. It throws before the model is called.

The linked PR adds RunnerTest.saveInputBlobsAsArtifacts_immutablePartsList_savesArtifactAndCompletes,
which fails on current main and passes with the fix.

Expected Behavior:

The blob is saved as an artifact and the appended message carries the "Uploaded file: ..." placeholder —
the documented behaviour — however the caller built the Content.

Observed Behavior:

UnsupportedOperationException out of Runner.appendNewMessageToSession, upstream of the model call: no
model request, and no user event appended to the session. Captured on main:

java.lang.UnsupportedOperationException
	at com.google.common.collect.ImmutableList.set(ImmutableList.java:544)
	at com.google.adk.runner.Runner.appendNewMessageToSession(Runner.java:379)
	at com.google.adk.runner.Runner.lambda$8(Runner.java:547)
	... (RxJava frames elided)

With List.of(...) the top frame is ImmutableCollections$AbstractImmutableList.set instead.

Environment Details:

  • ADK Library Version: 1.7.1-SNAPSHOT
  • OS: Windows 11 (not OS-specific)
  • TS Version: N/A — Java

Model Information:

  • Any — the failure precedes the model call. Reproduced with a stub and with gemini-2.5-flash.

🟡 Optional Information

Minimal Reproduction Code:

Part text = Part.fromText("hello");
Part blob = Part.fromBytes("payload".getBytes(UTF_8), "text/plain");
RunConfig config = RunConfig.builder().saveInputBlobsAsArtifacts(true).build();

// throws
runner.runAsync(userId, sessionId,
    Content.builder().role("user").parts(List.of(text, blob)).build(), config).blockingSubscribe();

// throws — no workaround; genai builds the ImmutableList itself
runner.runAsync(userId, sessionId,
    Content.builder().role("user").parts(text.toBuilder(), blob.toBuilder()).build(), config)
    .blockingSubscribe();

// works — Arrays.asList, settable slots
runner.runAsync(userId, sessionId, Content.fromParts(text, blob), config).blockingSubscribe();

How often has this issue occurred?: Always (100%) — deterministic for any parts list that rejects
set.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions