agno 2.0.1__py3-none-any.whl → 2.3.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.
- agno/agent/agent.py +6015 -2823
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +594 -186
- agno/knowledge/reader/base.py +9 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +2 -8
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +72 -0
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +999 -519
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +103 -31
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +139 -0
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +59 -5
- agno/models/openai/chat.py +69 -29
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +77 -1
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -178
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +248 -94
- agno/run/base.py +44 -5
- agno/run/team.py +238 -97
- agno/run/workflow.py +144 -33
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1610
- agno/tools/dalle.py +2 -4
- agno/tools/decorator.py +4 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +14 -7
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +250 -30
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +217 -2
- agno/utils/gemini.py +180 -22
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +124 -133
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +638 -129
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +61 -23
- agno/workflow/workflow.py +2085 -272
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
- agno-2.3.0.dist-info/RECORD +577 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.1.dist-info/RECORD +0 -515
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
from hashlib import md5
|
|
4
|
-
from typing import Any, Dict, List, Optional
|
|
4
|
+
from typing import Any, Dict, List, Optional, Union
|
|
5
5
|
|
|
6
6
|
try:
|
|
7
7
|
from sqlalchemy.dialects import mysql
|
|
@@ -14,10 +14,11 @@ try:
|
|
|
14
14
|
except ImportError:
|
|
15
15
|
raise ImportError("`sqlalchemy` not installed")
|
|
16
16
|
|
|
17
|
+
from agno.filters import FilterExpr
|
|
17
18
|
from agno.knowledge.document import Document
|
|
18
19
|
from agno.knowledge.embedder import Embedder
|
|
19
20
|
from agno.knowledge.reranker.base import Reranker
|
|
20
|
-
from agno.utils.log import log_debug, log_error, log_info
|
|
21
|
+
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
21
22
|
from agno.vectordb.base import VectorDb
|
|
22
23
|
from agno.vectordb.distance import Distance
|
|
23
24
|
|
|
@@ -32,6 +33,8 @@ class SingleStore(VectorDb):
|
|
|
32
33
|
embedder: Optional[Embedder] = None,
|
|
33
34
|
distance: Distance = Distance.cosine,
|
|
34
35
|
reranker: Optional[Reranker] = None,
|
|
36
|
+
name: Optional[str] = None,
|
|
37
|
+
description: Optional[str] = None,
|
|
35
38
|
# index: Optional[Union[Ivfflat, HNSW]] = HNSW(),
|
|
36
39
|
):
|
|
37
40
|
_engine: Optional[Engine] = db_engine
|
|
@@ -44,9 +47,11 @@ class SingleStore(VectorDb):
|
|
|
44
47
|
self.collection: str = collection
|
|
45
48
|
self.schema: Optional[str] = schema
|
|
46
49
|
self.db_url: Optional[str] = db_url
|
|
50
|
+
# Initialize base class with name and description
|
|
51
|
+
super().__init__(name=name, description=description)
|
|
52
|
+
|
|
47
53
|
self.db_engine: Engine = _engine
|
|
48
54
|
self.metadata: MetaData = MetaData(schema=self.schema)
|
|
49
|
-
|
|
50
55
|
if embedder is None:
|
|
51
56
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
52
57
|
|
|
@@ -278,7 +283,9 @@ class SingleStore(VectorDb):
|
|
|
278
283
|
sess.commit()
|
|
279
284
|
log_debug(f"Committed {counter} documents")
|
|
280
285
|
|
|
281
|
-
def search(
|
|
286
|
+
def search(
|
|
287
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
288
|
+
) -> List[Document]:
|
|
282
289
|
"""
|
|
283
290
|
Search for documents based on a query and optional filters.
|
|
284
291
|
|
|
@@ -290,6 +297,8 @@ class SingleStore(VectorDb):
|
|
|
290
297
|
Returns:
|
|
291
298
|
List[Document]: List of documents that match the query.
|
|
292
299
|
"""
|
|
300
|
+
if filters is not None:
|
|
301
|
+
log_warning("Filters are not supported in SingleStore. No filters will be applied.")
|
|
293
302
|
query_embedding = self.embedder.get_embedding(query)
|
|
294
303
|
if query_embedding is None:
|
|
295
304
|
log_error(f"Error getting embedding for Query: {query}")
|
|
@@ -428,9 +437,9 @@ class SingleStore(VectorDb):
|
|
|
428
437
|
try:
|
|
429
438
|
with self.Session.begin() as sess:
|
|
430
439
|
stmt = delete(self.table).where(self.table.c.id == id)
|
|
431
|
-
result = sess.execute(stmt)
|
|
432
|
-
log_info(f"Deleted {result.rowcount} records with ID {id} from table '{self.table.name}'.")
|
|
433
|
-
return result.rowcount > 0
|
|
440
|
+
result = sess.execute(stmt) # type: ignore
|
|
441
|
+
log_info(f"Deleted {result.rowcount} records with ID {id} from table '{self.table.name}'.") # type: ignore
|
|
442
|
+
return result.rowcount > 0 # type: ignore
|
|
434
443
|
except Exception as e:
|
|
435
444
|
log_error(f"Error deleting document with ID {id}: {e}")
|
|
436
445
|
return False
|
|
@@ -444,11 +453,11 @@ class SingleStore(VectorDb):
|
|
|
444
453
|
try:
|
|
445
454
|
with self.Session.begin() as sess:
|
|
446
455
|
stmt = delete(self.table).where(self.table.c.content_id == content_id)
|
|
447
|
-
result = sess.execute(stmt)
|
|
456
|
+
result = sess.execute(stmt) # type: ignore
|
|
448
457
|
log_info(
|
|
449
|
-
f"Deleted {result.rowcount} records with content_id {content_id} from table '{self.table.name}'."
|
|
458
|
+
f"Deleted {result.rowcount} records with content_id {content_id} from table '{self.table.name}'." # type: ignore
|
|
450
459
|
)
|
|
451
|
-
return result.rowcount > 0
|
|
460
|
+
return result.rowcount > 0 # type: ignore
|
|
452
461
|
except Exception as e:
|
|
453
462
|
log_error(f"Error deleting document with content_id {content_id}: {e}")
|
|
454
463
|
return False
|
|
@@ -462,9 +471,9 @@ class SingleStore(VectorDb):
|
|
|
462
471
|
try:
|
|
463
472
|
with self.Session.begin() as sess:
|
|
464
473
|
stmt = delete(self.table).where(self.table.c.name == name)
|
|
465
|
-
result = sess.execute(stmt)
|
|
466
|
-
log_info(f"Deleted {result.rowcount} records with name '{name}' from table '{self.table.name}'.")
|
|
467
|
-
return result.rowcount > 0
|
|
474
|
+
result = sess.execute(stmt) # type: ignore
|
|
475
|
+
log_info(f"Deleted {result.rowcount} records with name '{name}' from table '{self.table.name}'.") # type: ignore
|
|
476
|
+
return result.rowcount > 0 # type: ignore
|
|
468
477
|
except Exception as e:
|
|
469
478
|
log_error(f"Error deleting document with name {name}: {e}")
|
|
470
479
|
return False
|
|
@@ -480,9 +489,9 @@ class SingleStore(VectorDb):
|
|
|
480
489
|
# Convert metadata to JSON string for comparison
|
|
481
490
|
metadata_json = json.dumps(metadata, sort_keys=True)
|
|
482
491
|
stmt = delete(self.table).where(self.table.c.meta_data == metadata_json)
|
|
483
|
-
result = sess.execute(stmt)
|
|
484
|
-
log_info(f"Deleted {result.rowcount} records with metadata {metadata} from table '{self.table.name}'.")
|
|
485
|
-
return result.rowcount > 0
|
|
492
|
+
result = sess.execute(stmt) # type: ignore
|
|
493
|
+
log_info(f"Deleted {result.rowcount} records with metadata {metadata} from table '{self.table.name}'.") # type: ignore
|
|
494
|
+
return result.rowcount > 0 # type: ignore
|
|
486
495
|
except Exception as e:
|
|
487
496
|
log_error(f"Error deleting documents with metadata {metadata}: {e}")
|
|
488
497
|
return False
|
|
@@ -496,8 +505,44 @@ class SingleStore(VectorDb):
|
|
|
496
505
|
documents: List[Document],
|
|
497
506
|
filters: Optional[Dict[str, Any]] = None,
|
|
498
507
|
) -> None:
|
|
499
|
-
|
|
500
|
-
|
|
508
|
+
if self.embedder.enable_batch and hasattr(self.embedder, "async_get_embeddings_batch_and_usage"):
|
|
509
|
+
# Use batch embedding when enabled and supported
|
|
510
|
+
try:
|
|
511
|
+
# Extract content from all documents
|
|
512
|
+
doc_contents = [doc.content for doc in documents]
|
|
513
|
+
|
|
514
|
+
# Get batch embeddings and usage
|
|
515
|
+
embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
|
|
516
|
+
|
|
517
|
+
# Process documents with pre-computed embeddings
|
|
518
|
+
for j, doc in enumerate(documents):
|
|
519
|
+
try:
|
|
520
|
+
if j < len(embeddings):
|
|
521
|
+
doc.embedding = embeddings[j]
|
|
522
|
+
doc.usage = usages[j] if j < len(usages) else None
|
|
523
|
+
except Exception as e:
|
|
524
|
+
log_error(f"Error assigning batch embedding to document '{doc.name}': {e}")
|
|
525
|
+
|
|
526
|
+
except Exception as e:
|
|
527
|
+
# Check if this is a rate limit error - don't fall back as it would make things worse
|
|
528
|
+
error_str = str(e).lower()
|
|
529
|
+
is_rate_limit = any(
|
|
530
|
+
phrase in error_str
|
|
531
|
+
for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
if is_rate_limit:
|
|
535
|
+
log_error(f"Rate limit detected during batch embedding. {e}")
|
|
536
|
+
raise e
|
|
537
|
+
else:
|
|
538
|
+
log_error(f"Async batch embedding failed, falling back to individual embeddings: {e}")
|
|
539
|
+
# Fall back to individual embedding
|
|
540
|
+
embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in documents]
|
|
541
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
542
|
+
else:
|
|
543
|
+
# Use individual embedding
|
|
544
|
+
embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
|
|
545
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
501
546
|
|
|
502
547
|
with self.Session.begin() as sess:
|
|
503
548
|
counter = 0
|
|
@@ -543,8 +588,45 @@ class SingleStore(VectorDb):
|
|
|
543
588
|
filters (Optional[Dict[str, Any]]): Optional filters for the upsert.
|
|
544
589
|
batch_size (int): Number of documents to upsert in each batch.
|
|
545
590
|
"""
|
|
546
|
-
|
|
547
|
-
|
|
591
|
+
|
|
592
|
+
if self.embedder.enable_batch and hasattr(self.embedder, "async_get_embeddings_batch_and_usage"):
|
|
593
|
+
# Use batch embedding when enabled and supported
|
|
594
|
+
try:
|
|
595
|
+
# Extract content from all documents
|
|
596
|
+
doc_contents = [doc.content for doc in documents]
|
|
597
|
+
|
|
598
|
+
# Get batch embeddings and usage
|
|
599
|
+
embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
|
|
600
|
+
|
|
601
|
+
# Process documents with pre-computed embeddings
|
|
602
|
+
for j, doc in enumerate(documents):
|
|
603
|
+
try:
|
|
604
|
+
if j < len(embeddings):
|
|
605
|
+
doc.embedding = embeddings[j]
|
|
606
|
+
doc.usage = usages[j] if j < len(usages) else None
|
|
607
|
+
except Exception as e:
|
|
608
|
+
log_error(f"Error assigning batch embedding to document '{doc.name}': {e}")
|
|
609
|
+
|
|
610
|
+
except Exception as e:
|
|
611
|
+
# Check if this is a rate limit error - don't fall back as it would make things worse
|
|
612
|
+
error_str = str(e).lower()
|
|
613
|
+
is_rate_limit = any(
|
|
614
|
+
phrase in error_str
|
|
615
|
+
for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
if is_rate_limit:
|
|
619
|
+
log_error(f"Rate limit detected during batch embedding. {e}")
|
|
620
|
+
raise e
|
|
621
|
+
else:
|
|
622
|
+
log_error(f"Async batch embedding failed, falling back to individual embeddings: {e}")
|
|
623
|
+
# Fall back to individual embedding
|
|
624
|
+
embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in documents]
|
|
625
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
626
|
+
else:
|
|
627
|
+
# Use individual embedding
|
|
628
|
+
embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
|
|
629
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
548
630
|
|
|
549
631
|
with self.Session.begin() as sess:
|
|
550
632
|
counter = 0
|
|
@@ -588,7 +670,7 @@ class SingleStore(VectorDb):
|
|
|
588
670
|
log_debug(f"Committed {counter} documents")
|
|
589
671
|
|
|
590
672
|
async def async_search(
|
|
591
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
673
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
592
674
|
) -> List[Document]:
|
|
593
675
|
return self.search(query=query, limit=limit, filters=filters)
|
|
594
676
|
|
|
@@ -616,11 +698,11 @@ class SingleStore(VectorDb):
|
|
|
616
698
|
try:
|
|
617
699
|
with self.Session.begin() as sess:
|
|
618
700
|
stmt = delete(self.table).where(self.table.c.content_hash == content_hash)
|
|
619
|
-
result = sess.execute(stmt)
|
|
701
|
+
result = sess.execute(stmt) # type: ignore
|
|
620
702
|
log_info(
|
|
621
|
-
f"Deleted {result.rowcount} records with content_hash '{content_hash}' from table '{self.table.name}'."
|
|
703
|
+
f"Deleted {result.rowcount} records with content_hash '{content_hash}' from table '{self.table.name}'." # type: ignore
|
|
622
704
|
)
|
|
623
|
-
return result.rowcount > 0
|
|
705
|
+
return result.rowcount > 0 # type: ignore
|
|
624
706
|
except Exception as e:
|
|
625
707
|
log_error(f"Error deleting documents with content_hash {content_hash}: {e}")
|
|
626
708
|
return False
|
|
@@ -639,7 +721,7 @@ class SingleStore(VectorDb):
|
|
|
639
721
|
with self.Session.begin() as sess:
|
|
640
722
|
# Find documents with the given content_id
|
|
641
723
|
stmt = select(self.table).where(self.table.c.content_id == content_id)
|
|
642
|
-
result = sess.execute(stmt)
|
|
724
|
+
result = sess.execute(stmt) # type: ignore
|
|
643
725
|
|
|
644
726
|
updated_count = 0
|
|
645
727
|
for row in result:
|
|
@@ -675,3 +757,7 @@ class SingleStore(VectorDb):
|
|
|
675
757
|
except Exception as e:
|
|
676
758
|
log_error(f"Error updating metadata for content_id '{content_id}': {e}")
|
|
677
759
|
raise
|
|
760
|
+
|
|
761
|
+
def get_supported_search_types(self) -> List[str]:
|
|
762
|
+
"""Get the supported search types for this vector database."""
|
|
763
|
+
return [] # SingleStore doesn't use SearchType enum
|
|
@@ -11,9 +11,10 @@ except ImportError as e:
|
|
|
11
11
|
msg = "The `surrealdb` package is not installed. Please install it via `pip install surrealdb`."
|
|
12
12
|
raise ImportError(msg) from e
|
|
13
13
|
|
|
14
|
+
from agno.filters import FilterExpr
|
|
14
15
|
from agno.knowledge.document import Document
|
|
15
16
|
from agno.knowledge.embedder import Embedder
|
|
16
|
-
from agno.utils.log import log_debug, log_error, log_info
|
|
17
|
+
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
17
18
|
from agno.vectordb.base import VectorDb
|
|
18
19
|
from agno.vectordb.distance import Distance
|
|
19
20
|
|
|
@@ -107,11 +108,13 @@ class SurrealDb(VectorDb):
|
|
|
107
108
|
m: int = 12,
|
|
108
109
|
search_ef: int = 40,
|
|
109
110
|
embedder: Optional[Embedder] = None,
|
|
111
|
+
name: Optional[str] = None,
|
|
112
|
+
description: Optional[str] = None,
|
|
113
|
+
id: Optional[str] = None,
|
|
110
114
|
):
|
|
111
115
|
"""Initialize SurrealDB connection.
|
|
112
116
|
|
|
113
117
|
Args:
|
|
114
|
-
url: SurrealDB server URL (e.g. ws://localhost:8000/rpc)
|
|
115
118
|
client: A blocking connection, either HTTP or WS
|
|
116
119
|
async_client: An async connection, either HTTP or WS (default: None)
|
|
117
120
|
collection: Collection name to store documents (default: documents)
|
|
@@ -122,6 +125,17 @@ class SurrealDb(VectorDb):
|
|
|
122
125
|
embedder: Embedder instance for creating embeddings (default: OpenAIEmbedder)
|
|
123
126
|
|
|
124
127
|
"""
|
|
128
|
+
# Dynamic ID generation based on unique identifiers
|
|
129
|
+
if id is None:
|
|
130
|
+
from agno.utils.string import generate_id
|
|
131
|
+
|
|
132
|
+
client_info = str(client) if client else str(async_client) if async_client else "default"
|
|
133
|
+
seed = f"{client_info}#{collection}"
|
|
134
|
+
id = generate_id(seed)
|
|
135
|
+
|
|
136
|
+
# Initialize base class with name, description, and generated ID
|
|
137
|
+
super().__init__(id=id, name=name, description=description)
|
|
138
|
+
|
|
125
139
|
# Embedder for embedding the document contents
|
|
126
140
|
if embedder is None:
|
|
127
141
|
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
@@ -131,7 +145,6 @@ class SurrealDb(VectorDb):
|
|
|
131
145
|
self.embedder: Embedder = embedder
|
|
132
146
|
self.dimensions = self.embedder.dimensions
|
|
133
147
|
self.collection = collection
|
|
134
|
-
|
|
135
148
|
# Convert Distance enum to SurrealDB distance type
|
|
136
149
|
self.distance = {Distance.cosine: "COSINE", Distance.l2: "EUCLIDEAN", Distance.max_inner_product: "DOT"}[
|
|
137
150
|
distance
|
|
@@ -306,7 +319,9 @@ class SurrealDb(VectorDb):
|
|
|
306
319
|
thing = f"{self.collection}:{doc.id}" if doc.id else self.collection
|
|
307
320
|
self.client.query(self.UPSERT_QUERY.format(thing=thing), data)
|
|
308
321
|
|
|
309
|
-
def search(
|
|
322
|
+
def search(
|
|
323
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
324
|
+
) -> List[Document]:
|
|
310
325
|
"""Search for similar documents.
|
|
311
326
|
|
|
312
327
|
Args:
|
|
@@ -318,6 +333,9 @@ class SurrealDb(VectorDb):
|
|
|
318
333
|
A list of documents that are similar to the query.
|
|
319
334
|
|
|
320
335
|
"""
|
|
336
|
+
if isinstance(filters, List):
|
|
337
|
+
log_warning("Filters Expressions are not supported in SurrealDB. No filters will be applied.")
|
|
338
|
+
filters = None
|
|
321
339
|
query_embedding = self.embedder.get_embedding(query)
|
|
322
340
|
if query_embedding is None:
|
|
323
341
|
log_error(f"Error getting embedding for Query: {query}")
|
|
@@ -548,7 +566,7 @@ class SurrealDb(VectorDb):
|
|
|
548
566
|
self,
|
|
549
567
|
query: str,
|
|
550
568
|
limit: int = 5,
|
|
551
|
-
filters: Optional[Dict[str, Any]] = None,
|
|
569
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
|
|
552
570
|
) -> List[Document]:
|
|
553
571
|
"""Search for similar documents asynchronously.
|
|
554
572
|
|
|
@@ -561,6 +579,10 @@ class SurrealDb(VectorDb):
|
|
|
561
579
|
A list of documents that are similar to the query.
|
|
562
580
|
|
|
563
581
|
"""
|
|
582
|
+
if isinstance(filters, List):
|
|
583
|
+
log_warning("Filters Expressions are not supported in SurrealDB. No filters will be applied.")
|
|
584
|
+
filters = None
|
|
585
|
+
|
|
564
586
|
query_embedding = self.embedder.get_embedding(query)
|
|
565
587
|
if query_embedding is None:
|
|
566
588
|
log_error(f"Error getting embedding for Query: {query}")
|
|
@@ -671,3 +693,7 @@ class SurrealDb(VectorDb):
|
|
|
671
693
|
except Exception as e:
|
|
672
694
|
log_error(f"Error updating metadata for content_id '{content_id}': {e}")
|
|
673
695
|
raise
|
|
696
|
+
|
|
697
|
+
def get_supported_search_types(self) -> List[str]:
|
|
698
|
+
"""Get the supported search types for this vector database."""
|
|
699
|
+
return [] # SurrealDb doesn't use SearchType enum
|
|
@@ -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 upstash_vector import Index, Vector
|
|
@@ -9,10 +9,11 @@ except ImportError:
|
|
|
9
9
|
"The `upstash-vector` package is not installed, please install using `pip install upstash-vector`"
|
|
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
|
|
15
|
-
from agno.utils.log import log_info, logger
|
|
16
|
+
from agno.utils.log import log_info, log_warning, logger
|
|
16
17
|
from agno.vectordb.base import VectorDb
|
|
17
18
|
|
|
18
19
|
DEFAULT_NAMESPACE = ""
|
|
@@ -32,6 +33,8 @@ class UpstashVectorDb(VectorDb):
|
|
|
32
33
|
embedder (Optional[Embedder], optional): The embedder to use. If None, uses Upstash hosted embedding models.
|
|
33
34
|
namespace (Optional[str], optional): The namespace to use. Defaults to DEFAULT_NAMESPACE.
|
|
34
35
|
reranker (Optional[Reranker], optional): The reranker to use. Defaults to None.
|
|
36
|
+
name (Optional[str], optional): The name of the vector database. Defaults to None.
|
|
37
|
+
description (Optional[str], optional): The description of the vector database. Defaults to None.
|
|
35
38
|
**kwargs: Additional keyword arguments.
|
|
36
39
|
"""
|
|
37
40
|
|
|
@@ -45,8 +48,28 @@ class UpstashVectorDb(VectorDb):
|
|
|
45
48
|
embedder: Optional[Embedder] = None,
|
|
46
49
|
namespace: Optional[str] = DEFAULT_NAMESPACE,
|
|
47
50
|
reranker: Optional[Reranker] = None,
|
|
51
|
+
name: Optional[str] = None,
|
|
52
|
+
description: Optional[str] = None,
|
|
53
|
+
id: Optional[str] = None,
|
|
48
54
|
**kwargs: Any,
|
|
49
55
|
) -> None:
|
|
56
|
+
# Validate required parameters
|
|
57
|
+
if not url:
|
|
58
|
+
raise ValueError("URL must be provided.")
|
|
59
|
+
if not token:
|
|
60
|
+
raise ValueError("Token must be provided.")
|
|
61
|
+
|
|
62
|
+
# Dynamic ID generation based on unique identifiers
|
|
63
|
+
if id is None:
|
|
64
|
+
from agno.utils.string import generate_id
|
|
65
|
+
|
|
66
|
+
namespace_identifier = namespace or DEFAULT_NAMESPACE
|
|
67
|
+
seed = f"{url}#{namespace_identifier}"
|
|
68
|
+
id = generate_id(seed)
|
|
69
|
+
|
|
70
|
+
# Initialize base class with name, description, and generated ID
|
|
71
|
+
super().__init__(id=id, name=name, description=description)
|
|
72
|
+
|
|
50
73
|
self._index: Optional[Index] = None
|
|
51
74
|
self.url: str = url
|
|
52
75
|
self.token: str = token
|
|
@@ -56,7 +79,6 @@ class UpstashVectorDb(VectorDb):
|
|
|
56
79
|
self.namespace: str = namespace if namespace is not None else DEFAULT_NAMESPACE
|
|
57
80
|
self.kwargs: Dict[str, Any] = kwargs
|
|
58
81
|
self.use_upstash_embeddings: bool = embedder is None
|
|
59
|
-
|
|
60
82
|
if embedder is None:
|
|
61
83
|
logger.warning(
|
|
62
84
|
"You have not provided an embedder, using Upstash hosted embedding models. "
|
|
@@ -303,7 +325,7 @@ class UpstashVectorDb(VectorDb):
|
|
|
303
325
|
self,
|
|
304
326
|
query: str,
|
|
305
327
|
limit: int = 5,
|
|
306
|
-
filters: Optional[Dict[str, Any]] = None,
|
|
328
|
+
filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
|
|
307
329
|
namespace: Optional[str] = None,
|
|
308
330
|
) -> List[Document]:
|
|
309
331
|
"""Search for documents in the index.
|
|
@@ -316,7 +338,9 @@ class UpstashVectorDb(VectorDb):
|
|
|
316
338
|
List[Document]: List of matching documents.
|
|
317
339
|
"""
|
|
318
340
|
_namespace = self.namespace if namespace is None else namespace
|
|
319
|
-
|
|
341
|
+
if isinstance(filters, List):
|
|
342
|
+
log_warning("Filters Expressions are not supported in UpstashDB. No filters will be applied.")
|
|
343
|
+
filters = None
|
|
320
344
|
filter_str = "" if filters is None else str(filters)
|
|
321
345
|
|
|
322
346
|
if not self.use_upstash_embeddings and self.embedder is not None:
|
|
@@ -504,8 +528,48 @@ class UpstashVectorDb(VectorDb):
|
|
|
504
528
|
_namespace = self.namespace if namespace is None else namespace
|
|
505
529
|
vectors = []
|
|
506
530
|
|
|
507
|
-
|
|
508
|
-
|
|
531
|
+
if (
|
|
532
|
+
self.embedder
|
|
533
|
+
and self.embedder.enable_batch
|
|
534
|
+
and hasattr(self.embedder, "async_get_embeddings_batch_and_usage")
|
|
535
|
+
):
|
|
536
|
+
# Use batch embedding when enabled and supported
|
|
537
|
+
try:
|
|
538
|
+
# Extract content from all documents
|
|
539
|
+
doc_contents = [doc.content for doc in documents]
|
|
540
|
+
|
|
541
|
+
# Get batch embeddings and usage
|
|
542
|
+
embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
|
|
543
|
+
|
|
544
|
+
# Process documents with pre-computed embeddings
|
|
545
|
+
for j, doc in enumerate(documents):
|
|
546
|
+
try:
|
|
547
|
+
if j < len(embeddings):
|
|
548
|
+
doc.embedding = embeddings[j]
|
|
549
|
+
doc.usage = usages[j] if j < len(usages) else None
|
|
550
|
+
except Exception as e:
|
|
551
|
+
logger.error(f"Error assigning batch embedding to document '{doc.name}': {e}")
|
|
552
|
+
|
|
553
|
+
except Exception as e:
|
|
554
|
+
# Check if this is a rate limit error - don't fall back as it would make things worse
|
|
555
|
+
error_str = str(e).lower()
|
|
556
|
+
is_rate_limit = any(
|
|
557
|
+
phrase in error_str
|
|
558
|
+
for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
if is_rate_limit:
|
|
562
|
+
logger.error(f"Rate limit detected during batch embedding. {e}")
|
|
563
|
+
raise e
|
|
564
|
+
else:
|
|
565
|
+
logger.warning(f"Async batch embedding failed, falling back to individual embeddings: {e}")
|
|
566
|
+
# Fall back to individual embedding
|
|
567
|
+
embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in documents]
|
|
568
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
569
|
+
else:
|
|
570
|
+
# Use individual embedding
|
|
571
|
+
embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
|
|
572
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
509
573
|
|
|
510
574
|
for i, document in enumerate(documents):
|
|
511
575
|
if document.id is None:
|
|
@@ -562,7 +626,7 @@ class UpstashVectorDb(VectorDb):
|
|
|
562
626
|
self.index.upsert(vectors, namespace=_namespace)
|
|
563
627
|
|
|
564
628
|
async def async_search(
|
|
565
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
629
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
566
630
|
) -> List[Document]:
|
|
567
631
|
raise NotImplementedError(f"Async not supported on {self.__class__.__name__}.")
|
|
568
632
|
|
|
@@ -648,3 +712,7 @@ class UpstashVectorDb(VectorDb):
|
|
|
648
712
|
except Exception as e:
|
|
649
713
|
logger.error(f"Error updating metadata for content_id '{content_id}': {e}")
|
|
650
714
|
raise
|
|
715
|
+
|
|
716
|
+
def get_supported_search_types(self) -> List[str]:
|
|
717
|
+
"""Get the supported search types for this vector database."""
|
|
718
|
+
return [] # UpstashVectorDb doesn't use SearchType enum
|