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.1.0.tar.gz (172.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.1.0-py3-none-any.whl (72.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: casual_memory-0.1.0.tar.gz
  • Upload date:
  • Size: 172.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.17

File hashes

Hashes for casual_memory-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6e48319ded6bcf83c69d1aaf4cccd30cb2f47b6b763a9e3f88e34ef53dd60d79
MD5 2a38cb199fe54bdd9e6e6eedce768ab7
BLAKE2b-256 cd655022acced123b44bf7e3857ca49429d4816660662c690a3f11794d07ac3a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for casual_memory-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 52525854c78c53c9c9ff067f74a085f47b4768efdf4cd9da31603980d6d9080f
MD5 af86cdabb13da59bea427695e78f6c50
BLAKE2b-256 27fec28ff95e8ace6a79710cacb99e8b735f4b8333ec8a2be57572aed2555aa3

See more details on using hashes here.

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