agno 2.2.11__py3-none-any.whl → 2.2.12__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.
- agno/agent/agent.py +62 -47
- agno/db/mysql/mysql.py +1 -1
- agno/db/singlestore/singlestore.py +1 -1
- agno/db/sqlite/async_sqlite.py +1 -1
- agno/db/sqlite/sqlite.py +1 -1
- agno/filters.py +354 -0
- agno/knowledge/knowledge.py +43 -22
- agno/os/interfaces/slack/router.py +53 -33
- agno/os/interfaces/slack/slack.py +9 -1
- agno/os/router.py +25 -1
- agno/run/base.py +3 -2
- agno/session/agent.py +10 -5
- agno/team/team.py +44 -17
- agno/utils/agent.py +22 -17
- agno/utils/gemini.py +15 -5
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/print_response/agent.py +5 -4
- agno/utils/print_response/team.py +5 -4
- agno/vectordb/base.py +2 -4
- agno/vectordb/cassandra/cassandra.py +12 -5
- agno/vectordb/chroma/chromadb.py +10 -4
- agno/vectordb/clickhouse/clickhousedb.py +12 -4
- agno/vectordb/couchbase/couchbase.py +12 -3
- agno/vectordb/lancedb/lance_db.py +69 -144
- agno/vectordb/langchaindb/langchaindb.py +13 -4
- agno/vectordb/lightrag/lightrag.py +8 -3
- agno/vectordb/llamaindex/llamaindexdb.py +10 -4
- agno/vectordb/milvus/milvus.py +16 -5
- agno/vectordb/mongodb/mongodb.py +14 -3
- agno/vectordb/pgvector/pgvector.py +73 -15
- agno/vectordb/pineconedb/pineconedb.py +6 -2
- agno/vectordb/qdrant/qdrant.py +25 -13
- agno/vectordb/redis/redisdb.py +37 -30
- agno/vectordb/singlestore/singlestore.py +9 -4
- agno/vectordb/surrealdb/surrealdb.py +13 -3
- agno/vectordb/upstashdb/upstashdb.py +8 -5
- agno/vectordb/weaviate/weaviate.py +29 -12
- agno/workflow/workflow.py +13 -7
- {agno-2.2.11.dist-info → agno-2.2.12.dist-info}/METADATA +1 -1
- {agno-2.2.11.dist-info → agno-2.2.12.dist-info}/RECORD +44 -43
- {agno-2.2.11.dist-info → agno-2.2.12.dist-info}/WHEEL +0 -0
- {agno-2.2.11.dist-info → agno-2.2.12.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.11.dist-info → agno-2.2.12.dist-info}/top_level.txt +0 -0
agno/vectordb/mongodb/mongodb.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import time
|
|
3
|
-
from typing import Any, Dict, List, Optional
|
|
3
|
+
from typing import Any, Dict, List, Optional, Union
|
|
4
4
|
|
|
5
5
|
from bson import ObjectId
|
|
6
6
|
|
|
7
|
+
from agno.filters import FilterExpr
|
|
7
8
|
from agno.knowledge.document import Document
|
|
8
9
|
from agno.knowledge.embedder import Embedder
|
|
9
10
|
from agno.utils.log import log_debug, log_info, log_warning, logger
|
|
@@ -585,9 +586,16 @@ class MongoDb(VectorDb):
|
|
|
585
586
|
return True
|
|
586
587
|
|
|
587
588
|
def search(
|
|
588
|
-
self,
|
|
589
|
+
self,
|
|
590
|
+
query: str,
|
|
591
|
+
limit: int = 5,
|
|
592
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
|
|
593
|
+
min_score: float = 0.0,
|
|
589
594
|
) -> List[Document]:
|
|
590
595
|
"""Search for documents using vector similarity."""
|
|
596
|
+
if isinstance(filters, List):
|
|
597
|
+
log_warning("Filters Expressions are not supported in MongoDB. No filters will be applied.")
|
|
598
|
+
filters = None
|
|
591
599
|
if self.search_type == SearchType.hybrid:
|
|
592
600
|
return self.hybrid_search(query, limit=limit, filters=filters)
|
|
593
601
|
|
|
@@ -1153,9 +1161,12 @@ class MongoDb(VectorDb):
|
|
|
1153
1161
|
logger.error(f"Error upserting document '{document.name}' asynchronously: {e}")
|
|
1154
1162
|
|
|
1155
1163
|
async def async_search(
|
|
1156
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
1164
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
1157
1165
|
) -> List[Document]:
|
|
1158
1166
|
"""Search for documents asynchronously."""
|
|
1167
|
+
if isinstance(filters, List):
|
|
1168
|
+
log_warning("Filters Expressions are not supported in MongoDB. No filters will be applied.")
|
|
1169
|
+
filters = None
|
|
1159
1170
|
query_embedding = self.embedder.get_embedding(query)
|
|
1160
1171
|
if query_embedding is None:
|
|
1161
1172
|
logger.error(f"Failed to generate embedding for query: {query}")
|
|
@@ -6,14 +6,15 @@ from typing import Any, Dict, List, Optional, Union, cast
|
|
|
6
6
|
from agno.utils.string import generate_id
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
|
-
from sqlalchemy import update
|
|
9
|
+
from sqlalchemy import and_, not_, or_, update
|
|
10
10
|
from sqlalchemy.dialects import postgresql
|
|
11
11
|
from sqlalchemy.engine import Engine, create_engine
|
|
12
12
|
from sqlalchemy.inspection import inspect
|
|
13
13
|
from sqlalchemy.orm import Session, scoped_session, sessionmaker
|
|
14
14
|
from sqlalchemy.schema import Column, Index, MetaData, Table
|
|
15
|
+
from sqlalchemy.sql.elements import ColumnElement
|
|
15
16
|
from sqlalchemy.sql.expression import bindparam, desc, func, select, text
|
|
16
|
-
from sqlalchemy.types import DateTime, String
|
|
17
|
+
from sqlalchemy.types import DateTime, Integer, String
|
|
17
18
|
|
|
18
19
|
except ImportError:
|
|
19
20
|
raise ImportError("`sqlalchemy` not installed. Please install using `pip install sqlalchemy psycopg`")
|
|
@@ -23,6 +24,7 @@ try:
|
|
|
23
24
|
except ImportError:
|
|
24
25
|
raise ImportError("`pgvector` not installed. Please install using `pip install pgvector`")
|
|
25
26
|
|
|
27
|
+
from agno.filters import FilterExpr
|
|
26
28
|
from agno.knowledge.document import Document
|
|
27
29
|
from agno.knowledge.embedder import Embedder
|
|
28
30
|
from agno.knowledge.reranker.base import Reranker
|
|
@@ -680,14 +682,16 @@ class PgVector(VectorDb):
|
|
|
680
682
|
logger.error(f"Error updating metadata for document {content_id}: {e}")
|
|
681
683
|
raise
|
|
682
684
|
|
|
683
|
-
def search(
|
|
685
|
+
def search(
|
|
686
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
687
|
+
) -> List[Document]:
|
|
684
688
|
"""
|
|
685
689
|
Perform a search based on the configured search type.
|
|
686
690
|
|
|
687
691
|
Args:
|
|
688
692
|
query (str): The search query.
|
|
689
693
|
limit (int): Maximum number of results to return.
|
|
690
|
-
filters (Optional[Dict[str, Any]]): Filters to apply to the search.
|
|
694
|
+
filters (Optional[Union[Dict[str, Any], List[FilterExpr]]]): Filters to apply to the search.
|
|
691
695
|
|
|
692
696
|
Returns:
|
|
693
697
|
List[Document]: List of matching documents.
|
|
@@ -703,19 +707,42 @@ class PgVector(VectorDb):
|
|
|
703
707
|
return []
|
|
704
708
|
|
|
705
709
|
async def async_search(
|
|
706
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
710
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
707
711
|
) -> List[Document]:
|
|
708
712
|
"""Search asynchronously by running in a thread."""
|
|
709
713
|
return await asyncio.to_thread(self.search, query, limit, filters)
|
|
710
714
|
|
|
711
|
-
def
|
|
715
|
+
def _dsl_to_sqlalchemy(self, filter_expr, table) -> ColumnElement[bool]:
|
|
716
|
+
op = filter_expr["op"]
|
|
717
|
+
|
|
718
|
+
if op == "EQ":
|
|
719
|
+
return table.c.meta_data[filter_expr["key"]].astext == str(filter_expr["value"])
|
|
720
|
+
elif op == "IN":
|
|
721
|
+
# Postgres JSONB array containment
|
|
722
|
+
return table.c.meta_data[filter_expr["key"]].astext.in_([str(v) for v in filter_expr["values"]])
|
|
723
|
+
elif op == "GT":
|
|
724
|
+
return table.c.meta_data[filter_expr["key"]].astext.cast(Integer) > filter_expr["value"]
|
|
725
|
+
elif op == "LT":
|
|
726
|
+
return table.c.meta_data[filter_expr["key"]].astext.cast(Integer) < filter_expr["value"]
|
|
727
|
+
elif op == "NOT":
|
|
728
|
+
return not_(self._dsl_to_sqlalchemy(filter_expr["condition"], table))
|
|
729
|
+
elif op == "AND":
|
|
730
|
+
return and_(*[self._dsl_to_sqlalchemy(cond, table) for cond in filter_expr["conditions"]])
|
|
731
|
+
elif op == "OR":
|
|
732
|
+
return or_(*[self._dsl_to_sqlalchemy(cond, table) for cond in filter_expr["conditions"]])
|
|
733
|
+
else:
|
|
734
|
+
raise ValueError(f"Unknown filter operator: {op}")
|
|
735
|
+
|
|
736
|
+
def vector_search(
|
|
737
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
738
|
+
) -> List[Document]:
|
|
712
739
|
"""
|
|
713
740
|
Perform a vector similarity search.
|
|
714
741
|
|
|
715
742
|
Args:
|
|
716
743
|
query (str): The search query.
|
|
717
744
|
limit (int): Maximum number of results to return.
|
|
718
|
-
filters (Optional[Dict[str, Any]]): Filters to apply to the search.
|
|
745
|
+
filters (Optional[Union[Dict[str, Any], List[FilterExpr]]]): Filters to apply to the search.
|
|
719
746
|
|
|
720
747
|
Returns:
|
|
721
748
|
List[Document]: List of matching documents.
|
|
@@ -742,7 +769,17 @@ class PgVector(VectorDb):
|
|
|
742
769
|
|
|
743
770
|
# Apply filters if provided
|
|
744
771
|
if filters is not None:
|
|
745
|
-
|
|
772
|
+
# Handle dict filters
|
|
773
|
+
if isinstance(filters, dict):
|
|
774
|
+
stmt = stmt.where(self.table.c.meta_data.contains(filters))
|
|
775
|
+
# Handle FilterExpr DSL
|
|
776
|
+
else:
|
|
777
|
+
# Convert each DSL expression to SQLAlchemy and AND them together
|
|
778
|
+
sqlalchemy_conditions = [
|
|
779
|
+
self._dsl_to_sqlalchemy(f.to_dict() if hasattr(f, "to_dict") else f, self.table)
|
|
780
|
+
for f in filters
|
|
781
|
+
]
|
|
782
|
+
stmt = stmt.where(and_(*sqlalchemy_conditions))
|
|
746
783
|
|
|
747
784
|
# Order the results based on the distance metric
|
|
748
785
|
if self.distance == Distance.l2:
|
|
@@ -815,14 +852,16 @@ class PgVector(VectorDb):
|
|
|
815
852
|
processed_words = [word + "*" for word in words]
|
|
816
853
|
return " ".join(processed_words)
|
|
817
854
|
|
|
818
|
-
def keyword_search(
|
|
855
|
+
def keyword_search(
|
|
856
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
857
|
+
) -> List[Document]:
|
|
819
858
|
"""
|
|
820
859
|
Perform a keyword search on the 'content' column.
|
|
821
860
|
|
|
822
861
|
Args:
|
|
823
862
|
query (str): The search query.
|
|
824
863
|
limit (int): Maximum number of results to return.
|
|
825
|
-
filters (Optional[Dict[str, Any]]): Filters to apply to the search.
|
|
864
|
+
filters (Optional[Union[Dict[str, Any], List[FilterExpr]]]): Filters to apply to the search.
|
|
826
865
|
|
|
827
866
|
Returns:
|
|
828
867
|
List[Document]: List of matching documents.
|
|
@@ -851,8 +890,17 @@ class PgVector(VectorDb):
|
|
|
851
890
|
|
|
852
891
|
# Apply filters if provided
|
|
853
892
|
if filters is not None:
|
|
854
|
-
#
|
|
855
|
-
|
|
893
|
+
# Handle dict filters
|
|
894
|
+
if isinstance(filters, dict):
|
|
895
|
+
stmt = stmt.where(self.table.c.meta_data.contains(filters))
|
|
896
|
+
# Handle FilterExpr DSL
|
|
897
|
+
else:
|
|
898
|
+
# Convert each DSL expression to SQLAlchemy and AND them together
|
|
899
|
+
sqlalchemy_conditions = [
|
|
900
|
+
self._dsl_to_sqlalchemy(f.to_dict() if hasattr(f, "to_dict") else f, self.table)
|
|
901
|
+
for f in filters
|
|
902
|
+
]
|
|
903
|
+
stmt = stmt.where(and_(*sqlalchemy_conditions))
|
|
856
904
|
|
|
857
905
|
# Order by the relevance rank
|
|
858
906
|
stmt = stmt.order_by(text_rank.desc())
|
|
@@ -898,7 +946,7 @@ class PgVector(VectorDb):
|
|
|
898
946
|
self,
|
|
899
947
|
query: str,
|
|
900
948
|
limit: int = 5,
|
|
901
|
-
filters: Optional[Dict[str, Any]] = None,
|
|
949
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
|
|
902
950
|
) -> List[Document]:
|
|
903
951
|
"""
|
|
904
952
|
Perform a hybrid search combining vector similarity and full-text search.
|
|
@@ -906,7 +954,7 @@ class PgVector(VectorDb):
|
|
|
906
954
|
Args:
|
|
907
955
|
query (str): The search query.
|
|
908
956
|
limit (int): Maximum number of results to return.
|
|
909
|
-
filters (Optional[Dict[str, Any]]): Filters to apply to the search.
|
|
957
|
+
filters (Optional[Union[Dict[str, Any], List[FilterExpr]]]): Filters to apply to the search.
|
|
910
958
|
|
|
911
959
|
Returns:
|
|
912
960
|
List[Document]: List of matching documents.
|
|
@@ -973,7 +1021,17 @@ class PgVector(VectorDb):
|
|
|
973
1021
|
|
|
974
1022
|
# Apply filters if provided
|
|
975
1023
|
if filters is not None:
|
|
976
|
-
|
|
1024
|
+
# Handle dict filters
|
|
1025
|
+
if isinstance(filters, dict):
|
|
1026
|
+
stmt = stmt.where(self.table.c.meta_data.contains(filters))
|
|
1027
|
+
# Handle FilterExpr DSL
|
|
1028
|
+
else:
|
|
1029
|
+
# Convert each DSL expression to SQLAlchemy and AND them together
|
|
1030
|
+
sqlalchemy_conditions = [
|
|
1031
|
+
self._dsl_to_sqlalchemy(f.to_dict() if hasattr(f, "to_dict") else f, self.table)
|
|
1032
|
+
for f in filters
|
|
1033
|
+
]
|
|
1034
|
+
stmt = stmt.where(and_(*sqlalchemy_conditions))
|
|
977
1035
|
|
|
978
1036
|
# Order the results by the hybrid score in descending order
|
|
979
1037
|
stmt = stmt.order_by(desc("hybrid_score"))
|
|
@@ -22,6 +22,7 @@ except ImportError:
|
|
|
22
22
|
raise ImportError("The `pinecone` package is not installed, please install using `pip install pinecone`.")
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
from agno.filters import FilterExpr
|
|
25
26
|
from agno.knowledge.document import Document
|
|
26
27
|
from agno.knowledge.embedder import Embedder
|
|
27
28
|
from agno.knowledge.reranker.base import Reranker
|
|
@@ -474,7 +475,7 @@ class PineconeDb(VectorDb):
|
|
|
474
475
|
self,
|
|
475
476
|
query: str,
|
|
476
477
|
limit: int = 5,
|
|
477
|
-
filters: Optional[Dict[str,
|
|
478
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
|
|
478
479
|
namespace: Optional[str] = None,
|
|
479
480
|
include_values: Optional[bool] = None,
|
|
480
481
|
) -> List[Document]:
|
|
@@ -492,6 +493,9 @@ class PineconeDb(VectorDb):
|
|
|
492
493
|
List[Document]: The list of matching documents.
|
|
493
494
|
|
|
494
495
|
"""
|
|
496
|
+
if isinstance(filters, List):
|
|
497
|
+
log_warning("Filters Expressions are not supported in PineconeDB. No filters will be applied.")
|
|
498
|
+
filters = None
|
|
495
499
|
dense_embedding = self.embedder.get_embedding(query)
|
|
496
500
|
|
|
497
501
|
if self.use_hybrid_search:
|
|
@@ -540,7 +544,7 @@ class PineconeDb(VectorDb):
|
|
|
540
544
|
self,
|
|
541
545
|
query: str,
|
|
542
546
|
limit: int = 5,
|
|
543
|
-
filters: Optional[Dict[str,
|
|
547
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
|
|
544
548
|
namespace: Optional[str] = None,
|
|
545
549
|
include_values: Optional[bool] = None,
|
|
546
550
|
) -> List[Document]:
|
agno/vectordb/qdrant/qdrant.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from hashlib import md5
|
|
2
|
-
from typing import Any, Dict, List, Optional
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
|
3
3
|
|
|
4
4
|
try:
|
|
5
5
|
from qdrant_client import AsyncQdrantClient, QdrantClient # noqa: F401
|
|
@@ -9,6 +9,7 @@ except ImportError:
|
|
|
9
9
|
"The `qdrant-client` package is not installed. Please install it via `pip install qdrant-client`."
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
+
from agno.filters import FilterExpr
|
|
12
13
|
from agno.knowledge.document import Document
|
|
13
14
|
from agno.knowledge.embedder import Embedder
|
|
14
15
|
from agno.knowledge.reranker.base import Reranker
|
|
@@ -528,7 +529,9 @@ class Qdrant(VectorDb):
|
|
|
528
529
|
log_debug("Redirecting the async request to async_insert")
|
|
529
530
|
await self.async_insert(content_hash=content_hash, documents=documents, filters=filters)
|
|
530
531
|
|
|
531
|
-
def search(
|
|
532
|
+
def search(
|
|
533
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
534
|
+
) -> List[Document]:
|
|
532
535
|
"""
|
|
533
536
|
Search for documents in the collection.
|
|
534
537
|
|
|
@@ -537,28 +540,37 @@ class Qdrant(VectorDb):
|
|
|
537
540
|
limit (int): Number of search results to return
|
|
538
541
|
filters (Optional[Dict[str, Any]]): Filters to apply while searching
|
|
539
542
|
"""
|
|
543
|
+
|
|
544
|
+
if isinstance(filters, List):
|
|
545
|
+
log_warning("Filters Expressions are not supported in Qdrant. No filters will be applied.")
|
|
546
|
+
filters = None
|
|
547
|
+
|
|
540
548
|
filters = self._format_filters(filters or {}) # type: ignore
|
|
541
549
|
if self.search_type == SearchType.vector:
|
|
542
|
-
results = self._run_vector_search_sync(query, limit, filters)
|
|
550
|
+
results = self._run_vector_search_sync(query, limit, filters) # type: ignore
|
|
543
551
|
elif self.search_type == SearchType.keyword:
|
|
544
|
-
results = self._run_keyword_search_sync(query, limit, filters)
|
|
552
|
+
results = self._run_keyword_search_sync(query, limit, filters) # type: ignore
|
|
545
553
|
elif self.search_type == SearchType.hybrid:
|
|
546
|
-
results = self._run_hybrid_search_sync(query, limit, filters)
|
|
554
|
+
results = self._run_hybrid_search_sync(query, limit, filters) # type: ignore
|
|
547
555
|
else:
|
|
548
556
|
raise ValueError(f"Unsupported search type: {self.search_type}")
|
|
549
557
|
|
|
550
558
|
return self._build_search_results(results, query)
|
|
551
559
|
|
|
552
560
|
async def async_search(
|
|
553
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
561
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
554
562
|
) -> List[Document]:
|
|
563
|
+
if isinstance(filters, List):
|
|
564
|
+
log_warning("Filters Expressions are not supported in Qdrant. No filters will be applied.")
|
|
565
|
+
filters = None
|
|
566
|
+
|
|
555
567
|
filters = self._format_filters(filters or {}) # type: ignore
|
|
556
568
|
if self.search_type == SearchType.vector:
|
|
557
|
-
results = await self._run_vector_search_async(query, limit, filters)
|
|
569
|
+
results = await self._run_vector_search_async(query, limit, filters) # type: ignore
|
|
558
570
|
elif self.search_type == SearchType.keyword:
|
|
559
|
-
results = await self._run_keyword_search_async(query, limit, filters)
|
|
571
|
+
results = await self._run_keyword_search_async(query, limit, filters) # type: ignore
|
|
560
572
|
elif self.search_type == SearchType.hybrid:
|
|
561
|
-
results = await self._run_hybrid_search_async(query, limit, filters)
|
|
573
|
+
results = await self._run_hybrid_search_async(query, limit, filters) # type: ignore
|
|
562
574
|
else:
|
|
563
575
|
raise ValueError(f"Unsupported search type: {self.search_type}")
|
|
564
576
|
|
|
@@ -568,7 +580,7 @@ class Qdrant(VectorDb):
|
|
|
568
580
|
self,
|
|
569
581
|
query: str,
|
|
570
582
|
limit: int,
|
|
571
|
-
filters: Optional[Dict[str, Any]],
|
|
583
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
|
|
572
584
|
) -> List[models.ScoredPoint]:
|
|
573
585
|
dense_embedding = self.embedder.get_embedding(query)
|
|
574
586
|
sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
|
|
@@ -594,7 +606,7 @@ class Qdrant(VectorDb):
|
|
|
594
606
|
self,
|
|
595
607
|
query: str,
|
|
596
608
|
limit: int,
|
|
597
|
-
filters: Optional[Dict[str, Any]],
|
|
609
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
|
|
598
610
|
) -> List[models.ScoredPoint]:
|
|
599
611
|
dense_embedding = self.embedder.get_embedding(query)
|
|
600
612
|
|
|
@@ -625,7 +637,7 @@ class Qdrant(VectorDb):
|
|
|
625
637
|
self,
|
|
626
638
|
query: str,
|
|
627
639
|
limit: int,
|
|
628
|
-
filters: Optional[Dict[str, Any]],
|
|
640
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
|
|
629
641
|
) -> List[models.ScoredPoint]:
|
|
630
642
|
sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
|
|
631
643
|
call = self.client.query_points(
|
|
@@ -692,7 +704,7 @@ class Qdrant(VectorDb):
|
|
|
692
704
|
self,
|
|
693
705
|
query: str,
|
|
694
706
|
limit: int,
|
|
695
|
-
filters: Optional[Dict[str, Any]],
|
|
707
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]],
|
|
696
708
|
) -> List[models.ScoredPoint]:
|
|
697
709
|
dense_embedding = self.embedder.get_embedding(query)
|
|
698
710
|
sparse_embedding = next(iter(self.sparse_encoder.embed([query]))).as_object()
|
agno/vectordb/redis/redisdb.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Any, Dict, List, Optional
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
|
3
3
|
|
|
4
4
|
try:
|
|
5
5
|
from redis import Redis
|
|
@@ -12,9 +12,10 @@ try:
|
|
|
12
12
|
except ImportError:
|
|
13
13
|
raise ImportError("`redis` and `redisvl` not installed. Please install using `pip install redis redisvl`")
|
|
14
14
|
|
|
15
|
+
from agno.filters import FilterExpr
|
|
15
16
|
from agno.knowledge.document import Document
|
|
16
17
|
from agno.knowledge.embedder import Embedder
|
|
17
|
-
from agno.utils.log import log_debug, log_info,
|
|
18
|
+
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
18
19
|
from agno.utils.string import hash_string_sha256
|
|
19
20
|
from agno.vectordb.base import VectorDb
|
|
20
21
|
from agno.vectordb.distance import Distance
|
|
@@ -167,7 +168,7 @@ class RedisDB(VectorDb):
|
|
|
167
168
|
else:
|
|
168
169
|
log_debug(f"Redis index already exists: {self.index_name}")
|
|
169
170
|
except Exception as e:
|
|
170
|
-
|
|
171
|
+
log_error(f"Error creating Redis index: {e}")
|
|
171
172
|
raise
|
|
172
173
|
|
|
173
174
|
async def async_create(self) -> None:
|
|
@@ -180,7 +181,7 @@ class RedisDB(VectorDb):
|
|
|
180
181
|
if "already exists" in str(e).lower():
|
|
181
182
|
log_debug(f"Redis index already exists: {self.index_name}")
|
|
182
183
|
else:
|
|
183
|
-
|
|
184
|
+
log_error(f"Error creating Redis index: {e}")
|
|
184
185
|
raise
|
|
185
186
|
|
|
186
187
|
def doc_exists(self, document: Document) -> bool:
|
|
@@ -189,7 +190,7 @@ class RedisDB(VectorDb):
|
|
|
189
190
|
doc_id = document.id or hash_string_sha256(document.content)
|
|
190
191
|
return self.id_exists(doc_id)
|
|
191
192
|
except Exception as e:
|
|
192
|
-
|
|
193
|
+
log_error(f"Error checking if document exists: {e}")
|
|
193
194
|
return False
|
|
194
195
|
|
|
195
196
|
async def async_doc_exists(self, document: Document) -> bool:
|
|
@@ -206,7 +207,7 @@ class RedisDB(VectorDb):
|
|
|
206
207
|
results = await async_index.query(query)
|
|
207
208
|
return len(results) > 0
|
|
208
209
|
except Exception as e:
|
|
209
|
-
|
|
210
|
+
log_error(f"Error checking if document exists: {e}")
|
|
210
211
|
return False
|
|
211
212
|
|
|
212
213
|
def name_exists(self, name: str) -> bool:
|
|
@@ -221,7 +222,7 @@ class RedisDB(VectorDb):
|
|
|
221
222
|
results = self.index.query(query)
|
|
222
223
|
return len(results) > 0
|
|
223
224
|
except Exception as e:
|
|
224
|
-
|
|
225
|
+
log_error(f"Error checking if name exists: {e}")
|
|
225
226
|
return False
|
|
226
227
|
|
|
227
228
|
async def async_name_exists(self, name: str) -> bool: # type: ignore[override]
|
|
@@ -237,7 +238,7 @@ class RedisDB(VectorDb):
|
|
|
237
238
|
results = await async_index.query(query)
|
|
238
239
|
return len(results) > 0
|
|
239
240
|
except Exception as e:
|
|
240
|
-
|
|
241
|
+
log_error(f"Error checking if name exists: {e}")
|
|
241
242
|
return False
|
|
242
243
|
|
|
243
244
|
def id_exists(self, id: str) -> bool:
|
|
@@ -252,7 +253,7 @@ class RedisDB(VectorDb):
|
|
|
252
253
|
results = self.index.query(query)
|
|
253
254
|
return len(results) > 0
|
|
254
255
|
except Exception as e:
|
|
255
|
-
|
|
256
|
+
log_error(f"Error checking if ID exists: {e}")
|
|
256
257
|
return False
|
|
257
258
|
|
|
258
259
|
def content_hash_exists(self, content_hash: str) -> bool:
|
|
@@ -267,7 +268,7 @@ class RedisDB(VectorDb):
|
|
|
267
268
|
results = self.index.query(query)
|
|
268
269
|
return len(results) > 0
|
|
269
270
|
except Exception as e:
|
|
270
|
-
|
|
271
|
+
log_error(f"Error checking if content hash exists: {e}")
|
|
271
272
|
return False
|
|
272
273
|
|
|
273
274
|
def _parse_redis_hash(self, doc: Document):
|
|
@@ -314,7 +315,7 @@ class RedisDB(VectorDb):
|
|
|
314
315
|
self.index.load(parsed_documents, id_field="id")
|
|
315
316
|
log_debug(f"Inserted {len(documents)} documents with content_hash: {content_hash}")
|
|
316
317
|
except Exception as e:
|
|
317
|
-
|
|
318
|
+
log_error(f"Error inserting documents: {e}")
|
|
318
319
|
raise
|
|
319
320
|
|
|
320
321
|
async def async_insert(
|
|
@@ -334,7 +335,7 @@ class RedisDB(VectorDb):
|
|
|
334
335
|
await async_index.load(parsed_documents, id_field="id")
|
|
335
336
|
log_debug(f"Inserted {len(documents)} documents with content_hash: {content_hash}")
|
|
336
337
|
except Exception as e:
|
|
337
|
-
|
|
338
|
+
log_error(f"Error inserting documents: {e}")
|
|
338
339
|
raise
|
|
339
340
|
|
|
340
341
|
def upsert_available(self) -> bool:
|
|
@@ -368,7 +369,7 @@ class RedisDB(VectorDb):
|
|
|
368
369
|
# Insert new docs
|
|
369
370
|
self.insert(content_hash, documents, filters)
|
|
370
371
|
except Exception as e:
|
|
371
|
-
|
|
372
|
+
log_error(f"Error upserting documents: {e}")
|
|
372
373
|
raise
|
|
373
374
|
|
|
374
375
|
async def async_upsert(
|
|
@@ -400,11 +401,17 @@ class RedisDB(VectorDb):
|
|
|
400
401
|
# Insert new docs
|
|
401
402
|
await self.async_insert(content_hash, documents, filters)
|
|
402
403
|
except Exception as e:
|
|
403
|
-
|
|
404
|
+
log_error(f"Error upserting documents: {e}")
|
|
404
405
|
raise
|
|
405
406
|
|
|
406
|
-
def search(
|
|
407
|
+
def search(
|
|
408
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
409
|
+
) -> List[Document]:
|
|
407
410
|
"""Search for documents using the specified search type."""
|
|
411
|
+
|
|
412
|
+
if filters and isinstance(filters, List):
|
|
413
|
+
log_warning("Filters Expressions are not supported in Redis. No filters will be applied.")
|
|
414
|
+
filters = None
|
|
408
415
|
try:
|
|
409
416
|
if self.search_type == SearchType.vector:
|
|
410
417
|
return self.vector_search(query, limit)
|
|
@@ -415,11 +422,11 @@ class RedisDB(VectorDb):
|
|
|
415
422
|
else:
|
|
416
423
|
raise ValueError(f"Unsupported search type: {self.search_type}")
|
|
417
424
|
except Exception as e:
|
|
418
|
-
|
|
425
|
+
log_error(f"Error in search: {e}")
|
|
419
426
|
return []
|
|
420
427
|
|
|
421
428
|
async def async_search(
|
|
422
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
429
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
423
430
|
) -> List[Document]:
|
|
424
431
|
"""Async version of search method."""
|
|
425
432
|
return await asyncio.to_thread(self.search, query, limit, filters)
|
|
@@ -448,7 +455,7 @@ class RedisDB(VectorDb):
|
|
|
448
455
|
|
|
449
456
|
return documents
|
|
450
457
|
except Exception as e:
|
|
451
|
-
|
|
458
|
+
log_error(f"Error in vector search: {e}")
|
|
452
459
|
return []
|
|
453
460
|
|
|
454
461
|
def keyword_search(self, query: str, limit: int = 5) -> List[Document]:
|
|
@@ -471,7 +478,7 @@ class RedisDB(VectorDb):
|
|
|
471
478
|
|
|
472
479
|
return documents
|
|
473
480
|
except Exception as e:
|
|
474
|
-
|
|
481
|
+
log_error(f"Error in keyword search: {e}")
|
|
475
482
|
return []
|
|
476
483
|
|
|
477
484
|
def hybrid_search(self, query: str, limit: int = 5) -> List[Document]:
|
|
@@ -500,7 +507,7 @@ class RedisDB(VectorDb):
|
|
|
500
507
|
|
|
501
508
|
return documents
|
|
502
509
|
except Exception as e:
|
|
503
|
-
|
|
510
|
+
log_error(f"Error in hybrid search: {e}")
|
|
504
511
|
return []
|
|
505
512
|
|
|
506
513
|
def drop(self) -> bool: # type: ignore[override]
|
|
@@ -510,7 +517,7 @@ class RedisDB(VectorDb):
|
|
|
510
517
|
log_debug(f"Deleted Redis index: {self.index_name}")
|
|
511
518
|
return True
|
|
512
519
|
except Exception as e:
|
|
513
|
-
|
|
520
|
+
log_error(f"Error dropping Redis index: {e}")
|
|
514
521
|
return False
|
|
515
522
|
|
|
516
523
|
async def async_drop(self) -> None:
|
|
@@ -520,7 +527,7 @@ class RedisDB(VectorDb):
|
|
|
520
527
|
await async_index.delete(drop=True)
|
|
521
528
|
log_debug(f"Deleted Redis index: {self.index_name}")
|
|
522
529
|
except Exception as e:
|
|
523
|
-
|
|
530
|
+
log_error(f"Error dropping Redis index: {e}")
|
|
524
531
|
raise
|
|
525
532
|
|
|
526
533
|
def exists(self) -> bool:
|
|
@@ -528,7 +535,7 @@ class RedisDB(VectorDb):
|
|
|
528
535
|
try:
|
|
529
536
|
return self.index.exists()
|
|
530
537
|
except Exception as e:
|
|
531
|
-
|
|
538
|
+
log_error(f"Error checking if index exists: {e}")
|
|
532
539
|
return False
|
|
533
540
|
|
|
534
541
|
async def async_exists(self) -> bool:
|
|
@@ -537,7 +544,7 @@ class RedisDB(VectorDb):
|
|
|
537
544
|
async_index = await self._get_async_index()
|
|
538
545
|
return await async_index.exists()
|
|
539
546
|
except Exception as e:
|
|
540
|
-
|
|
547
|
+
log_error(f"Error checking if index exists: {e}")
|
|
541
548
|
return False
|
|
542
549
|
|
|
543
550
|
def optimize(self) -> None:
|
|
@@ -551,7 +558,7 @@ class RedisDB(VectorDb):
|
|
|
551
558
|
self.index.clear()
|
|
552
559
|
return True
|
|
553
560
|
except Exception as e:
|
|
554
|
-
|
|
561
|
+
log_error(f"Error deleting Redis index: {e}")
|
|
555
562
|
return False
|
|
556
563
|
|
|
557
564
|
def delete_by_id(self, id: str) -> bool:
|
|
@@ -562,7 +569,7 @@ class RedisDB(VectorDb):
|
|
|
562
569
|
log_debug(f"Deleted document with id '{id}' from Redis index")
|
|
563
570
|
return result > 0
|
|
564
571
|
except Exception as e:
|
|
565
|
-
|
|
572
|
+
log_error(f"Error deleting document by ID: {e}")
|
|
566
573
|
return False
|
|
567
574
|
|
|
568
575
|
def delete_by_name(self, name: str) -> bool:
|
|
@@ -588,7 +595,7 @@ class RedisDB(VectorDb):
|
|
|
588
595
|
log_debug(f"Deleted {deleted_count} documents with name '{name}'")
|
|
589
596
|
return deleted_count > 0
|
|
590
597
|
except Exception as e:
|
|
591
|
-
|
|
598
|
+
log_error(f"Error deleting documents by name: {e}")
|
|
592
599
|
return False
|
|
593
600
|
|
|
594
601
|
def delete_by_metadata(self, metadata: Dict[str, Any]) -> bool:
|
|
@@ -626,7 +633,7 @@ class RedisDB(VectorDb):
|
|
|
626
633
|
log_debug(f"Deleted {deleted_count} documents with metadata {metadata}")
|
|
627
634
|
return deleted_count > 0
|
|
628
635
|
except Exception as e:
|
|
629
|
-
|
|
636
|
+
log_error(f"Error deleting documents by metadata: {e}")
|
|
630
637
|
return False
|
|
631
638
|
|
|
632
639
|
def delete_by_content_id(self, content_id: str) -> bool:
|
|
@@ -652,7 +659,7 @@ class RedisDB(VectorDb):
|
|
|
652
659
|
log_debug(f"Deleted {deleted_count} documents with content_id '{content_id}'")
|
|
653
660
|
return deleted_count > 0
|
|
654
661
|
except Exception as e:
|
|
655
|
-
|
|
662
|
+
log_error(f"Error deleting documents by content_id: {e}")
|
|
656
663
|
return False
|
|
657
664
|
|
|
658
665
|
def update_metadata(self, content_id: str, metadata: Dict[str, Any]) -> None:
|
|
@@ -679,7 +686,7 @@ class RedisDB(VectorDb):
|
|
|
679
686
|
|
|
680
687
|
log_debug(f"Updated metadata for documents with content_id '{content_id}'")
|
|
681
688
|
except Exception as e:
|
|
682
|
-
|
|
689
|
+
log_error(f"Error updating metadata: {e}")
|
|
683
690
|
raise
|
|
684
691
|
|
|
685
692
|
def get_supported_search_types(self) -> List[str]:
|