From e8f1dea3ec8bc14a840247cf707a5b6ec023077b Mon Sep 17 00:00:00 2001 From: Alexander Whitestone <8633216+AlexanderWhitestone@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:07:59 -0500 Subject: [PATCH] Remove unused deps from poetry build, speed test suite to ~16s (#130) --- Makefile | 4 ++-- pyproject.toml | 13 ++++++------- pytest.ini | 3 ++- src/brain/embeddings.py | 11 ++++++----- src/brain/memory.py | 2 ++ src/timmy/memory/vector_store.py | 12 ++++++------ src/timmy/semantic_memory.py | 4 ++++ tests/conftest.py | 2 +- 8 files changed, 29 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index 8707a8b..346a7f4 100644 --- a/Makefile +++ b/Makefile @@ -92,10 +92,10 @@ test-integration: $(PYTEST) tests -m "integration" --tb=short -v test-functional: - $(PYTEST) tests -m "functional and not slow and not selenium" --tb=short -v + $(PYTEST) tests -m "functional and not slow and not selenium" --tb=short -v -n0 test-e2e: - $(PYTEST) tests -m "e2e" --tb=short -v + $(PYTEST) tests -m "e2e" --tb=short -v -n0 test-fast: $(PYTEST) tests -m "unit or integration" --tb=short -v diff --git a/pyproject.toml b/pyproject.toml index 176909d..9980af6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,16 +29,9 @@ uvicorn = { version = ">=0.32.0", extras = ["standard"] } jinja2 = ">=3.1.0" httpx = ">=0.27.0" python-multipart = ">=0.0.12" -aiofiles = ">=22.0.0" typer = ">=0.12.0" rich = ">=13.0.0" pydantic-settings = ">=2.0.0" -websockets = ">=12.0" -GitPython = ">=3.1.40" -moviepy = ">=2.0.0" -requests = ">=2.31.0" -sentence-transformers = ">=2.0.0" # Local embeddings for brain -numpy = ">=1.24.0" # Optional extras redis = { version = ">=5.0.0", optional = true } celery = { version = ">=5.3.0", extras = ["redis"], optional = true } @@ -46,6 +39,10 @@ python-telegram-bot = { version = ">=21.0", optional = true } "discord.py" = { version = ">=2.3.0", optional = true } airllm = { version = ">=2.9.0", optional = true } pyttsx3 = { version = ">=2.90", optional = true } +sentence-transformers = { version = ">=2.0.0", optional = true } +numpy = { version = ">=1.24.0", optional = true } +requests = { version = ">=2.31.0", optional = true } +GitPython = { version = ">=3.1.40", optional = true } pytest = { version = ">=8.0.0", optional = true } pytest-asyncio = { version = ">=0.24.0", optional = true } pytest-cov = { version = ">=5.0.0", optional = true } @@ -60,6 +57,8 @@ discord = ["discord.py"] bigbrain = ["airllm"] voice = ["pyttsx3"] celery = ["celery"] +embeddings = ["sentence-transformers", "numpy"] +git = ["GitPython"] dev = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-timeout", "pytest-randomly", "pytest-xdist", "selenium"] [tool.poetry.group.dev.dependencies] diff --git a/pytest.ini b/pytest.ini index 8f9060e..a87584d 100644 --- a/pytest.ini +++ b/pytest.ini @@ -29,7 +29,8 @@ addopts = --tb=short --strict-markers --disable-warnings -# -n auto + -n auto + --dist worksteal # Coverage configuration [coverage:run] diff --git a/src/brain/embeddings.py b/src/brain/embeddings.py index 89db75d..4ce527b 100644 --- a/src/brain/embeddings.py +++ b/src/brain/embeddings.py @@ -7,7 +7,6 @@ from __future__ import annotations import json import logging -import numpy as np from typing import List, Union logger = logging.getLogger(__name__) @@ -47,7 +46,7 @@ class LocalEmbedder: logger.error("sentence-transformers not installed. Run: pip install sentence-transformers") raise - def encode(self, text: Union[str, List[str]]) -> np.ndarray: + def encode(self, text: Union[str, List[str]]): """Encode text to embedding vector(s). Args: @@ -64,20 +63,22 @@ class LocalEmbedder: def encode_single(self, text: str) -> bytes: """Encode single text to bytes for SQLite storage. - + Returns: Float32 bytes """ + import numpy as np embedding = self.encode(text) if len(embedding.shape) > 1: embedding = embedding[0] return embedding.astype(np.float32).tobytes() - def similarity(self, a: np.ndarray, b: np.ndarray) -> float: + def similarity(self, a, b) -> float: """Compute cosine similarity between two vectors. - + Vectors should already be normalized from encode(). """ + import numpy as np return float(np.dot(a, b)) diff --git a/src/brain/memory.py b/src/brain/memory.py index 0d40bd1..148857d 100644 --- a/src/brain/memory.py +++ b/src/brain/memory.py @@ -106,6 +106,8 @@ class UnifiedMemory: def _get_embedder(self): """Lazy-load the embedding model.""" if self._embedder is None: + if os.environ.get("TIMMY_SKIP_EMBEDDINGS") == "1": + return None try: from brain.embeddings import LocalEmbedder self._embedder = LocalEmbedder() diff --git a/src/timmy/memory/vector_store.py b/src/timmy/memory/vector_store.py index 15911c8..247a400 100644 --- a/src/timmy/memory/vector_store.py +++ b/src/timmy/memory/vector_store.py @@ -29,14 +29,14 @@ def _get_model(): if _model is not None: return _model + import os + # In test mode or low-memory environments, skip embedding model load + if os.environ.get("TIMMY_SKIP_EMBEDDINGS") == "1": + _has_embeddings = False + return None + try: from sentence_transformers import SentenceTransformer - import os - # In test mode or low-memory environments, we might want to skip this - if os.environ.get("TIMMY_SKIP_EMBEDDINGS") == "1": - _has_embeddings = False - return None - _model = SentenceTransformer('all-MiniLM-L6-v2') _has_embeddings = True return _model diff --git a/src/timmy/semantic_memory.py b/src/timmy/semantic_memory.py index 86f980c..f4d3468 100644 --- a/src/timmy/semantic_memory.py +++ b/src/timmy/semantic_memory.py @@ -37,6 +37,10 @@ def _get_embedding_model(): """Lazy-load embedding model.""" global EMBEDDING_MODEL if EMBEDDING_MODEL is None: + import os + if os.environ.get("TIMMY_SKIP_EMBEDDINGS") == "1": + EMBEDDING_MODEL = False + return EMBEDDING_MODEL try: from sentence_transformers import SentenceTransformer EMBEDDING_MODEL = SentenceTransformer('all-MiniLM-L6-v2') diff --git a/tests/conftest.py b/tests/conftest.py index 292acec..a64ec5c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,6 @@ try: from . import conftest_markers # noqa: F401 except ImportError: import conftest_markers # noqa: F401 -from fastapi.testclient import TestClient # ── Stub heavy optional dependencies so tests run without them installed ────── # Uses setdefault: real module is used if already installed, mock otherwise. @@ -134,6 +133,7 @@ def cleanup_event_loops(): @pytest.fixture def client(): """FastAPI test client with fresh app instance.""" + from fastapi.testclient import TestClient from dashboard.app import app with TestClient(app) as c: yield c