Skip to content

DOC: Recommend MemoryService approach over function tools for Google ADK integration #3999

@miyannishar

Description

@miyannishar

Issue with current documentation:

The current Google ADK integration documentation (docs/integrations/google-ai-adk.mdx) shows integration using function tools (search_memory, save_memory), but the better and more idiomatic approach is to use ADK's native MemoryService interface with PreloadMemoryTool and callbacks. The approach shown in the docs is just adding the overload to the agents with an extra tool, but this approach is more aligned with ADK's architecture and provides better automatic memory management.

Current Approach (Tools-Based)

The current docs show:

# Define memory function tools
def search_memory(query: str, user_id: str) -> dict:
    """Search through past conversations and memories"""
    filters = {"user_id": user_id}
    memories = mem0.search(query, filters=filters)
    # ... return formatted results

def save_memory(content: str, user_id: str) -> dict:
    """Save important information to memory"""
    result = mem0.add([{"role": "user", "content": content}], user_id=user_id)
    # ... return status

# Add as tools to agent
agent = Agent(
    name="personal_assistant",
    tools=[search_memory, save_memory]  # Manual tool management
)

Issues with this approach:

  • Requires manual tool invocation by the agent
  • Agent must explicitly decide when to search/save memory
  • No automatic memory injection at conversation start
  • No automatic session saving after conversations
  • More verbose prompts needed to guide agent behavior
  • Memory operations consume tool call tokens

Recommended Approach (MemoryService-Based)

The better approach uses ADK's MemoryService interface:

from google.adk.memory.base_memory_service import BaseMemoryService, SearchMemoryResponse
from google.adk.memory.memory_entry import MemoryEntry
from google.adk.tools.preload_memory_tool import PreloadMemoryTool
from google.adk.agents.callback_context import CallbackContext
from mem0 import MemoryClient

class Mem0MemoryService(BaseMemoryService):
    """MemoryService implementation using Mem0 Platform."""
    
    def __init__(self, api_key: Optional[str] = None):
        super().__init__()
        api_key = api_key or os.environ.get("MEM0_API_KEY")
        self._client = MemoryClient(api_key=api_key) if api_key else None
    
    @override
    async def search_memory(
        self, *, app_name: str, user_id: str, query: str
    ) -> SearchMemoryResponse:
        """Search for relevant memories."""
        if not self.is_enabled():
            return SearchMemoryResponse(memories=[])
        
        filters = {"user_id": user_id}
        memories = self._client.search(query, filters=filters, limit=5)
        
        # Convert Mem0 memories to ADK MemoryEntry format
        memory_entries = []
        for mem in memories.get("results", []):
            memory_text = mem.get("memory", "")
            if memory_text:
                content = Content(parts=[Part(text=memory_text)])
                memory_entry = MemoryEntry(
                    content=content,
                    author=mem.get("metadata", {}).get("author", "user"),
                    timestamp=mem.get("created_at"),
                )
                memory_entries.append(memory_entry)
        
        return SearchMemoryResponse(memories=memory_entries)
    
    @override
    async def add_session_to_memory(self, session: Session) -> None:
        """Add session content to memory."""
        if not self.is_enabled():
            return
        
        user_id = getattr(session, "user_id", None)
        if not user_id:
            return
        
        # Extract messages from session events
        messages = []
        for event in session.events:
            if event.content and event.content.parts:
                role = getattr(event.content, "role", "user")
                # Map ADK's "model" role to Mem0's "assistant"
                if role == "model":
                    role = "assistant"
                
                text_parts = [
                    part.text for part in event.content.parts 
                    if hasattr(part, "text") and part.text
                ]
                if text_parts:
                    messages.append({
                        "role": role,
                        "content": " ".join(text_parts)
                    })
        
        if messages:
            self._client.add(messages, user_id=user_id)

# Setup with automatic memory management
async def auto_save_session_to_memory_callback(callback_context: CallbackContext) -> None:
    """Automatically save session to memory after agent completes."""
    invocation_context = getattr(callback_context, "_invocation_context", None)
    if not invocation_context:
        return
    
    session = getattr(invocation_context, "session", None)
    memory_service = getattr(invocation_context, "memory_service", None)
    
    if session and memory_service:
        await memory_service.add_session_to_memory(session)

# Create memory service
memory_service = Mem0MemoryService()

# Create agent with PreloadMemoryTool (automatically injects memories)
agent = Agent(
    name="personal_assistant",
    tools=[PreloadMemoryTool()],  # Automatically searches and injects memories
    after_agent_callback=auto_save_session_to_memory_callback,  # Auto-saves sessions
)

# Pass memory_service to Runner
runner = Runner(
    agent=agent,
    session_service=session_service,
    memory_service=memory_service,  # Native ADK integration
    app_name="memory_assistant",
)

Benefits of MemoryService Approach

  1. Automatic Memory Injection: PreloadMemoryTool automatically searches and injects relevant memories at the start of each conversation turn, without requiring explicit tool calls
  2. Automatic Session Saving: Callbacks can automatically save sessions to memory after each turn
  3. Native ADK Integration: Uses ADK's built-in memory architecture, not custom tools
  4. Better Token Efficiency: Memory injection happens at prompt construction time, not as tool calls
  5. Cleaner Agent Prompts: No need to instruct agents to "use search_memory function" - it happens automatically
  6. Consistent Across Agents: Works seamlessly in multi-agent hierarchies
  7. User-Scoped by Default: user_id is automatically passed from session context
  8. Better Control: Can limit memory injection (e.g., max 5 memories, 2000 chars total) to prevent prompt bloat

Recommendation

Update the Google ADK integration documentation to:

  1. Primary recommendation: Show the MemoryService approach as the main/recommended method
  2. Secondary option: Keep the tools-based approach as an alternative for cases where manual control is needed
  3. Add examples showing:
    • How to implement BaseMemoryService with Mem0
    • How to use PreloadMemoryTool for automatic memory injection
    • How to use callbacks for automatic session saving
    • How to configure memory limits to prevent prompt bloat

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2-mediumAnnoying but has workaroundsdocumentationImprovements or additions to documentationsdk-pythonPython SDK specific

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions