🔴 Required Information
Describe the Bug:
InMemoryMemoryService.searchMemory calls part.text().get() without checking whether the Optional holds a value:
// InMemoryMemoryService.java:108
for (Part part : event.content().get().parts().get()) {
if (!Strings.isNullOrEmpty(part.text().get())) { // NoSuchElementException for a non-text part
Matcher matcher = WORD_PATTERN.matcher(part.text().get());
Part.text() is empty for functionCall, functionResponse, inlineData and fileData parts. addSessionToMemory stores those events — it only drops events whose parts list is empty. searchMemory then scans every session stored under the same app and user, so one tool call in any one session makes every later search for that app and user throw, whatever the query.
Steps to Reproduce:
- Store a session holding an event with a
functionCall part. No model or network is involved.
- Search that app and user.
InMemoryMemoryService memoryService = new InMemoryMemoryService();
Session session =
Session.builder("session")
.appName("app")
.userId("user")
.events(
ImmutableList.of(
Event.builder()
.author("agent")
.content(
Content.fromParts(
Part.builder()
.functionCall(FunctionCall.builder().name("get_weather").build())
.build()))
.build()))
.build();
memoryService.addSessionToMemory(session).blockingAwait();
memoryService.searchMemory("app", "user", "weather").blockingGet();
Expected Behavior:
Parts without text are skipped and the search returns the entries whose text matched.
Observed Behavior:
java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:143)
at com.google.adk.memory.InMemoryMemoryService.lambda$searchMemory$5(InMemoryMemoryService.java:108)
at io.reactivex.rxjava3.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:43)
at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
at io.reactivex.rxjava3.core.Single.blockingGet(Single.java:3644)
Environment Details:
- ADK Library Version: 1.8.1-SNAPSHOT (
main), also present in 1.8.0
- OS: macOS 26.4.1
- TS Version: N/A
Model Information:
🟡 Optional Information
Regression:
No. The unguarded .get() has been there since the class was added in c8fed2d (2025-06-28).
Additional Context:
Reachable from an agent: LoadMemoryTool → ToolContext.searchMemory → BaseMemoryService.searchMemory. The trigger is an app that saves a tool-using session with addSessionToMemory and later lets the agent call load_memory. InMemoryMemoryService is the default in InMemoryRunner and in the dev server, so this is the path most people are on.
Every other part.text() read in core guards with isPresent(), filter or orElse (e.g. LlmAgent, Claude, LoggingPlugin, CodeExecutionUtils; LlmEventSummarizer uses part.text().filter(not(String::isEmpty)) for the same job). This is the one that does not.
adk-python filters the same loop on if part.text, and adk-go skips with if part.Text == "".
Same class of bug as #1279 and #1280.
How often has this issue occurred?:
- Always (100%) once a stored session holds a part without text.
I have a fix and tests ready and will open a PR.
🔴 Required Information
Describe the Bug:
InMemoryMemoryService.searchMemorycallspart.text().get()without checking whether theOptionalholds a value:Part.text()is empty forfunctionCall,functionResponse,inlineDataandfileDataparts.addSessionToMemorystores those events — it only drops events whosepartslist is empty.searchMemorythen scans every session stored under the same app and user, so one tool call in any one session makes every later search for that app and user throw, whatever the query.Steps to Reproduce:
functionCallpart. No model or network is involved.Expected Behavior:
Parts without text are skipped and the search returns the entries whose text matched.
Observed Behavior:
Environment Details:
main), also present in 1.8.0Model Information:
🟡 Optional Information
Regression:
No. The unguarded
.get()has been there since the class was added inc8fed2d(2025-06-28).Additional Context:
Reachable from an agent:
LoadMemoryTool→ToolContext.searchMemory→BaseMemoryService.searchMemory. The trigger is an app that saves a tool-using session withaddSessionToMemoryand later lets the agent callload_memory.InMemoryMemoryServiceis the default inInMemoryRunnerand in the dev server, so this is the path most people are on.Every other
part.text()read incoreguards withisPresent(),filterororElse(e.g.LlmAgent,Claude,LoggingPlugin,CodeExecutionUtils;LlmEventSummarizerusespart.text().filter(not(String::isEmpty))for the same job). This is the one that does not.adk-python filters the same loop on
if part.text, and adk-go skips withif part.Text == "".Same class of bug as #1279 and #1280.
How often has this issue occurred?:
I have a fix and tests ready and will open a PR.