Memoria: Conocimiento a Largo Plazo con MemoryService¶
Hemos visto cómo Session rastrea el historial (events) y los datos temporales (state) para una única conversación en curso. Pero ¿qué pasa si un agente necesita recordar información de conversaciones pasadas? Aquí es donde entra en juego el concepto de Conocimiento a Largo Plazo y el MemoryService.
Piénsalo de esta manera:
Session/State: Como tu memoria a corto plazo durante un chat específico.- Conocimiento a Largo Plazo (
MemoryService): Como un archivo o biblioteca de conocimientos consultable que el agente puede consultar, potencialmente conteniendo información de muchos chats pasados u otras fuentes.
El Rol del MemoryService¶
El BaseMemoryService define la interfaz para gestionar este almacén de conocimiento consultable a largo plazo. Sus responsabilidades principales son:
- Ingerir Información (
add_session_to_memory): Tomar el contenido de unaSession(generalmente completada) y agregar información relevante al almacén de conocimiento a largo plazo. - Buscar Información (
search_memory): Permitir que un agente (típicamente a través de unaTool) consulte el almacén de conocimiento y recupere fragmentos o contexto relevante basado en una consulta de búsqueda.
Elegir el Memory Service Correcto¶
El ADK ofrece dos implementaciones distintas de MemoryService, cada una adaptada a diferentes casos de uso. Utiliza la tabla a continuación para decidir cuál es la más adecuada para tu agente.
| Característica | InMemoryMemoryService | VertexAiMemoryBankService |
|---|---|---|
| Persistencia | Ninguna (los datos se pierden al reiniciar) | Sí (Gestionado por Vertex AI) |
| Caso de Uso Principal | Prototipado, desarrollo local y pruebas simples. | Construir memorias significativas y evolutivas a partir de conversaciones de usuarios. |
| Extracción de Memoria | Almacena la conversación completa | Extrae información significativa de las conversaciones y la consolida con memorias existentes (impulsado por LLM) |
| Capacidad de Búsqueda | Coincidencia básica de palabras clave. | Búsqueda semántica avanzada. |
| Complejidad de Configuración | Ninguna. Es el predeterminado. | Baja. Requiere una instancia de Agent Engine en Vertex AI. |
| Dependencias | Ninguna. | Proyecto de Google Cloud, API de Vertex AI |
| Cuándo usarlo | Cuando quieras buscar en los historiales de chat de múltiples sesiones para prototipado. | Cuando quieras que tu agente recuerde y aprenda de interacciones pasadas. |
Memoria In-Memory¶
El InMemoryMemoryService almacena información de la sesión en la memoria de la aplicación y realiza coincidencias básicas de palabras clave para las búsquedas. No requiere configuración y es ideal para escenarios de prototipado y pruebas simples donde no se requiere persistencia.
Ejemplo: Agregar y Buscar Memoria
Este ejemplo demuestra el flujo básico usando el InMemoryMemoryService para simplificar.
import asyncio
from google.adk.agents import LlmAgent
from google.adk.sessions import InMemorySessionService, Session
from google.adk.memory import InMemoryMemoryService # Importar MemoryService
from google.adk.runners import Runner
from google.adk.tools import load_memory # Herramienta para consultar memoria
from google.genai.types import Content, Part
# --- Constantes ---
APP_NAME = "memory_example_app"
USER_ID = "mem_user"
MODEL = "gemini-2.0-flash" # Usar un modelo válido
# --- Definiciones de Agentes ---
# Agente 1: Agente simple para capturar información
info_capture_agent = LlmAgent(
model=MODEL,
name="InfoCaptureAgent",
instruction="Acknowledge the user's statement.",
)
# Agente 2: Agente que puede usar memoria
memory_recall_agent = LlmAgent(
model=MODEL,
name="MemoryRecallAgent",
instruction="Answer the user's question. Use the 'load_memory' tool "
"if the answer might be in past conversations.",
tools=[load_memory] # Dar al agente la herramienta
)
# --- Servicios ---
# Los servicios deben compartirse entre runners para compartir estado y memoria
session_service = InMemorySessionService()
memory_service = InMemoryMemoryService() # Usar in-memory para demostración
async def run_scenario():
# --- Escenario ---
# Turno 1: Capturar algo de información en una sesión
print("--- Turn 1: Capturing Information ---")
runner1 = Runner(
# Comenzar con el agente de captura de información
agent=info_capture_agent,
app_name=APP_NAME,
session_service=session_service,
memory_service=memory_service # Proporcionar el servicio de memoria al Runner
)
session1_id = "session_info"
await runner1.session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=session1_id)
user_input1 = Content(parts=[Part(text="My favorite project is Project Alpha.")], role="user")
# Ejecutar el agente
final_response_text = "(No final response)"
async for event in runner1.run_async(user_id=USER_ID, session_id=session1_id, new_message=user_input1):
if event.is_final_response() and event.content and event.content.parts:
final_response_text = event.content.parts[0].text
print(f"Agent 1 Response: {final_response_text}")
# Obtener la sesión completada
completed_session1 = await runner1.session_service.get_session(app_name=APP_NAME, user_id=USER_ID, session_id=session1_id)
# Agregar el contenido de esta sesión al Memory Service
print("\n--- Adding Session 1 to Memory ---")
await memory_service.add_session_to_memory(completed_session1)
print("Session added to memory.")
# Turno 2: Recordar la información en una nueva sesión
print("\n--- Turn 2: Recalling Information ---")
runner2 = Runner(
# Usar el segundo agente, que tiene la herramienta de memoria
agent=memory_recall_agent,
app_name=APP_NAME,
session_service=session_service, # Reutilizar el mismo servicio
memory_service=memory_service # Reutilizar el mismo servicio
)
session2_id = "session_recall"
await runner2.session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=session2_id)
user_input2 = Content(parts=[Part(text="What is my favorite project?")], role="user")
# Ejecutar el segundo agente
final_response_text_2 = "(No final response)"
async for event in runner2.run_async(user_id=USER_ID, session_id=session2_id, new_message=user_input2):
if event.is_final_response() and event.content and event.content.parts:
final_response_text_2 = event.content.parts[0].text
print(f"Agent 2 Response: {final_response_text_2}")
# Para ejecutar este ejemplo, puedes usar el siguiente fragmento:
# asyncio.run(run_scenario())
# await run_scenario()
import (
"context"
"fmt"
"log"
"strings"
"google.golang.org/adk/agent"
"google.golang.org/adk/agent/llmagent"
"google.golang.org/adk/memory"
"google.golang.org/adk/model/gemini"
"google.golang.org/adk/runner"
"google.golang.org/adk/session"
"google.golang.org/adk/tool"
"google.golang.org/adk/tool/functiontool"
"google.golang.org/genai"
)
const (
appName = "go_memory_example_app"
userID = "go_mem_user"
modelID = "gemini-2.5-pro"
)
// Args defines the input structure for the memory search tool.
type Args struct {
Query string `json:"query" jsonschema:"The query to search for in the memory."`
}
// Result defines the output structure for the memory search tool.
type Result struct {
Results []string `json:"results"`
}
// memorySearchToolFunc is the implementation of the memory search tool.
// This function demonstrates accessing memory via tool.Context.
func memorySearchToolFunc(tctx tool.Context, args Args) (Result, error) {
fmt.Printf("Tool: Searching memory for query: '%s'\n", args.Query)
// The SearchMemory function is available on the context.
searchResults, err := tctx.SearchMemory(context.Background(), args.Query)
if err != nil {
log.Printf("Error searching memory: %v", err)
return Result{}, fmt.Errorf("failed memory search")
}
var results []string
for _, res := range searchResults.Memories {
if res.Content != nil {
results = append(results, textParts(res.Content)...)
}
}
return Result{Results: results}, nil
}
// Define a tool that can search memory.
var memorySearchTool = must(functiontool.New(
functiontool.Config{
Name: "search_past_conversations",
Description: "Searches past conversations for relevant information.",
},
memorySearchToolFunc,
))
// This example demonstrates how to use the MemoryService in the Go ADK.
// It covers two main scenarios:
// 1. Adding a completed session to memory and recalling it in a new session.
// 2. Searching memory from within a custom tool using the tool.Context.
func main() {
ctx := context.Background()
// --- Services ---
// Services must be shared across runners to share state and memory.
sessionService := session.InMemoryService()
memoryService := memory.InMemoryService() // Use in-memory for this demo.
// --- Scenario 1: Capture information in one session ---
fmt.Println("--- Turn 1: Capturing Information ---")
infoCaptureAgent := must(llmagent.New(llmagent.Config{
Name: "InfoCaptureAgent",
Model: must(gemini.NewModel(ctx, modelID, nil)),
Instruction: "Acknowledge the user's statement.",
}))
runner1 := must(runner.New(runner.Config{
AppName: appName,
Agent: infoCaptureAgent,
SessionService: sessionService,
MemoryService: memoryService, // Provide the memory service to the Runner
}))
session1ID := "session_info"
must(sessionService.Create(ctx, &session.CreateRequest{AppName: appName, UserID: userID, SessionID: session1ID}))
userInput1 := genai.NewContentFromText("My favorite project is Project Alpha.", "user")
var finalResponseText string
for event, err := range runner1.Run(ctx, userID, session1ID, userInput1, agent.RunConfig{}) {
if err != nil {
log.Printf("Agent 1 Error: %v", err)
continue
}
if event.Content != nil && !event.LLMResponse.Partial {
finalResponseText = strings.Join(textParts(event.LLMResponse.Content), "")
}
}
fmt.Printf("Agent 1 Response: %s\n", finalResponseText)
// Add the completed session to the Memory Service
fmt.Println("\n--- Adding Session 1 to Memory ---")
resp, err := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: session1ID})
if err != nil {
log.Fatalf("Failed to get completed session: %v", err)
}
if err := memoryService.AddSession(ctx, resp.Session); err != nil {
log.Fatalf("Failed to add session to memory: %v", err)
}
fmt.Println("Session added to memory.")
// --- Scenario 2: Recall the information in a new session using a tool ---
fmt.Println("\n--- Turn 2: Recalling Information ---")
memoryRecallAgent := must(llmagent.New(llmagent.Config{
Name: "MemoryRecallAgent",
Model: must(gemini.NewModel(ctx, modelID, nil)),
Instruction: "Answer the user's question. Use the 'search_past_conversations' tool if the answer might be in past conversations.",
Tools: []tool.Tool{memorySearchTool}, // Give the agent the tool
}))
runner2 := must(runner.New(runner.Config{
Agent: memoryRecallAgent,
AppName: appName,
SessionService: sessionService,
MemoryService: memoryService,
}))
session2ID := "session_recall"
must(sessionService.Create(ctx, &session.CreateRequest{AppName: appName, UserID: userID, SessionID: session2ID}))
userInput2 := genai.NewContentFromText("What is my favorite project?", "user")
var finalResponseText2 string
for event, err := range runner2.Run(ctx, userID, session2ID, userInput2, agent.RunConfig{}) {
if err != nil {
log.Printf("Agent 2 Error: %v", err)
continue
}
if event.Content != nil && !event.LLMResponse.Partial {
finalResponseText2 = strings.Join(textParts(event.LLMResponse.Content), "")
}
}
fmt.Printf("Agent 2 Response: %s\n", finalResponseText2)
}
Buscar Memoria Dentro de una Herramienta¶
También puedes buscar memoria desde dentro de una herramienta personalizada usando el tool.Context.
// memorySearchToolFunc is the implementation of the memory search tool.
// This function demonstrates accessing memory via tool.Context.
func memorySearchToolFunc(tctx tool.Context, args Args) (Result, error) {
fmt.Printf("Tool: Searching memory for query: '%s'\n", args.Query)
// The SearchMemory function is available on the context.
searchResults, err := tctx.SearchMemory(context.Background(), args.Query)
if err != nil {
log.Printf("Error searching memory: %v", err)
return Result{}, fmt.Errorf("failed memory search")
}
var results []string
for _, res := range searchResults.Memories {
if res.Content != nil {
results = append(results, textParts(res.Content)...)
}
}
return Result{Results: results}, nil
}
// Define a tool that can search memory.
var memorySearchTool = must(functiontool.New(
functiontool.Config{
Name: "search_past_conversations",
Description: "Searches past conversations for relevant information.",
},
memorySearchToolFunc,
))
Vertex AI Memory Bank¶
El VertexAiMemoryBankService conecta tu agente a Vertex AI Memory Bank, un servicio de Google Cloud completamente gestionado que proporciona capacidades de memoria sofisticadas y persistentes para agentes conversacionales.
Cómo Funciona¶
El servicio maneja dos operaciones clave:
- Generar Memorias: Al final de una conversación, puedes enviar los eventos de la sesión al Memory Bank, que procesa y almacena inteligentemente la información como "memorias".
- Recuperar Memorias: Tu código de agente puede emitir una consulta de búsqueda contra el Memory Bank para recuperar memorias relevantes de conversaciones pasadas.
Requisitos Previos¶
Antes de poder usar esta característica, debes tener:
- Un Proyecto de Google Cloud: Con la API de Vertex AI habilitada.
- Un Agent Engine: Necesitas crear un Agent Engine en Vertex AI. No necesitas desplegar tu agente en Agent Engine Runtime para usar Memory Bank. Esto te proporcionará el ID del Agent Engine requerido para la configuración.
- Autenticación: Asegúrate de que tu entorno local esté autenticado para acceder a los servicios de Google Cloud. La forma más simple es ejecutar:
- Variables de Entorno: El servicio requiere tu ID de Proyecto de Google Cloud y Ubicación. Configúralos como variables de entorno:
Configuración¶
Para conectar tu agente al Memory Bank, usas la bandera --memory_service_uri al iniciar el servidor ADK (adk web o adk api_server). El URI debe estar en el formato agentengine://<agent_engine_id>.
O puedes configurar tu agente para usar el Memory Bank instanciando manualmente el VertexAiMemoryBankService y pasándolo al Runner.
from google.adk.memory import VertexAiMemoryBankService
agent_engine_id = agent_engine.api_resource.name.split("/")[-1]
memory_service = VertexAiMemoryBankService(
project="PROJECT_ID",
location="LOCATION",
agent_engine_id=agent_engine_id
)
runner = adk.Runner(
...
memory_service=memory_service
)
Usar Memoria en tu Agente¶
Cuando un servicio de memoria está configurado, tu agente puede usar una herramienta o callback para recuperar memorias. ADK incluye dos herramientas preconstruidas para recuperar memorias:
PreloadMemory: Siempre recupera memoria al comienzo de cada turno (similar a un callback).LoadMemory: Recupera memoria cuando tu agente decide que sería útil.
Ejemplo:
from google.adk.agents import Agent
from google.adk.tools.preload_memory_tool import PreloadMemoryTool
agent = Agent(
model=MODEL_ID,
name='weather_sentiment_agent',
instruction="...",
tools=[PreloadMemoryTool()]
)
Para extraer memorias de tu sesión, necesitas llamar a add_session_to_memory. Por ejemplo, puedes automatizar esto a través de un callback:
from google import adk
async def auto_save_session_to_memory_callback(callback_context):
await callback_context._invocation_context.memory_service.add_session_to_memory(
callback_context._invocation_context.session)
agent = Agent(
model=MODEL,
name="Generic_QA_Agent",
instruction="Answer the user's questions",
tools=[adk.tools.preload_memory_tool.PreloadMemoryTool()],
after_agent_callback=auto_save_session_to_memory_callback,
)
Conceptos Avanzados¶
Cómo Funciona la Memoria en la Práctica¶
El flujo de trabajo de memoria internamente involucra estos pasos:
- Interacción de Sesión: Un usuario interactúa con un agente a través de una
Session, gestionada por unSessionService. Se agregan eventos, y el estado puede actualizarse. - Ingestión en Memoria: En algún punto (a menudo cuando una sesión se considera completa o ha producido información significativa), tu aplicación llama a
memory_service.add_session_to_memory(session). Esto extrae información relevante de los eventos de la sesión y la agrega al almacén de conocimiento a largo plazo (diccionario en memoria o Agent Engine Memory Bank). - Consulta Posterior: En una sesión diferente (o la misma), el usuario podría hacer una pregunta que requiere contexto pasado (por ejemplo, "¿De qué hablamos sobre el proyecto X la semana pasada?").
- El Agente Usa la Herramienta de Memoria: Un agente equipado con una herramienta de recuperación de memoria (como la herramienta incorporada
load_memory) reconoce la necesidad de contexto pasado. Llama a la herramienta, proporcionando una consulta de búsqueda (por ejemplo, "discusión proyecto X la semana pasada"). - Ejecución de Búsqueda: La herramienta internamente llama a
memory_service.search_memory(app_name, user_id, query). - Resultados Devueltos: El
MemoryServicebusca en su almacén (usando coincidencia de palabras clave o búsqueda semántica) y devuelve fragmentos relevantes como unSearchMemoryResponseque contiene una lista de objetosMemoryResult(cada uno potencialmente conteniendo eventos de una sesión pasada relevante). - El Agente Usa los Resultados: La herramienta devuelve estos resultados al agente, generalmente como parte del contexto o respuesta de función. El agente puede entonces usar esta información recuperada para formular su respuesta final al usuario.
¿Puede un agente tener acceso a más de un servicio de memoria?¶
-
A través de la Configuración Estándar: No. El framework (
adk web,adk api_server) está diseñado para configurarse con un único servicio de memoria a la vez a través de la bandera--memory_service_uri. Este único servicio se proporciona entonces al agente y se accede a través del método incorporadoself.search_memory(). Desde un punto de vista de configuración, solo puedes elegir un backend (InMemory,VertexAiMemoryBankService) para todos los agentes servidos por ese proceso. -
Dentro del Código de tu Agente: Sí, absolutamente. No hay nada que te impida importar e instanciar manualmente otro servicio de memoria directamente dentro del código de tu agente. Esto te permite acceder a múltiples fuentes de memoria dentro de un único turno de agente.
Por ejemplo, tu agente podría usar el InMemoryMemoryService configurado por el framework para recordar el historial conversacional, y también instanciar manualmente un VertexAiMemoryBankService para buscar información en un manual técnico.
Ejemplo: Usar Dos Servicios de Memoria¶
Aquí está cómo podrías implementar eso en el código de tu agente:
from google.adk.agents import Agent
from google.adk.memory import InMemoryMemoryService, VertexAiMemoryBankService
from google.genai import types
class MultiMemoryAgent(Agent):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.memory_service = InMemoryMemoryService()
# Instanciar manualmente un segundo servicio de memoria para búsquedas de documentos
self.vertexai_memorybank_service = VertexAiMemoryBankService(
project="PROJECT_ID",
location="LOCATION",
agent_engine_id="AGENT_ENGINE_ID"
)
async def run(self, request: types.Content, **kwargs) -> types.Content:
user_query = request.parts[0].text
# 1. Buscar historial conversacional usando la memoria proporcionada por el framework
# (Esto sería InMemoryMemoryService si está configurado)
conversation_context = await self.memory_service.search_memory(query=user_query)
# 2. Buscar en la base de conocimientos de documentos usando el servicio creado manualmente
document_context = await self.vertexai_memorybank_service.search_memory(query=user_query)
# Combinar el contexto de ambas fuentes para generar una mejor respuesta
prompt = "From our past conversations, I remember:\n"
prompt += f"{conversation_context.memories}\n\n"
prompt += "From the technical manuals, I found:\n"
prompt += f"{document_context.memories}\n\n"
prompt += f"Based on all this, here is my answer to '{user_query}':"
return await self.llm.generate_content_async(prompt)