mirror of
https://github.com/tkenaz/breathe-memory.git
synced 2026-09-07 08:26:01 +00:00
Context optimization and associative memory for LLM applications. Two-phase system: SYNAPSE (pre-generation memory injection) + GraphCompactor (structured context compression). - Interface-based, storage-agnostic, LLM-agnostic - Memory Nexus: PostgreSQL + pgvector reference backend - Zero mandatory dependencies beyond stdlib - 28 tests passing, clean install verified Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
147 lines
4.4 KiB
Python
147 lines
4.4 KiB
Python
"""
|
||
Interfaces — abstract contracts for external dependencies.
|
||
|
||
BREATHE is storage-agnostic and LLM-agnostic by design.
|
||
Implement these interfaces to integrate with any backend.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from abc import ABC, abstractmethod
|
||
from dataclasses import dataclass
|
||
from typing import Optional
|
||
|
||
|
||
@dataclass
|
||
class RetrievedNode:
|
||
"""A node retrieved from graph traversal or memory search."""
|
||
|
||
node_id: str
|
||
concept: str
|
||
node_type: str # entity, theme, insight, event, memory_vector, memory_keyword
|
||
summary: Optional[str] = None
|
||
importance: float = 0.5
|
||
depth: int = 0 # BFS depth from anchor
|
||
relation: Optional[str] = None # edge relation that led here
|
||
memory_content: Optional[str] = None # raw text from memory store
|
||
|
||
|
||
class MemoryRepository(ABC):
|
||
"""
|
||
Abstract storage backend for BREATHE.
|
||
|
||
Implement this to connect BREATHE to your own database.
|
||
See ``breathe.backends.postgres`` for the asyncpg/PostgreSQL reference
|
||
implementation.
|
||
|
||
The interface deliberately stays minimal — you only need what SYNAPSE
|
||
actually calls. Graph BFS and keyword search are optional; implement the
|
||
ones you need and raise ``NotImplementedError`` for the rest.
|
||
"""
|
||
|
||
@abstractmethod
|
||
async def get_concepts(self) -> dict[str, str]:
|
||
"""
|
||
Return all active known concepts as ``{concept_text: node_uuid}``.
|
||
|
||
Called once at initialization to build the concept regex.
|
||
An empty dict is valid — SYNAPSE falls back to regex-only extraction.
|
||
"""
|
||
|
||
@abstractmethod
|
||
async def graph_bfs(
|
||
self,
|
||
start_ids: list[str],
|
||
max_depth: int = 2,
|
||
min_strength: float = 0.2,
|
||
limit: int = 20,
|
||
) -> list[RetrievedNode]:
|
||
"""
|
||
BFS traversal from ``start_ids`` through the concept graph.
|
||
|
||
Args:
|
||
start_ids: UUIDs of matched memory_nodes.
|
||
max_depth: Maximum edge hops to follow.
|
||
min_strength: Minimum edge strength to follow (0–1).
|
||
limit: Maximum nodes to return.
|
||
|
||
Returns:
|
||
List of RetrievedNode, sorted by importance descending.
|
||
"""
|
||
|
||
@abstractmethod
|
||
async def keyword_search(
|
||
self, keywords: list[str], limit: int = 5
|
||
) -> list[RetrievedNode]:
|
||
"""
|
||
Full-text keyword search over memory content.
|
||
|
||
Called for anchors that didn't match any known concept (no node_id).
|
||
ILIKE or equivalent is fine — precision matters less than recall here.
|
||
|
||
Args:
|
||
keywords: Words to search for (case-insensitive).
|
||
limit: Maximum memories to return.
|
||
"""
|
||
|
||
async def flush_edges(self, edges: list) -> int:
|
||
"""
|
||
Persist new session graph edges to long-term storage.
|
||
|
||
Called at session end by SessionGraph.flush(). Optional — if you don't
|
||
need cross-session graph persistence, leave this as a no-op.
|
||
|
||
Returns number of edges flushed.
|
||
"""
|
||
return 0
|
||
|
||
|
||
class VectorSearchClient(ABC):
|
||
"""
|
||
Abstract client for semantic / vector search.
|
||
|
||
Wraps any dense-embedding search backend (pgvector, Pinecone, Weaviate, etc.).
|
||
BREATHE uses this for Strategy 2 in SYNAPSE traversal — the highest-quality
|
||
but most expensive retrieval path.
|
||
"""
|
||
|
||
@abstractmethod
|
||
async def search(self, query: str, limit: int = 5) -> list[RetrievedNode]:
|
||
"""
|
||
Return the most semantically similar memories for ``query``.
|
||
|
||
Args:
|
||
query: Short anchor phrase (NOT the full user message).
|
||
limit: Max results.
|
||
|
||
Returns:
|
||
List of RetrievedNode sorted by similarity descending.
|
||
Set ``importance`` to the similarity score (0–1).
|
||
"""
|
||
|
||
|
||
class LLMClient(ABC):
|
||
"""
|
||
Abstract LLM client for GraphCompactor.
|
||
|
||
GraphCompactor needs a single call: given a long prompt, return text.
|
||
Implement this to use any LLM (Anthropic, OpenAI, local, etc.).
|
||
"""
|
||
|
||
@abstractmethod
|
||
async def complete(
|
||
self,
|
||
prompt: str,
|
||
max_tokens: int = 4000,
|
||
temperature: float = 0.2,
|
||
) -> Optional[str]:
|
||
"""
|
||
Generate a completion for ``prompt``.
|
||
|
||
Args:
|
||
prompt: The full extraction prompt (can be long).
|
||
max_tokens: Max tokens to generate.
|
||
temperature: Low values (0.1–0.3) work best for structured extraction.
|
||
|
||
Returns:
|
||
Generated text, or None on failure.
|
||
"""
|