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.
Files changed (314) hide show
  1. agno/agent/agent.py +6015 -2823
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +594 -186
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +2 -8
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +72 -0
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +999 -519
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +103 -31
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +139 -0
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +59 -5
  142. agno/models/openai/chat.py +69 -29
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +77 -1
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -178
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +248 -94
  205. agno/run/base.py +44 -5
  206. agno/run/team.py +238 -97
  207. agno/run/workflow.py +144 -33
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1610
  213. agno/tools/dalle.py +2 -4
  214. agno/tools/decorator.py +4 -2
  215. agno/tools/duckduckgo.py +15 -11
  216. agno/tools/e2b.py +14 -7
  217. agno/tools/eleven_labs.py +23 -25
  218. agno/tools/exa.py +21 -16
  219. agno/tools/file.py +153 -23
  220. agno/tools/file_generation.py +350 -0
  221. agno/tools/firecrawl.py +4 -4
  222. agno/tools/function.py +250 -30
  223. agno/tools/gmail.py +238 -14
  224. agno/tools/google_drive.py +270 -0
  225. agno/tools/googlecalendar.py +36 -8
  226. agno/tools/googlesheets.py +20 -5
  227. agno/tools/jira.py +20 -0
  228. agno/tools/knowledge.py +3 -3
  229. agno/tools/mcp/__init__.py +10 -0
  230. agno/tools/mcp/mcp.py +331 -0
  231. agno/tools/mcp/multi_mcp.py +347 -0
  232. agno/tools/mcp/params.py +24 -0
  233. agno/tools/mcp_toolbox.py +284 -0
  234. agno/tools/mem0.py +11 -17
  235. agno/tools/memori.py +1 -53
  236. agno/tools/memory.py +419 -0
  237. agno/tools/models/nebius.py +5 -5
  238. agno/tools/models_labs.py +20 -10
  239. agno/tools/notion.py +204 -0
  240. agno/tools/parallel.py +314 -0
  241. agno/tools/scrapegraph.py +58 -31
  242. agno/tools/searxng.py +2 -2
  243. agno/tools/serper.py +2 -2
  244. agno/tools/slack.py +18 -3
  245. agno/tools/spider.py +2 -2
  246. agno/tools/tavily.py +146 -0
  247. agno/tools/whatsapp.py +1 -1
  248. agno/tools/workflow.py +278 -0
  249. agno/tools/yfinance.py +12 -11
  250. agno/utils/agent.py +820 -0
  251. agno/utils/audio.py +27 -0
  252. agno/utils/common.py +90 -1
  253. agno/utils/events.py +217 -2
  254. agno/utils/gemini.py +180 -22
  255. agno/utils/hooks.py +57 -0
  256. agno/utils/http.py +111 -0
  257. agno/utils/knowledge.py +12 -5
  258. agno/utils/log.py +1 -0
  259. agno/utils/mcp.py +92 -2
  260. agno/utils/media.py +188 -10
  261. agno/utils/merge_dict.py +22 -1
  262. agno/utils/message.py +60 -0
  263. agno/utils/models/claude.py +40 -11
  264. agno/utils/print_response/agent.py +105 -21
  265. agno/utils/print_response/team.py +103 -38
  266. agno/utils/print_response/workflow.py +251 -34
  267. agno/utils/reasoning.py +22 -1
  268. agno/utils/serialize.py +32 -0
  269. agno/utils/streamlit.py +16 -10
  270. agno/utils/string.py +41 -0
  271. agno/utils/team.py +98 -9
  272. agno/utils/tools.py +1 -1
  273. agno/vectordb/base.py +23 -4
  274. agno/vectordb/cassandra/cassandra.py +65 -9
  275. agno/vectordb/chroma/chromadb.py +182 -38
  276. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  277. agno/vectordb/couchbase/couchbase.py +105 -10
  278. agno/vectordb/lancedb/lance_db.py +124 -133
  279. agno/vectordb/langchaindb/langchaindb.py +25 -7
  280. agno/vectordb/lightrag/lightrag.py +17 -3
  281. agno/vectordb/llamaindex/__init__.py +3 -0
  282. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  283. agno/vectordb/milvus/milvus.py +126 -9
  284. agno/vectordb/mongodb/__init__.py +7 -1
  285. agno/vectordb/mongodb/mongodb.py +112 -7
  286. agno/vectordb/pgvector/pgvector.py +142 -21
  287. agno/vectordb/pineconedb/pineconedb.py +80 -8
  288. agno/vectordb/qdrant/qdrant.py +125 -39
  289. agno/vectordb/redis/__init__.py +9 -0
  290. agno/vectordb/redis/redisdb.py +694 -0
  291. agno/vectordb/singlestore/singlestore.py +111 -25
  292. agno/vectordb/surrealdb/surrealdb.py +31 -5
  293. agno/vectordb/upstashdb/upstashdb.py +76 -8
  294. agno/vectordb/weaviate/weaviate.py +86 -15
  295. agno/workflow/__init__.py +2 -0
  296. agno/workflow/agent.py +299 -0
  297. agno/workflow/condition.py +112 -18
  298. agno/workflow/loop.py +69 -10
  299. agno/workflow/parallel.py +266 -118
  300. agno/workflow/router.py +110 -17
  301. agno/workflow/step.py +638 -129
  302. agno/workflow/steps.py +65 -6
  303. agno/workflow/types.py +61 -23
  304. agno/workflow/workflow.py +2085 -272
  305. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
  306. agno-2.3.0.dist-info/RECORD +577 -0
  307. agno/knowledge/reader/url_reader.py +0 -128
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -610
  310. agno/utils/models/aws_claude.py +0 -170
  311. agno-2.0.1.dist-info/RECORD +0 -515
  312. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  313. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.0.1.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
- # Embed document
274
- embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
275
- await asyncio.gather(*embed_tasks, return_exceptions=True)
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(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
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(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
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(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
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(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
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