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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. agno/agent/agent.py +62 -47
  2. agno/db/mysql/mysql.py +1 -1
  3. agno/db/singlestore/singlestore.py +1 -1
  4. agno/db/sqlite/async_sqlite.py +1 -1
  5. agno/db/sqlite/sqlite.py +1 -1
  6. agno/filters.py +354 -0
  7. agno/knowledge/knowledge.py +43 -22
  8. agno/os/interfaces/slack/router.py +53 -33
  9. agno/os/interfaces/slack/slack.py +9 -1
  10. agno/os/router.py +25 -1
  11. agno/run/base.py +3 -2
  12. agno/session/agent.py +10 -5
  13. agno/team/team.py +44 -17
  14. agno/utils/agent.py +22 -17
  15. agno/utils/gemini.py +15 -5
  16. agno/utils/knowledge.py +12 -5
  17. agno/utils/log.py +1 -0
  18. agno/utils/print_response/agent.py +5 -4
  19. agno/utils/print_response/team.py +5 -4
  20. agno/vectordb/base.py +2 -4
  21. agno/vectordb/cassandra/cassandra.py +12 -5
  22. agno/vectordb/chroma/chromadb.py +10 -4
  23. agno/vectordb/clickhouse/clickhousedb.py +12 -4
  24. agno/vectordb/couchbase/couchbase.py +12 -3
  25. agno/vectordb/lancedb/lance_db.py +69 -144
  26. agno/vectordb/langchaindb/langchaindb.py +13 -4
  27. agno/vectordb/lightrag/lightrag.py +8 -3
  28. agno/vectordb/llamaindex/llamaindexdb.py +10 -4
  29. agno/vectordb/milvus/milvus.py +16 -5
  30. agno/vectordb/mongodb/mongodb.py +14 -3
  31. agno/vectordb/pgvector/pgvector.py +73 -15
  32. agno/vectordb/pineconedb/pineconedb.py +6 -2
  33. agno/vectordb/qdrant/qdrant.py +25 -13
  34. agno/vectordb/redis/redisdb.py +37 -30
  35. agno/vectordb/singlestore/singlestore.py +9 -4
  36. agno/vectordb/surrealdb/surrealdb.py +13 -3
  37. agno/vectordb/upstashdb/upstashdb.py +8 -5
  38. agno/vectordb/weaviate/weaviate.py +29 -12
  39. agno/workflow/workflow.py +13 -7
  40. {agno-2.2.11.dist-info → agno-2.2.12.dist-info}/METADATA +1 -1
  41. {agno-2.2.11.dist-info → agno-2.2.12.dist-info}/RECORD +44 -43
  42. {agno-2.2.11.dist-info → agno-2.2.12.dist-info}/WHEEL +0 -0
  43. {agno-2.2.11.dist-info → agno-2.2.12.dist-info}/licenses/LICENSE +0 -0
  44. {agno-2.2.11.dist-info → agno-2.2.12.dist-info}/top_level.txt +0 -0
agno/session/agent.py CHANGED
@@ -1,11 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import asdict, dataclass
4
- from typing import Any, Dict, List, Mapping, Optional
4
+ from typing import Any, Dict, List, Mapping, Optional, Union
5
5
 
6
6
  from agno.models.message import Message
7
7
  from agno.run.agent import RunOutput
8
8
  from agno.run.base import RunStatus
9
+ from agno.run.team import TeamRunOutput
9
10
  from agno.session.summary import SessionSummary
10
11
  from agno.utils.log import log_debug, log_warning
11
12
 
@@ -33,7 +34,7 @@ class AgentSession:
33
34
  # Agent Data: agent_id, name and model
34
35
  agent_data: Optional[Dict[str, Any]] = None
35
36
  # List of all runs in the session
36
- runs: Optional[List[RunOutput]] = None
37
+ runs: Optional[List[Union[RunOutput, TeamRunOutput]]] = None
37
38
  # Summary of the session
38
39
  summary: Optional["SessionSummary"] = None
39
40
 
@@ -57,9 +58,13 @@ class AgentSession:
57
58
  return None
58
59
 
59
60
  runs = data.get("runs")
60
- serialized_runs: List[RunOutput] = []
61
+ serialized_runs: List[Union[RunOutput, TeamRunOutput]] = []
61
62
  if runs is not None and isinstance(runs[0], dict):
62
- serialized_runs = [RunOutput.from_dict(run) for run in runs]
63
+ for run in runs:
64
+ if "agent_id" in run:
65
+ serialized_runs.append(RunOutput.from_dict(run))
66
+ elif "team_id" in run:
67
+ serialized_runs.append(TeamRunOutput.from_dict(run))
63
68
 
