agno 2.3.25__py3-none-any.whl → 2.4.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 (128) hide show
  1. agno/agent/__init__.py +4 -0
  2. agno/agent/agent.py +1428 -558
  3. agno/agent/remote.py +13 -0
  4. agno/db/base.py +339 -0
  5. agno/db/postgres/async_postgres.py +116 -12
  6. agno/db/postgres/postgres.py +1229 -25
  7. agno/db/postgres/schemas.py +48 -1
  8. agno/db/sqlite/async_sqlite.py +119 -4
  9. agno/db/sqlite/schemas.py +51 -0
  10. agno/db/sqlite/sqlite.py +1173 -13
  11. agno/db/utils.py +37 -1
  12. agno/knowledge/__init__.py +4 -0
  13. agno/knowledge/chunking/code.py +1 -1
  14. agno/knowledge/chunking/semantic.py +1 -1
  15. agno/knowledge/chunking/strategy.py +4 -0
  16. agno/knowledge/filesystem.py +412 -0
  17. agno/knowledge/knowledge.py +2767 -2254
  18. agno/knowledge/protocol.py +134 -0
  19. agno/knowledge/reader/arxiv_reader.py +2 -2
  20. agno/knowledge/reader/base.py +9 -7
  21. agno/knowledge/reader/csv_reader.py +5 -5
  22. agno/knowledge/reader/docx_reader.py +2 -2
  23. agno/knowledge/reader/field_labeled_csv_reader.py +2 -2
  24. agno/knowledge/reader/firecrawl_reader.py +2 -2
  25. agno/knowledge/reader/json_reader.py +2 -2
  26. agno/knowledge/reader/markdown_reader.py +2 -2
  27. agno/knowledge/reader/pdf_reader.py +5 -4
  28. agno/knowledge/reader/pptx_reader.py +2 -2
  29. agno/knowledge/reader/reader_factory.py +110 -0
  30. agno/knowledge/reader/s3_reader.py +2 -2
  31. agno/knowledge/reader/tavily_reader.py +2 -2
  32. agno/knowledge/reader/text_reader.py +2 -2
  33. agno/knowledge/reader/web_search_reader.py +2 -2
  34. agno/knowledge/reader/website_reader.py +5 -3
  35. agno/knowledge/reader/wikipedia_reader.py +2 -2
  36. agno/knowledge/reader/youtube_reader.py +2 -2
  37. agno/knowledge/utils.py +37 -29
  38. agno/learn/__init__.py +6 -0
  39. agno/learn/machine.py +35 -0
  40. agno/learn/schemas.py +82 -11
  41. agno/learn/stores/__init__.py +3 -0
  42. agno/learn/stores/decision_log.py +1156 -0
  43. agno/learn/stores/learned_knowledge.py +6 -6
  44. agno/models/anthropic/claude.py +24 -0
  45. agno/models/aws/bedrock.py +20 -0
  46. agno/models/base.py +48 -4
  47. agno/models/cohere/chat.py +25 -0
  48. agno/models/google/gemini.py +50 -5
  49. agno/models/litellm/chat.py +38 -0
  50. agno/models/openai/chat.py +7 -0
  51. agno/models/openrouter/openrouter.py +46 -0
  52. agno/models/response.py +16 -0
  53. agno/os/app.py +83 -44
  54. agno/os/middleware/__init__.py +2 -0
  55. agno/os/middleware/trailing_slash.py +27 -0
  56. agno/os/router.py +1 -0
  57. agno/os/routers/agents/router.py +29 -16
  58. agno/os/routers/agents/schema.py +6 -4
  59. agno/os/routers/components/__init__.py +3 -0
  60. agno/os/routers/components/components.py +466 -0
  61. agno/os/routers/evals/schemas.py +4 -3
  62. agno/os/routers/health.py +3 -3
  63. agno/os/routers/knowledge/knowledge.py +3 -3
  64. agno/os/routers/memory/schemas.py +4 -2
  65. agno/os/routers/metrics/metrics.py +9 -11
  66. agno/os/routers/metrics/schemas.py +10 -6
  67. agno/os/routers/registry/__init__.py +3 -0
  68. agno/os/routers/registry/registry.py +337 -0
  69. agno/os/routers/teams/router.py +20 -8
  70. agno/os/routers/teams/schema.py +6 -4
  71. agno/os/routers/traces/traces.py +5 -5
  72. agno/os/routers/workflows/router.py +38 -11
  73. agno/os/routers/workflows/schema.py +1 -1
  74. agno/os/schema.py +92 -26
  75. agno/os/utils.py +133 -16
  76. agno/reasoning/anthropic.py +2 -2
  77. agno/reasoning/azure_ai_foundry.py +2 -2
  78. agno/reasoning/deepseek.py +2 -2
  79. agno/reasoning/default.py +6 -7
  80. agno/reasoning/gemini.py +2 -2
  81. agno/reasoning/helpers.py +6 -7
  82. agno/reasoning/manager.py +4 -10
  83. agno/reasoning/ollama.py +2 -2
  84. agno/reasoning/openai.py +2 -2
  85. agno/reasoning/vertexai.py +2 -2
  86. agno/registry/__init__.py +3 -0
  87. agno/registry/registry.py +68 -0
  88. agno/run/agent.py +57 -0
  89. agno/run/base.py +7 -0
  90. agno/run/team.py +57 -0
  91. agno/skills/agent_skills.py +10 -3
  92. agno/team/__init__.py +3 -1
  93. agno/team/team.py +1276 -326
  94. agno/tools/duckduckgo.py +25 -71
  95. agno/tools/exa.py +0 -21
  96. agno/tools/function.py +35 -83
  97. agno/tools/knowledge.py +9 -4
  98. agno/tools/mem0.py +11 -10
  99. agno/tools/memory.py +47 -46
  100. agno/tools/parallel.py +0 -7
  101. agno/tools/reasoning.py +30 -23
  102. agno/tools/tavily.py +4 -1
  103. agno/tools/websearch.py +93 -0
  104. agno/tools/website.py +1 -1
  105. agno/tools/wikipedia.py +1 -1
  106. agno/tools/workflow.py +48 -47
  107. agno/utils/agent.py +42 -5
  108. agno/utils/events.py +160 -2
  109. agno/utils/print_response/agent.py +0 -31
  110. agno/utils/print_response/team.py +0 -2
  111. agno/utils/print_response/workflow.py +0 -2
  112. agno/utils/team.py +61 -11
  113. agno/vectordb/lancedb/lance_db.py +4 -1
  114. agno/vectordb/mongodb/mongodb.py +1 -1
  115. agno/vectordb/qdrant/qdrant.py +4 -4
  116. agno/workflow/__init__.py +3 -1
  117. agno/workflow/condition.py +0 -21
  118. agno/workflow/loop.py +0 -21
  119. agno/workflow/parallel.py +0 -21
  120. agno/workflow/router.py +0 -21
  121. agno/workflow/step.py +117 -24
  122. agno/workflow/steps.py +0 -21
  123. agno/workflow/workflow.py +625 -63
  124. {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/METADATA +46 -76
  125. {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/RECORD +128 -117
  126. {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/WHEEL +0 -0
  127. {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/licenses/LICENSE +0 -0
  128. {agno-2.3.25.dist-info → agno-2.4.0.dist-info}/top_level.txt +0 -0
agno/knowledge/utils.py CHANGED
@@ -49,26 +49,28 @@ def _import_class(module_name: str, class_name: str):
49
49
 
50
50
 
51
51
  def get_reader_info(reader_key: str) -> Dict:
52
- """Get information about a reader without instantiating it."""
53
- # Try to create the reader to get its info, but don't cache it
52
+ """Get information about a reader without instantiating it.
53
+
54
+ Uses class methods and static metadata from ReaderFactory to avoid
55
+ the overhead of creating reader instances.
56
+ """
54
57
  try:
55
- reader_factory_method = ReaderFactory._get_reader_method(reader_key)
58
+ # Get the reader CLASS without instantiation
59
+ reader_class = ReaderFactory.get_reader_class(reader_key)
56
60
 
57
- # Create an instance to get the class, then call class methods
58
- reader_instance = reader_factory_method()
59
- reader_class = reader_instance.__class__
61
+ # Get metadata from static registry (no instantiation needed)
62
+ metadata = ReaderFactory.READER_METADATA.get(reader_key, {})
60
63
 
61
- supported_strategies = reader_class.get_supported_chunking_strategies()
62
- supported_content_types = reader_class.get_supported_content_types()
64
+ # Call class methods directly (no instance needed)
65
+ supported_strategies = reader_class.get_supported_chunking_strategies() # type: ignore[attr-defined]
66
+ supported_content_types = reader_class.get_supported_content_types() # type: ignore[attr-defined]
63
67
 
64
68
  return {
65
69
  "id": reader_key,
66
- "name": "".join(word.capitalize() for word in reader_key.split("_")) + "Reader",
67
- "description": reader_instance.description,
68
- "chunking_strategies": [
69
- strategy.value for strategy in supported_strategies
70
- ], # Convert enums to string values
71
- "content_types": [ct.value for ct in supported_content_types], # Convert enums to string values
70
+ "name": metadata.get("name", reader_class.__name__),
71
+ "description": metadata.get("description", f"{reader_class.__name__} reader"),
72
+ "chunking_strategies": [strategy.value for strategy in supported_strategies],
73
+ "content_types": [ct.value for ct in supported_content_types],
72
74
  }
73
75
  except ImportError as e:
74
76
  # Skip readers with missing dependencies
@@ -98,38 +100,44 @@ def get_reader_info_from_instance(reader: Reader, reader_id: str) -> Dict:
98
100
  def get_all_readers_info(knowledge_instance: Optional[Any] = None) -> List[Dict]:
99
101
  """Get information about all available readers, including custom readers from a Knowledge instance.
100
102
 
103
+ Custom readers are added first and take precedence over factory readers with the same ID.
104
+
101
105
  Args:
102
106
  knowledge_instance: Optional Knowledge instance to include custom readers from.
103
107
 
104
108
  Returns:
105
- List of reader info dictionaries.
109
+ List of reader info dictionaries (custom readers first, then factory readers).
106
110
  """
107
111
  readers_info = []
108
- keys = ReaderFactory.get_all_reader_keys()
109
- for key in keys:
110
- try:
111
- reader_info = get_reader_info(key)
112
- readers_info.append(reader_info)
113
- except ValueError as e:
114
- # Skip readers with missing dependencies or other issues
115
- # Log the error but don't fail the entire request
116
- log_debug(f"Skipping reader '{key}': {e}")
117
- continue
112
+ seen_ids: set = set()
118
113
 
119
- # Add custom readers from knowledge instance if provided
114
+ # 1. Add custom readers FIRST (they take precedence over factory readers)
120
115
  if knowledge_instance is not None:
121
116
  custom_readers = knowledge_instance.get_readers()
122
117
  if isinstance(custom_readers, dict):
123
118
  for reader_id, reader in custom_readers.items():
124
119
  try:
125
120
  reader_info = get_reader_info_from_instance(reader, reader_id)
126
- # Only add if not already present (custom readers take precedence)
127
- if not any(r["id"] == reader_id for r in readers_info):
128
- readers_info.append(reader_info)
121
+ readers_info.append(reader_info)
122
+ seen_ids.add(reader_id)
129
123
  except ValueError as e:
130
124
  log_debug(f"Skipping custom reader '{reader_id}': {e}")
131
125
  continue
132
126
 
127
+ # 2. Add factory readers (skip if custom reader with same ID already exists)
128
+ keys = ReaderFactory.get_all_reader_keys()
129
+ for key in keys:
130
+ if key in seen_ids:
131
+ # Custom reader with this ID already added, skip factory version
132
+ continue
133
+ try:
134
+ reader_info = get_reader_info(key)
135
+ readers_info.append(reader_info)
136
+ except ValueError as e:
137
+ # Skip readers with missing dependencies or other issues
138
+ log_debug(f"Skipping reader '{key}': {e}")
139
+ continue
140
+
133
141
  return readers_info
134
142
 
135
143
 
agno/learn/__init__.py CHANGED
@@ -11,6 +11,7 @@ Main Components:
11
11
  """
12
12
 
13
13
  from agno.learn.config import (
14
+ DecisionLogConfig,
14
15
  EntityMemoryConfig,
15
16
  LearnedKnowledgeConfig,
16
17
  LearningMode,
@@ -21,6 +22,7 @@ from agno.learn.config import (
21
22
  )
22
23
  from agno.learn.machine import LearningMachine
23
24
  from agno.learn.schemas import (
25
+ DecisionLog,
24
26
  EntityMemory,
25
27
  LearnedKnowledge,
26
28
  Memories,
@@ -28,6 +30,7 @@ from agno.learn.schemas import (
28
30
  UserProfile,
29
31
  )
30
32
  from agno.learn.stores import (
33
+ DecisionLogStore,
31
34
  EntityMemoryStore,
32
35
  LearnedKnowledgeStore,
33
36
  LearningStore,
@@ -48,12 +51,14 @@ __all__ = [
48
51
  "EntityMemoryConfig",
49
52
  "SessionContextConfig",
50
53
  "LearnedKnowledgeConfig",
54
+ "DecisionLogConfig", # Phase 2
51
55
  # Schemas
52
56
  "UserProfile",
53
57
  "Memories",
54
58
  "EntityMemory",
55
59
  "SessionContext",
56
60
  "LearnedKnowledge",
61
+ "DecisionLog", # Phase 2
57
62
  # Stores
58
63
  "LearningStore",
59
64
  "UserProfileStore",
@@ -62,4 +67,5 @@ __all__ = [
62
67
  "SessionContextStore",
63
68
  "LearnedKnowledgeStore",
64
69
  "EntityMemoryStore",
70
+ "DecisionLogStore", # Phase 2
65
71
  ]
agno/learn/machine.py CHANGED
@@ -17,6 +17,7 @@ from os import getenv
17
17
  from typing import Any, Callable, Dict, List, Optional, Union
18
18
 
19
19
  from agno.learn.config import (
20
+ DecisionLogConfig,
20
21
  EntityMemoryConfig,
21
22
  LearnedKnowledgeConfig,
22
23
  LearningMode,
@@ -45,6 +46,7 @@ UserMemoryInput = Union[bool, UserMemoryConfig, LearningStore, None]
45
46
  EntityMemoryInput = Union[bool, EntityMemoryConfig, LearningStore, None]
46
47
  SessionContextInput = Union[bool, SessionContextConfig, LearningStore, None]
47
48
  LearnedKnowledgeInput = Union[bool, LearnedKnowledgeConfig, LearningStore, None]
49
+ DecisionLogInput = Union[bool, DecisionLogConfig, LearningStore, None]
48
50
 
49
51
 
50
52
  @dataclass
@@ -80,6 +82,7 @@ class LearningMachine:
80
82
  session_context: SessionContextInput = False
81
83
  entity_memory: EntityMemoryInput = False
82
84
  learned_knowledge: LearnedKnowledgeInput = False
85
+ decision_log: DecisionLogInput = False # Phase 2
83
86
 
84
87
  # Namespace for entity_memory and learned_knowledge
85
88
  namespace: str = "global"
@@ -144,6 +147,13 @@ class LearningMachine:
144
147
  store_type="learned_knowledge",
145
148
  )
146
149
 
150
+ # Decision Log (Phase 2)
151
+ if self.decision_log:
152
+ self._stores["decision_log"] = self._resolve_store(
153
+ input_value=self.decision_log,
154
+ store_type="decision_log",
155
+ )
156
+
147
157
  # Custom stores
148
158
  if self.custom_stores:
149
159
  for name, store in self.custom_stores.items():
@@ -180,6 +190,8 @@ class LearningMachine:
180
190
  return self._create_entity_memory_store(config=input_value)
181
191
  elif store_type == "learned_knowledge":
182
192
  return self._create_learned_knowledge_store(config=input_value)
193
+ elif store_type == "decision_log":
194
+ return self._create_decision_log_store(config=input_value)
183
195
  else:
184
196
  raise ValueError(f"Unknown store type: {store_type}")
185
197
 
@@ -274,6 +286,24 @@ class LearningMachine:
274
286
 
275
287
  return LearnedKnowledgeStore(config=config, debug_mode=self.debug_mode)
276
288
 
289
+ def _create_decision_log_store(self, config: Any) -> LearningStore:
290
+ """Create DecisionLogStore with resolved config."""
291
+ from agno.learn.stores import DecisionLogStore
292
+
293
+ if isinstance(config, DecisionLogConfig):
294
+ if config.db is None:
295
+ config.db = self.db
296
+ if config.model is None:
297
+ config.model = self.model
298
+ else:
299
+ config = DecisionLogConfig(
300
+ db=self.db,
301
+ model=self.model,
302
+ mode=LearningMode.AGENTIC, # Default to AGENTIC for explicit logging
303
+ )
304
+
305
+ return DecisionLogStore(config=config, debug_mode=self.debug_mode)
306
+
277
307
  # =========================================================================
278
308
  # Store Accessors (Type-Safe)
279
309
  # =========================================================================
@@ -303,6 +333,11 @@ class LearningMachine:
303
333
  """Get learned knowledge store if enabled."""
304
334
  return self.stores.get("learned_knowledge")
305
335
 
336
+ @property
337
+ def decision_log_store(self) -> Optional[LearningStore]:
338
+ """Get decision log store if enabled."""
339
+ return self.stores.get("decision_log")
340
+
306
341
  @property
307
342
  def was_updated(self) -> bool:
308
343
  """True if any store was updated in the last operation."""
agno/learn/schemas.py CHANGED
@@ -893,22 +893,72 @@ class SessionPlanningExtractionResponse:
893
893
 
894
894
 
895
895
  @dataclass
896
- class Decision:
897
- """Schema for Decision Logs. (Phase 2)
896
+ class DecisionLog:
897
+ """Schema for Decision Logs.
898
898
 
899
899
  Records decisions made by the agent with reasoning and context.
900
+ Useful for:
901
+ - Auditing agent behavior
902
+ - Learning from past decisions
903
+ - Debugging unexpected outcomes
904
+ - Building feedback loops
905
+
906
+ Example:
907
+ DecisionLog(
908
+ id="dec_abc123",
909
+ decision="Used web search instead of knowledge base",
910
+ reasoning="User asked about current events which require fresh data",
911
+ decision_type="tool_selection",
912
+ context="User query: 'What happened in the news today?'",
913
+ alternatives=["search_knowledge_base", "ask_for_clarification"],
914
+ confidence=0.85,
915
+ )
916
+
917
+ Attributes:
918
+ id: Unique identifier for this decision.
919
+ decision: What was decided (the choice made).
920
+ reasoning: Why this decision was made.
921
+ decision_type: Category of decision (tool_selection, response_style, etc).
922
+ context: The situation that required a decision.
923
+ alternatives: Other options that were considered.
924
+ confidence: How confident the agent was (0.0 to 1.0).
925
+ outcome: What happened as a result (can be updated later).
926
+ outcome_quality: Was the outcome good/bad/neutral.
927
+ tags: Categories for organization.
928
+ session_id: Which session this decision was made in.
929
+ user_id: Which user this decision was for.
930
+ agent_id: Which agent made this decision.
931
+ team_id: Which team context.
932
+ created_at: When the decision was made.
933
+ updated_at: When the outcome was recorded.
900
934
  """
901
935
 
936
+ id: str
902
937
  decision: str
903
- reasoning: Optional[str] = None
904
- context: Optional[str] = None
905
- outcome: Optional[str] = None
906
- agent_id: Optional[str] = None
907
- team_id: Optional[str] = None
908
- created_at: Optional[str] = None
938
+ reasoning: Optional[str] = field(default=None, metadata={"description": "Why this decision was made"})
939
+ decision_type: Optional[str] = field(
940
+ default=None,
941
+ metadata={"description": "Category: tool_selection, response_style, clarification, escalation, etc"},
942
+ )
943
+ context: Optional[str] = field(default=None, metadata={"description": "The situation that required a decision"})
944
+ alternatives: Optional[List[str]] = field(
945
+ default=None, metadata={"description": "Other options that were considered"}
946
+ )
947
+ confidence: Optional[float] = field(default=None, metadata={"description": "Confidence level 0.0 to 1.0"})
948
+ outcome: Optional[str] = field(default=None, metadata={"description": "What happened as a result"})
949
+ outcome_quality: Optional[str] = field(default=None, metadata={"description": "Was outcome good/bad/neutral"})
950
+ tags: Optional[List[str]] = field(default=None, metadata={"description": "Categories for organization"})
951
+
952
+ # Scope
953
+ session_id: Optional[str] = field(default=None, metadata={"internal": True})
954
+ user_id: Optional[str] = field(default=None, metadata={"internal": True})
955
+ agent_id: Optional[str] = field(default=None, metadata={"internal": True})
956
+ team_id: Optional[str] = field(default=None, metadata={"internal": True})
957
+ created_at: Optional[str] = field(default=None, metadata={"internal": True})
958
+ updated_at: Optional[str] = field(default=None, metadata={"internal": True})
909
959
 
910
960
  @classmethod
911
- def from_dict(cls, data: Any) -> Optional["Decision"]:
961
+ def from_dict(cls, data: Any) -> Optional["DecisionLog"]:
912
962
  """Parse from dict/JSON, returning None on any failure."""
913
963
  if data is None:
914
964
  return None
@@ -921,8 +971,9 @@ class Decision:
921
971
  log_debug(f"{cls.__name__}.from_dict: _parse_json returned None for data={_truncate_for_log(data)}")
922
972
  return None
923
973
 
924
- if not parsed.get("decision"):
925
- log_debug(f"{cls.__name__}.from_dict: missing required field 'decision'")
974
+ # id and decision are required
975
+ if not parsed.get("id") or not parsed.get("decision"):
976
+ log_debug(f"{cls.__name__}.from_dict: missing required fields 'id' or 'decision'")
926
977
  return None
927
978
 
928
979
  field_names = {f.name for f in fields(cls)}
@@ -941,6 +992,26 @@ class Decision:
941
992
  log_debug(f"{self.__class__.__name__}.to_dict failed: {e}")
942
993
  return {}
943
994
 
995
+ def to_text(self) -> str:
996
+ """Convert to searchable text format."""
997
+ parts = [f"Decision: {self.decision}"]
998
+ if self.reasoning:
999
+ parts.append(f"Reasoning: {self.reasoning}")
1000
+ if self.context:
1001
+ parts.append(f"Context: {self.context}")
1002
+ if self.decision_type:
1003
+ parts.append(f"Type: {self.decision_type}")
1004
+ if self.outcome:
1005
+ parts.append(f"Outcome: {self.outcome}")
1006
+ return "\n".join(parts)
1007
+
1008
+ def __repr__(self) -> str:
1009
+ return f"DecisionLog(id={self.id}, decision={self.decision[:50]}...)"
1010
+
1011
+
1012
+ # Backwards compatibility alias
1013
+ Decision = DecisionLog
1014
+
944
1015
 
945
1016
  @dataclass
946
1017
  class Feedback:
@@ -15,8 +15,10 @@ Available Stores:
15
15
  - SessionContextStore: Current session state
16
16
  - LearnedKnowledgeStore: Reusable knowledge/insights
17
17
  - EntityMemoryStore: Third-party entity facts
18
+ - DecisionLogStore: Agent decision logging (Phase 2)
18
19
  """
19
20
 
21
+ from agno.learn.stores.decision_log import DecisionLogStore
20
22
  from agno.learn.stores.entity_memory import EntityMemoryStore
21
23
  from agno.learn.stores.learned_knowledge import LearnedKnowledgeStore
22
24
  from agno.learn.stores.protocol import LearningStore
@@ -32,4 +34,5 @@ __all__ = [
32
34
  "SessionContextStore",
33
35
  "LearnedKnowledgeStore",
34
36
  "EntityMemoryStore",
37
+ "DecisionLogStore",
35
38
  ]