Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions contributing/samples/open_memory/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# OpenMemory Sample

This sample demonstrates how to use OpenMemory as a self-hosted memory backend
for ADK agents.

## Prerequisites

- Python 3.9+ (Python 3.11+ recommended)
- Docker (for running OpenMemory)
- ADK installed with dependencies

## Setup

### 1. Start OpenMemory Server

Start OpenMemory using Docker:

```bash
docker run -p 3000:3000 cavira/openmemory
```

Or use the production network build:

```bash
docker run -p 3000:3000 cavira/openmemory:production
```

Verify it's running:

```bash
curl http://localhost:3000/health
```

### 2. Install Dependencies

Install ADK with OpenMemory support:

```bash
pip install google-adk[openmemory]
```

This installs `httpx` for making HTTP requests to the OpenMemory API.

### 3. Configure Environment Variables

Create a `.env` file in this directory (optional):

```bash
# Required: Google API key for the agent
GOOGLE_API_KEY=your-google-api-key

# Optional: OpenMemory base URL (defaults to localhost:3000)
OPENMEMORY_BASE_URL=http://localhost:3000

# Optional: API key if your OpenMemory instance requires authentication
# OPENMEMORY_API_KEY=your-api-key
```

**Note:** API key is only needed if your OpenMemory server is configured with authentication.

## Usage

### Basic Usage

```python
from google.adk.memory import OpenMemoryService
from google.adk.runners import Runner

# Create OpenMemory service with defaults
memory_service = OpenMemoryService(
base_url="http://localhost:3000"
)

# Use with runner
runner = Runner(
app_name="my_app",
agent=my_agent,
memory_service=memory_service
)
```

### Advanced Configuration

```python
from google.adk.memory import OpenMemoryService, OpenMemoryServiceConfig

# Custom configuration
config = OpenMemoryServiceConfig(
search_top_k=20, # Retrieve more memories per query
timeout=10.0, # Faster timeout for production
user_content_salience=0.9, # Higher importance for user messages
model_content_salience=0.75, # Medium importance for model responses
enable_metadata_tags=True # Use tags for filtering
)

memory_service = OpenMemoryService(
base_url="http://localhost:3000",
api_key="your-api-key",
config=config
)
```

## Running the Sample

```bash
cd contributing/samples/open_memory
python main.py
```

## Expected Output

```
----Session to create memory: <session-id> ----------------------
** User says: {'role': 'user', 'parts': [{'text': 'Hi'}]}
** model: Hello! How can I help you today?
...
Saving session to memory service...
-------------------------------------------------------------------
----Session to use memory: <session-id> ----------------------
** User says: {'role': 'user', 'parts': [{'text': 'What do I like to do?'}]}
** model: You like badminton.
...
-------------------------------------------------------------------
```

## Configuration Options

### OpenMemoryServiceConfig

- `search_top_k` (int, default: 10): Maximum memories to retrieve per search
- `timeout` (float, default: 30.0): HTTP request timeout in seconds
- `user_content_salience` (float, default: 0.8): Importance for user messages
- `model_content_salience` (float, default: 0.7): Importance for model responses
- `default_salience` (float, default: 0.6): Fallback importance value
- `enable_metadata_tags` (bool, default: True): Include session/app tags

## Features

OpenMemory provides:

- **Multi-sector embeddings**: Factual, emotional, temporal, relational memory
- **Graceful decay curves**: Automatic reinforcement keeps relevant context sharp
- **Self-hosted**: Full data ownership, no vendor lock-in
- **High performance**: 2-3× faster than hosted alternatives
- **Cost-effective**: 6-10× cheaper than SaaS memory APIs

## Learn More