64
69
  summary = data.get("summary")
65
70
  if summary is not None and isinstance(summary, dict):
@@ -101,7 +106,7 @@ class AgentSession:
101
106
 
102
107
  log_debug("Added RunOutput to Agent Session")
103
108
 
104
- def get_run(self, run_id: str) -> Optional[RunOutput]:
109
+ def get_run(self, run_id: str) -> Optional[Union[RunOutput, TeamRunOutput]]:
105
110
  for run in self.runs or []:
106
111
  if run.run_id == run_id:
107
112
  return run
agno/team/team.py CHANGED
@@ -36,6 +36,7 @@ from agno.exceptions import (
36
36
  OutputCheckError,
37
37
  RunCancelledException,
38
38
  )
39
+ from agno.filters import FilterExpr
39
40
  from agno.guardrails import BaseGuardrail
40
41
  from agno.knowledge.knowledge import Knowledge
41
42
  from agno.knowledge.types import KnowledgeFilter
@@ -288,7 +289,7 @@ class Team:
288
289
  # --- Agent Knowledge ---
289
290
  knowledge: Optional[Knowledge] = None
290
291
  # Add knowledge_filters to the Agent class attributes
291
- knowledge_filters: Optional[Dict[str, Any]] = None
292
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
292
293
  # Let the agent choose the knowledge filters
293
294
  enable_agentic_knowledge_filters: Optional[bool] = False
294
295
  # Add a tool that allows the Team to update Knowledge.
@@ -474,7 +475,7 @@ class Team:
474
475
  dependencies: Optional[Dict[str, Any]] = None,
475
476
  add_dependencies_to_context: bool = False,
476
477
  knowledge: Optional[Knowledge] = None,
477
- knowledge_filters: Optional[Dict[str, Any]] = None,
478
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
478
479
  add_knowledge_to_context: bool = False,
479
480
  enable_agentic_knowledge_filters: Optional[bool] = False,
480
481
  update_knowledge: bool = False,
@@ -1742,7 +1743,7 @@ class Team:
1742
1743
  images: Optional[Sequence[Image]] = None,
1743
1744
  videos: Optional[Sequence[Video]] = None,
1744
1745
  files: Optional[Sequence[File]] = None,
1745
- knowledge_filters: Optional[Dict[str, Any]] = None,
1746
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
1746
1747
  add_history_to_context: Optional[bool] = None,
1747
1748
  add_dependencies_to_context: Optional[bool] = None,
1748
1749
  add_session_state_to_context: Optional[bool] = None,
@@ -1769,7 +1770,7 @@ class Team:
1769
1770
  images: Optional[Sequence[Image]] = None,
1770
1771
  videos: Optional[Sequence[Video]] = None,
1771
1772
  files: Optional[Sequence[File]] = None,
1772
- knowledge_filters: Optional[Dict[str, Any]] = None,
1773
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
1773
1774
  add_history_to_context: Optional[bool] = None,
1774
1775
  add_dependencies_to_context: Optional[bool] = None,
1775
1776
  add_session_state_to_context: Optional[bool] = None,
@@ -1797,7 +1798,7 @@ class Team:
1797
1798
  images: Optional[Sequence[Image]] = None,
1798
1799
  videos: Optional[Sequence[Video]] = None,
1799
1800
  files: Optional[Sequence[File]] = None,
1800
- knowledge_filters: Optional[Dict[str, Any]] = None,
1801
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
1801
1802
  add_history_to_context: Optional[bool] = None,
1802
1803
  add_dependencies_to_context: Optional[bool] = None,
1803
1804
  add_session_state_to_context: Optional[bool] = None,
@@ -2602,7 +2603,7 @@ class Team:
2602
2603
  images: Optional[Sequence[Image]] = None,
2603
2604
  videos: Optional[Sequence[Video]] = None,
2604
2605
  files: Optional[Sequence[File]] = None,
2605
- knowledge_filters: Optional[Dict[str, Any]] = None,
2606
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
2606
2607
  add_history_to_context: Optional[bool] = None,
2607
2608
  add_dependencies_to_context: Optional[bool] = None,
2608
2609
  add_session_state_to_context: Optional[bool] = None,
@@ -2629,7 +2630,7 @@ class Team:
2629
2630
  images: Optional[Sequence[Image]] = None,
2630
2631
  videos: Optional[Sequence[Video]] = None,
2631
2632
  files: Optional[Sequence[File]] = None,
