vector-inspector 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. vector_inspector/__init__.py +3 -0
  2. vector_inspector/__main__.py +4 -0
  3. vector_inspector/core/__init__.py +1 -0
  4. vector_inspector/core/connections/__init__.py +7 -0
  5. vector_inspector/core/connections/base_connection.py +233 -0
  6. vector_inspector/core/connections/chroma_connection.py +384 -0
  7. vector_inspector/core/connections/qdrant_connection.py +723 -0
  8. vector_inspector/core/connections/template_connection.py +346 -0
  9. vector_inspector/main.py +21 -0
  10. vector_inspector/services/__init__.py +1 -0
  11. vector_inspector/services/backup_restore_service.py +286 -0
  12. vector_inspector/services/filter_service.py +72 -0
  13. vector_inspector/services/import_export_service.py +287 -0
  14. vector_inspector/services/settings_service.py +60 -0
  15. vector_inspector/services/visualization_service.py +116 -0
  16. vector_inspector/ui/__init__.py +1 -0
  17. vector_inspector/ui/components/__init__.py +1 -0
  18. vector_inspector/ui/components/backup_restore_dialog.py +350 -0
  19. vector_inspector/ui/components/filter_builder.py +370 -0
  20. vector_inspector/ui/components/item_dialog.py +118 -0
  21. vector_inspector/ui/components/loading_dialog.py +30 -0
  22. vector_inspector/ui/main_window.py +288 -0
  23. vector_inspector/ui/views/__init__.py +1 -0
  24. vector_inspector/ui/views/collection_browser.py +112 -0
  25. vector_inspector/ui/views/connection_view.py +423 -0
  26. vector_inspector/ui/views/metadata_view.py +555 -0
  27. vector_inspector/ui/views/search_view.py +268 -0
  28. vector_inspector/ui/views/visualization_view.py +245 -0
  29. vector_inspector-0.2.0.dist-info/METADATA +382 -0
  30. vector_inspector-0.2.0.dist-info/RECORD +32 -0
  31. vector_inspector-0.2.0.dist-info/WHEEL +4 -0
  32. vector_inspector-0.2.0.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,3 @@
