diff --git a/memory_thread/cli.py b/memory_thread/cli.py index 1a6ba1f..51a5e4e 100644 --- a/memory_thread/cli.py +++ b/memory_thread/cli.py @@ -33,6 +33,9 @@ from rich.panel import Panel from rich.text import Text from rich import print as rprint +from dotenv import load_dotenv + +load_dotenv() # ═══════════════════════════════════════════════════════════════════════════════ # WINDOWS UTF-8 FIX — no more Wakandan runes @@ -216,6 +219,26 @@ def _enter_chat(): console.print("[dim]Goodbye.[/dim]") break + # Slash commands (e.g., /provider use groq) + if raw.startswith("/"): + import shlex + import subprocess + try: + args = shlex.split(raw[1:]) + if not args: + continue + + # Construct command to invoke same entry point + cmd_args = [sys.executable, sys.argv[0]] if sys.argv[0].endswith(".py") else [sys.argv[0]] + cmd_args.extend(args) + + subprocess.run(cmd_args) + console.print() + continue + except Exception as e: + console.print(f"[red]Command failed: {e}[/red]") + continue + # Chat handles EVERYTHING: remember, extract, contradict, respond try: response = client.chat(raw) diff --git a/memory_thread/db/qdrant_client.py b/memory_thread/db/qdrant_client.py index 05ee65e..e9bbdc2 100644 --- a/memory_thread/db/qdrant_client.py +++ b/memory_thread/db/qdrant_client.py @@ -48,11 +48,21 @@ def upsert(self, collection_name: str, points: List[Dict[str, Any]]): def search(self, collection_name: str, query_vector: List[float], limit: int = 5) -> List[Any]: """Search for similar vectors.""" try: - return self.client.search( - collection_name=collection_name, - query_vector=query_vector, - limit=limit - ) + # Fallback for deprecated 'search' if needed, or use 'query_points' for future compat + if hasattr(self.client, "search"): + return self.client.search( + collection_name=collection_name, + query_vector=query_vector, + limit=limit + ) + elif hasattr(self.client, "query_points"): + return self.client.query_points( + collection_name=collection_name, + query=query_vector, + limit=limit + ).points + else: + raise AttributeError("QdrantClient has no search method") except Exception as e: from memory_thread.utils.logger import get_logger log = get_logger(__name__) diff --git a/memory_thread/sdk.py b/memory_thread/sdk.py index 35e61de..afedb73 100644 --- a/memory_thread/sdk.py +++ b/memory_thread/sdk.py @@ -170,9 +170,9 @@ def _generate_embedding(self, text: str) -> List[float]: try: from memory_thread.utils.embeddings import generate_embeddings return generate_embeddings(tuple([text]))[0] - except ImportError: + except ImportError as e: # sentence-transformers not installed - log.debug("sentence-transformers not installed, using zero vector") + log.warning(f"sentence-transformers failed: {e}") return [0.0] * settings.EMBEDDING_DIMENSION except Exception as e: # Return zeros if embedding fails @@ -188,8 +188,8 @@ def _extract_entities(self, text: str) -> List[Dict]: try: from memory_thread.services.hybrid_ner_service import extract_entities return extract_entities(text) - except ImportError: - log.warning("hybrid_ner_service not available, skipping entity extraction") + except ImportError as e: + log.warning(f"hybrid_ner_service not available (ImportError: {e}), skipping entity extraction") return [] except Exception as e: log.warning(f"Entity extraction failed: {e}") @@ -439,6 +439,9 @@ def remember( def _persist_to_postgres(self, entity_id: uuid.UUID, content: str, memory_type: str, state: EntityState, event: Event): """Persist memory to PostgreSQL.""" + # Ensure event is persisted first to satisfy foreign key constraint + self.tms.persist_event(event) + with self._pg.get_cursor() as cur: # Upsert into memories table (or entity_state) cur.execute(""" @@ -1130,11 +1133,13 @@ def chat( # Check explicit env override first env_provider = os.environ.get("MT_PROVIDER") - if env_provider: + if env_provider and env_provider != "auto": active_provider = env_provider.lower() + log.info(f"Using provider from ENV: {active_provider}") else: # Ensure we respect the vault's setting (which includes env vars now) active_provider = vault.get_active_provider(user_id) + log.info(f"Using provider from Vault: {active_provider}") log.debug(f"Chat request - Provider: {active_provider}, User: {user_id}") @@ -1296,6 +1301,8 @@ def _generate_cloud(self, prompt: str, provider: str = "auto") -> str: key = creds.get("api_key") if creds else os.environ.get("GROQ_API_KEY") model = creds.get("model") if creds else os.environ.get("GROQ_MODEL", "llama-3.3-70b-versatile") + log.info(f"Groq API Key Check: Vault={'Found' if creds else 'Missing'}, Env={'Found' if os.environ.get('GROQ_API_KEY') else 'Missing'}") + if key: log.info(f"Using Groq ({model})") response = requests.post( diff --git a/memory_thread/services/hybrid_ner_service.py b/memory_thread/services/hybrid_ner_service.py index 47dd4ce..0e2a47c 100644 --- a/memory_thread/services/hybrid_ner_service.py +++ b/memory_thread/services/hybrid_ner_service.py @@ -48,8 +48,17 @@ def extract_entities(text: str): return ner_cache.get(text_hash) # Run all stages - regex_entities = regex_ner(text) - spacy_entities, confidence = spacy_ner(text) + try: + regex_entities = regex_ner(text) + except Exception as e: + print(f"Regex NER failed: {e}") + regex_entities = [] + + try: + spacy_entities, confidence = spacy_ner(text) + except Exception as e: + print(f"Spacy NER failed: {e}") + spacy_entities, confidence = [], 0.0 final_entities = regex_entities + spacy_entities diff --git a/memory_thread/utils/embeddings.py b/memory_thread/utils/embeddings.py index cd8611b..b0f8940 100644 --- a/memory_thread/utils/embeddings.py +++ b/memory_thread/utils/embeddings.py @@ -38,7 +38,7 @@ def _get_local_model(): log.warning("sentence-transformers not installed. pip install sentence-transformers") return None except Exception as e: - log.warning(f"Failed to load embedding model: {e}") + log.warning(f"Failed to load embedding model: {e}", exc_info=True) return None diff --git a/requirements.txt b/requirements.txt index bfddf4c..17bd09b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,3 +20,4 @@ prometheus-client fastapi uvicorn zmq +sentence-transformers