- [OpenMemory Documentation](https://openmemory.cavira.app/)
- [OpenMemory API Reference](https://openmemory.cavira.app/docs/api/add-memory)
- [ADK Memory Documentation](https://google.github.io/adk-docs)

16 changes: 16 additions & 0 deletions contributing/samples/open_memory/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2025 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.

from . import agent

39 changes: 39 additions & 0 deletions contributing/samples/open_memory/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2025 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.


from datetime import datetime

from google.adk import Agent
from google.adk.agents.callback_context import CallbackContext
from google.adk.tools.load_memory_tool import load_memory_tool
from google.adk.tools.preload_memory_tool import preload_memory_tool


def update_current_time(callback_context: CallbackContext):
callback_context.state['_time'] = datetime.now().isoformat()


root_agent = Agent(
model='gemini-2.5-flash',
name='open_memory_agent',
description='agent that has access to memory tools with OpenMemory.',
before_agent_callback=update_current_time,
instruction=(
'You are an agent that helps user answer questions. You have access to memory tools.\n'
'You can use the memory tools to look up the information in the memory. Current time: {_time}'
),
tools=[load_memory_tool, preload_memory_tool],
)

143 changes: 143 additions & 0 deletions contributing/samples/open_memory/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Copyright 2025 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.

import asyncio
import os
from datetime import datetime
from datetime import timedelta
from typing import cast

import agent
from dotenv import load_dotenv
from google.adk.cli.utils import logs
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
from google.adk.memory.open_memory_service import (
OpenMemoryService,
OpenMemoryServiceConfig,
)
from google.adk.runners import Runner
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.sessions.session import Session
from google.genai import types

load_dotenv(override=True)
logs.log_to_tmp_folder()


async def main():
app_name = 'my_app'
user_id_1 = 'user1'

# Configure OpenMemory from environment variables or use defaults
openmemory_base_url = os.getenv('OPENMEMORY_BASE_URL', 'http://localhost:3000')
openmemory_api_key = os.getenv('OPENMEMORY_API_KEY') # Optional

# Create OpenMemory service with custom configuration
config = OpenMemoryServiceConfig(
search_top_k=10,
user_content_salience=0.8,
model_content_salience=0.7,
enable_metadata_tags=True
)
memory_service = OpenMemoryService(
base_url=openmemory_base_url,
api_key=openmemory_api_key,
config=config
)

runner = Runner(
app_name=app_name,
agent=agent.root_agent,
artifact_service=InMemoryArtifactService(),
session_service=InMemorySessionService(),
memory_service=memory_service,
)

async def run_prompt(session: Session, new_message: str) -> Session:
content = types.Content(
role='user', parts=[types.Part.from_text(text=new_message)]
)
print('** User says:', content.model_dump(exclude_none=True))
async for event in runner.run_async(
user_id=user_id_1,
session_id=session.id,
new_message=content,
):
if not event.content or not event.content.parts:
continue
if event.content.parts[0].text:
print(f'** {event.author}: {event.content.parts[0].text}')
elif event.content.parts[0].function_call:
print(
f'** {event.author}: fc /'
f' {event.content.parts[0].function_call.name} /'
f' {event.content.parts[0].function_call.args}\n'
)
elif event.content.parts[0].function_response:
print(
f'** {event.author}: fr /'
f' {event.content.parts[0].function_response.name} /'
f' {event.content.parts[0].function_response.response}\n'
)

return cast(
Session,
await runner.session_service.get_session(
app_name=app_name, user_id=user_id_1, session_id=session.id
),
)

session_1 = await runner.session_service.create_session(
app_name=app_name, user_id=user_id_1
)

print(
f'----Session to create memory: {session_1.id} ----------------------'
)
session_1 = await run_prompt(session_1, 'Hi')
session_1 = await run_prompt(session_1, 'My name is Jack')
session_1 = await run_prompt(session_1, 'I like badminton.')
session_1 = await run_prompt(
session_1,
f'I ate a burger on {(datetime.now() - timedelta(days=1)).date()}.',
)
session_1 = await run_prompt(
session_1,
f'I ate a banana on {(datetime.now() - timedelta(days=2)).date()}.',
)
print('Saving session to memory service...')
if runner.memory_service:
await runner.memory_service.add_session_to_memory(session_1)
print('-------------------------------------------------------------------')

session_2 = await runner.session_service.create_session(
app_name=app_name, user_id=user_id_1
)
print(f'----Session to use memory: {session_2.id} ----------------------')
session_2 = await run_prompt(session_2, 'Hi')
session_2 = await run_prompt(session_2, 'What do I like to do?')
# ** open_memory_agent: You like badminton.
session_2 = await run_prompt(session_2, 'When did I say that?')
# ** open_memory_agent: You said you liked badminton on ...
session_2 = await run_prompt(session_2, 'What did I eat yesterday?')
# ** open_memory_agent: You ate a burger yesterday...
print('-------------------------------------------------------------------')

# Cleanup
await memory_service.close()


if __name__ == '__main__':
asyncio.run(main())

6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ eval = [
# go/keep-sorted end
]

openmemory = [
# go/keep-sorted start
"httpx>=0.27.0, <1.0.0",
# go/keep-sorted end
]

test = [
# go/keep-sorted start
"a2a-sdk>=0.3.0,<0.4.0;python_version>='3.10'",
Expand Down
5 changes: 4 additions & 1 deletion src/google/adk/cli/cli_tools_click.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,10 @@ def decorator(func):
- Use 'agentengine://<agent_engine>' to connect to Agent Engine
sessions. <agent_engine> can either be the full qualified resource
name 'projects/abc/locations/us-central1/reasoningEngines/123' or
the resource id '123'."""),
the resource id '123'.
- Use 'openmemory://<host>:<port>' to connect to OpenMemory.
Example: 'openmemory://localhost:3000' or
'openmemory://localhost:3000?api_key=secret'."""),
default=None,
)
@functools.wraps(func)
Expand Down
Loading