agno 2.2.10__py3-none-any.whl → 2.2.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. agno/agent/agent.py +75 -48
  2. agno/db/dynamo/utils.py +1 -1
  3. agno/db/firestore/utils.py +1 -1
  4. agno/db/gcs_json/utils.py +1 -1
  5. agno/db/in_memory/utils.py +1 -1
  6. agno/db/json/utils.py +1 -1
  7. agno/db/mongo/utils.py +3 -3
  8. agno/db/mysql/mysql.py +1 -1
  9. agno/db/mysql/utils.py +1 -1
  10. agno/db/postgres/utils.py +1 -1
  11. agno/db/redis/utils.py +1 -1
  12. agno/db/singlestore/singlestore.py +1 -1
  13. agno/db/singlestore/utils.py +1 -1
  14. agno/db/sqlite/async_sqlite.py +1 -1
  15. agno/db/sqlite/sqlite.py +1 -1
  16. agno/db/sqlite/utils.py +1 -1
  17. agno/filters.py +354 -0
  18. agno/knowledge/chunking/agentic.py +8 -9
  19. agno/knowledge/chunking/strategy.py +59 -15
  20. agno/knowledge/embedder/sentence_transformer.py +6 -2
  21. agno/knowledge/knowledge.py +43 -22
  22. agno/knowledge/reader/base.py +6 -2
  23. agno/knowledge/utils.py +20 -0
  24. agno/models/anthropic/claude.py +45 -9
  25. agno/models/base.py +4 -0
  26. agno/os/app.py +23 -7
  27. agno/os/interfaces/slack/router.py +53 -33
  28. agno/os/interfaces/slack/slack.py +9 -1
  29. agno/os/router.py +25 -1
  30. agno/os/routers/health.py +5 -3
  31. agno/os/routers/knowledge/knowledge.py +43 -17
  32. agno/os/routers/knowledge/schemas.py +4 -3
  33. agno/run/agent.py +11 -1
  34. agno/run/base.py +3 -2
  35. agno/session/agent.py +10 -5
  36. agno/team/team.py +57 -18
  37. agno/tools/file_generation.py +4 -4
  38. agno/tools/gmail.py +179 -0
  39. agno/tools/parallel.py +314 -0
  40. agno/utils/agent.py +22 -17
  41. agno/utils/gemini.py +15 -5
  42. agno/utils/knowledge.py +12 -5
  43. agno/utils/log.py +1 -0
  44. agno/utils/models/claude.py +2 -1
  45. agno/utils/print_response/agent.py +5 -4
  46. agno/utils/print_response/team.py +5 -4
  47. agno/vectordb/base.py +2 -4
  48. agno/vectordb/cassandra/cassandra.py +12 -5
  49. agno/vectordb/chroma/chromadb.py +10 -4
  50. agno/vectordb/clickhouse/clickhousedb.py +12 -4
  51. agno/vectordb/couchbase/couchbase.py +12 -3
  52. agno/vectordb/lancedb/lance_db.py +69 -144
  53. agno/vectordb/langchaindb/langchaindb.py +13 -4
  54. agno/vectordb/lightrag/lightrag.py +8 -3
  55. agno/vectordb/llamaindex/llamaindexdb.py +10 -4
  56. agno/vectordb/milvus/milvus.py +16 -5
  57. agno/vectordb/mongodb/mongodb.py +14 -3
  58. agno/vectordb/pgvector/pgvector.py +73 -15
  59. agno/vectordb/pineconedb/pineconedb.py +6 -2
  60. agno/vectordb/qdrant/qdrant.py +25 -13
  61. agno/vectordb/redis/redisdb.py +37 -30
  62. agno/vectordb/singlestore/singlestore.py +9 -4
  63. agno/vectordb/surrealdb/surrealdb.py +13 -3
  64. agno/vectordb/upstashdb/upstashdb.py +8 -5
  65. agno/vectordb/weaviate/weaviate.py +29 -12
  66. agno/workflow/step.py +3 -2
  67. agno/workflow/types.py +20 -1
  68. agno/workflow/workflow.py +103 -14
  69. {agno-2.2.10.dist-info → agno-2.2.12.dist-info}/METADATA +4 -1
  70. {agno-2.2.10.dist-info → agno-2.2.12.dist-info}/RECORD +73 -71
  71. {agno-2.2.10.dist-info → agno-2.2.12.dist-info}/WHEEL +0 -0
  72. {agno-2.2.10.dist-info → agno-2.2.12.dist-info}/licenses/LICENSE +0 -0
  73. {agno-2.2.10.dist-info → agno-2.2.12.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from typing import Any, Dict, List, Optional
2
+ from typing import Any, Dict, List, Optional, Union
3
3
 
4
4
  try:
5
5
  from redis import Redis
@@ -12,9 +12,10 @@ try:
12
12
  except ImportError:
13
13
  raise ImportError("`redis` and `redisvl` not installed. Please install using `pip install redis redisvl`")
14
14
 
15
+ from agno.filters import FilterExpr
15
16
  from agno.knowledge.document import Document
16
17
  from agno.knowledge.embedder import Embedder
17
- from agno.utils.log import log_debug, log_info, logger
18
+ from agno.utils.log import log_debug, log_error, log_info, log_warning
18
19
  from agno.utils.string import hash_string_sha256
19
20
  from agno.vectordb.base import VectorDb
20
21
  from agno.vectordb.distance import Distance
@@ -167,7 +168,7 @@ class RedisDB(VectorDb):
167
168
  else:
168
169
  log_debug(f"Redis index already exists: {self.index_name}")
169
170
  except Exception as e:
170
- logger.error(f"Error creating Redis index: {e}")
171
+ log_error(f"Error creating Redis index: {e}")
171
172
  raise
172
173
 
173
174
  async def async_create(self) -> None:
@@ -180,7 +181,7 @@ class RedisDB(VectorDb):
180
181
  if "already exists" in str(e).lower():
181
182
  log_debug(f"Redis index already exists: {self.index_name}")
182
183
  else:
183
- logger.error(f"Error creating Redis index: {e}")
184
+ log_error(f"Error creating Redis index: {e}")
184
185
  raise
185
186
 
186
187
  def doc_exists(self, document: Document) -> bool:
@@ -189,7 +190,7 @@ class RedisDB(VectorDb):
189
190
  doc_id = document.id or hash_string_sha256(document.content)
190
191
  return self.id_exists(doc_id)
191
192
  except Exception as e:
192
- logger.error(f"Error checking if document exists: {e}")
193
+ log_error(f"Error checking if document exists: {e}")
193
194
  return False
194
195
 
195
196
  async def async_doc_exists(self, document: Document) -> bool:
@@ -206,7 +207,7 @@ class RedisDB(VectorDb):
206
207
  results = await async_index.query(query)
207
208
  return len(results) > 0
208
209
  except Exception as e:
209
- logger.error(f"Error checking if document exists: {e}")
210
+ log_error(f"Error checking if document exists: {e}")
210
211
  return False
211
212
 
212
213
  def name_exists(self, name: str) -> bool:
@@ -221,7 +222,7 @@ class RedisDB(VectorDb):
221
222
  results = self.index.query(query)
222
223
  return len(results) > 0
223
224
  except Exception as e:
224
- logger.error(f"Error checking if name exists: {e}")
225
+ log_error(f"Error checking if name exists: {e}")
225
226
  return False
226
227
 
227
228
  async def async_name_exists(self, name: str) -> bool: # type: ignore[override]
@@ -237,7 +238,7 @@ class RedisDB(VectorDb):
237
238
  results = await async_index.query(query)
238
239
  return len(results) > 0
239
240
  except Exception as e:
240
- logger.error(f"Error checking if name exists: {e}")
241
+ log_error(f"Error checking if name exists: {e}")
241
242
  return False
242
243
 
243
244
  def id_exists(self, id: str) -> bool:
@@ -252,7 +253,7 @@ class RedisDB(VectorDb):
252
253
  results = self.index.query(query)
253
254
  return len(results) > 0
254
255
  except Exception as e:
255
- logger.error(f"Error checking if ID exists: {e}")
256
+ log_error(f"Error checking if ID exists: {e}")
256
257
  return False
257
258
 
258
259
  def content_hash_exists(self, content_hash: str) -> bool:
@@ -267,7 +268,7 @@ class RedisDB(VectorDb):
267
268
  results = self.index.query(query)
268
269
  return len(results) > 0
269
270
  except Exception as e:
270
- logger.error(f"Error checking if content hash exists: {e}")
271
+ log_error(f"Error checking if content hash exists: {e}")
271
272
  return False
272
273
 
273
274
  def _parse_redis_hash(self, doc: Document):
@@ -314,7 +315,7 @@ class RedisDB(VectorDb):
314
315
  self.index.load(parsed_documents, id_field="id")
315
316
  log_debug(f"Inserted {len(documents)} documents with content_hash: {content_hash}")
316
317
  except Exception as e:
317
- logger.error(f"Error inserting documents: {e}")
318
+ log_error(f"Error inserting documents: {e}")
318
319
  raise
319
320
 
320
321
  async def async_insert(
@@ -334,7 +335,7 @@ class RedisDB(VectorDb):
334
335
  await async_index.load(parsed_documents, id_field="id")
335
336
  log_debug(f"Inserted {len(documents)} documents with content_hash: {content_hash}")
336
337
  except Exception as e:
337
- logger.error(f"Error inserting documents: {e}")
338
+ log_error(f"Error inserting documents: {e}")
338
339
  raise
339
340
 
340
341
  def upsert_available(self) -> bool:
@@ -368,7 +369,7 @@ class RedisDB(VectorDb):
368
369
  # Insert new docs
369
370
  self.insert(content_hash, documents, filters)
370
371
  except Exception as e:
371
- logger.error(f"Error upserting documents: {e}")
372
+ log_error(f"Error upserting documents: {e}")
372
373
  raise
373
374
 
374
375
  async def async_upsert(
@@ -400,11 +401,17 @@ class RedisDB(VectorDb):
400
401
  # Insert new docs
401
402
  await self.async_insert(content_hash, documents, filters)
402
403
  except Exception as e:
403
- logger.error(f"Error upserting documents: {e}")
404
+ log_error(f"Error upserting documents: {e}")
404
405
  raise
405
406
 
406
- def search(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
407
+ def search(
408
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
409
+ ) -> List[Document]:
407
410
  """Search for documents using the specified search type."""
411
+
412
+ if filters and isinstance(filters, List):
413
+ log_warning("Filters Expressions are not supported in Redis. No filters will be applied.")
414
+ filters = None
408
415
  try:
409
416
  if self.search_type == SearchType.vector:
410
417
  return self.vector_search(query, limit)
@@ -415,11 +422,11 @@ class RedisDB(VectorDb):
415
422
  else:
416
423
  raise ValueError(f"Unsupported search type: {self.search_type}")
417
424
  except Exception as e:
418
- logger.error(f"Error in search: {e}")
425
+ log_error(f"Error in search: {e}")
419
426
  return []
420
427
 
421
428
  async def async_search(
422
- self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
429
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
423
430
  ) -> List[Document]:
424
431
  """Async version of search method."""
425
432
  return await asyncio.to_thread(self.search, query, limit, filters)
@@ -448,7 +455,7 @@ class RedisDB(VectorDb):
448
455
 
449
456
  return documents
450
457
  except Exception as e:
451
- logger.error(f"Error in vector search: {e}")
458
+ log_error(f"Error in vector search: {e}")
452
459
  return []
453
460
 
454
461
  def keyword_search(self, query: str, limit: int = 5) -> List[Document]:
@@ -471,7 +478,7 @@ class RedisDB(VectorDb):
471
478
 
472
479
  return documents
473
480
  except Exception as e:
474
- logger.error(f"Error in keyword search: {e}")
481
+ log_error(f"Error in keyword search: {e}")
475
482
  return []
476
483
 
477
484
  def hybrid_search(self, query: str, limit: int = 5) -> List[Document]:
@@ -500,7 +507,7 @@ class RedisDB(VectorDb):
500
507
 
501
508
  return documents
502
509
  except Exception as e:
503
- logger.error(f"Error in hybrid search: {e}")
510
+ log_error(f"Error in hybrid search: {e}")
504
511
  return []
505
512
 
506
513
  def drop(self) -> bool: # type: ignore[override]
@@ -510,7 +517,7 @@ class RedisDB(VectorDb):
510
517
  log_debug(f"Deleted Redis index: {self.index_name}")
511
518
  return True
512
519
  except Exception as e:
513
- logger.error(f"Error dropping Redis index: {e}")
520
+ log_error(f"Error dropping Redis index: {e}")
514
521
  return False
515
522
 
516
523
  async def async_drop(self) -> None:
@@ -520,7 +527,7 @@ class RedisDB(VectorDb):
520
527
  await async_index.delete(drop=True)
521
528
  log_debug(f"Deleted Redis index: {self.index_name}")
522
529
  except Exception as e:
523
- logger.error(f"Error dropping Redis index: {e}")
530
+ log_error(f"Error dropping Redis index: {e}")
524
531
  raise
525
532
 
526
533
  def exists(self) -> bool:
@@ -528,7 +535,7 @@ class RedisDB(VectorDb):
528
535
  try:
529
536
  return self.index.exists()
530
537
  except Exception as e:
531
- logger.error(f"Error checking if index exists: {e}")
538
+ log_error(f"Error checking if index exists: {e}")
532
539
  return False
533
540
 
534
541
  async def async_exists(self) -> bool:
@@ -537,7 +544,7 @@ class RedisDB(VectorDb):
537
544
  async_index = await self._get_async_index()
538
545
  return await async_index.exists()
539
546
  except Exception as e:
540
- logger.error(f"Error checking if index exists: {e}")
547
+ log_error(f"Error checking if index exists: {e}")
541
548
  return False
542
549
 
543
550
  def optimize(self) -> None:
@@ -551,7 +558,7 @@ class RedisDB(VectorDb):
551
558
  self.index.clear()
552
559
  return True
553
560
  except Exception as e:
554
- logger.error(f"Error deleting Redis index: {e}")
561
+ log_error(f"Error deleting Redis index: {e}")
555
562
  return False
556
563
 
557
564
  def delete_by_id(self, id: str) -> bool:
@@ -562,7 +569,7 @@ class RedisDB(VectorDb):
562
569
  log_debug(f"Deleted document with id '{id}' from Redis index")
563
570
  return result > 0
564
571
  except Exception as e:
565
- logger.error(f"Error deleting document by ID: {e}")
572
+ log_error(f"Error deleting document by ID: {e}")
566
573
  return False
567
574
 
568
575
  def delete_by_name(self, name: str) -> bool:
@@ -588,7 +595,7 @@ class RedisDB(VectorDb):
588
595
  log_debug(f"Deleted {deleted_count} documents with name '{name}'")
589
596
  return deleted_count > 0
590
597
  except Exception as e:
591
- logger.error(f"Error deleting documents by name: {e}")
598
+ log_error(f"Error deleting documents by name: {e}")
592
599
  return False
593
600
 
594
601
  def delete_by_metadata(self, metadata: Dict[str, Any]) -> bool:
@@ -626,7 +633,7 @@ class RedisDB(VectorDb):
626
633
  log_debug(f"Deleted {deleted_count} documents with metadata {metadata}")
627
634
  return deleted_count > 0
628
635
  except Exception as e:
629
- logger.error(f"Error deleting documents by metadata: {e}")
636
+ log_error(f"Error deleting documents by metadata: {e}")
630
637
  return False
631
638
 
632
639
  def delete_by_content_id(self, content_id: str) -> bool:
@@ -652,7 +659,7 @@ class RedisDB(VectorDb):
652
659
  log_debug(f"Deleted {deleted_count} documents with content_id '{content_id}'")
653
660
  return deleted_count > 0
654
661
  except Exception as e:
655
- logger.error(f"Error deleting documents by content_id: {e}")
662
+ log_error(f"Error deleting documents by content_id: {e}")
656
663
  return False
657
664
 
658
665
  def update_metadata(self, content_id: str, metadata: Dict[str, Any]) -> None:
@@ -679,7 +686,7 @@ class RedisDB(VectorDb):
679
686
 
680
687
  log_debug(f"Updated metadata for documents with content_id '{content_id}'")
681
688
  except Exception as e:
682
- logger.error(f"Error updating metadata: {e}")
689
+ log_error(f"Error updating metadata: {e}")
683
690
  raise
684
691
 
685
692
  def get_supported_search_types(self) -> List[str]:
@@ -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
 
@@ -282,7 +283,9 @@ class SingleStore(VectorDb):
282
283
  sess.commit()
283
284
  log_debug(f"Committed {counter} documents")
284
285
 
285
- def search(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
286
+ def search(
287
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
288
+ ) -> List[Document]:
286
289
  """
287
290
  Search for documents based on a query and optional filters.
288
291
 
@@ -294,6 +297,8 @@ class SingleStore(VectorDb):
294
297
  Returns:
295
298
  List[Document]: List of documents that match the query.
296
299
  """
300
+ if filters is not None:
301
+ log_warning("Filters are not supported in SingleStore. No filters will be applied.")
297
302
  query_embedding = self.embedder.get_embedding(query)
298
303
  if query_embedding is None:
299
304
  log_error(f"Error getting embedding for Query: {query}")
@@ -665,7 +670,7 @@ class SingleStore(VectorDb):
665
670
  log_debug(f"Committed {counter} documents")
666
671
 
667
672
  async def async_search(
668
- 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
669
674
  ) -> List[Document]:
670
675
  return self.search(query=query, limit=limit, filters=filters)
671
676
 
@@ -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
 
@@ -318,7 +319,9 @@ class SurrealDb(VectorDb):
318
319
  thing = f"{self.collection}:{doc.id}" if doc.id else self.collection
319
320
  self.client.query(self.UPSERT_QUERY.format(thing=thing), data)
320
321
 
321
- def search(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
322
+ def search(
323
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
324
+ ) -> List[Document]:
322
325
  """Search for similar documents.
323
326
 
324
327
  Args:
@@ -330,6 +333,9 @@ class SurrealDb(VectorDb):
330
333
  A list of documents that are similar to the query.
331
334
 
332
335
  """
336
+ if isinstance(filters, List):
337
+ log_warning("Filters Expressions are not supported in SurrealDB. No filters will be applied.")
338
+ filters = None
333
339
  query_embedding = self.embedder.get_embedding(query)
334
340
  if query_embedding is None:
335
341
  log_error(f"Error getting embedding for Query: {query}")
@@ -560,7 +566,7 @@ class SurrealDb(VectorDb):
560
566
  self,
561
567
  query: str,
562
568
  limit: int = 5,
563
- filters: Optional[Dict[str, Any]] = None,
569
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
564
570
  ) -> List[Document]:
565
571
  """Search for similar documents asynchronously.
566
572
 
@@ -573,6 +579,10 @@ class SurrealDb(VectorDb):
573
579
  A list of documents that are similar to the query.
574
580
 
575
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
+
576
586
  query_embedding = self.embedder.get_embedding(query)
577
587
  if query_embedding is None:
578
588
  log_error(f"Error getting embedding for Query: {query}")
@@ -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 = ""
@@ -324,7 +325,7 @@ class UpstashVectorDb(VectorDb):
324
325
  self,
325
326
  query: str,
326
327
  limit: int = 5,
327
- filters: Optional[Dict[str, Any]] = None,
328
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
328
329
  namespace: Optional[str] = None,
329
330
  ) -> List[Document]:
330
331
  """Search for documents in the index.
@@ -337,7 +338,9 @@ class UpstashVectorDb(VectorDb):
337
338
  List[Document]: List of matching documents.
338
339
  """
339
340
  _namespace = self.namespace if namespace is None else namespace
340
-
341
+ if isinstance(filters, List):
342
+ log_warning("Filters Expressions are not supported in UpstashDB. No filters will be applied.")
343
+ filters = None
341
344
  filter_str = "" if filters is None else str(filters)
342
345
 
343
346
  if not self.use_upstash_embeddings and self.embedder is not None:
@@ -623,7 +626,7 @@ class UpstashVectorDb(VectorDb):
623
626
  self.index.upsert(vectors, namespace=_namespace)
624
627
 
625
628
  async def async_search(
626
- 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
627
630
  ) -> List[Document]:
628
631
  raise NotImplementedError(f"Async not supported on {self.__class__.__name__}.")
629
632
 
@@ -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
@@ -392,7 +393,9 @@ class Weaviate(VectorDb):
392
393
  await self.async_insert(content_hash=content_hash, documents=documents, filters=filters)
393
394
  return
394
395
 
395
- 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]:
396
399
  """
397
400
  Perform a search based on the configured search type.
398
401
 
@@ -404,6 +407,9 @@ class Weaviate(VectorDb):
404
407
  Returns:
405
408
  List[Document]: List of matching documents.
406
409
  """
410
+ if isinstance(filters, List):
411
+ log_warning("Filters Expressions are not supported in Weaviate. No filters will be applied.")
412
+ filters = None
407
413
  if self.search_type == SearchType.vector:
408
414
  return self.vector_search(query, limit, filters)
409
415
  elif self.search_type == SearchType.keyword:
@@ -415,7 +421,7 @@ class Weaviate(VectorDb):
415
421
  return []
416
422
 
417
423
  async def async_search(
418
- 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
419
425
  ) -> List[Document]:
420
426
  """
421
427
  Perform a search based on the configured search type asynchronously.
@@ -428,6 +434,9 @@ class Weaviate(VectorDb):
428
434
  Returns:
429
435
  List[Document]: List of matching documents.
430
436
  """
437
+ if isinstance(filters, List):
438
+ log_warning("Filters Expressions are not supported in Weaviate. No filters will be applied.")
439
+ filters = None
431
440
  if self.search_type == SearchType.vector:
432
441
  return await self.async_vector_search(query, limit, filters)
433
442
  elif self.search_type == SearchType.keyword:
@@ -438,7 +447,9 @@ class Weaviate(VectorDb):
438
447
  logger.error(f"Invalid search type '{self.search_type}'.")
439
448
  return []
440
449
 
441
- 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]:
442
453
  try:
443
454
  query_embedding = self.embedder.get_embedding(query)
444
455
  if query_embedding is None:
@@ -473,7 +484,7 @@ class Weaviate(VectorDb):
473
484
  self.get_client().close()
474
485
 
475
486
  async def async_vector_search(
476
- 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
477
488
  ) -> List[Document]:
478
489
  """
479
490
  Perform a vector search in Weaviate asynchronously.
@@ -518,7 +529,9 @@ class Weaviate(VectorDb):
518
529
  logger.error(f"Error searching for documents: {e}")
519
530
  return []
520
531
 
521
- 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]:
522
535
  try:
523
536
  collection = self.get_client().collections.get(self.collection)
524
537
  filter_expr = self._build_filter_expression(filters)
@@ -549,7 +562,7 @@ class Weaviate(VectorDb):
549
562
  self.get_client().close()
550
563
 
551
564
  async def async_keyword_search(
552
- 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
553
566
  ) -> List[Document]:
554
567
  """
555
568
  Perform a keyword search in Weaviate asynchronously.
@@ -590,7 +603,9 @@ class Weaviate(VectorDb):
590
603
  logger.error(f"Error searching for documents: {e}")
591
604
  return []
592
605
 
593
- 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]:
594
609
  try:
595
610
  query_embedding = self.embedder.get_embedding(query)
596
611
  if query_embedding is None:
@@ -628,7 +643,7 @@ class Weaviate(VectorDb):
628
643
  self.get_client().close()
629
644
 
630
645
  async def async_hybrid_search(
631
- 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
632
647
  ) -> List[Document]:
633
648
  """
634
649
  Perform a hybrid search combining vector and keyword search in Weaviate asynchronously.
@@ -849,7 +864,7 @@ class Weaviate(VectorDb):
849
864
  """Indicate that upsert functionality is available."""
850
865
  return True
851
866
 
852
- def _build_filter_expression(self, filters: Optional[Dict[str, Any]]):
867
+ def _build_filter_expression(self, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]]):
853
868
  """
854
869
  Build a filter expression for Weaviate queries.
855
870
 
@@ -861,7 +876,9 @@ class Weaviate(VectorDb):
861
876
  """
862
877
  if not filters:
863
878
  return None
864
-
879
+ if isinstance(filters, List):
880
+ log_warning("Filters Expressions are not supported in Weaviate. No filters will be applied.")
881
+ return None
865
882
  try:
866
883
  # Create a filter for each key-value pair
867
884
  filter_conditions = []
agno/workflow/step.py CHANGED
@@ -11,9 +11,10 @@ from agno.agent import Agent
11
11
  from agno.media import Audio, Image, Video
12
12
  from agno.models.metrics import Metrics
13
13
  from agno.run import RunContext
14
- from agno.run.agent import RunCompletedEvent, RunOutput, RunContentEvent
14
+ from agno.run.agent import RunCompletedEvent, RunContentEvent, RunOutput
15
15
  from agno.run.base import BaseRunOutputEvent
16
- from agno.run.team import RunCompletedEvent as TeamRunCompletedEvent, RunContentEvent as TeamRunContentEvent
16
+ from agno.run.team import RunCompletedEvent as TeamRunCompletedEvent
17
+ from agno.run.team import RunContentEvent as TeamRunContentEvent
17
18
  from agno.run.team import TeamRunOutput
18
19
  from agno.run.workflow import (
19
20
  StepCompletedEvent,
agno/workflow/types.py CHANGED
@@ -17,6 +17,7 @@ from agno.utils.media import (
17
17
  reconstruct_videos,
18
18
  )
19
19
  from agno.utils.serialize import json_serializer
20
+ from agno.utils.timer import Timer
20
21
 
21
22
 
22
23
  @dataclass
@@ -405,12 +406,18 @@ class WorkflowMetrics:
405
406
  """Complete metrics for a workflow execution"""
406
407
 
407
408
  steps: Dict[str, StepMetrics]
409
+ # Timer utility for tracking execution time
410
+ timer: Optional[Timer] = None
411
+ # Total workflow execution time
412
+ duration: Optional[float] = None
408
413
 
409
414
  def to_dict(self) -> Dict[str, Any]:
410
415
  """Convert to dictionary"""
411
- return {
416
+ result: Dict[str, Any] = {
412
417
  "steps": {name: step.to_dict() for name, step in self.steps.items()},
418
+ "duration": self.duration,
413
419
  }
420
+ return result
414
421
 
415
422
  @classmethod
416
423
  def from_dict(cls, data: Dict[str, Any]) -> "WorkflowMetrics":
@@ -419,8 +426,20 @@ class WorkflowMetrics:
419
426
 
420
427
  return cls(
421
428
  steps=steps,
429
+ duration=data.get("duration"),
422
430
  )
423
431
 
432
+ def start_timer(self):
433
+ if self.timer is None:
434
+ self.timer = Timer()
435
+ self.timer.start()
436
+
437
+ def stop_timer(self, set_duration: bool = True):
438
+ if self.timer is not None:
439
+ self.timer.stop()
440
+ if set_duration:
441
+ self.duration = self.timer.elapsed
442
+
424
443
 
425
444
  @dataclass
426
445
  class WebSocketHandler: