Skip to main content

Intelligent semantic memory with conflict detection, classification pipeline, and storage abstraction

Project description

casual-memory

Intelligent semantic memory with conflict detection, classification pipeline, and storage abstraction

PyPI version Python 3.10+ License: MIT Tests


๐Ÿš€ Features

๐Ÿ‘‘ Classification Pipeline (Core Innovation)

  • Protocol-based architecture - Composable, extensible classifiers
  • NLI Pre-filtering - Fast semantic filtering (~50-200ms)
  • LLM Conflict Detection - High-accuracy contradiction detection (96%+)
  • LLM Duplicate Detection - Smart deduplication vs distinct facts
  • Auto-Resolution - Confidence-based conflict resolution
  • Graceful degradation - Heuristic fallback when LLM unavailable

๐Ÿง  Memory Intelligence

  • Memory extraction from conversations (user & assistant messages)
  • Conflict detection with categorization (location, preference, temporal, factual)
  • Confidence scoring based on mention frequency and recency
  • Memory archiving with soft-delete patterns
  • Temporal memory with date normalization and expiry

๐Ÿ”Œ Storage Abstraction

  • Protocol-based - Works with any vector database
  • Optional adapters - Qdrant, PostgreSQL, Redis
  • In-memory implementations - For testing
  • Bring your own - Implement custom backends

๐Ÿ“ฆ Installation

Minimal (core only)

pip install casual-memory

With specific backends

# With NLI support (sentence-transformers)
pip install casual-memory[transformers]

# With Qdrant vector store
pip install casual-memory[qdrant]

# With PostgreSQL conflict store
pip install casual-memory[postgres]

# With Redis short-term store
pip install casual-memory[redis]

# Full installation (all extras)
pip install casual-memory[all]

CPU-only installation (no CUDA)

By default, PyTorch includes CUDA which is a large download. For CPU-only machines:

# Install CPU-only PyTorch first
pip install torch --index-url https://download.pytorch.org/whl/cpu

# Then install casual-memory with transformers
pip install casual-memory[transformers]

For development

git clone https://github.com/yourusername/casual-memory
cd casual-memory
uv sync --all-extras

๐ŸŽฏ Quick Start

Classification Pipeline

from casual_memory.classifiers import (
    MemoryClassificationPipeline,
    NLIClassifier,
    ConflictClassifier,
    DuplicateClassifier,
    AutoResolutionClassifier,
    SimilarMemory,
)
from casual_memory.intelligence import NLIPreFilter, LLMConflictVerifier, LLMDuplicateDetector
from casual_memory import MemoryFact
from casual_llm import create_client, create_model, ClientConfig, ModelConfig, Provider

# Initialize components
nli_filter = NLIPreFilter()
client = create_client(ClientConfig(
    provider=Provider.OLLAMA,
    base_url="http://localhost:11434"
))
model = create_model(client, ModelConfig(name="qwen2.5:7b-instruct"))

conflict_verifier = LLMConflictVerifier(model)
duplicate_detector = LLMDuplicateDetector(model)

# Build pipeline
pipeline = MemoryClassificationPipeline(
    classifiers=[
        NLIClassifier(nli_filter=nli_filter),
        ConflictClassifier(llm_conflict_verifier=conflict_verifier),
        DuplicateClassifier(llm_duplicate_detector=duplicate_detector),
        AutoResolutionClassifier(supersede_threshold=1.3, keep_threshold=0.7),
    ],
    strategy="tiered",  # "single", "tiered", or "all"
)

# Create new memory and similar memories from vector search
new_memory = MemoryFact(
    text="I live in Paris",
    type="fact",
    tags=["location"],
    importance=0.9,
    confidence=0.8,
    entity_id="user-123",
)

similar_memories = [
    SimilarMemory(
        memory_id="mem_001",
        memory=MemoryFact(
            text="I live in London",
            type="fact",
            tags=["location"],
            importance=0.8,
            confidence=0.6,
            entity_id="user-123",
        ),
        similarity_score=0.91,
    )
]

# Classify the new memory against similar memories
result = await pipeline.classify(new_memory, similar_memories)

# Check overall outcome: "add", "skip", or "conflict"
print(f"Overall outcome: {result.overall_outcome}")

# Check individual similarity results
for sim_result in result.similarity_results:
    print(f"Similar memory: {sim_result.similar_memory.memory.text}")
    print(f"Outcome: {sim_result.outcome}")  # "conflict", "superseded", "same", "neutral"
    print(f"Classifier: {sim_result.classifier_name}")
    if sim_result.outcome == "conflict":
        print(f"Category: {sim_result.metadata.get('category')}")

Memory Extraction

from casual_memory.extractors import LLMMemoryExtracter
from casual_llm import UserMessage, AssistantMessage

# Create extractors for user and assistant memories
user_extractor = LLMMemoryExtracter(model=model, source="user")
assistant_extractor = LLMMemoryExtracter(model=model, source="assistant")

messages = [
    UserMessage(content="My name is Alex and I live in Bangkok"),
    AssistantMessage(content="Nice to meet you, Alex!"),
]

# Extract user-stated memories
user_memories = await user_extractor.extract(messages)
# [MemoryFact(text="My name is Alex", type="fact", importance=0.9, ...),
#  MemoryFact(text="I live in Bangkok", type="fact", importance=0.8, ...)]

# Extract assistant-observed memories
assistant_memories = await assistant_extractor.extract(messages)

Custom Storage Backend

from typing import Any, Optional

class MyVectorStore:
    """Custom vector store implementing VectorMemoryStore protocol"""

    def add(self, embedding: list[float], payload: dict) -> str:
        """Add memory to store, return memory ID."""
        # Your implementation
        return "memory_id"

    def search(
        self,
        query_embedding: list[float],
        top_k: int = 5,
        min_score: float = 0.7,
        filters: Optional[Any] = None,
    ) -> list[Any]:
        """Search for similar memories."""
        # Your implementation
        return []

    def update_memory(self, memory_id: str, updates: dict) -> bool:
        """Update memory metadata (mention_count, last_seen, etc.)."""
        # Your implementation
        return True

    def archive_memory(self, memory_id: str, superseded_by: Optional[str] = None) -> bool:
        """Soft-delete memory."""
        # Your implementation
        return True

๐Ÿ—๏ธ Architecture

Classification Pipeline Flow

Input: New Memory + Similar Memories (from vector search)
  โ†“
1. NLI Classifier (~50-200ms)
  โ”œโ”€ High entailment (โ‰ฅ0.85) โ†’ same (duplicate)
  โ”œโ”€ High neutral (โ‰ฅ0.5) โ†’ neutral (distinct)
  โ””โ”€ Uncertain โ†’ Pass to next classifier
  โ†“
2. Conflict Classifier (~500-2000ms)
  โ”œโ”€ LLM detects contradiction โ†’ conflict
  โ””โ”€ No conflict โ†’ Pass to next classifier
  โ†“
3. Duplicate Classifier (~500-2000ms)
  โ”œโ”€ Same fact โ†’ same
  โ”œโ”€ Refinement (more detail) โ†’ superseded
  โ””โ”€ Distinct facts โ†’ neutral
  โ†“
4. Auto-Resolution Classifier
  โ”œโ”€ Analyze conflict results
  โ”œโ”€ High new confidence (ratio โ‰ฅ1.3) โ†’ superseded (keep_new)
  โ”œโ”€ High old confidence (ratio โ‰ค0.7) โ†’ same (keep_old)
  โ””โ”€ Similar confidence โ†’ Keep as conflict
  โ†“
Output: MemoryClassificationResult
  โ”œโ”€ overall_outcome: "add" | "skip" | "conflict"
  โ””โ”€ similarity_results: Individual outcomes for each similar memory

Key Concepts

Similarity Outcomes (for each similar memory):

  • conflict - Contradictory memories requiring user resolution
  • superseded - Similar memory should be archived (new one is better)
  • same - Duplicate memory (update existing metadata)
  • neutral - Distinct facts that can coexist

Memory Outcomes (overall action):

  • add - Insert new memory to vector store
  • skip - Update existing memory (increment mention_count)
  • conflict - Create conflict record for user resolution

Confidence Scoring:

  • Based on mention frequency (1 mention = 0.5, 5+ mentions = 0.95)
  • Recency factor (decay after 30 days)
  • Spread factor (boost if mentioned over time)

Memory Types:

  • fact - Factual information (name, location, job, etc.)
  • preference - User preferences (likes, dislikes, habits)
  • goal - User goals and aspirations
  • event - Events (past or future)

๐Ÿ“š Documentation


๐Ÿงช Testing

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=casual_memory --cov-report=html

# Run specific test file
uv run pytest tests/classifiers/test_pipeline.py -v

# Run specific test
uv run pytest tests/classifiers/test_pipeline.py::test_pipeline_sequential_execution -v

๐ŸŽฏ Benchmarks

Classification pipeline performance on our test dataset:

Model Conflict Accuracy Avg Time
qwen2.5:7b-instruct 96.2% 1.2s
llama3:8b 94.5% 1.5s
gpt-3.5-turbo 97.1% 0.8s

NLI Pre-filter performance:

  • Accuracy: 92.38% (SNLI), 90.04% (MNLI)
  • Speed: ~200ms CPU, ~50ms GPU
  • Filters: 70-85% of obvious cases before LLM

๐Ÿค Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.


๐Ÿ“„ License

MIT License - see LICENSE for details.


๐Ÿ™ Acknowledgments

Built with:


๐Ÿ”— Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

casual_memory-0.5.0.tar.gz (392.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

casual_memory-0.5.0-py3-none-any.whl (83.8 kB view details)

Uploaded Python 3

File details

Details for the file casual_memory-0.5.0.tar.gz.

File metadata

  • Download URL: casual_memory-0.5.0.tar.gz
  • Upload date:
  • Size: 392.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for casual_memory-0.5.0.tar.gz
Algorithm Hash digest
SHA256 12f4bedec034fefb33ffb7c1548bf6f0b2e6143b236fef367b3b21f97e607fec
MD5 c4b545742b4240c972cd096cf99470bd
BLAKE2b-256 ba6fcc3c84ecf5dc12489ca97de25344ac412e8c071e29b76ce7dd3e13603be3

See more details on using hashes here.

Provenance

The following attestation bundles were made for casual_memory-0.5.0.tar.gz:

Publisher: release.yml on casualgenius/casual-memory

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file casual_memory-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: casual_memory-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 83.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for casual_memory-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ca27bd746e12192f583b8edf323c1858bcc4b36bf810ee26d48ba3d96a4170c5
MD5 58aab5e98a94caee213f0dceaeefcf2a
BLAKE2b-256 1d8af68a28b000450db4c96da884052c58bc9cf2f78eabe6d73d370576217c23

See more details on using hashes here.

Provenance

The following attestation bundles were made for casual_memory-0.5.0-py3-none-any.whl:

Publisher: release.yml on casualgenius/casual-memory

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page