2632
- knowledge_filters: Optional[Dict[str, Any]] = None,
2633
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
2633
2634
  add_history_to_context: Optional[bool] = None,
2634
2635
  add_dependencies_to_context: Optional[bool] = None,
2635
2636
  add_session_state_to_context: Optional[bool] = None,
@@ -2657,7 +2658,7 @@ class Team:
2657
2658
  images: Optional[Sequence[Image]] = None,
2658
2659
  videos: Optional[Sequence[Video]] = None,
2659
2660
  files: Optional[Sequence[File]] = None,
2660
- knowledge_filters: Optional[Dict[str, Any]] = None,
2661
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
2661
2662
  add_history_to_context: Optional[bool] = None,
2662
2663
  add_dependencies_to_context: Optional[bool] = None,
2663
2664
  add_session_state_to_context: Optional[bool] = None,
@@ -3872,7 +3873,7 @@ class Team:
3872
3873
  videos: Optional[Sequence[Video]] = None,
3873
3874
  files: Optional[Sequence[File]] = None,
3874
3875
  markdown: Optional[bool] = None,
3875
- knowledge_filters: Optional[Dict[str, Any]] = None,
3876
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
3876
3877
  add_history_to_context: Optional[bool] = None,
3877
3878
  add_dependencies_to_context: Optional[bool] = None,
3878
3879
  add_session_state_to_context: Optional[bool] = None,
@@ -3982,7 +3983,7 @@ class Team:
3982
3983
  videos: Optional[Sequence[Video]] = None,
3983
3984
  files: Optional[Sequence[File]] = None,
3984
3985
  markdown: Optional[bool] = None,
3985
- knowledge_filters: Optional[Dict[str, Any]] = None,
3986
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
3986
3987
  add_history_to_context: Optional[bool] = None,
3987
3988
  dependencies: Optional[Dict[str, Any]] = None,
3988
3989
  add_dependencies_to_context: Optional[bool] = None,
@@ -7144,7 +7145,7 @@ class Team:
7144
7145
 
7145
7146
  # Yield the member event directly
7146
7147
  member_agent_run_response_event.parent_run_id = (
7147
- member_agent_run_response_event.parent_run_id or run_response.run_id
7148
+ getattr(member_agent_run_response_event, 'parent_run_id', None) or run_response.run_id
7148
7149
  )
7149
7150
  yield member_agent_run_response_event
7150
7151
  else:
@@ -8391,7 +8392,11 @@ class Team:
8391
8392
  return "Successfully added to knowledge base"
8392
8393
 
8393
8394
  def get_relevant_docs_from_knowledge(
8394
- self, query: str, num_documents: Optional[int] = None, filters: Optional[Dict[str, Any]] = None, **kwargs
8395
+ self,
8396
+ query: str,
8397
+ num_documents: Optional[int] = None,
8398
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
8399
+ **kwargs,
8395
8400
  ) -> Optional[List[Union[Dict[str, Any], str]]]:
8396
8401
  """Return a list of references from the knowledge base"""
8397
8402
  from agno.knowledge.document import Document
@@ -8414,6 +8419,10 @@ class Team:
8414
8419
  if not filters:
8415
8420
  log_warning("No valid filters remain after validation. Search will proceed without filters.")
8416
8421
 
8422
+ if invalid_keys == [] and valid_filters == {}:
8423
+ log_warning("No valid filters provided. Search will proceed without filters.")
8424
+ filters = None
8425
+
8417
8426
  if self.knowledge_retriever is not None and callable(self.knowledge_retriever):
8418
8427
  from inspect import signature
8419
8428
 
@@ -8451,7 +8460,11 @@ class Team:
8451
8460
  raise e
8452
8461
 
8453
8462
  async def aget_relevant_docs_from_knowledge(
8454
- self, query: str, num_documents: Optional[int] = None, filters: Optional[Dict[str, Any]] = None, **kwargs
8463
+ self,
8464
+ query: str,
8465
+ num_documents: Optional[int] = None,
8466
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
8467
+ **kwargs,
8455
8468
  ) -> Optional[List[Union[Dict[str, Any], str]]]:
8456
8469
  """Get relevant documents from knowledge base asynchronously."""
8457
8470
  from agno.knowledge.document import Document
@@ -8475,6 +8488,10 @@ class Team:
8475
8488
  if not filters:
8476
8489
  log_warning("No valid filters remain after validation. Search will proceed without filters.")
8477
8490
 
8491
+ if invalid_keys == [] and valid_filters == {}:
8492
+ log_warning("No valid filters provided. Search will proceed without filters.")
8493
+ filters = None
8494
+
8478
8495
  if self.knowledge_retriever is not None and callable(self.knowledge_retriever):
8479
8496
  from inspect import isawaitable, signature
8480
8497
 
@@ -8531,7 +8548,9 @@ class Team:
8531
8548
 
8532
8549
  return json.dumps(docs, indent=2)
8533
8550
 
8534
- def _get_effective_filters(self, knowledge_filters: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
8551
+ def _get_effective_filters(
8552
+ self, knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
8553
+ ) -> Optional[Any]:
8535
8554
  """
8536
8555
  Determine effective filters for the team, considering:
8537
8556
  1. Team-level filters (self.knowledge_filters)
@@ -8548,7 +8567,15 @@ class Team:
8548
8567
  # Apply run-time filters if they exist
8549
8568
  if knowledge_filters:
8550
8569
  if effective_filters:
8551
- effective_filters.update(knowledge_filters)
8570
+ if isinstance(effective_filters, dict):
8571
+ if isinstance(knowledge_filters, dict):
8572
+ effective_filters.update(cast(Dict[str, Any], knowledge_filters))
8573
+ else:
8574
+ # If knowledge_filters is not a dict (e.g., list of FilterExpr), combine as list if effective_filters is dict
8575
+ # Convert the dict to a list and concatenate
8576
+ effective_filters = cast(Any, [effective_filters, *knowledge_filters])
8577
+ else:
8578
+ effective_filters = [*effective_filters, *knowledge_filters]
8552
8579
  else:
8553
8580
  effective_filters = knowledge_filters
8554
8581
 
@@ -8557,7 +8584,7 @@ class Team:
8557
8584
  def _get_search_knowledge_base_function(
8558
8585
  self,
8559
8586
  run_response: TeamRunOutput,
8560
- knowledge_filters: Optional[Dict[str, Any]] = None,
8587
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
8561
8588
  async_mode: bool = False,
8562
8589
  ) -> Function:
8563
8590
  """Factory function to create a search_knowledge_base function with filters."""
@@ -8626,7 +8653,7 @@ class Team:
8626
8653
  def _get_search_knowledge_base_with_agentic_filters_function(
8627
8654
  self,
8628
8655
  run_response: TeamRunOutput,
8629
- knowledge_filters: Optional[Dict[str, Any]] = None,
8656
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
8630
8657
  async_mode: bool = False,
8631
8658
  ) -> Function:
8632
8659
  """Factory function to create a search_knowledge_base function with filters."""
agno/utils/agent.py CHANGED
@@ -8,7 +8,7 @@ from agno.models.response import ModelResponse
8
8
  from agno.run.agent import RunEvent, RunInput, RunOutput, RunOutputEvent
9
9
  from agno.run.team import RunOutputEvent as TeamRunOutputEvent
10
10
  from agno.run.team import TeamRunOutput
11
- from agno.session import AgentSession, TeamSession
11
+ from agno.session import AgentSession, TeamSession, WorkflowSession
12
12
  from agno.utils.events import (
13
13
  create_memory_update_completed_event,
14
14
  create_memory_update_started_event,
@@ -457,7 +457,12 @@ def scrub_history_messages_from_run_output(run_response: Union[RunOutput, TeamRu
457
457
 
458
458
  def get_run_output_util(
459
459
  entity: Union["Agent", "Team"], run_id: str, session_id: Optional[str] = None
460
- ) -> Optional[Union[RunOutput, TeamRunOutput]]:
460
+ ) -> Optional[
461
+ Union[
462
+ RunOutput,
463
+ TeamRunOutput,
464
+ ]
465
+ ]:
461
466
  """
462
467
  Get a RunOutput from the database.
463
468
 
@@ -473,13 +478,13 @@ def get_run_output_util(
473
478
  if session is not None:
474
479
  run_response = session.get_run(run_id=run_id)
475
480
  if run_response is not None:
476
- return run_response
481
+ return run_response # type: ignore
477
482
  else:
478
483
  log_warning(f"RunOutput {run_id} not found in Session {session_id}")
479
484
  elif entity.cached_session is not None:
480
485
  run_response = entity.cached_session.get_run(run_id=run_id)
481
486
  if run_response is not None:
482
- return run_response
487
+ return run_response # type: ignore
483
488
  else:
484
489
  log_warning(f"RunOutput {run_id} not found in Session {entity.cached_session.session_id}")
485
490
  return None
@@ -501,7 +506,7 @@ async def aget_run_output_util(
501
506
  if session is not None:
502
507
  run_response = session.get_run(run_id=run_id)
503
508
  if run_response is not None:
504
- return run_response
509
+ return run_response # type: ignore
505
510
  else:
506
511
  log_warning(f"RunOutput {run_id} not found in Session {session_id}")
507
512
  elif entity.cached_session is not None:
@@ -535,10 +540,10 @@ def get_last_run_output_util(
535
540
  for run_output in reversed(session.runs):
536
541
  if entity.__class__.__name__ == "Agent":
537
542
  if hasattr(run_output, "agent_id") and run_output.agent_id == entity.id:
538
- return run_output
543
+ return run_output # type: ignore
539
544
  elif entity.__class__.__name__ == "Team":
540
545
  if hasattr(run_output, "team_id") and run_output.team_id == entity.id:
541
- return run_output
546
+ return run_output # type: ignore
542
547
  else:
543
548
  log_warning(f"No run responses found in Session {session_id}")
544
549
 
@@ -550,10 +555,10 @@ def get_last_run_output_util(
550
555
  for run_output in reversed(entity.cached_session.runs):
551
556
  if entity.__class__.__name__ == "Agent":
552
557
  if hasattr(run_output, "agent_id") and run_output.agent_id == entity.id:
553
- return run_output
558
+ return run_output # type: ignore
554
559
  elif entity.__class__.__name__ == "Team":
555
560
  if hasattr(run_output, "team_id") and run_output.team_id == entity.id:
556
- return run_output
561
+ return run_output # type: ignore
557
562
  return None
558
563
 
559
564
 
@@ -575,10 +580,10 @@ async def aget_last_run_output_util(
575
580
  for run_output in reversed(session.runs):
576
581
  if entity.__class__.__name__ == "Agent":
577
582
  if hasattr(run_output, "agent_id") and run_output.agent_id == entity.id:
578
- return run_output
583
+ return run_output # type: ignore
579
584
  elif entity.__class__.__name__ == "Team":
580
585
  if hasattr(run_output, "team_id") and run_output.team_id == entity.id:
581
- return run_output
586
+ return run_output # type: ignore
582
587
  else:
583
588
  log_warning(f"No run responses found in Session {session_id}")
584
589
 
@@ -590,16 +595,16 @@ async def aget_last_run_output_util(
590
595
  for run_output in reversed(entity.cached_session.runs):
591
596
  if entity.__class__.__name__ == "Agent":
592
597
  if hasattr(run_output, "agent_id") and run_output.agent_id == entity.id:
593
- return run_output
598
+ return run_output # type: ignore
594
599
  elif entity.__class__.__name__ == "Team":
595
600
  if hasattr(run_output, "team_id") and run_output.team_id == entity.id:
596
- return run_output
601
+ return run_output # type: ignore
597
602
  return None
598
603
 
599
604
 
600
605
  def set_session_name_util(
601
606
  entity: Union["Agent", "Team"], session_id: str, autogenerate: bool = False, session_name: Optional[str] = None
602
- ) -> Union[AgentSession, TeamSession]:
607
+ ) -> Union[AgentSession, TeamSession, WorkflowSession]:
603
608
  """Set the session name and save to storage"""
604
609
  if entity._has_async_db():
605
610
  raise ValueError("Async database not supported for sync functions")
@@ -629,7 +634,7 @@ def set_session_name_util(
629
634
 
630
635
  async def aset_session_name_util(
631
636
  entity: Union["Agent", "Team"], session_id: str, autogenerate: bool = False, session_name: Optional[str] = None
632
- ) -> Union[AgentSession, TeamSession]:
637
+ ) -> Union[AgentSession, TeamSession, WorkflowSession]:
633
638
  """Set the session name and save to storage"""
634
639
  session = await entity.aget_session(session_id=session_id) # type: ignore
635
640
 
@@ -796,7 +801,7 @@ def get_chat_history_util(entity: Union["Agent", "Team"], session_id: str) -> Li
796
801
  if session is None:
797
802
  raise Exception("Session not found")
798
803
 
799
- return session.get_chat_history()
804
+ return session.get_chat_history() # type: ignore
800
805
 
801
806
 
802
807
  async def aget_chat_history_util(entity: Union["Agent", "Team"], session_id: str) -> List[Message]:
@@ -812,4 +817,4 @@ async def aget_chat_history_util(entity: Union["Agent", "Team"], session_id: str
812
817
  if session is None:
813
818
  raise Exception("Session not found")
814
819
 
815
- return session.get_chat_history()
820
+ return session.get_chat_history() # type: ignore
agno/utils/gemini.py CHANGED
@@ -225,12 +225,13 @@ def convert_schema(
225
225
  if schema_type is None or schema_type == "null":
226
226
  return None
227
227
  description = schema_dict.get("description", None)
228
+ title = schema_dict.get("title", None)
228
229
  default = schema_dict.get("default", None)
229
230
 
230
231
  # Handle enum types
231
232
  if "enum" in schema_dict:
232
233
  enum_values = schema_dict["enum"]
233
- return Schema(type=GeminiType.STRING, enum=enum_values, description=description, default=default)
234
+ return Schema(type=GeminiType.STRING, enum=enum_values, description=description, default=default, title=title)
234
235
 
235
236
  if schema_type == "object":
236
237
  # Handle regular objects with properties
@@ -250,6 +251,10 @@ def convert_schema(
250
251
  if is_nullable:
251
252
  converted_schema.nullable = True
252
253
  properties[key] = converted_schema
254
+ else:
255
+ properties[key] = Schema(
256
+ title=prop_def.get("title", None), description=prop_def.get("description", None)
257
+ )
253
258
 
254
259
  required = schema_dict.get("required", [])
255
260
 
@@ -260,9 +265,10 @@ def convert_schema(
260
265
  required=required,
261
266
  description=description,
262
267
  default=default,
268
+ title=title,
263
269
  )
264
270
  else:
265
- return Schema(type=GeminiType.OBJECT, description=description, default=default)
271
+ return Schema(type=GeminiType.OBJECT, description=description, default=default, title=title)
266
272
 
267
273
  # Handle Dict types (objects with additionalProperties but no properties)
268
274
  elif "additionalProperties" in schema_dict:
@@ -305,11 +311,11 @@ def convert_schema(
305
311
  )
306
312
  else:
307
313
  # additionalProperties is false or true
308
- return Schema(type=GeminiType.OBJECT, description=description, default=default)
314
+ return Schema(type=GeminiType.OBJECT, description=description, default=default, title=title)
309
315
 
310
316
  # Handle empty objects
311
317
  else:
312
- return Schema(type=GeminiType.OBJECT, description=description, default=default)
318
+ return Schema(type=GeminiType.OBJECT, description=description, default=default, title=title)
313
319
 
314
320
  elif schema_type == "array" and "items" in schema_dict:
315
321
  if not schema_dict["items"]: # Handle empty {}
@@ -325,6 +331,7 @@ def convert_schema(
325
331
  items=items,
326
332
  min_items=min_items,
327
333
  max_items=max_items,
334
+ title=title,
328
335
  )
329
336
 
330
337
  elif schema_type == "string":
@@ -332,6 +339,7 @@ def convert_schema(
332
339
  "type": GeminiType.STRING,
333
340
  "description": description,
334
341
  "default": default,
342
+ "title": title,
335
343
  }
336
344
  if "format" in schema_dict:
337
345
  schema_kwargs["format"] = schema_dict["format"]
@@ -342,6 +350,7 @@ def convert_schema(
342
350
  "type": schema_type.upper(),
343
351
  "description": description,
344
352
  "default": default,
353
+ "title": title,
345
354
  }
346
355
  if "maximum" in schema_dict:
347
356
  schema_kwargs["maximum"] = schema_dict["maximum"]
@@ -373,6 +382,7 @@ def convert_schema(
373
382
  any_of=any_of,
374
383
  description=description,
375
384
  default=default,
385
+ title=title,
376
386
  )
377
387
  else:
378
388
  if isinstance(schema_type, list):
@@ -384,7 +394,7 @@ def convert_schema(
384
394
  # Only convert to uppercase if schema_type is not empty
385
395
  if schema_type:
386
396
  schema_type = schema_type.upper()
387
- return Schema(type=schema_type, description=description, default=default)
397
+ return Schema(type=schema_type, description=description, default=default, title=title)
388
398
  else:
389
399
  # If we get here with an empty type and no other handlers matched,
390
400
  # something is wrong with the schema
agno/utils/knowledge.py CHANGED
@@ -1,10 +1,11 @@
1
- from typing import Any, Dict, Optional
1
+ from typing import Any, Dict, List, Optional, Union
2
2
 
3
+ from agno.filters import FilterExpr
3
4
  from agno.utils.log import log_info
4
5
 
5
6
 
6
7
  def get_agentic_or_user_search_filters(
7
- filters: Optional[Dict[str, Any]], effective_filters: Optional[Dict[str, Any]]
8
+ filters: Optional[Dict[str, Any]], effective_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]]
8
9
  ) -> Dict[str, Any]:
9
10
  """Helper function to determine the final filters to use for the search.
10
11
 
@@ -15,7 +16,7 @@ def get_agentic_or_user_search_filters(
15
16
  Returns:
16
17
  Dict[str, Any]: The final filters to use for the search.
17
18
  """
18
- search_filters = {}
19
+ search_filters = None
19
20
 
20
21
  # If agentic filters exist and manual filters (passed by user) do not, use agentic filters
21
22
  if filters and not effective_filters:
@@ -23,7 +24,13 @@ def get_agentic_or_user_search_filters(
23
24
 
24
25
  # If both agentic filters exist and manual filters (passed by user) exist, use manual filters (give priority to user and override)
25
26
  if filters and effective_filters:
26
- search_filters = effective_filters
27
+ if isinstance(effective_filters, dict):
28
+ search_filters = effective_filters
29
+ elif isinstance(effective_filters, list):
30
+ # If effective_filters is a list (likely List[FilterExpr]), convert both filters and effective_filters to a dict if possible, otherwise raise
31
+ raise ValueError(
32
+ "Merging dict and list of filters is not supported; effective_filters should be a dict for search compatibility."
33
+ )
27
34
 
28
35
  log_info(f"Filters used by Agent: {search_filters}")
29
- return search_filters
36
+ return search_filters or {}
agno/utils/log.py CHANGED
@@ -108,6 +108,7 @@ workflow_logger: AgnoLogger = build_logger(WORKFLOW_LOGGER_NAME, source_type="wo
108
108
  # Set the default logger to the agent logger
109
109
  logger: AgnoLogger = agent_logger
110
110
 
111
+
111
112
  debug_on: bool = False
112
113
  debug_level: Literal[1, 2] = 1
113
114
 
@@ -10,6 +10,7 @@ from rich.markdown import Markdown
10
10
  from rich.status import Status
11
11
  from rich.text import Text
12
12
 
13
+ from agno.filters import FilterExpr
13
14
  from agno.media import Audio, File, Image, Video
14
15
  from agno.models.message import Message
15
16
  from agno.reasoning.step import ReasoningStep
@@ -35,7 +36,7 @@ def print_response_stream(
35
36
  files: Optional[Sequence[File]] = None,
36
37
  stream_events: bool = False,
37
38
  stream_intermediate_steps: bool = False,
38
- knowledge_filters: Optional[Dict[str, Any]] = None,
39
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
39
40
  debug_mode: Optional[bool] = None,
40
41
  markdown: bool = False,
41
42
  show_message: bool = True,
@@ -227,7 +228,7 @@ async def aprint_response_stream(
227
228
  files: Optional[Sequence[File]] = None,
228
229
  stream_events: bool = False,
229
230
  stream_intermediate_steps: bool = False,
230
- knowledge_filters: Optional[Dict[str, Any]] = None,
231
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
231
232
  debug_mode: Optional[bool] = None,
232
233
  markdown: bool = False,
233
234
  show_message: bool = True,
@@ -505,7 +506,7 @@ def print_response(
505
506
  images: Optional[Sequence[Image]] = None,
506
507
  videos: Optional[Sequence[Video]] = None,
507
508
  files: Optional[Sequence[File]] = None,
508
- knowledge_filters: Optional[Dict[str, Any]] = None,
509
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
509
510
  debug_mode: Optional[bool] = None,
510
511
  markdown: bool = False,
511
512
  show_message: bool = True,
@@ -621,7 +622,7 @@ async def aprint_response(
621
622
  images: Optional[Sequence[Image]] = None,
622
623
  videos: Optional[Sequence[Video]] = None,
623
624
  files: Optional[Sequence[File]] = None,
624
- knowledge_filters: Optional[Dict[str, Any]] = None,
625
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
625
626
  debug_mode: Optional[bool] = None,
626
627
  markdown: bool = False,
627
628
  show_message: bool = True,
@@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Set, Unio
2
2
 
3
3
  from pydantic import BaseModel
4
4
 
5
+ from agno.filters import FilterExpr
5
6
  from agno.media import Audio, File, Image, Video
6
7
  from agno.models.message import Message
7
8
  from agno.models.response import ToolExecution
@@ -33,7 +34,7 @@ def print_response(
33
34
  videos: Optional[Sequence[Video]] = None,
34
35
  files: Optional[Sequence[File]] = None,
35
36
  markdown: bool = False,
36
- knowledge_filters: Optional[Dict[str, Any]] = None,
37
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
37
38
  add_history_to_context: Optional[bool] = None,
38
39
  dependencies: Optional[Dict[str, Any]] = None,
39
40
  add_dependencies_to_context: Optional[bool] = None,
@@ -333,7 +334,7 @@ def print_response_stream(
333
334
  markdown: bool = False,
334
335
  stream_events: bool = False,
335
336
  stream_intermediate_steps: bool = False, # type: ignore
336
- knowledge_filters: Optional[Dict[str, Any]] = None,
337
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
337
338
  add_history_to_context: Optional[bool] = None,
338
339
  dependencies: Optional[Dict[str, Any]] = None,
339
340
  add_dependencies_to_context: Optional[bool] = None,
@@ -865,7 +866,7 @@ async def aprint_response(
865
866
  videos: Optional[Sequence[Video]] = None,
866
867
  files: Optional[Sequence[File]] = None,
867
868
  markdown: bool = False,
868
- knowledge_filters: Optional[Dict[str, Any]] = None,
869
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
869
870
  add_history_to_context: Optional[bool] = None,
870
871
  dependencies: Optional[Dict[str, Any]] = None,
871
872
  add_dependencies_to_context: Optional[bool] = None,
@@ -1163,7 +1164,7 @@ async def aprint_response_stream(
1163
1164
  markdown: bool = False,
1164
1165
  stream_events: bool = False,
1165
1166
  stream_intermediate_steps: bool = False, # type: ignore
1166
- knowledge_filters: Optional[Dict[str, Any]] = None,
1167
+ knowledge_filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
1167
1168
  add_history_to_context: Optional[bool] = None,
1168
1169
  dependencies: Optional[Dict[str, Any]] = None,
1169
1170
  add_dependencies_to_context: Optional[bool] = None,
agno/vectordb/base.py CHANGED
@@ -72,13 +72,11 @@ class VectorDb(ABC):
72
72
  raise NotImplementedError
73
73
 
74
74
  @abstractmethod
75
- def search(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
75
+ def search(self, query: str, limit: int = 5, filters: Optional[Any] = None) -> List[Document]:
76
76
  raise NotImplementedError
77
77
 
78
78
  @abstractmethod
79
- async def async_search(
80
- self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
81
- ) -> List[Document]:
79
+ async def async_search(self, query: str, limit: int = 5, filters: Optional[Any] = None) -> List[Document]:
82
80
  raise NotImplementedError
83
81
 
84
82
  @abstractmethod
@@ -1,9 +1,10 @@
1
1
  import asyncio
2
- from typing import Any, Dict, Iterable, List, Optional
2
+ from typing import Any, Dict, Iterable, List, Optional, Union
3
3
 
4
+ from agno.filters import FilterExpr
4
5
  from agno.knowledge.document import Document
5
6
  from agno.knowledge.embedder import Embedder
6
- from agno.utils.log import log_debug, log_error, log_info
7
+ from agno.utils.log import log_debug, log_error, log_info, log_warning
7
8
  from agno.vectordb.base import VectorDb
8
9
  from agno.vectordb.cassandra.index import AgnoMetadataVectorCassandraTable
9
10
 
@@ -204,13 +205,17 @@ class Cassandra(VectorDb):
204
205
  self.delete_by_content_hash(content_hash)
205
206
  await self.async_insert(content_hash, documents, filters)
206
207
 
207
- def search(self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None) -> List[Document]:
208
+ def search(
209
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
210
+ ) -> List[Document]:
208
211
  """Keyword-based search on document metadata."""
209
212
  log_debug(f"Cassandra VectorDB : Performing Vector Search on {self.table_name} with query {query}")
213
+ if filters is not None:
214
+ log_warning("Filters are not yet supported in Cassandra. No filters will be applied.")
210
215
  return self.vector_search(query=query, limit=limit)
211
216
 
212
217
  async def async_search(
213
- self, query: str, limit: int = 5, filters: Optional[Dict[str, Any]] = None
218
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
214
219
  ) -> List[Document]:
215
220
  """Search asynchronously by running in a thread."""
216
221
  return await asyncio.to_thread(self.search, query, limit, filters)
@@ -221,7 +226,9 @@ class Cassandra(VectorDb):
221
226
  ) -> List[Document]:
222
227
  return [self._row_to_document(row=hit) for hit in hits]
223
228
 
224
- def vector_search(self, query: str, limit: int = 5) -> List[Document]:
229
+ def vector_search(
230
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
231
+ ) -> List[Document]:
225
232
  """Vector similarity search implementation."""
226
233
  query_embedding = self.embedder.get_embedding(query)
227
234
  hits = list(