diff --git a/docs/sessions/memory.md b/docs/sessions/memory.md index 982a3d6fe0..b6938ece2b 100644 --- a/docs/sessions/memory.md +++ b/docs/sessions/memory.md @@ -1,7 +1,7 @@ # Memory: Long-Term Knowledge with `MemoryService`
- Supported in ADKPython v0.1.0Typescript v0.2.0Go v0.1.0Java v0.1.0Kotlin v0.1.0 + Supported in ADKPython v0.1.0TypeScript v0.2.0Go v0.1.0Java v0.1.0Kotlin v0.1.0
We've seen how `Session` tracks the history (`events`) and temporary data (`state`) for a *single, ongoing conversation*. But what if an agent needs to recall information from *past* conversations? This is where the concept of **Long-Term Knowledge** and the **`MemoryService`** come into play. @@ -230,12 +230,6 @@ You can also search memory from within a custom tool by using the tool context. } ``` -=== "Go" - - ```go - --8<-- "examples/go/snippets/sessions/memory_example/memory_example.go:tool_search" - ``` - === "TypeScript" ```typescript @@ -250,6 +244,12 @@ You can also search memory from within a custom tool by using the tool context. } ``` +=== "Go" + + ```go + --8<-- "examples/go/snippets/sessions/memory_example/memory_example.go:tool_search" + ``` + === "Java" ```java @@ -508,6 +508,7 @@ For example, your agent can use the framework-configured `InMemoryMemoryService` #### Example: Using Two Memory Services === "Python" + ```python from google.adk.agents import Agent from google.adk.memory import InMemoryMemoryService @@ -549,3 +550,9 @@ For example, your agent can use the framework-configured `InMemoryMemoryService` tools=[search_all_memory], ) ``` + +=== "Kotlin" + + ```kotlin + --8<-- "examples/kotlin/snippets/sessions/MemoryExample.kt:multi_memory" + ``` diff --git a/examples/kotlin/snippets/sessions/MemoryExample.kt b/examples/kotlin/snippets/sessions/MemoryExample.kt index bebc657cd8..678f759e2d 100644 --- a/examples/kotlin/snippets/sessions/MemoryExample.kt +++ b/examples/kotlin/snippets/sessions/MemoryExample.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.google.adk.kt.examples.sessions import com.google.adk.kt.agents.CallbackContext @@ -71,17 +87,18 @@ fun main() = val userInput1 = Content.fromText(Role.USER, "My favorite project is Project Alpha.") // Run the agent - runner1.runAsync( - userId = userId, - sessionId = sessionId1, - newMessage = userInput1, - ).collect { event -> - event.content?.parts?.forEach { part -> - if (!part.text.isNullOrBlank()) { - println("Agent Response: ${part.text}") + runner1 + .runAsync( + userId = userId, + sessionId = sessionId1, + newMessage = userInput1, + ).collect { event -> + event.content?.parts?.forEach { part -> + if (!part.text.isNullOrBlank()) { + println("Agent Response: ${part.text}") + } } } - } // Get the completed session using SessionKey val session1 = sessionService.getSession(SessionKey(appName, userId, sessionId1)) @@ -106,17 +123,18 @@ fun main() = val userInput2 = Content.fromText(Role.USER, "What is my favorite project?") // Run the second agent - runner2.runAsync( - userId = userId, - sessionId = sessionId2, - newMessage = userInput2, - ).collect { event -> - event.content?.parts?.forEach { part -> - if (!part.text.isNullOrBlank()) { - println("Agent Response: ${part.text}") + runner2 + .runAsync( + userId = userId, + sessionId = sessionId2, + newMessage = userInput2, + ).collect { event -> + event.content?.parts?.forEach { part -> + if (!part.text.isNullOrBlank()) { + println("Agent Response: ${part.text}") + } } } - } } // --8<-- [end:full_example] @@ -157,6 +175,65 @@ fun preloadMemoryAgent(model: Gemini) { } // --8<-- [end:preload_memory_agent] +// --8<-- [start:multi_memory] + +/** + * Example of using two memory services in Kotlin. + */ +suspend fun searchAllMemory( + toolContext: ToolContext, + query: String, + docsMemory: InMemoryMemoryService, +): Map> { + // Search the conversational memory (configured in the runner) + val conversational = + toolContext.invocationContext.memoryService?.searchMemory( + appName = toolContext.invocationContext.session.key.appName, + userId = toolContext.invocationContext.session.key.userId, + query = query, + ) + + // Search a separate docs knowledge base + val docs = + docsMemory.searchMemory( + appName = "docs", + userId = "shared", + query = query, + ) + + return mapOf( + "from_conversations" to + ( + conversational?.memories?.map { + it.content.parts.joinToString(" ") { p -> p.text ?: "" } + } ?: emptyList() + ), + "from_docs" to + docs.memories.map { + it.content.parts.joinToString(" ") { p -> p.text ?: "" } + }, + ) +} + +fun multiMemoryAgent(model: Gemini) { + // docs_memory could be any MemoryService implementation + val docsMemory = InMemoryMemoryService() + + val agent = + LlmAgent( + model = model, + name = "multi_memory_agent", + instruction = + Instruction( + "Answer questions using both your conversation history and the " + + "docs knowledge base. Use the search_all_memory tool.", + ), + // In a real app, you'd wrap searchAllMemory in a @Tool annotated class + // and pass docsMemory to its constructor. + ) +} +// --8<-- [end:multi_memory] + // --8<-- [start:auto_save_callback] suspend fun autoSaveSessionToMemoryCallback( context: CallbackContext,