mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-06 08:16:03 +00:00
initial setup
This commit is contained in:
parent
8eaea21619
commit
ec26706668
3 changed files with 393 additions and 0 deletions
86
packages/adk-sdk-python/src/supermemory_adk/__init__.py
Normal file
86
packages/adk-sdk-python/src/supermemory_adk/__init__.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
"""Supermemory ADK - Memory-enhanced AI agents with Google Agent Development Kit.
|
||||
|
||||
This package provides seamless integration between Supermemory and Google's Agent
|
||||
Development Kit (ADK), enabling persistent memory and context enhancement for AI agents.
|
||||
|
||||
Example (Tools Mode):
|
||||
```python
|
||||
from google.adk.agents import Agent
|
||||
from supermemory_adk import create_supermemory_tools
|
||||
|
||||
# Create Supermemory tools
|
||||
tools = create_supermemory_tools(
|
||||
api_key="your-api-key",
|
||||
container_tags=["user-123"]
|
||||
)
|
||||
|
||||
# Add tools to agent
|
||||
root_agent = Agent(
|
||||
model='gemini-2.5-flash',
|
||||
tools=[tools.search_memories, tools.add_memory],
|
||||
instruction="Use memory tools when needed"
|
||||
)
|
||||
```
|
||||
|
||||
Example (Wrapper Mode):
|
||||
```python
|
||||
from google.adk.agents import Agent
|
||||
from supermemory_adk import with_supermemory, MemoryMode
|
||||
|
||||
# Create base agent
|
||||
base_agent = Agent(
|
||||
model='gemini-2.5-flash',
|
||||
instruction="You are a helpful assistant"
|
||||
)
|
||||
|
||||
# Wrap with automatic memory injection
|
||||
root_agent = with_supermemory(
|
||||
base_agent,
|
||||
container_tag="user-123",
|
||||
mode=MemoryMode.FULL,
|
||||
auto_save=True
|
||||
)
|
||||
```
|
||||
"""
|
||||
|
||||
from .exceptions import (
|
||||
SupermemoryADKError,
|
||||
SupermemoryAPIError,
|
||||
SupermemoryConfigurationError,
|
||||
SupermemoryMemoryOperationError,
|
||||
SupermemoryNetworkError,
|
||||
SupermemoryTimeoutError,
|
||||
SupermemoryToolError,
|
||||
)
|
||||
from .utils import (
|
||||
DeduplicatedMemories,
|
||||
Logger,
|
||||
create_logger,
|
||||
deduplicate_memories,
|
||||
format_memories_to_markdown,
|
||||
format_memories_to_text,
|
||||
)
|
||||
|
||||
__version__ = "0.1.0"
|
||||
|
||||
__all__ = [
|
||||
# Version
|
||||
"__version__",
|
||||
# Exceptions
|
||||
"SupermemoryADKError",
|
||||
"SupermemoryConfigurationError",
|
||||
"SupermemoryAPIError",
|
||||
"SupermemoryMemoryOperationError",
|
||||
"SupermemoryNetworkError",
|
||||
"SupermemoryTimeoutError",
|
||||
"SupermemoryToolError",
|
||||
# Utils
|
||||
"Logger",
|
||||
"create_logger",
|
||||
"DeduplicatedMemories",
|
||||
"deduplicate_memories",
|
||||
"format_memories_to_markdown",
|
||||
"format_memories_to_text",
|
||||
# Tools
|
||||
# Wrapper
|
||||
]
|
||||
72
packages/adk-sdk-python/src/supermemory_adk/exceptions.py
Normal file
72
packages/adk-sdk-python/src/supermemory_adk/exceptions.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
"""Custom exceptions for Supermemory ADK integration."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class SupermemoryADKError(Exception):
|
||||
"""Base exception for all Supermemory ADK errors."""
|
||||
|
||||
def __init__(self, message: str, original_error: Optional[Exception] = None):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.original_error = original_error
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.original_error:
|
||||
return f"{self.message}: {self.original_error}"
|
||||
return self.message
|
||||
|
||||
|
||||
class SupermemoryConfigurationError(SupermemoryADKError):
|
||||
"""Raised when there are configuration issues (e.g., missing API key, invalid params)."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class SupermemoryAPIError(SupermemoryADKError):
|
||||
"""Raised when Supermemory API requests fail."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
status_code: Optional[int] = None,
|
||||
response_text: Optional[str] = None,
|
||||
original_error: Optional[Exception] = None,
|
||||
):
|
||||
super().__init__(message, original_error)
|
||||
self.status_code = status_code
|
||||
self.response_text = response_text
|
||||
|
||||
def __str__(self) -> str:
|
||||
parts = [self.message]
|
||||
if self.status_code:
|
||||
parts.append(f"Status: {self.status_code}")
|
||||
if self.response_text:
|
||||
parts.append(f"Response: {self.response_text}")
|
||||
if self.original_error:
|
||||
parts.append(f"Cause: {self.original_error}")
|
||||
return " | ".join(parts)
|
||||
|
||||
|
||||
class SupermemoryMemoryOperationError(SupermemoryADKError):
|
||||
"""Raised when memory operations (search, add) fail."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class SupermemoryTimeoutError(SupermemoryADKError):
|
||||
"""Raised when operations timeout."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class SupermemoryNetworkError(SupermemoryADKError):
|
||||
"""Raised when network operations fail."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class SupermemoryToolError(SupermemoryADKError):
|
||||
"""Raised when ADK tool execution fails."""
|
||||
|
||||
pass
|
||||
235
packages/adk-sdk-python/src/supermemory_adk/utils.py
Normal file
235
packages/adk-sdk-python/src/supermemory_adk/utils.py
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
"""Utility functions for Supermemory ADK integration."""
|
||||
|
||||
import json
|
||||
from typing import Any, Optional, Protocol
|
||||
|
||||
|
||||
class Logger(Protocol):
|
||||
"""Logger protocol for type safety."""
|
||||
|
||||
def debug(self, message: str, data: Optional[dict[str, Any]] = None) -> None:
|
||||
"""Log debug message."""
|
||||
...
|
||||
|
||||
def info(self, message: str, data: Optional[dict[str, Any]] = None) -> None:
|
||||
"""Log info message."""
|
||||
...
|
||||
|
||||
def warn(self, message: str, data: Optional[dict[str, Any]] = None) -> None:
|
||||
"""Log warning message."""
|
||||
...
|
||||
|
||||
def error(self, message: str, data: Optional[dict[str, Any]] = None) -> None:
|
||||
"""Log error message."""
|
||||
...
|
||||
|
||||
|
||||
class SimpleLogger:
|
||||
"""Simple logger implementation."""
|
||||
|
||||
def __init__(self, verbose: bool = False):
|
||||
self.verbose: bool = verbose
|
||||
|
||||
def _log(self, level: str, message: str, data: Optional[dict[str, Any]] = None) -> None:
|
||||
"""Internal logging method."""
|
||||
if not self.verbose:
|
||||
return
|
||||
|
||||
log_message = f"[supermemory-adk] {message}"
|
||||
if data:
|
||||
log_message += f" {json.dumps(data, indent=2)}"
|
||||
|
||||
if level == "error":
|
||||
print(f"ERROR: {log_message}", flush=True)
|
||||
elif level == "warn":
|
||||
print(f"WARN: {log_message}", flush=True)
|
||||
else:
|
||||
print(log_message, flush=True)
|
||||
|
||||
def debug(self, message: str, data: Optional[dict[str, Any]] = None) -> None:
|
||||
"""Log debug message."""
|
||||
self._log("debug", message, data)
|
||||
|
||||
def info(self, message: str, data: Optional[dict[str, Any]] = None) -> None:
|
||||
"""Log info message."""
|
||||
self._log("info", message, data)
|
||||
|
||||
def warn(self, message: str, data: Optional[dict[str, Any]] = None) -> None:
|
||||
"""Log warning message."""
|
||||
self._log("warn", message, data)
|
||||
|
||||
def error(self, message: str, data: Optional[dict[str, Any]] = None) -> None:
|
||||
"""Log error message."""
|
||||
self._log("error", message, data)
|
||||
|
||||
|
||||
def create_logger(verbose: bool) -> Logger:
|
||||
"""Create a logger instance.
|
||||
|
||||
Args:
|
||||
verbose: Whether to enable verbose logging
|
||||
|
||||
Returns:
|
||||
Logger instance
|
||||
"""
|
||||
return SimpleLogger(verbose)
|
||||
|
||||
|
||||
class DeduplicatedMemories:
|
||||
"""Deduplicated memory strings organized by source."""
|
||||
|
||||
def __init__(self, static: list[str], dynamic: list[str], search_results: list[str]):
|
||||
self.static = static
|
||||
self.dynamic = dynamic
|
||||
self.search_results = search_results
|
||||
|
||||
|
||||
def deduplicate_memories(
|
||||
static: Optional[list[Any]] = None,
|
||||
dynamic: Optional[list[Any]] = None,
|
||||
search_results: Optional[list[Any]] = None,
|
||||
) -> DeduplicatedMemories:
|
||||
"""Deduplicate memory items across sources.
|
||||
|
||||
Priority: Static > Dynamic > Search Results.
|
||||
Same memory appearing in multiple sources is kept only in the highest-priority source.
|
||||
|
||||
Args:
|
||||
static: Static profile memories
|
||||
dynamic: Dynamic profile memories
|
||||
search_results: Search result memories
|
||||
|
||||
Returns:
|
||||
DeduplicatedMemories with deduplicated lists
|
||||
"""
|
||||
static_items = static or []
|
||||
dynamic_items = dynamic or []
|
||||
search_items = search_results or []
|
||||
|
||||
def extract_memory_text(item: Any) -> Optional[str]:
|
||||
"""Extract memory text from various formats."""
|
||||
if item is None:
|
||||
return None
|
||||
if isinstance(item, dict):
|
||||
memory = item.get("memory")
|
||||
if isinstance(memory, str):
|
||||
trimmed = memory.strip()
|
||||
return trimmed if trimmed else None
|
||||
return None
|
||||
if isinstance(item, str):
|
||||
trimmed = item.strip()
|
||||
return trimmed if trimmed else None
|
||||
return None
|
||||
|
||||
static_memories: list[str] = []
|
||||
seen_memories: set[str] = set()
|
||||
|
||||
# Add static memories first
|
||||
for item in static_items:
|
||||
memory = extract_memory_text(item)
|
||||
if memory is not None:
|
||||
static_memories.append(memory)
|
||||
seen_memories.add(memory)
|
||||
|
||||
# Add dynamic memories (skip duplicates)
|
||||
dynamic_memories: list[str] = []
|
||||
for item in dynamic_items:
|
||||
memory = extract_memory_text(item)
|
||||
if memory is not None and memory not in seen_memories:
|
||||
dynamic_memories.append(memory)
|
||||
seen_memories.add(memory)
|
||||
|
||||
# Add search results (skip duplicates)
|
||||
search_memories: list[str] = []
|
||||
for item in search_items:
|
||||
memory = extract_memory_text(item)
|
||||
if memory is not None and memory not in seen_memories:
|
||||
search_memories.append(memory)
|
||||
seen_memories.add(memory)
|
||||
|
||||
return DeduplicatedMemories(
|
||||
static=static_memories,
|
||||
dynamic=dynamic_memories,
|
||||
search_results=search_memories,
|
||||
)
|
||||
|
||||
|
||||
def format_memories_to_markdown(
|
||||
memories: DeduplicatedMemories,
|
||||
include_static: bool = True,
|
||||
include_dynamic: bool = True,
|
||||
include_search: bool = True,
|
||||
) -> str:
|
||||
"""Format deduplicated memories into markdown.
|
||||
|
||||
Args:
|
||||
memories: Deduplicated memories
|
||||
include_static: Whether to include static profile memories
|
||||
include_dynamic: Whether to include dynamic profile memories
|
||||
include_search: Whether to include search result memories
|
||||
|
||||
Returns:
|
||||
Markdown formatted string
|
||||
|
||||
Example:
|
||||
```python
|
||||
memories = DeduplicatedMemories(
|
||||
static=["User prefers Python"],
|
||||
dynamic=["Recently asked about AI"],
|
||||
search_results=["Likes coffee"]
|
||||
)
|
||||
|
||||
markdown = format_memories_to_markdown(memories)
|
||||
# Returns formatted markdown with sections
|
||||
```
|
||||
"""
|
||||
sections = []
|
||||
|
||||
if include_static and memories.static:
|
||||
sections.append("## User Profile (Persistent)")
|
||||
sections.append("\n".join(f"- {item}" for item in memories.static))
|
||||
|
||||
if include_dynamic and memories.dynamic:
|
||||
sections.append("## Recent Context")
|
||||
sections.append("\n".join(f"- {item}" for item in memories.dynamic))
|
||||
|
||||
if include_search and memories.search_results:
|
||||
sections.append("## Relevant Memories")
|
||||
sections.append("\n".join(f"- {item}" for item in memories.search_results))
|
||||
|
||||
if not sections:
|
||||
return ""
|
||||
|
||||
return "\n\n".join(sections)
|
||||
|
||||
|
||||
def format_memories_to_text(
|
||||
memories: DeduplicatedMemories,
|
||||
system_prompt: str = "Based on previous conversations, I recall:\n\n",
|
||||
include_static: bool = True,
|
||||
include_dynamic: bool = True,
|
||||
include_search: bool = True,
|
||||
) -> str:
|
||||
"""Format deduplicated memories into text with system prompt.
|
||||
|
||||
Args:
|
||||
memories: Deduplicated memories
|
||||
system_prompt: Prefix text for memory context
|
||||
include_static: Whether to include static profile memories
|
||||
include_dynamic: Whether to include dynamic profile memories
|
||||
include_search: Whether to include search result memories
|
||||
|
||||
Returns:
|
||||
Formatted text string with system prompt prefix
|
||||
"""
|
||||
markdown = format_memories_to_markdown(
|
||||
memories,
|
||||
include_static=include_static,
|
||||
include_dynamic=include_dynamic,
|
||||
include_search=include_search,
|
||||
)
|
||||
|
||||
if not markdown:
|
||||
return ""
|
||||
|
||||
return f"{system_prompt}{markdown}"
|
||||
Loading…
Add table
Reference in a new issue