agno 2.0.0rc2__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 +6009 -2874
- 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 +595 -187
- 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 +3 -0
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +339 -266
- 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 +1011 -566
- 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 +110 -37
- 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 +143 -4
- 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 +60 -6
- agno/models/openai/chat.py +102 -43
- 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 +81 -5
- 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 -175
- 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 +266 -112
- agno/run/base.py +53 -24
- agno/run/team.py +252 -111
- agno/run/workflow.py +156 -45
- 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 -1692
- agno/tools/brightdata.py +3 -3
- agno/tools/cartesia.py +3 -5
- agno/tools/dalle.py +9 -8
- agno/tools/decorator.py +4 -2
- agno/tools/desi_vocal.py +2 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +20 -13
- agno/tools/eleven_labs.py +26 -28
- agno/tools/exa.py +21 -16
- agno/tools/fal.py +4 -4
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +257 -37
- agno/tools/giphy.py +2 -2
- 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/lumalab.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/azure_openai.py +2 -2
- agno/tools/models/gemini.py +3 -3
- agno/tools/models/groq.py +3 -5
- agno/tools/models/nebius.py +7 -7
- agno/tools/models_labs.py +25 -15
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +4 -9
- agno/tools/opencv.py +3 -3
- agno/tools/parallel.py +314 -0
- agno/tools/replicate.py +7 -7
- 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 +222 -7
- agno/utils/gemini.py +181 -23
- 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 +95 -5
- 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/models/cohere.py +1 -1
- agno/utils/models/watsonx.py +1 -1
- agno/utils/openai.py +1 -1
- 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 +183 -135
- 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 +645 -136
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +71 -33
- agno/workflow/workflow.py +2113 -300
- agno-2.3.0.dist-info/METADATA +618 -0
- agno-2.3.0.dist-info/RECORD +577 -0
- agno-2.3.0.dist-info/licenses/LICENSE +201 -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.0rc2.dist-info/METADATA +0 -355
- agno-2.0.0rc2.dist-info/RECORD +0 -515
- agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@ import json
|
|
|
3
3
|
import uuid
|
|
4
4
|
from hashlib import md5
|
|
5
5
|
from os import getenv
|
|
6
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
9
|
from warnings import filterwarnings
|
|
@@ -18,10 +18,11 @@ try:
|
|
|
18
18
|
except ImportError:
|
|
19
19
|
raise ImportError("Weaviate is not installed. Install using 'pip install weaviate-client'.")
|
|
20
20
|
|
|
21
|
+
from agno.filters import FilterExpr
|
|
21
22
|
from agno.knowledge.document import Document
|
|
22
23
|
from agno.knowledge.embedder import Embedder
|
|
23
24
|
from agno.knowledge.reranker.base import Reranker
|
|
24
|
-
from agno.utils.log import log_debug, log_info, logger
|
|
25
|
+
from agno.utils.log import log_debug, log_info, log_warning, logger
|
|
25
26
|
from agno.vectordb.base import VectorDb
|
|
26
27
|
from agno.vectordb.search import SearchType
|
|
27
28
|
from agno.vectordb.weaviate.index import Distance, VectorIndex
|
|
@@ -41,6 +42,9 @@ class Weaviate(VectorDb):
|
|
|
41
42
|
local: bool = False,
|
|
42
43
|
# Collection params
|
|
43
44
|
collection: str = "default",
|
|
45
|
+
name: Optional[str] = None,
|
|
46
|
+
description: Optional[str] = None,
|
|
47
|
+
id: Optional[str] = None,
|
|
44
48
|
vector_index: VectorIndex = VectorIndex.HNSW,
|
|
45
49
|
distance: Distance = Distance.COSINE,
|
|
46
50
|
# Search/Embedding params
|
|
@@ -49,6 +53,17 @@ class Weaviate(VectorDb):
|
|
|
49
53
|
reranker: Optional[Reranker] = None,
|
|
50
54
|
hybrid_search_alpha: float = 0.5,
|
|
51
55
|
):
|
|
56
|
+
# Dynamic ID generation based on unique identifiers
|
|
57
|
+
if id is None:
|
|
58
|
+
from agno.utils.string import generate_id
|
|
59
|
+
|
|
60
|
+
connection_identifier = wcd_url or "local" if local else "default"
|
|
61
|
+
seed = f"{connection_identifier}#{collection}"
|
|
62
|
+
id = generate_id(seed)
|
|
63
|
+
|
|
64
|
+
# Initialize base class with name, description, and generated ID
|
|
65
|
+
super().__init__(id=id, name=name, description=description)
|
|
66
|
+
|
|
52
67
|
# Connection setup
|
|
53
68
|
self.wcd_url = wcd_url or getenv("WCD_URL")
|
|
54
69
|
self.wcd_api_key = wcd_api_key or getenv("WCD_API_KEY")
|
|
@@ -270,9 +285,45 @@ class Weaviate(VectorDb):
|
|
|
270
285
|
if not documents:
|
|
271
286
|
return
|
|
272
287
|
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
|
|
288
|
+
# Apply batch embedding logic
|
|
289
|
+
if self.embedder.enable_batch and hasattr(self.embedder, "async_get_embeddings_batch_and_usage"):
|
|
290
|
+
# Use batch embedding when enabled and supported
|
|
291
|
+
try:
|
|
292
|
+
# Extract content from all documents
|
|
293
|
+
doc_contents = [doc.content for doc in documents]
|
|
294
|
+
|
|
295
|
+
# Get batch embeddings and usage
|
|
296
|
+
embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
|
|
297
|
+
|
|
298
|
+
# Process documents with pre-computed embeddings
|
|
299
|
+
for j, doc in enumerate(documents):
|
|
300
|
+
try:
|
|
301
|
+
if j < len(embeddings):
|
|
302
|
+
doc.embedding = embeddings[j]
|
|
303
|
+
doc.usage = usages[j] if j < len(usages) else None
|
|
304
|
+
except Exception as e:
|
|
305
|
+
logger.error(f"Error assigning batch embedding to document '{doc.name}': {e}")
|
|
306
|
+
|
|
307
|
+
except Exception as e:
|
|
308
|
+
# Check if this is a rate limit error - don't fall back as it would make things worse
|
|
309
|
+
error_str = str(e).lower()
|
|
310
|
+
is_rate_limit = any(
|
|
311
|
+
phrase in error_str
|
|
312
|
+
for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
if is_rate_limit:
|
|
316
|
+
logger.error(f"Rate limit detected during batch embedding. {e}")
|
|
317
|
+
raise e
|
|
318
|
+
else:
|
|
319
|
+
logger.warning(f"Async batch embedding failed, falling back to individual embeddings: {e}")
|
|
320
|
+
# Fall back to individual embedding
|
|
321
|
+
embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in documents]
|
|
322
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
323
|
+
else:
|
|
324
|
+
# Use individual embedding
|
|
325
|
+
embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
|
|
326
|
+
await asyncio.gather(*embed_tasks, return_exceptions=True)
|
|
276
327
|
|
|
277
328
|
client = await self.get_async_client()
|
|
278
329
|
try:
|
|
@@ -342,7 +393,9 @@ class Weaviate(VectorDb):
|
|
|
342
393
|
await self.async_insert(content_hash=content_hash, documents=documents, filters=filters)
|
|
343
394
|
return
|
|
344
395
|
|
|
345
|
-
def search(
|
|
396
|
+
def search(
|
|
397
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
398
|
+
) -> List[Document]:
|
|
346
399
|
"""
|
|
347
400
|
Perform a search based on the configured search type.
|
|
348
401
|
|
|
@@ -354,6 +407,9 @@ class Weaviate(VectorDb):
|
|
|
354
407
|
Returns:
|
|
355
408
|
List[Document]: List of matching documents.
|
|
356
409
|
"""
|
|
410
|
+
if isinstance(filters, List):
|
|
411
|
+
log_warning("Filters Expressions are not supported in Weaviate. No filters will be applied.")
|
|
412
|
+
filters = None
|
|
357
413
|
if self.search_type == SearchType.vector:
|
|
358
414
|
return self.vector_search(query, limit, filters)
|
|
359
415
|
elif self.search_type == SearchType.keyword:
|
|
@@ -365,7 +421,7 @@ class Weaviate(VectorDb):
|
|
|
365
421
|
return []
|
|
366
422
|
|
|
367
423
|
async def async_search(
|
|
368
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
424
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
369
425
|
) -> List[Document]:
|
|
370
426
|
"""
|
|
371
427
|
Perform a search based on the configured search type asynchronously.
|
|
@@ -378,6 +434,9 @@ class Weaviate(VectorDb):
|
|
|
378
434
|
Returns:
|
|
379
435
|
List[Document]: List of matching documents.
|
|
380
436
|
"""
|
|
437
|
+
if isinstance(filters, List):
|
|
438
|
+
log_warning("Filters Expressions are not supported in Weaviate. No filters will be applied.")
|
|
439
|
+
filters = None
|
|
381
440
|
if self.search_type == SearchType.vector:
|
|
382
441
|
return await self.async_vector_search(query, limit, filters)
|
|
383
442
|
elif self.search_type == SearchType.keyword:
|
|
@@ -388,7 +447,9 @@ class Weaviate(VectorDb):
|
|
|
388
447
|
logger.error(f"Invalid search type '{self.search_type}'.")
|
|
389
448
|
return []
|
|
390
449
|
|
|
391
|
-
def vector_search(
|
|
450
|
+
def vector_search(
|
|
451
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
452
|
+
) -> List[Document]:
|
|
392
453
|
try:
|
|
393
454
|
query_embedding = self.embedder.get_embedding(query)
|
|
394
455
|
if query_embedding is None:
|
|
@@ -423,7 +484,7 @@ class Weaviate(VectorDb):
|
|
|
423
484
|
self.get_client().close()
|
|
424
485
|
|
|
425
486
|
async def async_vector_search(
|
|
426
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
487
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
427
488
|
) -> List[Document]:
|
|
428
489
|
"""
|
|
429
490
|
Perform a vector search in Weaviate asynchronously.
|
|
@@ -468,7 +529,9 @@ class Weaviate(VectorDb):
|
|
|
468
529
|
logger.error(f"Error searching for documents: {e}")
|
|
469
530
|
return []
|
|
470
531
|
|
|
471
|
-
def keyword_search(
|
|
532
|
+
def keyword_search(
|
|
533
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
534
|
+
) -> List[Document]:
|
|
472
535
|
try:
|
|
473
536
|
collection = self.get_client().collections.get(self.collection)
|
|
474
537
|
filter_expr = self._build_filter_expression(filters)
|
|
@@ -499,7 +562,7 @@ class Weaviate(VectorDb):
|
|
|
499
562
|
self.get_client().close()
|
|
500
563
|
|
|
501
564
|
async def async_keyword_search(
|
|
502
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
565
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
503
566
|
) -> List[Document]:
|
|
504
567
|
"""
|
|
505
568
|
Perform a keyword search in Weaviate asynchronously.
|
|
@@ -540,7 +603,9 @@ class Weaviate(VectorDb):
|
|
|
540
603
|
logger.error(f"Error searching for documents: {e}")
|
|
541
604
|
return []
|
|
542
605
|
|
|
543
|
-
def hybrid_search(
|
|
606
|
+
def hybrid_search(
|
|
607
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
608
|
+
) -> List[Document]:
|
|
544
609
|
try:
|
|
545
610
|
query_embedding = self.embedder.get_embedding(query)
|
|
546
611
|
if query_embedding is None:
|
|
@@ -578,7 +643,7 @@ class Weaviate(VectorDb):
|
|
|
578
643
|
self.get_client().close()
|
|
579
644
|
|
|
580
645
|
async def async_hybrid_search(
|
|
581
|
-
self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
|
|
646
|
+
self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
|
|
582
647
|
) -> List[Document]:
|
|
583
648
|
"""
|
|
584
649
|
Perform a hybrid search combining vector and keyword search in Weaviate asynchronously.
|
|
@@ -799,7 +864,7 @@ class Weaviate(VectorDb):
|
|
|
799
864
|
"""Indicate that upsert functionality is available."""
|
|
800
865
|
return True
|
|
801
866
|
|
|
802
|
-
def _build_filter_expression(self, filters: Optional[Dict[str, Any]]):
|
|
867
|
+
def _build_filter_expression(self, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]]):
|
|
803
868
|
"""
|
|
804
869
|
Build a filter expression for Weaviate queries.
|
|
805
870
|
|
|
@@ -811,7 +876,9 @@ class Weaviate(VectorDb):
|
|
|
811
876
|
"""
|
|
812
877
|
if not filters:
|
|
813
878
|
return None
|
|
814
|
-
|
|
879
|
+
if isinstance(filters, List):
|
|
880
|
+
log_warning("Filters Expressions are not supported in Weaviate. No filters will be applied.")
|
|
881
|
+
return None
|
|
815
882
|
try:
|
|
816
883
|
# Create a filter for each key-value pair
|
|
817
884
|
filter_conditions = []
|
|
@@ -932,3 +999,7 @@ class Weaviate(VectorDb):
|
|
|
932
999
|
except Exception as e:
|
|
933
1000
|
logger.error(f"Error deleting documents by content_hash '{content_hash}': {e}")
|
|
934
1001
|
return False
|
|
1002
|
+
|
|
1003
|
+
def get_supported_search_types(self) -> List[str]:
|
|
1004
|
+
"""Get the supported search types for this vector database."""
|
|
1005
|
+
return [SearchType.vector, SearchType.keyword, SearchType.hybrid]
|
agno/workflow/__init__.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from agno.workflow.agent import WorkflowAgent
|
|
1
2
|
from agno.workflow.condition import Condition
|
|
2
3
|
from agno.workflow.loop import Loop
|
|
3
4
|
from agno.workflow.parallel import Parallel
|
|
@@ -9,6 +10,7 @@ from agno.workflow.workflow import Workflow
|
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
11
12
|
"Workflow",
|
|
13
|
+
"WorkflowAgent",
|
|
12
14
|
"Steps",
|
|
13
15
|
"Step",
|
|
14
16
|
"Loop",
|
agno/workflow/agent.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""WorkflowAgent - A restricted Agent for workflow orchestration"""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
4
|
+
|
|
5
|
+
from agno.agent import Agent
|
|
6
|
+
from agno.models.base import Model
|
|
7
|
+
from agno.run import RunContext
|
|
8
|
+
from agno.workflow.types import WebSocketHandler
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from agno.session.workflow import WorkflowSession
|
|
12
|
+
from agno.workflow.types import WorkflowExecutionInput
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class WorkflowAgent(Agent):
|
|
16
|
+
"""
|
|
17
|
+
A restricted Agent class specifically designed for workflow orchestration.
|
|
18
|
+
This agent can:
|
|
19
|
+
1. Decide whether to run the workflow or answer directly from history
|
|
20
|
+
2. Call the workflow execution tool when needed
|
|
21
|
+
3. Access workflow session history for context
|
|
22
|
+
Restrictions:
|
|
23
|
+
- Only model configuration allowed
|
|
24
|
+
- No custom tools (tools are set by workflow)
|
|
25
|
+
- No knowledge base
|
|
26
|
+
- Limited configuration options
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
model: Model,
|
|
32
|
+
instructions: Optional[str] = None,
|
|
33
|
+
add_workflow_history: bool = True,
|
|
34
|
+
num_history_runs: int = 5,
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Initialize WorkflowAgent with restricted parameters.
|
|
38
|
+
Args:
|
|
39
|
+
model: The model to use for the agent (required)
|
|
40
|
+
instructions: Custom instructions (will be combined with workflow context)
|
|
41
|
+
add_workflow_history: Whether to add workflow history to context (default: True)
|
|
42
|
+
num_history_runs: Number of previous workflow runs to include in context (default: 5)
|
|
43
|
+
"""
|
|
44
|
+
self.add_workflow_history = add_workflow_history
|
|
45
|
+
|
|
46
|
+
default_instructions = """You are a workflow orchestration agent. Your job is to help users by either:
|
|
47
|
+
1. **Answering directly** from the workflow history context if the question can be answered from previous runs
|
|
48
|
+
2. **Running the workflow** by calling the run_workflow tool ONCE when you need to process a new query
|
|
49
|
+
|
|
50
|
+
Guidelines:
|
|
51
|
+
- ALWAYS check the workflow history first before calling the tool
|
|
52
|
+
- Answer directly from history if:
|
|
53
|
+
* The user asks about something already in history
|
|
54
|
+
* The user asks for comparisons/analysis of things in history (e.g., "compare X and Y")
|
|
55
|
+
* The user asks follow-up questions about previous results
|
|
56
|
+
- Only call the run_workflow tool for NEW topics not covered in history
|
|
57
|
+
- IMPORTANT: Do NOT call the tool multiple times. Call it once and use the result.
|
|
58
|
+
- Keep your responses concise and helpful
|
|
59
|
+
- When you must call the workflow, pass a clear and concise query
|
|
60
|
+
|
|
61
|
+
{workflow_context}
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
if instructions:
|
|
65
|
+
if "{workflow_context}" not in instructions:
|
|
66
|
+
# Add the workflow context placeholder
|
|
67
|
+
final_instructions = f"{instructions}\n\n{{workflow_context}}"
|
|
68
|
+
else:
|
|
69
|
+
final_instructions = instructions
|
|
70
|
+
else:
|
|
71
|
+
final_instructions = default_instructions
|
|
72
|
+
|
|
73
|
+
super().__init__(
|
|
74
|
+
model=model,
|
|
75
|
+
instructions=final_instructions,
|
|
76
|
+
resolve_in_context=True,
|
|
77
|
+
num_history_runs=num_history_runs,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def create_workflow_tool(
|
|
81
|
+
self,
|
|
82
|
+
workflow: "Any", # Workflow type
|
|
83
|
+
session: "WorkflowSession",
|
|
84
|
+
execution_input: "WorkflowExecutionInput",
|
|
85
|
+
run_context: RunContext,
|
|
86
|
+
stream: bool = False,
|
|
87
|
+
) -> Callable:
|
|
88
|
+
"""
|
|
89
|
+
Create the workflow execution tool that this agent can call.
|
|
90
|
+
This is similar to how Agent has search_knowledge_base() method.
|
|
91
|
+
Args:
|
|
92
|
+
workflow: The workflow instance
|
|
93
|
+
session: The workflow session
|
|
94
|
+
execution_input: The execution input
|
|
95
|
+
run_context: The run context
|
|
96
|
+
stream: Whether to stream the workflow execution
|
|
97
|
+
Returns:
|
|
98
|
+
Callable tool function
|
|
99
|
+
"""
|
|
100
|
+
from datetime import datetime
|
|
101
|
+
from uuid import uuid4
|
|
102
|
+
|
|
103
|
+
from pydantic import BaseModel
|
|
104
|
+
|
|
105
|
+
from agno.run.workflow import WorkflowRunOutput
|
|
106
|
+
from agno.utils.log import log_debug
|
|
107
|
+
from agno.workflow.types import WorkflowExecutionInput
|
|
108
|
+
|
|
109
|
+
def run_workflow(query: str):
|
|
110
|
+
"""
|
|
111
|
+
Execute the complete workflow with the given query.
|
|
112
|
+
Use this tool when you need to run the workflow to answer the user's question.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
query: The input query/question to process through the workflow
|
|
116
|
+
Returns:
|
|
117
|
+
The workflow execution result (str in non-streaming, generator in streaming)
|
|
118
|
+
"""
|
|
119
|
+
# Reload session to get latest data from database
|
|
120
|
+
# This ensures we don't overwrite any updates made after the tool was created
|
|
121
|
+
session_from_db = workflow.get_session(session_id=session.session_id)
|
|
122
|
+
if session_from_db is None:
|
|
123
|
+
session_from_db = session # Fallback to closure session if reload fails
|
|
124
|
+
log_debug(f"Fallback to closure session: {len(session_from_db.runs or [])} runs")
|
|
125
|
+
else:
|
|
126
|
+
log_debug(f"Reloaded session before tool execution: {len(session_from_db.runs or [])} runs")
|
|
127
|
+
|
|
128
|
+
# Create a new run ID for this execution
|
|
129
|
+
run_id = str(uuid4())
|
|
130
|
+
|
|
131
|
+
workflow_run_response = WorkflowRunOutput(
|
|
132
|
+
run_id=run_id,
|
|
133
|
+
input=execution_input.input, # Use original user input
|
|
134
|
+
session_id=session_from_db.session_id,
|
|
135
|
+
workflow_id=workflow.id,
|
|
136
|
+
workflow_name=workflow.name,
|
|
137
|
+
created_at=int(datetime.now().timestamp()),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
workflow_execution_input = WorkflowExecutionInput(
|
|
141
|
+
input=query, # Agent's refined query for execution
|
|
142
|
+
additional_data=execution_input.additional_data,
|
|
143
|
+
audio=execution_input.audio,
|
|
144
|
+
images=execution_input.images,
|
|
145
|
+
videos=execution_input.videos,
|
|
146
|
+
files=execution_input.files,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# ===== EXECUTION LOGIC (Based on streaming mode) =====
|
|
150
|
+
if stream:
|
|
151
|
+
final_content = ""
|
|
152
|
+
for event in workflow._execute_stream(
|
|
153
|
+
session=session_from_db,
|
|
154
|
+
run_context=run_context,
|
|
155
|
+
execution_input=workflow_execution_input,
|
|
156
|
+
workflow_run_response=workflow_run_response,
|
|
157
|
+
stream_events=True,
|
|
158
|
+
):
|
|
159
|
+
yield event
|
|
160
|
+
|
|
161
|
+
# Capture final content from WorkflowCompletedEvent
|
|
162
|
+
from agno.run.workflow import WorkflowCompletedEvent
|
|
163
|
+
|
|
164
|
+
if isinstance(event, WorkflowCompletedEvent):
|
|
165
|
+
final_content = str(event.content) if event.content else ""
|
|
166
|
+
|
|
167
|
+
return final_content
|
|
168
|
+
else:
|
|
169
|
+
# NON-STREAMING MODE: Execute synchronously
|
|
170
|
+
result = workflow._execute(
|
|
171
|
+
session=session_from_db,
|
|
172
|
+
execution_input=workflow_execution_input,
|
|
173
|
+
workflow_run_response=workflow_run_response,
|
|
174
|
+
run_context=run_context,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if isinstance(result.content, str):
|
|
178
|
+
return result.content
|
|
179
|
+
elif isinstance(result.content, BaseModel):
|
|
180
|
+
return result.content.model_dump_json(exclude_none=True)
|
|
181
|
+
else:
|
|
182
|
+
return str(result.content)
|
|
183
|
+
|
|
184
|
+
return run_workflow
|
|
185
|
+
|
|
186
|
+
def async_create_workflow_tool(
|
|
187
|
+
self,
|
|
188
|
+
workflow: "Any", # Workflow type
|
|
189
|
+
session: "WorkflowSession",
|
|
190
|
+
execution_input: "WorkflowExecutionInput",
|
|
191
|
+
run_context: RunContext,
|
|
192
|
+
stream: bool = False,
|
|
193
|
+
websocket_handler: Optional[WebSocketHandler] = None,
|
|
194
|
+
) -> Callable:
|
|
195
|
+
"""
|
|
196
|
+
Create the async workflow execution tool that this agent can call.
|
|
197
|
+
This is the async counterpart of create_workflow_tool.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
workflow: The workflow instance
|
|
201
|
+
session: The workflow session
|
|
202
|
+
execution_input: The execution input
|
|
203
|
+
run_context: The run context
|
|
204
|
+
stream: Whether to stream the workflow execution
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Async callable tool function
|
|
208
|
+
"""
|
|
209
|
+
from datetime import datetime
|
|
210
|
+
from uuid import uuid4
|
|
211
|
+
|
|
212
|
+
from pydantic import BaseModel
|
|
213
|
+
|
|
214
|
+
from agno.run.workflow import WorkflowRunOutput
|
|
215
|
+
from agno.utils.log import log_debug
|
|
216
|
+
from agno.workflow.types import WorkflowExecutionInput
|
|
217
|
+
|
|
218
|
+
async def run_workflow(query: str):
|
|
219
|
+
"""
|
|
220
|
+
Execute the complete workflow with the given query asynchronously.
|
|
221
|
+
Use this tool when you need to run the workflow to answer the user's question.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
query: The input query/question to process through the workflow
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
The workflow execution result (str in non-streaming, async generator in streaming)
|
|
228
|
+
"""
|
|
229
|
+
# Reload session to get latest data from database
|
|
230
|
+
# This ensures we don't overwrite any updates made after the tool was created
|
|
231
|
+
# Use async or sync method based on database type
|
|
232
|
+
if workflow._has_async_db():
|
|
233
|
+
session_from_db = await workflow.aget_session(session_id=session.session_id)
|
|
234
|
+
else:
|
|
235
|
+
session_from_db = workflow.get_session(session_id=session.session_id)
|
|
236
|
+
|
|
237
|
+
if session_from_db is None:
|
|
238
|
+
session_from_db = session # Fallback to closure session if reload fails
|
|
239
|
+
log_debug(f"Fallback to closure session: {len(session_from_db.runs or [])} runs")
|
|
240
|
+
else:
|
|
241
|
+
log_debug(f"Reloaded session before async tool execution: {len(session_from_db.runs or [])} runs")
|
|
242
|
+
|
|
243
|
+
# Create a new run ID for this execution
|
|
244
|
+
run_id = str(uuid4())
|
|
245
|
+
|
|
246
|
+
workflow_run_response = WorkflowRunOutput(
|
|
247
|
+
run_id=run_id,
|
|
248
|
+
input=execution_input.input, # Use original user input
|
|
249
|
+
session_id=session_from_db.session_id,
|
|
250
|
+
workflow_id=workflow.id,
|
|
251
|
+
workflow_name=workflow.name,
|
|
252
|
+
created_at=int(datetime.now().timestamp()),
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
workflow_execution_input = WorkflowExecutionInput(
|
|
256
|
+
input=query, # Agent's refined query for execution
|
|
257
|
+
additional_data=execution_input.additional_data,
|
|
258
|
+
audio=execution_input.audio,
|
|
259
|
+
images=execution_input.images,
|
|
260
|
+
videos=execution_input.videos,
|
|
261
|
+
files=execution_input.files,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if stream:
|
|
265
|
+
final_content = ""
|
|
266
|
+
async for event in workflow._aexecute_stream(
|
|
267
|
+
session_id=session_from_db.session_id,
|
|
268
|
+
user_id=session_from_db.user_id,
|
|
269
|
+
execution_input=workflow_execution_input,
|
|
270
|
+
workflow_run_response=workflow_run_response,
|
|
271
|
+
run_context=run_context,
|
|
272
|
+
stream_events=True,
|
|
273
|
+
websocket_handler=websocket_handler,
|
|
274
|
+
):
|
|
275
|
+
yield event
|
|
276
|
+
|
|
277
|
+
from agno.run.workflow import WorkflowCompletedEvent
|
|
278
|
+
|
|
279
|
+
if isinstance(event, WorkflowCompletedEvent):
|
|
280
|
+
final_content = str(event.content) if event.content else ""
|
|
281
|
+
|
|
282
|
+
yield final_content
|
|
283
|
+
else:
|
|
284
|
+
result = await workflow._aexecute(
|
|
285
|
+
session_id=session_from_db.session_id,
|
|
286
|
+
user_id=session_from_db.user_id,
|
|
287
|
+
execution_input=workflow_execution_input,
|
|
288
|
+
workflow_run_response=workflow_run_response,
|
|
289
|
+
run_context=run_context,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if isinstance(result.content, str):
|
|
293
|
+
yield result.content
|
|
294
|
+
elif isinstance(result.content, BaseModel):
|
|
295
|
+
yield result.content.model_dump_json(exclude_none=True)
|
|
296
|
+
else:
|
|
297
|
+
yield str(result.content)
|
|
298
|
+
|
|
299
|
+
return run_workflow
|