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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions