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_provider, ModelConfig, Provider

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

conflict_verifier = LLMConflictVerifier(llm_provider, "qwen2.5:7b-instruct")
duplicate_detector = LLMDuplicateDetector(llm_provider, "qwen2.5:7b-instruct")

# 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,
    user_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,
            user_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(llm_provider=llm_provider, source="user")
assistant_extractor = LLMMemoryExtracter(llm_provider=llm_provider, 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.2.1.tar.gz (359.3 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.2.1-py3-none-any.whl (75.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: casual_memory-0.2.1.tar.gz
  • Upload date:
  • Size: 359.3 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.2.1.tar.gz
Algorithm Hash digest
SHA256 12b0b8613aabbdd1f3ed7359ef1f191458e898a8cf8f2edfead8ac039ab5f524
MD5 14220d11783230a133c65f69d7624286
BLAKE2b-256 b90bb93bf1ccdd36baf284d368cdbb6f12527128899ce06ec7367c7a23076a38

See more details on using hashes here.

Provenance

The following attestation bundles were made for casual_memory-0.2.1.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.2.1-py3-none-any.whl.

File metadata

  • Download URL: casual_memory-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 75.6 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.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 09d5de0ff361505e64f7cbdcd5af4468099d42dd980117d238f41f7b69d38b50
MD5 c6b97b74ad1a537a12ade9247277e102
BLAKE2b-256 7738c31853b0c9f3152dd15da9aa709c0a72835855496b0c7b2d4d675c0b0513

See more details on using hashes here.

Provenance

The following attestation bundles were made for casual_memory-0.2.1-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