1
+ """Vector Inspector - A comprehensive desktop application for vector database visualization."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from .main import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1 @@
1
+ """Core functionality for vector database operations."""
@@ -0,0 +1,7 @@
1
+ """Connection managers for vector databases."""
2
+
3
+ from .base_connection import VectorDBConnection
4
+ from .chroma_connection import ChromaDBConnection
5
+ from .qdrant_connection import QdrantConnection
6
+
7
+ __all__ = ["VectorDBConnection", "ChromaDBConnection", "QdrantConnection"]
@@ -0,0 +1,233 @@
1
+ """Abstract base class for vector database connections."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Optional, List, Dict, Any
5
+
6
+
7
+ class VectorDBConnection(ABC):
8
+ """Abstract base class for vector database connections.
9
+
10
+ This class defines the interface that all vector database providers
11
+ must implement to be compatible with Vector Inspector.
12
+ """
13
+
14
+ @abstractmethod
15
+ def connect(self) -> bool:
16
+ """
17
+ Establish connection to the vector database.
18
+
19
+ Returns:
20
+ True if connection successful, False otherwise
21
+ """
22
+ pass
23
+
24
+ @abstractmethod
25
+ def disconnect(self):
26
+ """Close connection to the vector database."""
27
+ pass
28
+
29
+ @property
30
+ @abstractmethod
31
+ def is_connected(self) -> bool:
32
+ """
33
+ Check if connected to the vector database.
34
+
35
+ Returns:
36
+ True if connected, False otherwise
37
+ """
38
+ pass
39
+
40
+ @abstractmethod
41
+ def list_collections(self) -> List[str]:
42
+ """
43
+ Get list of all collections/indexes.
44
+
45
+ Returns:
46
+ List of collection/index names
47
+ """
48
+ pass
49
+
50
+ @abstractmethod
51
+ def get_collection_info(self, name: str) -> Optional[Dict[str, Any]]:
52
+ """
53
+ Get collection metadata and statistics.
54
+
55
+ Args:
56
+ name: Collection/index name
57
+
58
+ Returns:
59
+ Dictionary with collection info including:
60
+ - name: Collection name
61
+ - count: Number of items
62
+ - metadata_fields: List of available metadata field names
63
+ """
64
+ pass
65
+
66
+ @abstractmethod
67
+ def create_collection(self, name: str, vector_size: int, distance: str = "Cosine") -> bool:
68
+ """Create a collection/index with a given vector size and distance metric."""
69
+ pass
70
+
71
+ @abstractmethod
72
+ def add_items(
73
+ self,
74
+ collection_name: str,
75
+ documents: List[str],
76
+ metadatas: Optional[List[Dict[str, Any]]] = None,
77
+ ids: Optional[List[str]] = None,
78
+ embeddings: Optional[List[List[float]]] = None,
79
+ ) -> bool:
80
+ """Add items to a collection."""
81
+ pass
82
+
83
+ @abstractmethod
84
+ def get_items(self, name: str, ids: List[str]) -> Dict[str, Any]:
85
+ """Retrieve items by original ids. Should return a dict with 'documents' and 'metadatas'."""
86
+ pass
87
+
88
+ @abstractmethod
89
+ def delete_collection(self, name: str) -> bool:
90
+ """Delete a collection/index."""
91
+ pass
92
+
93
+ @abstractmethod
94
+ def count_collection(self, name: str) -> int:
95
+ """Return the number of items in the collection."""
96
+ pass
97
+
98
+ @abstractmethod
99
+ def query_collection(
100
+ self,
101
+ collection_name: str,
102
+ query_texts: Optional[List[str]] = None,
103
+ query_embeddings: Optional[List[List[float]]] = None,
104
+ n_results: int = 10,
105
+ where: Optional[Dict[str, Any]] = None,
106
+ where_document: Optional[Dict[str, Any]] = None,
107
+ ) -> Optional[Dict[str, Any]]:
108
+ """
109
+ Query a collection for similar vectors.
110
+
111
+ Args:
112
+ collection_name: Name of collection to query
113
+ query_texts: Text queries to embed and search
114
+ query_embeddings: Direct embedding vectors to search
115
+ n_results: Number of results to return
116
+ where: Metadata filter
117
+ where_document: Document content filter
118
+
119
+ Returns:
120
+ Query results dictionary with keys:
121
+ - ids: List of result IDs
122
+ - distances: List of distances/scores
123
+ - documents: List of document texts
124
+ - metadatas: List of metadata dicts
125
+ - embeddings: List of embedding vectors (optional)
126
+ """
127
+ pass
128
+
129
+ @abstractmethod
130
+ def get_all_items(
131
+ self,
132
+ collection_name: str,
133
+ limit: Optional[int] = None,
134
+ offset: Optional[int] = None,
135
+ where: Optional[Dict[str, Any]] = None,
136
+ ) -> Optional[Dict[str, Any]]:
137
+ """
138
+ Get all items from a collection.
139
+
140
+ Args:
141
+ collection_name: Name of collection
142
+ limit: Maximum number of items to return
143
+ offset: Number of items to skip
144
+ where: Metadata filter
145
+
146
+ Returns:
147
+ Dictionary with collection items:
148
+ - ids: List of item IDs
149
+ - documents: List of document texts
150
+ - metadatas: List of metadata dicts
151
+ - embeddings: List of embedding vectors
152
+ """
153
+ pass
154
+
155
+ @abstractmethod
156
+ def update_items(
157
+ self,
158
+ collection_name: str,
159
+ ids: List[str],
160
+ documents: Optional[List[str]] = None,
161
+ metadatas: Optional[List[Dict[str, Any]]] = None,
162
+ embeddings: Optional[List[List[float]]] = None,
163
+ ) -> bool:
164
+ """
165
+ Update items in a collection.
166
+
167
+ Args:
168
+ collection_name: Name of collection
169
+ ids: IDs of items to update
170
+ documents: New document texts
171
+ metadatas: New metadata
172
+ embeddings: New embeddings
173
+
174
+ Returns:
175
+ True if successful, False otherwise
176
+ """
177
+ pass
178
+
179
+ @abstractmethod
180
+ def delete_items(
181
+ self,
182
+ collection_name: str,
183
+ ids: Optional[List[str]] = None,
184
+ where: Optional[Dict[str, Any]] = None,
185
+ ) -> bool:
186
+ """
187
+ Delete items from a collection.
188
+
189
+ Args:
190
+ collection_name: Name of collection
191
+ ids: IDs of items to delete
192
+ where: Metadata filter for items to delete
193
+
194
+ Returns:
195
+ True if successful, False otherwise
196
+ """
197
+ pass
198
+
199
+
200
+ # Optional: Methods that may be provider-specific but useful to define
201
+
202
+ def get_connection_info(self) -> Dict[str, Any]:
203
+ """
204
+ Get information about the current connection.
205
+
206
+ Returns:
207
+ Dictionary with connection details (provider-specific)
208
+ """
209
+ return {
210
+ "provider": self.__class__.__name__,
211
+ "connected": self.is_connected
212
+ }
213
+
214
+ def get_supported_filter_operators(self) -> List[Dict[str, Any]]:
215
+ """
216
+ Get list of filter operators supported by this provider.
217
+
218
+ Returns:
219
+ List of operator dictionaries with 'name' and 'server_side' keys
220
+ """
221
+ # Default operators supported by most providers
222
+ return [
223
+ {"name": "=", "server_side": True},
224
+ {"name": "!=", "server_side": True},
225
+ {"name": ">", "server_side": True},
226
+ {"name": ">=", "server_side": True},
227
+ {"name": "<", "server_side": True},
228
+ {"name": "<=", "server_side": True},
229
+ {"name": "in", "server_side": True},
230
+ {"name": "not in", "server_side": True},
231
+ {"name": "contains", "server_side": False},
232
+ {"name": "not contains", "server_side": False},
233
+ ]
@@ -0,0 +1,384 @@
1
+ """ChromaDB connection manager."""
2
+
3
+ from typing import Optional, List, Dict, Any, cast
4
+ import os
5
+ from pathlib import Path
6
+ import chromadb
7
+ from chromadb.api import ClientAPI
8
+ from chromadb.api.models.Collection import Collection
9
+
10
+ from .base_connection import VectorDBConnection
11
+
12
+
13
+ class ChromaDBConnection(VectorDBConnection):
14
+ """Manages connection to ChromaDB and provides query interface."""
15
+
16
+ def __init__(self, path: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None):
17
+ """
18
+ Initialize ChromaDB connection.
19
+
20
+ Args:
21
+ path: Path for persistent client (local storage)
22
+ host: Host for HTTP client
23
+ port: Port for HTTP client
24
+ """
25
+ self.path = path
26
+ self.host = host
27
+ self.port = port
28
+ self._client: Optional[ClientAPI] = None
29
+ self._current_collection: Optional[Collection] = None
30
+
31
+ def connect(self) -> bool:
32
+ """
33
+ Establish connection to ChromaDB.
34
+
35
+ Returns:
36
+ True if connection successful, False otherwise
37
+ """
38
+ try:
39
+ if self.path:
40
+ # Resolve relative paths to project root
41
+ path_to_use = self._resolve_path(self.path)
42
+ # Ensure directory exists
43
+ os.makedirs(path_to_use, exist_ok=True)
44
+ self._client = chromadb.PersistentClient(path=path_to_use)
45
+ elif self.host and self.port:
46
+ self._client = chromadb.HttpClient(host=self.host, port=self.port)
47
+ else:
48
+ # Default to ephemeral client for testing
49
+ self._client = chromadb.Client()
50
+ return True
51
+ except Exception as e:
52
+ print(f"Connection failed: {e}")
53
+ return False
54
+
55
+ def _resolve_path(self, input_path: str) -> str:
56
+ """Resolve a path relative to the project root if not absolute."""
57
+ if os.path.isabs(input_path):
58
+ return input_path
59
+ # Find project root by searching for pyproject.toml
60
+ current = Path(__file__).resolve()
61
+ for parent in current.parents:
62
+ if (parent / "pyproject.toml").exists():
63
+ return str((parent / input_path).resolve())
64
+ # Fallback to CWD if project root not found
65
+ return str(Path(input_path).resolve())
66
+
67
+ def disconnect(self):
68
+ """Close connection to ChromaDB."""
69
+ self._client = None
70
+ self._current_collection = None
71
+
72
+ @property
73
+ def is_connected(self) -> bool:
74
+ """Check if connected to ChromaDB."""
75
+ return self._client is not None
76
+
77
+ def list_collections(self) -> List[str]:
78
+ """
79
+ Get list of all collections.
80
+
81
+ Returns:
82
+ List of collection names
83
+ """
84
+ if not self._client:
85
+ return []
86
+ try:
87
+ collections = self._client.list_collections()
88
+ return [col.name for col in collections]
89
+ except Exception as e:
90
+ print(f"Failed to list collections: {e}")
91
+ return []
92
+
93
+ def get_collection(self, name: str) -> Optional[Collection]:
94
+ """
95
+ Get or create a collection.
96
+
97
+ Args:
98
+ name: Collection name
99
+
100
+ Returns:
101
+ Collection object or None if failed
102
+ """
103
+ if not self._client:
104
+ return None
105
+ try:
106
+ self._current_collection = self._client.get_or_create_collection(name=name)
107
+ return self._current_collection
108
+ except Exception as e:
109
+ print(f"Failed to get collection: {e}")
110
+ return None
111
+
112
+ def get_collection_info(self, name: str) -> Optional[Dict[str, Any]]:
113
+ """
114
+ Get collection metadata and statistics.
115
+
116
+ Args:
117
+ name: Collection name
118
+
119
+ Returns:
120
+ Dictionary with collection info
121
+ """
122
+ collection = self.get_collection(name)
123
+ if not collection:
124
+ return None
125
+
126
+ try:
127
+ count = collection.count()
128
+ # Get a sample to determine metadata fields
129
+ sample = collection.get(limit=1, include=["metadatas"])
130
+ metadata_fields = []
131
+ if sample and sample["metadatas"]:
132
+ metadata_fields = list(sample["metadatas"][0].keys()) if sample["metadatas"][0] else []
133
+
134
+ return {
135
+ "name": name,
136
+ "count": count,
137
+ "metadata_fields": metadata_fields,
138
+ }
139
+ except Exception as e:
140
+ print(f"Failed to get collection info: {e}")
141
+ return None
142
+
143
+ def query_collection(
144
+ self,
145
+ collection_name: str,
146
+ query_texts: Optional[List[str]] = None,
147
+ query_embeddings: Optional[List[List[float]]] = None,
148
+ n_results: int = 10,
149
+ where: Optional[Dict[str, Any]] = None,
150
+ where_document: Optional[Dict[str, Any]] = None,
151
+ ) -> Optional[Dict[str, Any]]:
152
+ """
153
+ Query a collection for similar vectors.
154
+
155
+ Args:
156
+ collection_name: Name of collection to query
157
+ query_texts: Text queries to embed and search
158
+ query_embeddings: Direct embedding vectors to search
159
+ n_results: Number of results to return
160
+ where: Metadata filter
161
+ where_document: Document content filter
162
+
163
+ Returns:
164
+ Query results or None if failed
165
+ """
166
+ collection = self.get_collection(collection_name)
167
+ if not collection:
168
+ return None
169
+
170
+ try:
171
+ results = collection.query(
172
+ query_texts=query_texts,
173
+ query_embeddings=query_embeddings, # type: ignore
174
+ n_results=n_results,
175
+ where=where,
176
+ where_document=where_document, # type: ignore
177
+ include=["metadatas", "documents", "distances", "embeddings"]
178
+ )
179
+ return cast(Dict[str, Any], results)
180
+ except Exception as e:
181
+ print(f"Query failed: {e}")
182
+ return None
183
+
184
+ def get_all_items(
185
+ self,
186
+ collection_name: str,
187
+ limit: Optional[int] = None,
188
+ offset: Optional[int] = None,
189
+ where: Optional[Dict[str, Any]] = None,
190
+ ) -> Optional[Dict[str, Any]]:
191
+ """
192
+ Get all items from a collection.
193
+
194
+ Args:
195
+ collection_name: Name of collection
196
+ limit: Maximum number of items to return
197
+ offset: Number of items to skip
198
+ where: Metadata filter
199
+
200
+ Returns:
201
+ Collection items or None if failed
202
+ """
203
+ collection = self.get_collection(collection_name)
204
+ if not collection:
205
+ return None
206
+
207
+ try:
208
+ results = collection.get(
209
+ limit=limit,
210
+ offset=offset,
211
+ where=where,
212
+ include=["metadatas", "documents", "embeddings"]
213
+ )
214
+ return cast(Dict[str, Any], results)
215
+ except Exception as e:
216
+ print(f"Failed to get items: {e}")
217
+ return None
218
+
219
+ def add_items(
220
+ self,
221
+ collection_name: str,
222
+ documents: List[str],
223
+ metadatas: Optional[List[Dict[str, Any]]] = None,
224
+ ids: Optional[List[str]] = None,
225
+ embeddings: Optional[List[List[float]]] = None,
226
+ ) -> bool:
227
+ """
228
+ Add items to a collection.
229
+
230
+ Args:
231
+ collection_name: Name of collection
232
+ documents: Document texts
233
+ metadatas: Metadata for each document
234
+ ids: IDs for each document
235
+ embeddings: Pre-computed embeddings
236
+
237
+ Returns:
238
+ True if successful, False otherwise
239
+ """
240
+ collection = self.get_collection(collection_name)
241
+ if not collection:
242
+ return False
243
+
244
+ try:
245
+ collection.add(
246
+ documents=documents,
247
+ metadatas=metadatas, # type: ignore
248
+ ids=ids, # type: ignore
249
+ embeddings=embeddings # type: ignore
250
+ )
251
+ return True
252
+ except Exception as e:
253
+ print(f"Failed to add items: {e}")
254
+ return False
255
+
256
+ def update_items(
257
+ self,
258
+ collection_name: str,
259
+ ids: List[str],
260
+ documents: Optional[List[str]] = None,
261
+ metadatas: Optional[List[Dict[str, Any]]] = None,
262
+ embeddings: Optional[List[List[float]]] = None,
263
+ ) -> bool:
264
+ """
265
+ Update items in a collection.
266
+
267
+ Args:
268
+ collection_name: Name of collection
269
+ ids: IDs of items to update
270
+ documents: New document texts
271
+ metadatas: New metadata
272
+ embeddings: New embeddings
273
+
274
+ Returns:
275
+ True if successful, False otherwise
276
+ """
277
+ collection = self.get_collection(collection_name)
278
+ if not collection:
279
+ return False
280
+
281
+ try:
282
+ collection.update(
283
+ ids=ids,
284
+ documents=documents,
285
+ metadatas=metadatas, # type: ignore
286
+ embeddings=embeddings # type: ignore
287
+ )
288
+ return True
289
+ except Exception as e:
290
+ print(f"Failed to update items: {e}")
291
+ return False
292
+
293
+ def delete_items(
294
+ self,
295
+ collection_name: str,
296
+ ids: Optional[List[str]] = None,
297
+ where: Optional[Dict[str, Any]] = None,
298
+ ) -> bool:
299
+ """
300
+ Delete items from a collection.
301
+
302
+ Args:
303
+ collection_name: Name of collection
304
+ ids: IDs of items to delete
305
+ where: Metadata filter for items to delete
306
+
307
+ Returns:
308
+ True if successful, False otherwise
309
+ """
310
+ collection = self.get_collection(collection_name)
311
+ if not collection:
312
+ return False
313
+
314
+ try:
315
+ collection.delete(ids=ids, where=where)
316
+ return True
317
+ except Exception as e:
318
+ print(f"Failed to delete items: {e}")
319
+ return False
320
+
321
+ def delete_collection(self, name: str) -> bool:
322
+ """
323
+ Delete an entire collection.
324
+
325
+ Args:
326
+ name: Collection name
327
+
328
+ Returns:
329
+ True if successful, False otherwise
330
+ """
331
+ if not self._client:
332
+ return False
333
+
334
+ try:
335
+ self._client.delete_collection(name=name)
336
+ if self._current_collection and self._current_collection.name == name:
337
+ self._current_collection = None
338
+ return True
339
+ except Exception as e:
340
+ print(f"Failed to delete collection: {e}")
341
+ return False
342
+
343
+ # Implement base connection uniform APIs
344
+ def create_collection(self, name: str, vector_size: int, distance: str = "Cosine") -> bool:
345
+ """Create a collection. Chroma doesn't require vector size at creation."""
346
+ return self.get_collection(name) is not None
347
+
348
+ def get_items(self, name: str, ids: List[str]) -> Dict[str, Any]:
349
+ """Retrieve items by IDs."""
350
+ col = self.get_collection(name)
351
+ if not col:
352
+ raise RuntimeError("Collection not available")
353
+ return cast(Dict[str, Any], col.get(ids=ids, include=["metadatas", "documents", "embeddings"]))
354
+
355
+ def count_collection(self, name: str) -> int:
356
+ """Count items in a collection."""
357
+ col = self.get_collection(name)
358
+ if not col:
359
+ return 0
360
+ try:
361
+ return col.count()
362
+ except Exception:
363
+ return 0
364
+
365
+ def get_supported_filter_operators(self) -> List[Dict[str, Any]]:
366
+ """
367
+ Get filter operators supported by ChromaDB.
368
+
369
+ Returns:
370
+ List of operator dictionaries
371
+ """
372
+ return [
373
+ {"name": "=", "server_side": True},
374
+ {"name": "!=", "server_side": True},
375
+ {"name": ">", "server_side": True},
376
+ {"name": ">=", "server_side": True},
377
+ {"name": "<", "server_side": True},
378
+ {"name": "<=", "server_side": True},
379
+ {"name": "in", "server_side": True},
380
+ {"name": "not in", "server_side": True},
381
+ # Client-side only operators
382
+ {"name": "contains", "server_side": False},
383
+ {"name": "not contains", "server_side": False},
384
+ ]