diff --git a/docs/integrations/google-ai-adk.mdx b/docs/integrations/google-ai-adk.mdx index d8b5d5e8f..cf157dd09 100644 --- a/docs/integrations/google-ai-adk.mdx +++ b/docs/integrations/google-ai-adk.mdx @@ -7,285 +7,338 @@ Integrate [**Mem0**](https://github.com/mem0ai/mem0) with [Google ADK (Agent Dev ## Overview -1. Store and retrieve memories from Mem0 within Google ADK agents -2. Multi-agent workflows with shared memory across hierarchies -3. Retrieve relevant memories from past conversations -4. Personalized responses based on user history +In this guide, we'll create a Google ADK agent that: +1. Uses ADK's native `MemoryService` interface to connect Mem0 +2. Automatically injects relevant memories using ADK's built-in `load_memory` tool +3. Persists session history to Mem0 after each turn via an after-agent callback +4. Shares memory seamlessly across multi-agent hierarchies -## Prerequisites +## Setup and Configuration -Before setting up Mem0 with Google ADK, ensure you have: +Install the necessary libraries: -1. Installed the required packages: ```bash pip install google-adk mem0ai python-dotenv ``` -2. Valid API keys: +Set up your API keys: - Mem0 API Key - Google AI Studio API Key -## Basic Integration Example - -The following example demonstrates how to create a Google ADK agent with Mem0 memory integration: +Remember to get your API key from Mem0 Platform and set up a [Google AI Studio API Key](https://aistudio.google.com/apikey). ```python import os -import asyncio -from google.adk.agents import Agent -from google.adk.runners import Runner -from google.adk.sessions import InMemorySessionService -from google.genai import types -from mem0 import MemoryClient from dotenv import load_dotenv load_dotenv() -# Set up environment variables # os.environ["GOOGLE_API_KEY"] = "your-google-api-key" # os.environ["MEM0_API_KEY"] = "your-mem0-api-key" +``` -# Initialize Mem0 client -mem0 = MemoryClient() +## Implement Mem0MemoryService -# Define memory function tools -def search_memory(query: str, user_id: str) -> dict: - """Search through past conversations and memories""" - # For Platform API, user_id goes in filters - filters = {"user_id": user_id} - memories = mem0.search(query, filters=filters) - if memories.get('results', []): - memory_list = memories['results'] - memory_context = "\n".join([f"- {mem['memory']}" for mem in memory_list]) - return {"status": "success", "memories": memory_context} - return {"status": "no_memories", "message": "No relevant memories found"} +Create a custom `MemoryService` by implementing ADK's `BaseMemoryService`. Save the following as **`mem0_memory_service.py`**: -def save_memory(content: str, user_id: str) -> dict: - """Save important information to memory""" +```python +import asyncio +import os +from typing import Optional +from typing_extensions import override + +from google.adk.memory.base_memory_service import BaseMemoryService, SearchMemoryResponse +from google.adk.memory.memory_entry import MemoryEntry +from google.adk.sessions import Session +from google.genai.types import Content, Part +from mem0 import MemoryClient + + +class Mem0MemoryService(BaseMemoryService): + """MemoryService implementation backed by the 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: Optional[MemoryClient] = 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 memories relevant to the current user and query.""" + if not self._client: + return SearchMemoryResponse(memories=[]) + + try: + results = await asyncio.to_thread( + self._client.search, + query, + filters={"AND": [{"user_id": user_id}, {"app_id": app_name}]}, + top_k=5, + ) + + entries = [] + for mem in results.get("results", []): + text = mem.get("memory", "") + if not text: + continue + + raw_ts = mem.get("created_at") or mem.get("updated_at") + entries.append( + MemoryEntry( + content=Content(parts=[Part(text=text)]), + author=mem.get("metadata", {}).get("author", "user"), + timestamp=str(raw_ts) if raw_ts else None, + ) + ) + + return SearchMemoryResponse(memories=entries) + + except Exception as e: + print(f"[Mem0MemoryService] search_memory error: {e}") + return SearchMemoryResponse(memories=[]) + + @override + async def add_session_to_memory(self, session: Session) -> None: + """Persist a completed ADK session into Mem0.""" + if not self._client: + return + + user_id = session.user_id + if not user_id: + return + + app_name = getattr(session, "app_name", None) + + try: + messages = [] + for event in session.events: + if not (event.content and event.content.parts): + continue + role = getattr(event.content, "role", None) or "user" + if role == "model": + role = "assistant" + elif role not in ("user", "assistant"): + continue + text_parts = [ + p.text for p in event.content.parts if hasattr(p, "text") and p.text + ] + if text_parts: + messages.append({"role": role, "content": " ".join(text_parts)}) + + if messages: + metadata = {"app_id": app_name} if app_name else {} + await asyncio.to_thread( + self._client.add, messages, user_id=user_id, metadata=metadata + ) + + except Exception as e: + print(f"[Mem0MemoryService] add_session_to_memory error: {e}") +``` + +## Add Auto-Save Callback + +This after-agent callback fires at the end of every turn and saves the session to Mem0. Save as **`memory_callbacks.py`**: + +```python +async def save_session_to_memory(callback_context) -> None: + """Persist the completed session to Mem0 after each agent turn.""" try: - result = mem0.add([{"role": "user", "content": content}], user_id=user_id) - return {"status": "success", "message": "Information saved to memory", "result": result} + await callback_context.add_session_to_memory() + except ValueError: + pass except Exception as e: - return {"status": "error", "message": f"Failed to save memory: {str(e)}"} + print(f"[save_session_to_memory] error: {e}") +``` -# Create agent with memory capabilities -personal_assistant = Agent( +## Basic Integration Example + +The following example demonstrates creating an ADK agent with automatic Mem0 memory: + +```python +import asyncio +from google.adk.agents import LlmAgent +from google.adk.runners import Runner +from google.adk.sessions import InMemorySessionService +from google.adk.tools import load_memory +from google.genai.types import Content, Part + +from mem0_memory_service import Mem0MemoryService +from memory_callbacks import save_session_to_memory + +memory_service = Mem0MemoryService() +session_service = InMemorySessionService() + +agent = LlmAgent( name="personal_assistant", model="gemini-2.0-flash", - instruction="""You are a helpful personal assistant with memory capabilities. - Use the search_memory function to recall past conversations and user preferences. - Use the save_memory function to store important information about the user. - Always personalize your responses based on available memory.""", + instruction="""You are a helpful personal assistant. + Relevant memories from past conversations are provided to you automatically. + Use them to personalize your responses.""", description="A personal assistant that remembers user preferences and past interactions", - tools=[search_memory, save_memory] + tools=[load_memory], + after_agent_callback=save_session_to_memory, ) -async def chat_with_agent(user_input: str, user_id: str) -> str: - """ - Handle user input with automatic memory integration. +runner = Runner( + agent=agent, + session_service=session_service, + memory_service=memory_service, + app_name="memory_assistant", +) - Args: - user_input: The user's message - user_id: Unique identifier for the user - Returns: - The agent's response - """ - # Set up session and runner - session_service = InMemorySessionService() +async def chat(user_input: str, user_id: str) -> str: session = await session_service.create_session( app_name="memory_assistant", user_id=user_id, - session_id=f"session_{user_id}" ) - runner = Runner(agent=personal_assistant, app_name="memory_assistant", session_service=session_service) - - # Create content and run agent - content = types.Content(role='user', parts=[types.Part(text=user_input)]) - events = runner.run(user_id=user_id, session_id=session.id, new_message=content) - - # Extract final response - for event in events: - if event.is_final_response(): - response = event.content.parts[0].text - - return response - + content = Content(role="user", parts=[Part(text=user_input)]) + async for event in runner.run_async(user_id=user_id, session_id=session.id, new_message=content): + if event.is_final_response() and event.content and event.content.parts: + return event.content.parts[0].text return "No response generated" -# Example usage + if __name__ == "__main__": - response = asyncio.run(chat_with_agent( + print(asyncio.run(chat( "I love Italian food and I'm planning a trip to Rome next month", - user_id="alice" - )) - print(response) + user_id="alice", + ))) + + print(asyncio.run(chat( + "Any food recommendations for my trip?", + user_id="alice", + ))) ``` ## Multi-Agent Hierarchy with Shared Memory -Create specialized agents in a hierarchy that share memory: +Because `memory_service` is passed to the `Runner`, every agent in the hierarchy shares the same memory automatically. Only the root coordinator needs the auto-save callback — ADK fires it once when the full turn completes: ```python +import asyncio +from google.adk.agents import LlmAgent +from google.adk.runners import Runner +from google.adk.sessions import InMemorySessionService from google.adk.tools.agent_tool import AgentTool +from google.adk.tools import load_memory +from google.genai.types import Content, Part -# Travel specialist agent -travel_agent = Agent( +from mem0_memory_service import Mem0MemoryService +from memory_callbacks import save_session_to_memory + +memory_service = Mem0MemoryService() +session_service = InMemorySessionService() + +travel_agent = LlmAgent( name="travel_specialist", model="gemini-2.0-flash", - instruction="""You are a travel planning specialist. Use search_memory to - understand the user's travel preferences and history before making recommendations. - After providing advice, use save_memory to save travel-related information.""", + instruction="""You are a travel planning specialist. + Relevant memories about the user's travel preferences are provided automatically. + Use them to make personalized recommendations.""", description="Specialist in travel planning and recommendations", - tools=[search_memory, save_memory] + tools=[load_memory], ) -# Health advisor agent -health_agent = Agent( +health_agent = LlmAgent( name="health_advisor", model="gemini-2.0-flash", - instruction="""You are a health and wellness advisor. Use search_memory to - understand the user's health goals and dietary preferences. - After providing advice, use save_memory to save health-related information.""", + instruction="""You are a health and wellness advisor. + Relevant memories about the user's health goals are provided automatically. + Use them to give personalized advice.""", description="Specialist in health and wellness advice", - tools=[search_memory, save_memory] + tools=[load_memory], ) -# Coordinator agent that delegates to specialists -coordinator_agent = Agent( +coordinator = LlmAgent( name="coordinator", model="gemini-2.0-flash", instruction="""You are a coordinator that delegates requests to specialist agents. - For travel-related questions (trips, hotels, flights, destinations), delegate to the travel specialist. - For health-related questions (fitness, diet, wellness, exercise), delegate to the health advisor. - Use search_memory to understand the user before delegation.""", + For travel-related questions, delegate to the travel specialist. + For health-related questions, delegate to the health advisor. + Relevant memories about the user are provided automatically.""", description="Coordinates requests between specialist agents", tools=[ + load_memory, AgentTool(agent=travel_agent, skip_summarization=False), - AgentTool(agent=health_agent, skip_summarization=False) - ] + AgentTool(agent=health_agent, skip_summarization=False), + ], + after_agent_callback=save_session_to_memory, ) -def chat_with_specialists(user_input: str, user_id: str) -> str: - """ - Handle user input with specialist agent delegation and memory. +runner = Runner( + agent=coordinator, + session_service=session_service, + memory_service=memory_service, + app_name="specialist_system", +) - Args: - user_input: The user's message - user_id: Unique identifier for the user - Returns: - The specialist agent's response - """ - session_service = InMemorySessionService() - session = session_service.create_session( +async def chat_with_specialists(user_input: str, user_id: str) -> str: + session = await session_service.create_session( app_name="specialist_system", user_id=user_id, - session_id=f"session_{user_id}" ) - runner = Runner(agent=coordinator_agent, app_name="specialist_system", session_service=session_service) - - content = types.Content(role='user', parts=[types.Part(text=user_input)]) - events = runner.run(user_id=user_id, session_id=session.id, new_message=content) - - for event in events: - if event.is_final_response(): - response = event.content.parts[0].text - - # Store the conversation in shared memory - conversation = [ - {"role": "user", "content": user_input}, - {"role": "assistant", "content": response} - ] - mem0.add(conversation, user_id=user_id) - - return response - + content = Content(role="user", parts=[Part(text=user_input)]) + async for event in runner.run_async(user_id=user_id, session_id=session.id, new_message=content): + if event.is_final_response() and event.content and event.content.parts: + return event.content.parts[0].text return "No response generated" -# Example usage -response = chat_with_specialists("Plan a healthy meal for my Italy trip", user_id="alice") -print(response) -``` - - - -## Quick Start Chat Interface - -Simple interactive chat with memory and Google ADK: - -```python -def interactive_chat(): - """Interactive chat interface with memory and ADK""" - user_id = input("Enter your user ID: ") or "demo_user" - print(f"Chat started for user: {user_id}") - print("Type 'quit' to exit") - print("=" * 50) - - while True: - user_input = input("\nYou: ") - - if user_input.lower() == 'quit': - print("Goodbye! Your conversation has been saved to memory.") - break - else: - response = chat_with_specialists(user_input, user_id) - print(f"Assistant: {response}") if __name__ == "__main__": - interactive_chat() + response = asyncio.run(chat_with_specialists("Plan a healthy meal for my Italy trip", user_id="alice")) + print(response) ``` ## Key Features -### 1. Memory-Enhanced Function Tools -- **Function Tools**: Standard Python functions that can search and save memories -- **Tool Context**: Access to session state and memory through function parameters -- **Structured Returns**: Dictionary-based returns with status indicators for better LLM understanding - -### 2. Multi-Agent Memory Sharing -- **Agent-as-a-Tool**: Specialists can be called as tools while maintaining shared memory -- **Hierarchical Delegation**: Coordinator agents route to specialists based on context -- **Memory Categories**: Store interactions with metadata for better organization - -### 3. Flexible Memory Operations -- **Search Capabilities**: Retrieve relevant memories through conversation history -- **User Segmentation**: Organize memories by user ID -- **Memory Management**: Built-in tools for saving and retrieving information +1. **Automatic Memory Injection**: ADK's built-in `load_memory` tool searches Mem0 at the start of each turn and injects relevant memories directly into the agent context — no prompt instructions needed. +2. **Automatic Session Saving**: The `save_session_to_memory` callback persists every completed turn to Mem0 without any manual calls. +3. **Native ADK Integration**: `Mem0MemoryService` implements ADK's `BaseMemoryService` and integrates via the `Runner` — works natively across the entire agent hierarchy. +4. **User Scoping**: `user_id` is passed automatically from the ADK session context, ensuring memories are always scoped to the correct user. +5. **Multi-Agent Support**: A single `Mem0MemoryService` instance shared through the `Runner` gives all agents — coordinators and specialists — access to the same user memory. ## Configuration Options -Customize memory behavior and agent setup: +### Using Vertex AI + +To use Google Cloud Vertex AI instead of AI Studio, set the following environment variables before creating agents: ```python -# Configure memory search with filters -# For Platform API, all filters including user_id go in filters object -memories = mem0.search( - query="travel preferences", - filters={ - "AND": [ - {"user_id": "alice"}, - {"categories": {"contains": "travel"}} - ] - }, - top_k=5 -) - -# Configure agent with custom model settings -agent = Agent( - name="custom_agent", - model="gemini-2.0-flash", # or use LiteLLM for other models - instruction="Custom agent behavior", - tools=[memory_tools], - # Additional ADK configurations -) - -# Use Google Cloud Vertex AI instead of AI Studio +import os os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "True" os.environ["GOOGLE_CLOUD_PROJECT"] = "your-project-id" os.environ["GOOGLE_CLOUD_LOCATION"] = "us-central1" ``` +### Advanced Memory Filtering + +You can customize how memories are searched by modifying `Mem0MemoryService.search_memory`. For example, to filter by category: + +```python +results = await asyncio.to_thread( + self._client.search, + query, + filters={ + "AND": [ + {"user_id": user_id}, + {"app_id": app_name}, + {"categories": {"contains": "travel"}} + ] + }, + top_k=10, +) +``` + +`InMemorySessionService` stores sessions in memory and is intended for prototyping. For production, use a persistent session service and clean up sessions when they are no longer needed. + +## Conclusion + +By implementing `Mem0MemoryService` as an ADK `BaseMemoryService`, you get persistent, user-scoped memory across single agents and complex multi-agent hierarchies with minimal code. Memory injection and session saving happen automatically, keeping your agent prompts clean and your token usage efficient. + Build HIPAA-compliant healthcare agents with Google ADK @@ -294,4 +347,3 @@ os.environ["GOOGLE_CLOUD_LOCATION"] = "us-central1" Compare with OpenAI's agent framework -