Saltar a contenido

Memoria: Conocimiento a Largo Plazo con MemoryService

Supported in ADKPython v0.1.0Typescript v0.2.0Go v0.1.0Java v0.1.0

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:

  1. Ingerir Información (add_session_to_memory): Tomar el contenido de una Session (generalmente completada) y agregar información relevante al almacén de conocimiento a largo plazo.
  2. Buscar Información (search_memory): Permitir que un agente (típicamente a través de una Tool) 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.

from google.adk.memory import InMemoryMemoryService
memory_service = InMemoryMemoryService()
import (
  "google.golang.org/adk/memory"
  "google.golang.org/adk/session"
)

// Los servicios deben compartirse entre runners para compartir estado y memoria.
sessionService := session.InMemoryService()
memoryService := memory.InMemoryService()

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:

  1. Un Proyecto de Google Cloud: Con la API de Vertex AI habilitada.
  2. 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.
  3. 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:
    gcloud auth application-default login
    
  4. Variables de Entorno: El servicio requiere tu ID de Proyecto de Google Cloud y Ubicación. Configúralos como variables de entorno:
    export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
    export GOOGLE_CLOUD_LOCATION="your-gcp-location"
    

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>.

bash
adk web path/to/your/agents_dir --memory_service_uri="agentengine://1234567890"

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:

  1. Interacción de Sesión: Un usuario interactúa con un agente a través de una Session, gestionada por un SessionService. Se agregan eventos, y el estado puede actualizarse.
  2. 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).
  3. 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?").
  4. 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").
  5. Ejecución de Búsqueda: La herramienta internamente llama a memory_service.search_memory(app_name, user_id, query).
  6. Resultados Devueltos: El MemoryService busca en su almacén (usando coincidencia de palabras clave o búsqueda semántica) y devuelve fragmentos relevantes como un SearchMemoryResponse que contiene una lista de objetos MemoryResult (cada uno potencialmente conteniendo eventos de una sesión pasada relevante).
  7. 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 incorporado self.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)