Skip to content

Commit c8fed2d

Browse files
cornellgitcopybara-github
authored andcommitted
feat: Add InMemoryMemoryService to ADK.
PiperOrigin-RevId: 776988199
1 parent 79fc8a6 commit c8fed2d

File tree

4 files changed

+322
-0
lines changed

4 files changed

+322
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.adk.memory;
18+
19+
import com.google.adk.sessions.Session;
20+
import io.reactivex.rxjava3.core.Completable;
21+
import io.reactivex.rxjava3.core.Single;
22+
23+
/**
24+
* Base contract for memory services.
25+
*
26+
* <p>The service provides functionalities to ingest sessions into memory so that the memory can be
27+
* used for user queries.
28+
*/
29+
public interface BaseMemoryService {
30+
31+
/**
32+
* Adds a session to the memory service.
33+
*
34+
* <p>A session may be added multiple times during its lifetime.
35+
*
36+
* @param session The session to add.
37+
*/
38+
Completable addSessionToMemory(Session session);
39+
40+
/**
41+
* Searches for sessions that match the query asynchronously.
42+
*
43+
* @param appName The name of the application.
44+
* @param userId The id of the user.
45+
* @param query The query to search for.
46+
* @return A {@link SearchMemoryResponse} containing the matching memories.
47+
*/
48+
Single<SearchMemoryResponse> searchMemory(String appName, String userId, String query);
49+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.adk.memory;
18+
19+
import static com.google.common.collect.ImmutableList.toImmutableList;
20+
21+
import com.google.adk.events.Event;
22+
import com.google.adk.sessions.Session;
23+
import com.google.common.base.Strings;
24+
import com.google.common.collect.ImmutableList;
25+
import com.google.common.collect.ImmutableSet;
26+
import com.google.genai.types.Part;
27+
import io.reactivex.rxjava3.core.Completable;
28+
import io.reactivex.rxjava3.core.Single;
29+
import java.time.Instant;
30+
import java.util.ArrayList;
31+
import java.util.Collections;
32+
import java.util.HashSet;
33+
import java.util.List;
34+
import java.util.Locale;
35+
import java.util.Map;
36+
import java.util.Set;
37+
import java.util.concurrent.ConcurrentHashMap;
38+
import java.util.regex.Matcher;
39+
import java.util.regex.Pattern;
40+
41+
/**
42+
* An in-memory memory service for prototyping purposes only.
43+
*
44+
* <p>Uses keyword matching instead of semantic search.
45+
*/
46+
public final class InMemoryMemoryService implements BaseMemoryService {
47+
48+
// Pattern to extract words, matching the Python version.
49+
private static final Pattern WORD_PATTERN = Pattern.compile("[A-Za-z]+");
50+
51+
/** Keys are "app_name/user_id", values are maps of "session_id" to a list of events. */
52+
private final Map<String, Map<String, List<Event>>> sessionEvents;
53+
54+
public InMemoryMemoryService() {
55+
this.sessionEvents = new ConcurrentHashMap<>();
56+
}
57+
58+
private static String userKey(String appName, String userId) {
59+
return appName + "/" + userId;
60+
}
61+
62+
@Override
63+
public Completable addSessionToMemory(Session session) {
64+
return Completable.fromAction(
65+
() -> {
66+
String key = userKey(session.appName(), session.userId());
67+
Map<String, List<Event>> userSessions =
68+
sessionEvents.computeIfAbsent(key, k -> new ConcurrentHashMap<>());
69+
ImmutableList<Event> nonEmptyEvents =
70+
session.events().stream()
71+
.filter(
72+
event ->
73+
event.content().isPresent()
74+
&& event.content().get().parts().isPresent()
75+
&& !event.content().get().parts().get().isEmpty())
76+
.collect(toImmutableList());
77+
userSessions.put(session.id(), nonEmptyEvents);
78+
});
79+
}
80+
81+
@Override
82+
public Single<SearchMemoryResponse> searchMemory(String appName, String userId, String query) {
83+
return Single.fromCallable(
84+
() -> {
85+
String key = userKey(appName, userId);
86+
87+
if (!sessionEvents.containsKey(key)) {
88+
return SearchMemoryResponse.builder().build();
89+
}
90+
91+
Map<String, List<Event>> userSessions = sessionEvents.get(key);
92+
93+
ImmutableSet<String> wordsInQuery =
94+
ImmutableSet.copyOf(query.toLowerCase(Locale.ROOT).split("\\s+"));
95+
96+
List<MemoryEntry> matchingMemories = new ArrayList<>();
97+
98+
for (List<Event> eventsInSession : userSessions.values()) {
99+
for (Event event : eventsInSession) {
100+
if (event.content().isEmpty() || event.content().get().parts().isEmpty()) {
101+
continue;
102+
}
103+
104+
Set<String> wordsInEvent = new HashSet<>();
105+
for (Part part : event.content().get().parts().get()) {
106+
if (!Strings.isNullOrEmpty(part.text().get())) {
107+
Matcher matcher = WORD_PATTERN.matcher(part.text().get());
108+
while (matcher.find()) {
109+
wordsInEvent.add(matcher.group().toLowerCase(Locale.ROOT));
110+
}
111+
}
112+
}
113+
114+
if (wordsInEvent.isEmpty()) {
115+
continue;
116+
}
117+
118+
if (!Collections.disjoint(wordsInQuery, wordsInEvent)) {
119+
MemoryEntry memory =
120+
MemoryEntry.builder()
121+
.setContent(event.content().get())
122+
.setAuthor(event.author())
123+
.setTimestamp(formatTimestamp(event.timestamp()))
124+
.build();
125+
matchingMemories.add(memory);
126+
}
127+
}
128+
}
129+
130+
return SearchMemoryResponse.builder()
131+
.setMemories(ImmutableList.copyOf(matchingMemories))
132+
.build();
133+
});
134+
}
135+
136+
private String formatTimestamp(long timestamp) {
137+
return Instant.ofEpochSecond(timestamp).toString();
138+
}
139+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.adk.memory;
18+
19+
import com.google.auto.value.AutoValue;
20+
import com.google.genai.types.Content;
21+
import java.time.Instant;
22+
import javax.annotation.Nullable;
23+
24+
/** Represents one memory entry. */
25+
@AutoValue
26+
public abstract class MemoryEntry {
27+
28+
/** Returns the main content of the memory. */
29+
public abstract Content content();
30+
31+
/** Returns the author of the memory, or null if not set. */
32+
@Nullable
33+
public abstract String author();
34+
35+
/**
36+
* Returns the timestamp when the original content of this memory happened, or null if not set.
37+
*
38+
* <p>This string will be forwarded to LLM. Preferred format is ISO 8601 format
39+
*/
40+
@Nullable
41+
public abstract String timestamp();
42+
43+
/** Returns a new builder for creating a {@link MemoryEntry}. */
44+
public static Builder builder() {
45+
return new AutoValue_MemoryEntry.Builder();
46+
}
47+
48+
/**
49+
* Creates a new builder with a copy of this entry's values.
50+
*
51+
* @return a new {@link Builder} instance.
52+
*/
53+
public abstract Builder toBuilder();
54+
55+
/** Builder for {@link MemoryEntry}. */
56+
@AutoValue.Builder
57+
public abstract static class Builder {
58+
59+
/**
60+
* Sets the main content of the memory.
61+
*
62+
* <p>This is a required field.
63+
*/
64+
public abstract Builder setContent(Content content);
65+
66+
/** Sets the author of the memory. */
67+
public abstract Builder setAuthor(@Nullable String author);
68+
69+
/** Sets the timestamp when the original content of this memory happened. */
70+
public abstract Builder setTimestamp(@Nullable String timestamp);
71+
72+
/**
73+
* A convenience method to set the timestamp from an {@link Instant} object, formatted as an ISO
74+
* 8601 string.
75+
*
76+
* @param instant The timestamp as an Instant object.
77+
*/
78+
public Builder setTimestamp(Instant instant) {
79+
return setTimestamp(instant.toString());
80+
}
81+
82+
/** Builds the immutable {@link MemoryEntry} object. */
83+
public abstract MemoryEntry build();
84+
}
85+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.adk.memory;
18+
19+
import com.google.auto.value.AutoValue;
20+
import com.google.common.collect.ImmutableList;
21+
import java.util.List;
22+
23+
/** Represents the response from a memory search. */
24+
@AutoValue
25+
public abstract class SearchMemoryResponse {
26+
27+
/** Returns a list of memory entries that relate to the search query. */
28+
public abstract ImmutableList<MemoryEntry> memories();
29+
30+
/** Creates a new builder for {@link SearchMemoryResponse}. */
31+
public static Builder builder() {
32+
return new AutoValue_SearchMemoryResponse.Builder().setMemories(ImmutableList.of());
33+
}
34+
35+
/** Builder for {@link SearchMemoryResponse}. */
36+
@AutoValue.Builder
37+
public abstract static class Builder {
38+
39+
abstract Builder setMemories(ImmutableList<MemoryEntry> memories);
40+
41+
/** Sets the list of memory entries using a list. */
42+
public Builder setMemories(List<MemoryEntry> memories) {
43+
return setMemories(ImmutableList.copyOf(memories));
44+
}
45+
46+
/** Builds the immutable {@link SearchMemoryResponse} object. */
47+
public abstract SearchMemoryResponse build();
48+
}
49+
}

0 commit comments

Comments
 (0)