diff --git a/docs/components/vectordbs/dbs/faiss.mdx b/docs/components/vectordbs/dbs/faiss.mdx index cf98a5e66..388b76bad 100644 --- a/docs/components/vectordbs/dbs/faiss.mdx +++ b/docs/components/vectordbs/dbs/faiss.mdx @@ -52,7 +52,7 @@ Here are the parameters available for configuring FAISS: | Parameter | Description | Default Value | | --- | --- | --- | | `collection_name` | The name of the collection | `mem0` | -| `path` | Path to store FAISS index and metadata | `/tmp/faiss/` | +| `path` | Path to store FAISS index and metadata | `~/.local/share/mem0/faiss/` | | `distance_strategy` | Distance metric strategy to use (options: 'euclidean', 'inner_product', 'cosine') | `euclidean` | | `normalize_L2` | Whether to normalize L2 vectors (only applicable for euclidean distance) | `False` | diff --git a/docs/components/vectordbs/dbs/qdrant.mdx b/docs/components/vectordbs/dbs/qdrant.mdx index 8f6d00ac9..9fbda87a4 100644 --- a/docs/components/vectordbs/dbs/qdrant.mdx +++ b/docs/components/vectordbs/dbs/qdrant.mdx @@ -73,7 +73,7 @@ Let's see the available parameters for the `qdrant` config: | `client` | Custom client for qdrant | `None` | | `host` | The host where the qdrant server is running | `None` | | `port` | The port where the qdrant server is running | `None` | -| `path` | Path for the qdrant database | `/tmp/qdrant` | +| `path` | Path for the qdrant database | `~/.local/share/mem0/qdrant` | | `url` | Full URL for the qdrant server | `None` | | `api_key` | API key for the qdrant server | `None` | | `on_disk` | For enabling persistent storage | `False` | @@ -85,7 +85,7 @@ Let's see the available parameters for the `qdrant` config: | `embeddingModelDims` | Dimensions of the embedding model | `1536` | | `host` | The host where the Qdrant server is running | `None` | | `port` | The port where the Qdrant server is running | `None` | -| `path` | Path for the Qdrant database | `/tmp/qdrant` | +| `path` | Path for the Qdrant database | `~/.local/share/mem0/qdrant` | | `url` | Full URL for the Qdrant server | `None` | | `apiKey` | API key for the Qdrant server | `None` | | `onDisk` | For enabling persistent storage | `False` | diff --git a/docs/open-source/overview.mdx b/docs/open-source/overview.mdx index f6e51854b..d315d6e84 100644 --- a/docs/open-source/overview.mdx +++ b/docs/open-source/overview.mdx @@ -77,7 +77,7 @@ Mem0 Open Source delivers the same adaptive memory engine as the platform, but p Mem0 OSS works out of the box with sensible defaults: - LLM: OpenAI `gpt-4.1-nano-2025-04-14` (via `OPENAI_API_KEY`) - Embeddings: OpenAI `text-embedding-3-small` - - Vector store: Local Qdrant instance storing data at `/tmp/qdrant` + - Vector store: Local Qdrant instance storing data at `~/.local/share/mem0/qdrant` - History store: SQLite database at `~/.mem0/history.db` - Reranker: Disabled until you configure a provider diff --git a/docs/open-source/python-quickstart.mdx b/docs/open-source/python-quickstart.mdx index 2e530ea61..458b4df53 100644 --- a/docs/open-source/python-quickstart.mdx +++ b/docs/open-source/python-quickstart.mdx @@ -81,7 +81,7 @@ print(results) By default `Memory()` wires up: - OpenAI `gpt-4.1-nano-2025-04-14` for fact extraction and updates - OpenAI `text-embedding-3-small` embeddings (1536 dimensions) -- Qdrant vector store with on-disk data at `/tmp/qdrant` +- Qdrant vector store with on-disk data at `~/.local/share/mem0/qdrant` - SQLite history at `~/.mem0/history.db` - No reranker (add one in the config when you need it) diff --git a/mem0/configs/vector_stores/chroma.py b/mem0/configs/vector_stores/chroma.py index 1b65e1436..c1c94f313 100644 --- a/mem0/configs/vector_stores/chroma.py +++ b/mem0/configs/vector_stores/chroma.py @@ -2,6 +2,8 @@ from typing import Any, ClassVar, Dict, Optional from pydantic import BaseModel, ConfigDict, Field, model_validator +from mem0.vector_stores.configs import get_default_vector_store_path + class ChromaDbConfig(BaseModel): try: @@ -23,24 +25,25 @@ class ChromaDbConfig(BaseModel): def check_connection_config(cls, values): host, port, path = values.get("host"), values.get("port"), values.get("path") api_key, tenant = values.get("api_key"), values.get("tenant") - + default_path = get_default_vector_store_path("chroma") + # Check if cloud configuration is provided cloud_config = bool(api_key and tenant) - + # If cloud configuration is provided, remove any default path that might have been added - if cloud_config and path == "/tmp/chroma": + if cloud_config and path == default_path: values.pop("path", None) return values - + # Check if local/server configuration is provided local_config = bool(path) or bool(host and port) - + if not cloud_config and not local_config: raise ValueError("Either ChromaDB Cloud configuration (api_key, tenant) or local configuration (path or host/port) must be provided.") - + if cloud_config and local_config: raise ValueError("Cannot specify both cloud configuration and local configuration. Choose one.") - + return values @model_validator(mode="before") diff --git a/mem0/configs/vector_stores/qdrant.py b/mem0/configs/vector_stores/qdrant.py index a61088388..3f782850d 100644 --- a/mem0/configs/vector_stores/qdrant.py +++ b/mem0/configs/vector_stores/qdrant.py @@ -2,6 +2,8 @@ from typing import Any, ClassVar, Dict, Optional from pydantic import BaseModel, ConfigDict, Field, model_validator +from mem0.vector_stores.configs import get_default_vector_store_path + class QdrantConfig(BaseModel): from qdrant_client import QdrantClient @@ -13,7 +15,7 @@ class QdrantConfig(BaseModel): client: Optional[QdrantClient] = Field(None, description="Existing Qdrant client instance") host: Optional[str] = Field(None, description="Host address for Qdrant server") port: Optional[int] = Field(None, description="Port for Qdrant server") - path: Optional[str] = Field("/tmp/qdrant", description="Path for local Qdrant database") + path: Optional[str] = Field(default_factory=lambda: get_default_vector_store_path("qdrant"), description="Path for local Qdrant database") url: Optional[str] = Field(None, description="Full URL for Qdrant server") api_key: Optional[str] = Field(None, description="API key for Qdrant server") on_disk: Optional[bool] = Field(False,description="Enables persistent storage. Vectors are kept on disk (True) or in memory (False). Does not delete the local database path.") diff --git a/mem0/vector_stores/configs.py b/mem0/vector_stores/configs.py index ab9ad8af3..efcf556b5 100644 --- a/mem0/vector_stores/configs.py +++ b/mem0/vector_stores/configs.py @@ -1,10 +1,33 @@ +import logging import os - from pathlib import Path from typing import Dict, Optional from pydantic import BaseModel, Field, model_validator +logger = logging.getLogger(__name__) + + +def get_default_vector_store_path(provider: str) -> str: + """Return a platform-appropriate default path for a vector store provider. + + Preference order: MEM0_DATA_DIR → XDG_DATA_HOME (Linux) → APPDATA (Windows) → ~/.local/share + """ + mem0_data_dir = os.environ.get("MEM0_DATA_DIR") + if mem0_data_dir: + base = Path(mem0_data_dir) + else: + xdg = os.environ.get("XDG_DATA_HOME") + app_data = os.environ.get("APPDATA") + if xdg: + base = Path(xdg) + elif app_data: + base = Path(app_data) + else: + base = Path.home() / ".local" / "share" + base = base / "mem0" + return str(base / provider) + class VectorStoreConfig(BaseModel): provider: str = Field( @@ -64,18 +87,16 @@ class VectorStoreConfig(BaseModel): # also check if path in allowed kays for pydantic model, and whether config extra fields are allowed if "path" not in config and "path" in config_class.__annotations__: - # Use a platform-appropriate user data directory instead of /tmp, which is - # ephemeral on many systems and may be unwritable in service/restricted environments. - # Preference order: XDG_DATA_HOME (Linux) → APPDATA (Windows) → ~/.local/share - xdg = os.environ.get("XDG_DATA_HOME") - app_data = os.environ.get("APPDATA") - if xdg: - base = Path(xdg) - elif app_data: - base = Path(app_data) - else: - base = Path.home() / ".local" / "share" - config["path"] = str(base / "mem0" / provider) + config["path"] = get_default_vector_store_path(provider) + + legacy_path = Path(f"/tmp/{provider}") + if legacy_path.exists() and any(legacy_path.iterdir()): + logger.warning( + f"Found existing data at legacy path '{legacy_path}'. " + f"mem0 now defaults to '{config['path']}'. " + f"To keep using the old location, set path='/tmp/{provider}' in your config, " + f"or move the data to the new path." + ) self.config = config_class(**config) return self diff --git a/mem0/vector_stores/faiss.py b/mem0/vector_stores/faiss.py index 141df5eae..4ff63d470 100644 --- a/mem0/vector_stores/faiss.py +++ b/mem0/vector_stores/faiss.py @@ -8,6 +8,8 @@ from typing import Dict, List, Optional import numpy as np from pydantic import BaseModel +from mem0.vector_stores.configs import get_default_vector_store_path + import warnings try: @@ -58,7 +60,7 @@ class FAISS(VectorStoreBase): Defaults to False. """ self.collection_name = collection_name - self.path = path or f"/tmp/faiss/{collection_name}" + self.path = path or f"{get_default_vector_store_path('faiss')}/{collection_name}" self.distance_strategy = distance_strategy self.normalize_L2 = normalize_L2 self.embedding_model_dims = embedding_model_dims diff --git a/tests/vector_stores/test_default_path.py b/tests/vector_stores/test_default_path.py new file mode 100644 index 000000000..4c0a4a199 --- /dev/null +++ b/tests/vector_stores/test_default_path.py @@ -0,0 +1,357 @@ +"""Tests for the vector store default path fix (issue #4279). + +Verifies that vector store defaults use platform-appropriate persistent directories +instead of /tmp, which is ephemeral and may be inaccessible in restricted environments. +""" + +import logging +import os +import tempfile +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from mem0.vector_stores.configs import VectorStoreConfig, get_default_vector_store_path + + +# --------------------------------------------------------------------------- +# get_default_vector_store_path unit tests +# --------------------------------------------------------------------------- + +class TestGetDefaultVectorStorePath: + """Tests for the get_default_vector_store_path helper function.""" + + def _clean_env(self, monkeypatch): + """Remove all relevant env vars to get a clean baseline.""" + for var in ("MEM0_DATA_DIR", "XDG_DATA_HOME", "APPDATA"): + monkeypatch.delenv(var, raising=False) + + def test_default_falls_back_to_local_share(self, monkeypatch): + """With no env vars set, path should be ~/.local/share/mem0/{provider}.""" + self._clean_env(monkeypatch) + result = get_default_vector_store_path("qdrant") + expected = str(Path.home() / ".local" / "share" / "mem0" / "qdrant") + assert result == expected + + def test_xdg_data_home_respected(self, monkeypatch): + """XDG_DATA_HOME should be used as the base when set.""" + self._clean_env(monkeypatch) + monkeypatch.setenv("XDG_DATA_HOME", "/custom/xdg/data") + result = get_default_vector_store_path("chroma") + assert result == "/custom/xdg/data/mem0/chroma" + + def test_appdata_respected_on_windows(self, monkeypatch): + """APPDATA should be used when XDG_DATA_HOME is not set (Windows).""" + self._clean_env(monkeypatch) + monkeypatch.setenv("APPDATA", "C:\\Users\\test\\AppData\\Roaming") + result = get_default_vector_store_path("faiss") + expected = str(Path("C:\\Users\\test\\AppData\\Roaming") / "mem0" / "faiss") + assert result == expected + + def test_xdg_takes_precedence_over_appdata(self, monkeypatch): + """XDG_DATA_HOME should take precedence over APPDATA.""" + self._clean_env(monkeypatch) + monkeypatch.setenv("XDG_DATA_HOME", "/xdg/path") + monkeypatch.setenv("APPDATA", "C:\\windows\\path") + result = get_default_vector_store_path("qdrant") + assert result == "/xdg/path/mem0/qdrant" + + def test_mem0_data_dir_takes_highest_precedence(self, monkeypatch): + """MEM0_DATA_DIR should override all other env vars.""" + self._clean_env(monkeypatch) + monkeypatch.setenv("MEM0_DATA_DIR", "/custom/mem0/data") + monkeypatch.setenv("XDG_DATA_HOME", "/should/be/ignored") + monkeypatch.setenv("APPDATA", "C:\\also\\ignored") + result = get_default_vector_store_path("qdrant") + assert result == "/custom/mem0/data/qdrant" + + def test_mem0_data_dir_no_extra_mem0_subdir(self, monkeypatch): + """MEM0_DATA_DIR is already mem0-specific, so no extra 'mem0' subdir.""" + self._clean_env(monkeypatch) + monkeypatch.setenv("MEM0_DATA_DIR", "/data/mem0") + result = get_default_vector_store_path("qdrant") + # Should be /data/mem0/qdrant, NOT /data/mem0/mem0/qdrant + assert result == "/data/mem0/qdrant" + + def test_different_providers_get_different_paths(self, monkeypatch): + """Each provider should get its own subdirectory.""" + self._clean_env(monkeypatch) + qdrant_path = get_default_vector_store_path("qdrant") + chroma_path = get_default_vector_store_path("chroma") + faiss_path = get_default_vector_store_path("faiss") + assert qdrant_path != chroma_path != faiss_path + assert qdrant_path.endswith("/qdrant") + assert chroma_path.endswith("/chroma") + assert faiss_path.endswith("/faiss") + + def test_path_never_contains_tmp(self, monkeypatch): + """Default path should never be under /tmp (the whole point of #4279).""" + self._clean_env(monkeypatch) + for provider in ("qdrant", "chroma", "faiss"): + result = get_default_vector_store_path(provider) + assert not result.startswith("/tmp"), f"Provider {provider} still defaults to /tmp: {result}" + + +# --------------------------------------------------------------------------- +# VectorStoreConfig integration tests +# --------------------------------------------------------------------------- + +class TestVectorStoreConfigPathInjection: + """Tests that VectorStoreConfig correctly injects the new default path.""" + + def _clean_env(self, monkeypatch): + for var in ("MEM0_DATA_DIR", "XDG_DATA_HOME", "APPDATA"): + monkeypatch.delenv(var, raising=False) + + def test_qdrant_gets_persistent_default_path(self, monkeypatch): + """Qdrant should get a persistent default path, not /tmp.""" + self._clean_env(monkeypatch) + vc = VectorStoreConfig(provider="qdrant", config={}) + assert "/tmp" not in vc.config.path + assert "mem0" in vc.config.path + assert vc.config.path.endswith("/qdrant") + + def test_explicit_path_not_overridden(self, monkeypatch): + """An explicitly provided path should be used as-is.""" + self._clean_env(monkeypatch) + custom_path = "/my/custom/qdrant/path" + vc = VectorStoreConfig(provider="qdrant", config={"path": custom_path}) + assert vc.config.path == custom_path + + def test_explicit_tmp_path_still_works(self, monkeypatch): + """Users who explicitly want /tmp should still be able to use it.""" + self._clean_env(monkeypatch) + vc = VectorStoreConfig(provider="qdrant", config={"path": "/tmp/qdrant"}) + assert vc.config.path == "/tmp/qdrant" + + def test_mem0_data_dir_flows_through_config(self, monkeypatch): + """MEM0_DATA_DIR should be respected through VectorStoreConfig.""" + self._clean_env(monkeypatch) + monkeypatch.setenv("MEM0_DATA_DIR", "/custom/data") + vc = VectorStoreConfig(provider="qdrant", config={}) + assert vc.config.path == "/custom/data/qdrant" + + def test_xdg_data_home_flows_through_config(self, monkeypatch): + """XDG_DATA_HOME should be respected through VectorStoreConfig.""" + self._clean_env(monkeypatch) + monkeypatch.setenv("XDG_DATA_HOME", "/xdg/data") + vc = VectorStoreConfig(provider="qdrant", config={}) + assert vc.config.path == "/xdg/data/mem0/qdrant" + + +# --------------------------------------------------------------------------- +# Qdrant-specific config tests +# --------------------------------------------------------------------------- + +class TestQdrantConfigDefault: + """Tests that QdrantConfig's own default_factory uses the new path.""" + + def _clean_env(self, monkeypatch): + for var in ("MEM0_DATA_DIR", "XDG_DATA_HOME", "APPDATA"): + monkeypatch.delenv(var, raising=False) + + def test_qdrant_field_default_is_persistent(self, monkeypatch): + """QdrantConfig's path Field default should not be /tmp.""" + self._clean_env(monkeypatch) + from mem0.configs.vector_stores.qdrant import QdrantConfig + + # When path is explicitly provided, the before-validator sees it + config = QdrantConfig(path=get_default_vector_store_path("qdrant")) + assert "/tmp" not in config.path + assert config.path.endswith("/qdrant") + + def test_qdrant_via_vector_store_config_no_path(self, monkeypatch): + """Creating QdrantConfig via VectorStoreConfig with empty config should work.""" + self._clean_env(monkeypatch) + vc = VectorStoreConfig(provider="qdrant", config={}) + assert vc.config.path is not None + assert "/tmp" not in vc.config.path + + +# --------------------------------------------------------------------------- +# ChromaDB-specific config tests +# --------------------------------------------------------------------------- + +class TestChromaDbConfigDefault: + """Tests that ChromaDB's sentinel check works with the new default path.""" + + def _clean_env(self, monkeypatch): + for var in ("MEM0_DATA_DIR", "XDG_DATA_HOME", "APPDATA"): + monkeypatch.delenv(var, raising=False) + + def test_chroma_default_path_via_vector_store_config(self, monkeypatch): + """ChromaDB via VectorStoreConfig should get a non-/tmp default path.""" + self._clean_env(monkeypatch) + vc = VectorStoreConfig(provider="chroma", config={}) + assert "/tmp" not in vc.config.path + assert vc.config.path.endswith("/chroma") + + def test_chroma_cloud_config_strips_default_path(self, monkeypatch): + """ChromaDB cloud config should strip the injected default path.""" + self._clean_env(monkeypatch) + default_path = get_default_vector_store_path("chroma") + from mem0.configs.vector_stores.chroma import ChromaDbConfig + + # Simulate what VectorStoreConfig does: inject default path, then create config + config = ChromaDbConfig( + path=default_path, + api_key="test-key", + tenant="test-tenant", + ) + # When cloud config is provided and path matches default, path should be stripped + assert config.path is None + + def test_chroma_explicit_path_with_cloud_raises(self, monkeypatch): + """ChromaDB with both explicit custom path and cloud config should raise.""" + self._clean_env(monkeypatch) + from mem0.configs.vector_stores.chroma import ChromaDbConfig + + with pytest.raises(ValueError, match="Cannot specify both"): + ChromaDbConfig( + path="/my/custom/path", + api_key="test-key", + tenant="test-tenant", + ) + + +# --------------------------------------------------------------------------- +# FAISS-specific tests +# --------------------------------------------------------------------------- + +class TestFaissDefaultPath: + """Tests that FAISS uses the new default path.""" + + def _clean_env(self, monkeypatch): + for var in ("MEM0_DATA_DIR", "XDG_DATA_HOME", "APPDATA"): + monkeypatch.delenv(var, raising=False) + + def test_faiss_default_path_via_vector_store_config(self, monkeypatch): + """FAISS via VectorStoreConfig should get a non-/tmp default path.""" + self._clean_env(monkeypatch) + vc = VectorStoreConfig(provider="faiss", config={}) + assert "/tmp" not in vc.config.path + assert vc.config.path.endswith("/faiss") + + def test_faiss_implementation_uses_new_default(self, monkeypatch): + """FAISS implementation's own fallback should not use /tmp.""" + self._clean_env(monkeypatch) + with patch("faiss.IndexFlatL2") as mock_index: + mock_index.return_value = MagicMock() + with patch("faiss.write_index"): + from mem0.vector_stores.faiss import FAISS + store = FAISS(collection_name="test") + assert "/tmp" not in store.path + assert "mem0" in store.path + + +# --------------------------------------------------------------------------- +# Legacy path migration warning tests +# --------------------------------------------------------------------------- + +class TestLegacyPathWarning: + """Tests that a warning is emitted when legacy /tmp data exists.""" + + def _clean_env(self, monkeypatch): + for var in ("MEM0_DATA_DIR", "XDG_DATA_HOME", "APPDATA"): + monkeypatch.delenv(var, raising=False) + + def test_warning_when_legacy_path_has_data(self, monkeypatch, tmp_path, caplog): + """Should warn when /tmp/{provider} contains data.""" + self._clean_env(monkeypatch) + # Create a fake legacy directory with data + legacy_dir = tmp_path / "qdrant" + legacy_dir.mkdir() + (legacy_dir / "some_data.bin").write_text("fake data") + + with patch("mem0.vector_stores.configs.Path") as MockPath: + # Make the default path resolution work normally for the new path + real_path = Path + def path_side_effect(*args): + if args and args[0] == f"/tmp/qdrant": + return real_path(str(legacy_dir)) + return real_path(*args) + MockPath.side_effect = path_side_effect + MockPath.home = real_path.home + + # Instead of mocking Path, let's directly test the warning logic + # by creating a real /tmp-like structure + + # Simpler approach: directly test the warning path in VectorStoreConfig + legacy_path = Path(f"/tmp/qdrant") + with patch.object(Path, "exists", return_value=True): + with patch.object(Path, "iterdir", return_value=iter(["somefile"])): + with caplog.at_level(logging.WARNING, logger="mem0.vector_stores.configs"): + vc = VectorStoreConfig(provider="qdrant", config={}) + + assert "legacy path" in caplog.text.lower() or "Found existing data" in caplog.text + + def test_no_warning_when_legacy_path_empty(self, monkeypatch, caplog): + """Should not warn when /tmp/{provider} doesn't exist.""" + self._clean_env(monkeypatch) + with patch.object(Path, "exists", return_value=False): + with caplog.at_level(logging.WARNING, logger="mem0.vector_stores.configs"): + vc = VectorStoreConfig(provider="qdrant", config={}) + + assert "legacy path" not in caplog.text.lower() + assert "Found existing data" not in caplog.text + + def test_no_warning_when_explicit_path_provided(self, monkeypatch, caplog): + """Should not warn when user provides an explicit path.""" + self._clean_env(monkeypatch) + with caplog.at_level(logging.WARNING, logger="mem0.vector_stores.configs"): + vc = VectorStoreConfig(provider="qdrant", config={"path": "/my/path"}) + + assert "legacy path" not in caplog.text.lower() + assert "Found existing data" not in caplog.text + + +# --------------------------------------------------------------------------- +# Cross-platform / issue #4279 scenario tests +# --------------------------------------------------------------------------- + +class TestIssue4279Scenarios: + """End-to-end tests for the specific scenarios described in issue #4279.""" + + def _clean_env(self, monkeypatch): + for var in ("MEM0_DATA_DIR", "XDG_DATA_HOME", "APPDATA"): + monkeypatch.delenv(var, raising=False) + + def test_windows_no_tmp(self, monkeypatch): + """Windows doesn't have /tmp — APPDATA should be used.""" + self._clean_env(monkeypatch) + monkeypatch.setenv("APPDATA", "C:\\Users\\test\\AppData\\Roaming") + for provider in ("qdrant", "chroma", "faiss"): + path = get_default_vector_store_path(provider) + assert not path.startswith("/tmp"), f"Windows path for {provider} still uses /tmp" + assert "AppData" in path + + def test_linux_systemd_with_xdg(self, monkeypatch): + """Linux systemd services should respect XDG_DATA_HOME.""" + self._clean_env(monkeypatch) + monkeypatch.setenv("XDG_DATA_HOME", "/var/lib/myservice") + path = get_default_vector_store_path("qdrant") + assert path == "/var/lib/myservice/mem0/qdrant" + + def test_docker_with_custom_data_dir(self, monkeypatch): + """Docker users can mount a volume and point MEM0_DATA_DIR to it.""" + self._clean_env(monkeypatch) + monkeypatch.setenv("MEM0_DATA_DIR", "/data/mem0") + path = get_default_vector_store_path("qdrant") + assert path == "/data/mem0/qdrant" + + def test_macos_default_is_persistent(self, monkeypatch): + """macOS default should be under home directory, not /tmp.""" + self._clean_env(monkeypatch) + path = get_default_vector_store_path("qdrant") + assert path.startswith(str(Path.home())) + assert "/tmp" not in path + + def test_all_affected_providers_fixed(self, monkeypatch): + """All providers with a path field (qdrant, chroma, faiss) should be fixed.""" + self._clean_env(monkeypatch) + for provider in ("qdrant", "chroma", "faiss"): + vc = VectorStoreConfig(provider=provider, config={}) + assert "/tmp" not in vc.config.path, ( + f"Provider '{provider}' still defaults to /tmp: {vc.config.path}" + )