Skip to content
Open
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
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,33 @@ memory.add("User likes coffee", user_id="user123")
results = memory.search("user preferences", user_id="user123")
for result in results.get('results', []):
print(f"- {result.get('memory')}")

# Advanced filters
results = memory.search(
"work updates",
filters={
"created_after": "2024-01-01",
"min_importance": 0.7,
"tags": ["work", "urgent"],
"tag_logic": "AND",
"memory_types": ["long_term"],
"metadata_contains": {"category": "shopping"},
},
user_id="user123",
)

# Fluent filter builder
from powermem import FilterBuilder

filters = (
FilterBuilder()
.after("2024-01-01")
.importance(minimum=0.7)
.tags(["work"], logic="AND")
.metadata_contains({"category": "shopping"})
.build()
)
results = memory.search("work updates", filters=filters, user_id="user123")
```

For more detailed examples and usage patterns, see the [Getting Started Guide](docs/guides/0001-getting_started.md).
Expand Down Expand Up @@ -223,4 +250,4 @@ The MCP server provides tools for memory management including adding, searching,

## 📄 License

This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
1 change: 1 addition & 0 deletions src/powermem/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .core.async_memory import AsyncMemory
from .core.base import MemoryBase
from .user_memory import UserMemory
from .search import FilterBuilder

# Import configuration loader
from .config_loader import load_config_from_env, create_config, validate_config, auto_config
Expand Down
6 changes: 5 additions & 1 deletion src/powermem/config_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,10 @@ class EmbeddingSettings(_BasePowermemSettings):
default=None,
validation_alias=AliasChoices("EMBEDDING_DIMS", "DIMS"),
)
ollama_base_url: Optional[str] = Field(
default=None,
validation_alias=AliasChoices("OLLAMA_EMBEDDING_BASE_URL", "OLLAMA_BASE_URL"),
)

def to_config(self) -> Dict[str, Any]:
embedding_provider = self.provider.lower()
Expand All @@ -405,7 +409,7 @@ def to_config(self) -> Dict[str, Any]:
)
provider_settings = config_cls()
overrides = {}
for field in ("api_key", "model", "embedding_dims"):
for field in ("api_key", "model", "embedding_dims", "ollama_base_url"):
if field in self.model_fields_set:
value = getattr(self, field)
if value is not None:
Expand Down
10 changes: 9 additions & 1 deletion src/powermem/core/async_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,8 @@ async def _simple_add_async(
"category": category,
"metadata": enhanced_metadata or {},
"filters": filters or {},
"scope": scope,
"memory_type": memory_type,
"created_at": get_current_datetime(),
"updated_at": get_current_datetime(),
}
Expand Down Expand Up @@ -734,7 +736,9 @@ async def _intelligent_add_async(
run_id=run_id,
metadata=metadata,
filters=filters,
existing_embeddings=fact_embeddings
existing_embeddings=fact_embeddings,
scope=scope,
memory_type=memory_type,
)
results.append({
"id": memory_id,
Expand Down Expand Up @@ -870,6 +874,8 @@ async def _create_memory_async(
metadata: Optional[Dict[str, Any]] = None,
filters: Optional[Dict[str, Any]] = None,
existing_embeddings: Optional[Dict[str, Any]] = None,
scope: Optional[str] = None,
memory_type: Optional[str] = None,
) -> int:
"""Create a memory asynchronously with optional embeddings."""
# Validate content is not empty
Expand Down Expand Up @@ -913,6 +919,8 @@ async def _create_memory_async(
"category": category,
"metadata": enhanced_metadata or {},
"filters": filters or {},
"scope": scope,
"memory_type": memory_type,
"created_at": get_current_datetime(),
"updated_at": get_current_datetime(),
}
Expand Down
10 changes: 9 additions & 1 deletion src/powermem/core/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,8 @@ def _simple_add(
"category": category,
"metadata": enhanced_metadata or {},
"filters": filters or {},
"scope": scope,
"memory_type": memory_type,
"created_at": get_current_datetime(),
"updated_at": get_current_datetime(),
}
Expand Down Expand Up @@ -860,7 +862,9 @@ def _intelligent_add(
run_id=run_id,
metadata=metadata,
filters=filters,
existing_embeddings=fact_embeddings
existing_embeddings=fact_embeddings,
scope=scope,
memory_type=memory_type,
)
results.append({
"id": memory_id,
Expand Down Expand Up @@ -994,6 +998,8 @@ def _create_memory(
metadata: Optional[Dict[str, Any]] = None,
filters: Optional[Dict[str, Any]] = None,
existing_embeddings: Optional[Dict[str, Any]] = None,
scope: Optional[str] = None,
memory_type: Optional[str] = None,
) -> int:
"""Create a memory with optional embeddings."""
# Validate content is not empty
Expand Down Expand Up @@ -1037,6 +1043,8 @@ def _create_memory(
"category": category,
"metadata": enhanced_metadata or {},
"filters": filters or {},
"scope": scope,
"memory_type": memory_type,
"created_at": get_current_datetime(),
"updated_at": get_current_datetime(),
}
Expand Down
5 changes: 4 additions & 1 deletion src/powermem/integrations/embeddings/config/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ class OllamaEmbeddingConfig(BaseEmbedderConfig):
model: Optional[str] = Field(default=None)
ollama_base_url: Optional[str] = Field(
default=None,
validation_alias=AliasChoices("OLLAMA_EMBEDDING_BASE_URL"),
validation_alias=AliasChoices(
"OLLAMA_EMBEDDING_BASE_URL",
"OLLAMA_BASE_URL",
),
)


Expand Down
5 changes: 5 additions & 0 deletions src/powermem/search/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Search utilities for PowerMem."""

from .filters import FilterBuilder

__all__ = ["FilterBuilder"]
85 changes: 85 additions & 0 deletions src/powermem/search/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Filter builder utilities for memory search."""

from __future__ import annotations

from datetime import datetime
from typing import Any, Dict, List, Optional


class FilterBuilder:
"""Fluent builder for advanced memory search filters."""

def __init__(self) -> None:
self._filters: Dict[str, Any] = {}

def created_after(self, value: datetime | str) -> "FilterBuilder":
self._filters["created_after"] = value
return self

def created_before(self, value: datetime | str) -> "FilterBuilder":
self._filters["created_before"] = value
return self

def updated_after(self, value: datetime | str) -> "FilterBuilder":
self._filters["updated_after"] = value
return self

def updated_before(self, value: datetime | str) -> "FilterBuilder":
self._filters["updated_before"] = value
return self

def after(self, value: datetime | str) -> "FilterBuilder":
return self.created_after(value)

def before(self, value: datetime | str) -> "FilterBuilder":
return self.created_before(value)

def importance(self, minimum: Optional[float] = None, maximum: Optional[float] = None) -> "FilterBuilder":
if minimum is not None:
self._filters["min_importance"] = minimum
if maximum is not None:
self._filters["max_importance"] = maximum
return self

def retention(self, minimum: Optional[float] = None, maximum: Optional[float] = None) -> "FilterBuilder":
if minimum is not None:
self._filters["min_retention"] = minimum
if maximum is not None:
self._filters["max_retention"] = maximum
return self

def memory_types(self, memory_types: List[str]) -> "FilterBuilder":
self._filters["memory_types"] = memory_types
return self

def tags(self, tags: List[str], logic: str = "OR") -> "FilterBuilder":
self._filters["tags"] = tags
self._filters["tag_logic"] = logic
return self

def scopes(self, scopes: List[str]) -> "FilterBuilder":
self._filters["scopes"] = scopes
return self

def user_ids(self, user_ids: List[str]) -> "FilterBuilder":
self._filters["user_ids"] = user_ids
return self

def agent_ids(self, agent_ids: List[str]) -> "FilterBuilder":
self._filters["agent_ids"] = agent_ids
return self

def metadata_contains(self, mapping: Dict[str, Any]) -> "FilterBuilder":
existing = self._filters.get("metadata_contains", {})
existing.update(mapping)
self._filters["metadata_contains"] = existing
return self

def metadata_equals(self, mapping: Dict[str, Any]) -> "FilterBuilder":
existing = self._filters.get("metadata_equals", {})
existing.update(mapping)
self._filters["metadata_equals"] = existing
return self

def build(self) -> Dict[str, Any]:
return dict(self._filters)
2 changes: 2 additions & 0 deletions src/powermem/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def settings_config(
env_prefix: str = "",
extra: str = "ignore",
arbitrary_types_allowed: bool = True,
populate_by_name: bool = True,
env_file: Optional[str] = _DEFAULT_ENV_FILE,
) -> SettingsConfigDict:
return SettingsConfigDict(
Expand All @@ -41,4 +42,5 @@ def settings_config(
env_file=env_file,
env_file_encoding="utf-8",
arbitrary_types_allowed=arbitrary_types_allowed,
populate_by_name=populate_by_name,
)
Loading