hammad-python 0.0.19__py3-none-any.whl → 0.0.21__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.
- hammad/__init__.py +7 -137
- hammad/_internal.py +1 -0
- hammad/cli/_runner.py +8 -8
- hammad/cli/plugins.py +55 -26
- hammad/cli/styles/utils.py +16 -8
- hammad/data/__init__.py +1 -5
- hammad/data/collections/__init__.py +2 -3
- hammad/data/collections/collection.py +41 -22
- hammad/data/collections/indexes/__init__.py +1 -1
- hammad/data/collections/indexes/qdrant/__init__.py +1 -1
- hammad/data/collections/indexes/qdrant/index.py +106 -118
- hammad/data/collections/indexes/qdrant/settings.py +14 -14
- hammad/data/collections/indexes/qdrant/utils.py +28 -38
- hammad/data/collections/indexes/tantivy/__init__.py +1 -1
- hammad/data/collections/indexes/tantivy/index.py +57 -59
- hammad/data/collections/indexes/tantivy/settings.py +8 -19
- hammad/data/collections/indexes/tantivy/utils.py +28 -52
- hammad/data/models/__init__.py +2 -7
- hammad/data/sql/__init__.py +1 -1
- hammad/data/sql/database.py +71 -73
- hammad/data/sql/types.py +37 -51
- hammad/formatting/__init__.py +2 -1
- hammad/formatting/json/converters.py +2 -2
- hammad/genai/__init__.py +96 -36
- hammad/genai/agents/__init__.py +47 -1
- hammad/genai/agents/agent.py +1298 -0
- hammad/genai/agents/run.py +615 -0
- hammad/genai/agents/types/__init__.py +29 -22
- hammad/genai/agents/types/agent_context.py +13 -0
- hammad/genai/agents/types/agent_event.py +128 -0
- hammad/genai/agents/types/agent_hooks.py +220 -0
- hammad/genai/agents/types/agent_messages.py +31 -0
- hammad/genai/agents/types/agent_response.py +122 -0
- hammad/genai/agents/types/agent_stream.py +318 -0
- hammad/genai/models/__init__.py +1 -0
- hammad/genai/models/embeddings/__init__.py +39 -0
- hammad/genai/{embedding_models/embedding_model.py → models/embeddings/model.py} +45 -41
- hammad/genai/{embedding_models → models/embeddings}/run.py +10 -8
- hammad/genai/models/embeddings/types/__init__.py +37 -0
- hammad/genai/{embedding_models → models/embeddings/types}/embedding_model_name.py +2 -4
- hammad/genai/{embedding_models → models/embeddings/types}/embedding_model_response.py +11 -4
- hammad/genai/{embedding_models/embedding_model_request.py → models/embeddings/types/embedding_model_run_params.py} +4 -3
- hammad/genai/models/embeddings/types/embedding_model_settings.py +47 -0
- hammad/genai/models/language/__init__.py +48 -0
- hammad/genai/{language_models/language_model.py → models/language/model.py} +496 -204
- hammad/genai/{language_models → models/language}/run.py +80 -57
- hammad/genai/models/language/types/__init__.py +40 -0
- hammad/genai/models/language/types/language_model_instructor_mode.py +47 -0
- hammad/genai/models/language/types/language_model_messages.py +28 -0
- hammad/genai/{language_models/_types.py → models/language/types/language_model_name.py} +3 -40
- hammad/genai/{language_models → models/language/types}/language_model_request.py +17 -25
- hammad/genai/{language_models → models/language/types}/language_model_response.py +60 -67
- hammad/genai/{language_models → models/language/types}/language_model_response_chunk.py +8 -5
- hammad/genai/models/language/types/language_model_settings.py +89 -0
- hammad/genai/{language_models/_streaming.py → models/language/types/language_model_stream.py} +221 -243
- hammad/genai/{language_models/_utils → models/language/utils}/__init__.py +8 -11
- hammad/genai/models/language/utils/requests.py +421 -0
- hammad/genai/{language_models/_utils/_structured_outputs.py → models/language/utils/structured_outputs.py} +31 -20
- hammad/genai/models/model_provider.py +4 -0
- hammad/genai/{multimodal_models.py → models/multimodal.py} +4 -5
- hammad/genai/models/reranking.py +26 -0
- hammad/genai/types/__init__.py +1 -0
- hammad/genai/types/base.py +215 -0
- hammad/genai/{agents/types → types}/history.py +101 -88
- hammad/genai/{agents/types/tool.py → types/tools.py} +157 -140
- hammad/logging/logger.py +9 -1
- hammad/mcp/client/__init__.py +2 -3
- hammad/mcp/client/client.py +10 -10
- hammad/mcp/servers/__init__.py +2 -1
- hammad/service/decorators.py +1 -3
- hammad/web/models.py +1 -3
- hammad/web/search/client.py +10 -22
- {hammad_python-0.0.19.dist-info → hammad_python-0.0.21.dist-info}/METADATA +10 -2
- hammad_python-0.0.21.dist-info/RECORD +127 -0
- hammad/genai/embedding_models/__init__.py +0 -41
- hammad/genai/language_models/__init__.py +0 -35
- hammad/genai/language_models/_utils/_completions.py +0 -131
- hammad/genai/language_models/_utils/_messages.py +0 -89
- hammad/genai/language_models/_utils/_requests.py +0 -202
- hammad/genai/rerank_models.py +0 -26
- hammad_python-0.0.19.dist-info/RECORD +0 -111
- {hammad_python-0.0.19.dist-info → hammad_python-0.0.21.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.19.dist-info → hammad_python-0.0.21.dist-info}/licenses/LICENSE +0 -0
@@ -12,11 +12,11 @@ from typing import (
|
|
12
12
|
final,
|
13
13
|
TYPE_CHECKING,
|
14
14
|
Tuple,
|
15
|
-
NamedTuple
|
15
|
+
NamedTuple,
|
16
16
|
)
|
17
17
|
|
18
18
|
if TYPE_CHECKING:
|
19
|
-
from .....genai.
|
19
|
+
from .....genai.models.embeddings.types import EmbeddingModelName
|
20
20
|
# import uuid # Unused import
|
21
21
|
from pathlib import Path
|
22
22
|
import json
|
@@ -34,9 +34,11 @@ from .settings import (
|
|
34
34
|
DistanceMetric,
|
35
35
|
)
|
36
36
|
|
37
|
+
|
37
38
|
class VectorSearchResult(NamedTuple):
|
38
39
|
"""Result from vector search containing item and similarity score."""
|
39
|
-
|
40
|
+
|
41
|
+
item: "DatabaseItem[DatabaseItemType]"
|
40
42
|
score: float
|
41
43
|
|
42
44
|
|
@@ -50,7 +52,7 @@ __all__ = (
|
|
50
52
|
class QdrantCollectionIndex:
|
51
53
|
"""A vector collection index that uses Qdrant for vector storage
|
52
54
|
and similarity search, with SQL Database as the primary storage backend.
|
53
|
-
|
55
|
+
|
54
56
|
This collection index provides vector-based functionality for storing
|
55
57
|
embeddings and performing semantic similarity searches while using
|
56
58
|
the Database class for reliable data persistence.
|
@@ -77,7 +79,7 @@ class QdrantCollectionIndex:
|
|
77
79
|
) -> None:
|
78
80
|
"""
|
79
81
|
Initialize a new QdrantCollectionIndex.
|
80
|
-
|
82
|
+
|
81
83
|
Args:
|
82
84
|
name: The name of the index.
|
83
85
|
vector_size: Size/dimension of the vectors to store.
|
@@ -105,7 +107,7 @@ class QdrantCollectionIndex:
|
|
105
107
|
self.embedding_api_key = embedding_api_key
|
106
108
|
self.embedding_base_url = embedding_base_url
|
107
109
|
self._embedding_function = None
|
108
|
-
|
110
|
+
|
109
111
|
# Rerank model configuration
|
110
112
|
self.rerank_model = rerank_model
|
111
113
|
self.rerank_api_key = rerank_api_key
|
@@ -121,7 +123,7 @@ class QdrantCollectionIndex:
|
|
121
123
|
qdrant_path = None
|
122
124
|
if self.path is not None:
|
123
125
|
qdrant_path = str(self.path / f"{name}_qdrant")
|
124
|
-
|
126
|
+
|
125
127
|
settings = QdrantCollectionIndexSettings(
|
126
128
|
vector_size=vector_size or 768, # Default fallback
|
127
129
|
distance_metric=distance_metric,
|
@@ -138,13 +140,13 @@ class QdrantCollectionIndex:
|
|
138
140
|
database_path = None
|
139
141
|
if self.path is not None:
|
140
142
|
database_path = self.path / f"{name}.db"
|
141
|
-
|
143
|
+
|
142
144
|
self._database = Database[DatabaseItemType](
|
143
145
|
name=name,
|
144
146
|
schema=schema,
|
145
147
|
ttl=ttl,
|
146
148
|
path=database_path,
|
147
|
-
table_name=f"qdrant_{name}"
|
149
|
+
table_name=f"qdrant_{name}",
|
148
150
|
)
|
149
151
|
|
150
152
|
# Initialize Qdrant client (lazily to handle import errors gracefully)
|
@@ -159,17 +161,14 @@ class QdrantCollectionIndex:
|
|
159
161
|
try:
|
160
162
|
self._client = utils.create_qdrant_client(self.settings)
|
161
163
|
self._client_wrapper = utils.QdrantClientWrapper(
|
162
|
-
client=self._client,
|
163
|
-
collection_name=self.name
|
164
|
+
client=self._client, collection_name=self.name
|
164
165
|
)
|
165
|
-
|
166
|
+
|
166
167
|
# Create collection if it doesn't exist
|
167
168
|
utils.create_collection_if_not_exists(
|
168
|
-
self._client,
|
169
|
-
self.name,
|
170
|
-
self.settings
|
169
|
+
self._client, self.name, self.settings
|
171
170
|
)
|
172
|
-
|
171
|
+
|
173
172
|
except utils.QdrantCollectionIndexError:
|
174
173
|
# Qdrant not available - only SQL storage will work
|
175
174
|
self._client = None
|
@@ -178,17 +177,17 @@ class QdrantCollectionIndex:
|
|
178
177
|
def _get_embedding_function(self) -> Optional[Callable[[Any], List[float]]]:
|
179
178
|
"""Get or create embedding function from model configuration."""
|
180
179
|
if self._embedding_function is None and self.embedding_model:
|
181
|
-
from .....genai.
|
182
|
-
|
180
|
+
from .....genai.models.embeddings.model import EmbeddingModel
|
181
|
+
|
183
182
|
model = EmbeddingModel(model=self.embedding_model)
|
184
|
-
|
183
|
+
|
185
184
|
def embedding_function(item: Any) -> List[float]:
|
186
185
|
response = model.run(
|
187
186
|
input=item,
|
188
187
|
dimensions=self.embedding_dimensions,
|
189
188
|
api_key=self.embedding_api_key,
|
190
189
|
api_base=self.embedding_base_url,
|
191
|
-
format=True
|
190
|
+
format=True,
|
192
191
|
)
|
193
192
|
if response.data and len(response.data) > 0:
|
194
193
|
return response.data[0].embedding
|
@@ -196,34 +195,34 @@ class QdrantCollectionIndex:
|
|
196
195
|
raise utils.QdrantCollectionIndexError(
|
197
196
|
"Failed to generate embedding: empty response"
|
198
197
|
)
|
199
|
-
|
198
|
+
|
200
199
|
self._embedding_function = embedding_function
|
201
|
-
|
200
|
+
|
202
201
|
return self._embedding_function
|
203
|
-
|
202
|
+
|
204
203
|
def _rerank_results(
|
205
|
-
self,
|
206
|
-
query: str,
|
204
|
+
self,
|
205
|
+
query: str,
|
207
206
|
results: List[Tuple[DatabaseItem[DatabaseItemType], float]],
|
208
|
-
top_n: Optional[int] = None
|
207
|
+
top_n: Optional[int] = None,
|
209
208
|
) -> List[Tuple[DatabaseItem[DatabaseItemType], float]]:
|
210
209
|
"""
|
211
210
|
Rerank search results using the configured rerank model.
|
212
|
-
|
211
|
+
|
213
212
|
Args:
|
214
213
|
query: The original search query
|
215
214
|
results: List of (DatabaseItem, similarity_score) tuples
|
216
215
|
top_n: Number of top results to return after reranking
|
217
|
-
|
216
|
+
|
218
217
|
Returns:
|
219
218
|
Reranked list of (DatabaseItem, rerank_score) tuples
|
220
219
|
"""
|
221
220
|
if not self.rerank_model or not results:
|
222
221
|
return results
|
223
|
-
|
222
|
+
|
224
223
|
try:
|
225
|
-
from .....genai.
|
226
|
-
|
224
|
+
from .....genai.models.reranking import run_reranking_model
|
225
|
+
|
227
226
|
# Extract documents for reranking
|
228
227
|
documents = []
|
229
228
|
for db_item, _ in results:
|
@@ -233,17 +232,17 @@ class QdrantCollectionIndex:
|
|
233
232
|
else:
|
234
233
|
doc_text = str(db_item.item)
|
235
234
|
documents.append(doc_text)
|
236
|
-
|
235
|
+
|
237
236
|
# Perform reranking
|
238
|
-
rerank_response =
|
237
|
+
rerank_response = run_reranking_model(
|
239
238
|
model=self.rerank_model,
|
240
239
|
query=query,
|
241
240
|
documents=documents,
|
242
241
|
top_n=top_n or len(results),
|
243
242
|
api_key=self.rerank_api_key,
|
244
|
-
api_base=self.rerank_base_url
|
243
|
+
api_base=self.rerank_base_url,
|
245
244
|
)
|
246
|
-
|
245
|
+
|
247
246
|
# Reorder results based on rerank scores
|
248
247
|
reranked_results = []
|
249
248
|
for rerank_result in rerank_response.results:
|
@@ -253,13 +252,13 @@ class QdrantCollectionIndex:
|
|
253
252
|
# Update the score on the DatabaseItem itself
|
254
253
|
db_item.score = rerank_score
|
255
254
|
reranked_results.append((db_item, rerank_score))
|
256
|
-
|
255
|
+
|
257
256
|
return reranked_results
|
258
|
-
|
257
|
+
|
259
258
|
except Exception:
|
260
259
|
# If reranking fails, return original results
|
261
260
|
return results
|
262
|
-
|
261
|
+
|
263
262
|
def _prepare_vector(self, item: Any) -> List[float]:
|
264
263
|
"""Prepare vector from item using embedding function or direct vector."""
|
265
264
|
embedding_function = self._get_embedding_function()
|
@@ -291,11 +290,11 @@ class QdrantCollectionIndex:
|
|
291
290
|
if not self._vector_size_determined:
|
292
291
|
self.vector_size = size
|
293
292
|
self._vector_size_determined = True
|
294
|
-
|
293
|
+
|
295
294
|
# Update settings with determined vector size
|
296
295
|
if self.settings:
|
297
296
|
self.settings.vector_size = size
|
298
|
-
|
297
|
+
|
299
298
|
# Initialize Qdrant client now that we have vector size
|
300
299
|
self._init_qdrant_client()
|
301
300
|
|
@@ -310,38 +309,31 @@ class QdrantCollectionIndex:
|
|
310
309
|
if not self._client:
|
311
310
|
# Qdrant not available, skip vector indexing
|
312
311
|
return
|
313
|
-
|
312
|
+
|
314
313
|
try:
|
315
314
|
try:
|
316
315
|
from qdrant_client.models import PointStruct
|
317
316
|
except ImportError:
|
318
317
|
raise ImportError(
|
319
|
-
"Using Qdrant requires the `qdrant-client` package. Please install with: pip install 'hammad-python[
|
318
|
+
"Using Qdrant requires the `qdrant-client` package. Please install with: pip install 'hammad-python[genai]'"
|
320
319
|
)
|
321
|
-
|
320
|
+
|
322
321
|
# Prepare payload with metadata
|
323
322
|
payload = {
|
324
323
|
"item_data": json.dumps(utils.serialize(item)),
|
325
324
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
326
325
|
}
|
327
|
-
|
326
|
+
|
328
327
|
# Add filters as top-level payload fields
|
329
328
|
if filters:
|
330
329
|
for key, value in filters.items():
|
331
330
|
payload[key] = value
|
332
331
|
|
333
332
|
# Create point and upsert to Qdrant
|
334
|
-
point = PointStruct(
|
335
|
-
|
336
|
-
|
337
|
-
payload=payload
|
338
|
-
)
|
333
|
+
point = PointStruct(id=item_id, vector=vector, payload=payload)
|
334
|
+
|
335
|
+
self._client.upsert(collection_name=self.name, points=[point])
|
339
336
|
|
340
|
-
self._client.upsert(
|
341
|
-
collection_name=self.name,
|
342
|
-
points=[point]
|
343
|
-
)
|
344
|
-
|
345
337
|
except Exception:
|
346
338
|
# Vector indexing failed, but data is still in SQL database
|
347
339
|
pass
|
@@ -357,14 +349,14 @@ class QdrantCollectionIndex:
|
|
357
349
|
) -> str:
|
358
350
|
"""
|
359
351
|
Add an item to the index.
|
360
|
-
|
352
|
+
|
361
353
|
Args:
|
362
354
|
item: The item to store.
|
363
355
|
id: Optional ID (will generate UUID if not provided).
|
364
356
|
filters: Optional filters/metadata.
|
365
357
|
ttl: Optional TTL in seconds.
|
366
358
|
vector: Optional pre-computed vector (if not provided, will use embedding_function).
|
367
|
-
|
359
|
+
|
368
360
|
Returns:
|
369
361
|
The ID of the stored item.
|
370
362
|
"""
|
@@ -375,7 +367,7 @@ class QdrantCollectionIndex:
|
|
375
367
|
filters=filters,
|
376
368
|
ttl=ttl,
|
377
369
|
)
|
378
|
-
|
370
|
+
|
379
371
|
# Prepare vector for Qdrant storage
|
380
372
|
if vector is None:
|
381
373
|
try:
|
@@ -385,10 +377,10 @@ class QdrantCollectionIndex:
|
|
385
377
|
return item_id
|
386
378
|
else:
|
387
379
|
prepared_vector = utils.prepare_vector(vector, self.vector_size)
|
388
|
-
|
380
|
+
|
389
381
|
# Add to Qdrant vector store
|
390
382
|
self._add_to_qdrant(item_id, prepared_vector, item, filters)
|
391
|
-
|
383
|
+
|
392
384
|
return item_id
|
393
385
|
|
394
386
|
def get(
|
@@ -399,11 +391,11 @@ class QdrantCollectionIndex:
|
|
399
391
|
) -> Optional[DatabaseItem[DatabaseItemType]]:
|
400
392
|
"""
|
401
393
|
Get an item by ID.
|
402
|
-
|
394
|
+
|
403
395
|
Args:
|
404
396
|
id: The item ID.
|
405
397
|
filters: Optional filters to match.
|
406
|
-
|
398
|
+
|
407
399
|
Returns:
|
408
400
|
The database item or None if not found.
|
409
401
|
"""
|
@@ -422,7 +414,7 @@ class QdrantCollectionIndex:
|
|
422
414
|
) -> Union[List[DatabaseItem[DatabaseItemType]], List[VectorSearchResult]]:
|
423
415
|
"""
|
424
416
|
Internal method to perform vector similarity search.
|
425
|
-
|
417
|
+
|
426
418
|
Args:
|
427
419
|
query_vector: Query vector for similarity search.
|
428
420
|
filters: Optional filters to apply.
|
@@ -431,7 +423,7 @@ class QdrantCollectionIndex:
|
|
431
423
|
query_text: Optional original query text for reranking.
|
432
424
|
enable_rerank: Whether to enable reranking if rerank model is configured.
|
433
425
|
return_scores: Whether to return scores with results.
|
434
|
-
|
426
|
+
|
435
427
|
Returns:
|
436
428
|
List of matching database items sorted by similarity score (and reranked if enabled),
|
437
429
|
or list of VectorSearchResult objects if return_scores is True.
|
@@ -439,14 +431,14 @@ class QdrantCollectionIndex:
|
|
439
431
|
if not self._client:
|
440
432
|
# Qdrant not available, return empty results
|
441
433
|
return []
|
442
|
-
|
434
|
+
|
443
435
|
# Prepare query vector
|
444
436
|
prepared_vector = utils.prepare_vector(query_vector, self.vector_size)
|
445
|
-
|
437
|
+
|
446
438
|
try:
|
447
439
|
# Build Qdrant filter
|
448
440
|
qdrant_filter = utils.build_qdrant_filter(filters)
|
449
|
-
|
441
|
+
|
450
442
|
# Perform search
|
451
443
|
results = self._client.query_points(
|
452
444
|
collection_name=self.name,
|
@@ -457,7 +449,7 @@ class QdrantCollectionIndex:
|
|
457
449
|
with_payload=True,
|
458
450
|
with_vectors=False,
|
459
451
|
)
|
460
|
-
|
452
|
+
|
461
453
|
# Get item IDs from results and fetch from database with scores
|
462
454
|
db_items_with_scores = []
|
463
455
|
for result in results.points:
|
@@ -467,23 +459,24 @@ class QdrantCollectionIndex:
|
|
467
459
|
# Set the score on the DatabaseItem itself
|
468
460
|
db_item.score = result.score
|
469
461
|
db_items_with_scores.append((db_item, result.score))
|
470
|
-
|
462
|
+
|
471
463
|
# Apply reranking if enabled and configured
|
472
464
|
if enable_rerank and self.rerank_model and query_text:
|
473
465
|
db_items_with_scores = self._rerank_results(
|
474
|
-
query=query_text,
|
475
|
-
results=db_items_with_scores,
|
476
|
-
top_n=limit
|
466
|
+
query=query_text, results=db_items_with_scores, top_n=limit
|
477
467
|
)
|
478
|
-
|
468
|
+
|
479
469
|
# Return results with or without scores based on return_scores parameter
|
480
470
|
if return_scores:
|
481
|
-
return [
|
471
|
+
return [
|
472
|
+
VectorSearchResult(item=item, score=score)
|
473
|
+
for item, score in db_items_with_scores
|
474
|
+
]
|
482
475
|
else:
|
483
476
|
# Extract just the database items (without scores) for backward compatibility
|
484
477
|
db_items = [item for item, score in db_items_with_scores]
|
485
478
|
return db_items
|
486
|
-
|
479
|
+
|
487
480
|
except Exception:
|
488
481
|
# Vector search failed, return empty results
|
489
482
|
return []
|
@@ -501,7 +494,7 @@ class QdrantCollectionIndex:
|
|
501
494
|
) -> Union[List[DatabaseItem[DatabaseItemType]], List[VectorSearchResult]]:
|
502
495
|
"""
|
503
496
|
Query items from the collection.
|
504
|
-
|
497
|
+
|
505
498
|
Args:
|
506
499
|
query: Search query string.
|
507
500
|
filters: Optional filters to apply.
|
@@ -510,12 +503,12 @@ class QdrantCollectionIndex:
|
|
510
503
|
rerank: Whether to use reranking (requires rerank_model to be configured).
|
511
504
|
query_vector: Optional pre-computed query vector for similarity search.
|
512
505
|
return_scores: Whether to return similarity scores with results (only applies to vector search).
|
513
|
-
|
506
|
+
|
514
507
|
Returns:
|
515
508
|
List of matching database items, or list of VectorSearchResult objects if return_scores is True.
|
516
509
|
"""
|
517
510
|
effective_limit = limit or self.query_settings.limit
|
518
|
-
|
511
|
+
|
519
512
|
# If explicit vector is provided, use it directly
|
520
513
|
if query_vector is not None:
|
521
514
|
return self._vector_search(
|
@@ -527,16 +520,16 @@ class QdrantCollectionIndex:
|
|
527
520
|
enable_rerank=rerank,
|
528
521
|
return_scores=return_scores,
|
529
522
|
)
|
530
|
-
|
523
|
+
|
531
524
|
# If vector=True, use vector search with embedding model
|
532
525
|
if vector:
|
533
526
|
if not query:
|
534
527
|
raise ValueError("Query string is required when vector=True")
|
535
|
-
|
528
|
+
|
536
529
|
embedding_function = self._get_embedding_function()
|
537
530
|
if not embedding_function:
|
538
531
|
raise ValueError("Embedding model not configured for vector search")
|
539
|
-
|
532
|
+
|
540
533
|
try:
|
541
534
|
query_vector = embedding_function(query)
|
542
535
|
return self._vector_search(
|
@@ -550,19 +543,19 @@ class QdrantCollectionIndex:
|
|
550
543
|
)
|
551
544
|
except Exception as e:
|
552
545
|
raise ValueError(f"Failed to generate embedding for query: {e}")
|
553
|
-
|
546
|
+
|
554
547
|
# If rerank=True but vector=False, perform both standard and vector search, then rerank
|
555
548
|
if rerank and query:
|
556
549
|
if not self.rerank_model:
|
557
550
|
raise ValueError("Rerank model not configured")
|
558
|
-
|
551
|
+
|
559
552
|
# Get results from both database and vector search (if possible)
|
560
553
|
db_results = self._database.query(
|
561
554
|
limit=effective_limit,
|
562
555
|
order_by="created_at",
|
563
556
|
ascending=False,
|
564
557
|
)
|
565
|
-
|
558
|
+
|
566
559
|
vector_results = []
|
567
560
|
embedding_function = self._get_embedding_function()
|
568
561
|
if embedding_function:
|
@@ -579,28 +572,26 @@ class QdrantCollectionIndex:
|
|
579
572
|
)
|
580
573
|
except Exception:
|
581
574
|
pass
|
582
|
-
|
575
|
+
|
583
576
|
# Combine and deduplicate results
|
584
577
|
combined_results = []
|
585
578
|
seen_ids = set()
|
586
|
-
|
579
|
+
|
587
580
|
for result in db_results + vector_results:
|
588
581
|
if result.id not in seen_ids:
|
589
582
|
combined_results.append((result, 0.0)) # Score placeholder
|
590
583
|
seen_ids.add(result.id)
|
591
|
-
|
584
|
+
|
592
585
|
# Apply reranking to combined results
|
593
586
|
if combined_results:
|
594
587
|
reranked_results = self._rerank_results(
|
595
|
-
query=query,
|
596
|
-
results=combined_results,
|
597
|
-
top_n=effective_limit
|
588
|
+
query=query, results=combined_results, top_n=effective_limit
|
598
589
|
)
|
599
590
|
# Scores are already set on the DatabaseItem objects by _rerank_results
|
600
591
|
return [item for item, _ in reranked_results]
|
601
|
-
|
592
|
+
|
602
593
|
return [item for item, _ in combined_results]
|
603
|
-
|
594
|
+
|
604
595
|
# Default: fall back to database query
|
605
596
|
return self._database.query(
|
606
597
|
limit=effective_limit,
|
@@ -611,27 +602,24 @@ class QdrantCollectionIndex:
|
|
611
602
|
def delete(self, id: str) -> bool:
|
612
603
|
"""
|
613
604
|
Delete an item by ID.
|
614
|
-
|
605
|
+
|
615
606
|
Args:
|
616
607
|
id: The item ID.
|
617
|
-
|
608
|
+
|
618
609
|
Returns:
|
619
610
|
True if item was deleted, False if not found.
|
620
611
|
"""
|
621
612
|
# Delete from database
|
622
613
|
deleted = self._database.delete(id)
|
623
|
-
|
614
|
+
|
624
615
|
if deleted and self._client:
|
625
616
|
# Delete from Qdrant
|
626
617
|
try:
|
627
|
-
self._client.delete(
|
628
|
-
collection_name=self.name,
|
629
|
-
points_selector=[id]
|
630
|
-
)
|
618
|
+
self._client.delete(collection_name=self.name, points_selector=[id])
|
631
619
|
except Exception:
|
632
620
|
# Vector deletion failed, but item was removed from database
|
633
621
|
pass
|
634
|
-
|
622
|
+
|
635
623
|
return deleted
|
636
624
|
|
637
625
|
def count(
|
@@ -640,26 +628,28 @@ class QdrantCollectionIndex:
|
|
640
628
|
) -> int:
|
641
629
|
"""
|
642
630
|
Count items matching the filters.
|
643
|
-
|
631
|
+
|
644
632
|
Args:
|
645
633
|
filters: Optional filters to apply.
|
646
|
-
|
634
|
+
|
647
635
|
Returns:
|
648
636
|
Number of matching items.
|
649
637
|
"""
|
650
638
|
if not self._client:
|
651
639
|
# Use database count
|
652
640
|
from ....sql.types import QueryFilter, QueryCondition
|
653
|
-
|
641
|
+
|
654
642
|
query_filter = None
|
655
643
|
if filters:
|
656
644
|
conditions = [
|
657
|
-
QueryCondition(
|
645
|
+
QueryCondition(
|
646
|
+
field="filters", operator="contains", value=json.dumps(filters)
|
647
|
+
)
|
658
648
|
]
|
659
649
|
query_filter = QueryFilter(conditions=conditions)
|
660
|
-
|
650
|
+
|
661
651
|
return self._database.count(query_filter)
|
662
|
-
|
652
|
+
|
663
653
|
try:
|
664
654
|
# Use Qdrant count
|
665
655
|
qdrant_filter = utils.build_qdrant_filter(filters)
|
@@ -676,38 +666,36 @@ class QdrantCollectionIndex:
|
|
676
666
|
def clear(self) -> int:
|
677
667
|
"""
|
678
668
|
Clear all items from the index.
|
679
|
-
|
669
|
+
|
680
670
|
Returns:
|
681
671
|
Number of items deleted.
|
682
672
|
"""
|
683
673
|
count = self._database.clear()
|
684
|
-
|
674
|
+
|
685
675
|
if self._client:
|
686
676
|
# Clear Qdrant collection by recreating it
|
687
677
|
try:
|
688
678
|
utils.create_collection_if_not_exists(
|
689
|
-
self._client,
|
690
|
-
self.name,
|
691
|
-
self.settings
|
679
|
+
self._client, self.name, self.settings
|
692
680
|
)
|
693
681
|
except Exception:
|
694
682
|
pass
|
695
|
-
|
683
|
+
|
696
684
|
return count
|
697
685
|
|
698
686
|
def get_vector(self, id: str) -> Optional[List[float]]:
|
699
687
|
"""
|
700
688
|
Get the vector for a specific item by ID.
|
701
|
-
|
689
|
+
|
702
690
|
Args:
|
703
691
|
id: The item ID.
|
704
|
-
|
692
|
+
|
705
693
|
Returns:
|
706
694
|
The vector or None if not found.
|
707
695
|
"""
|
708
696
|
if not self._client:
|
709
697
|
return None
|
710
|
-
|
698
|
+
|
711
699
|
try:
|
712
700
|
points = self._client.retrieve(
|
713
701
|
collection_name=self.name,
|
@@ -715,16 +703,16 @@ class QdrantCollectionIndex:
|
|
715
703
|
with_payload=False,
|
716
704
|
with_vectors=True,
|
717
705
|
)
|
718
|
-
|
706
|
+
|
719
707
|
if not points:
|
720
708
|
return None
|
721
|
-
|
709
|
+
|
722
710
|
vector = points[0].vector
|
723
711
|
if isinstance(vector, dict):
|
724
712
|
# Handle named vectors if used
|
725
713
|
return list(vector.values())[0] if vector else None
|
726
714
|
return vector
|
727
|
-
|
715
|
+
|
728
716
|
except Exception:
|
729
717
|
return None
|
730
718
|
|
@@ -732,4 +720,4 @@ class QdrantCollectionIndex:
|
|
732
720
|
"""String representation of the index."""
|
733
721
|
location = str(self.path) if self.path else "memory"
|
734
722
|
vector_available = "yes" if self._client else "no"
|
735
|
-
return f"<QdrantCollectionIndex name='{self.name}' location='{location}' vector_size={self.vector_size} qdrant_available={vector_available}>"
|
723
|
+
return f"<QdrantCollectionIndex name='{self.name}' location='{location}' vector_size={self.vector_size} qdrant_available={vector_available}>"
|