agno 2.3.26__py3-none-any.whl → 2.4.1__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 (140) hide show
  1. agno/agent/__init__.py +4 -0
  2. agno/agent/agent.py +1368 -541
  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 +1242 -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 +1186 -13
  11. agno/db/utils.py +37 -1
  12. agno/integrations/discord/client.py +12 -1
  13. agno/knowledge/__init__.py +4 -0
  14. agno/knowledge/chunking/code.py +1 -1
  15. agno/knowledge/chunking/semantic.py +1 -1
  16. agno/knowledge/chunking/strategy.py +4 -0
  17. agno/knowledge/filesystem.py +412 -0
  18. agno/knowledge/knowledge.py +3722 -2182
  19. agno/knowledge/protocol.py +134 -0
  20. agno/knowledge/reader/arxiv_reader.py +2 -2
  21. agno/knowledge/reader/base.py +9 -7
  22. agno/knowledge/reader/csv_reader.py +236 -13
  23. agno/knowledge/reader/docx_reader.py +2 -2
  24. agno/knowledge/reader/field_labeled_csv_reader.py +169 -5
  25. agno/knowledge/reader/firecrawl_reader.py +2 -2
  26. agno/knowledge/reader/json_reader.py +2 -2
  27. agno/knowledge/reader/markdown_reader.py +2 -2
  28. agno/knowledge/reader/pdf_reader.py +5 -4
  29. agno/knowledge/reader/pptx_reader.py +2 -2
  30. agno/knowledge/reader/reader_factory.py +118 -1
  31. agno/knowledge/reader/s3_reader.py +2 -2
  32. agno/knowledge/reader/tavily_reader.py +2 -2
  33. agno/knowledge/reader/text_reader.py +2 -2
  34. agno/knowledge/reader/web_search_reader.py +2 -2
  35. agno/knowledge/reader/website_reader.py +5 -3
  36. agno/knowledge/reader/wikipedia_reader.py +2 -2
  37. agno/knowledge/reader/youtube_reader.py +2 -2
  38. agno/knowledge/remote_content/__init__.py +29 -0
  39. agno/knowledge/remote_content/config.py +204 -0
  40. agno/knowledge/remote_content/remote_content.py +74 -17
  41. agno/knowledge/utils.py +37 -29
  42. agno/learn/__init__.py +6 -0
  43. agno/learn/machine.py +35 -0
  44. agno/learn/schemas.py +82 -11
  45. agno/learn/stores/__init__.py +3 -0
  46. agno/learn/stores/decision_log.py +1156 -0
  47. agno/learn/stores/learned_knowledge.py +6 -6
  48. agno/models/anthropic/claude.py +24 -0
  49. agno/models/aws/bedrock.py +20 -0
  50. agno/models/base.py +60 -6
  51. agno/models/cerebras/cerebras.py +34 -2
  52. agno/models/cohere/chat.py +25 -0
  53. agno/models/google/gemini.py +50 -5
  54. agno/models/litellm/chat.py +38 -0
  55. agno/models/n1n/__init__.py +3 -0
  56. agno/models/n1n/n1n.py +57 -0
  57. agno/models/openai/chat.py +25 -1
  58. agno/models/openrouter/openrouter.py +46 -0
  59. agno/models/perplexity/perplexity.py +2 -0
  60. agno/models/response.py +16 -0
  61. agno/os/app.py +83 -44
  62. agno/os/interfaces/slack/router.py +10 -1
  63. agno/os/interfaces/whatsapp/router.py +6 -0
  64. agno/os/middleware/__init__.py +2 -0
  65. agno/os/middleware/trailing_slash.py +27 -0
  66. agno/os/router.py +1 -0
  67. agno/os/routers/agents/router.py +29 -16
  68. agno/os/routers/agents/schema.py +6 -4
  69. agno/os/routers/components/__init__.py +3 -0
  70. agno/os/routers/components/components.py +475 -0
  71. agno/os/routers/evals/schemas.py +4 -3
  72. agno/os/routers/health.py +3 -3
  73. agno/os/routers/knowledge/knowledge.py +128 -3
  74. agno/os/routers/knowledge/schemas.py +12 -0
  75. agno/os/routers/memory/schemas.py +4 -2
  76. agno/os/routers/metrics/metrics.py +9 -11
  77. agno/os/routers/metrics/schemas.py +10 -6
  78. agno/os/routers/registry/__init__.py +3 -0
  79. agno/os/routers/registry/registry.py +337 -0
  80. agno/os/routers/teams/router.py +20 -8
  81. agno/os/routers/teams/schema.py +6 -4
  82. agno/os/routers/traces/traces.py +5 -5
  83. agno/os/routers/workflows/router.py +38 -11
  84. agno/os/routers/workflows/schema.py +1 -1
  85. agno/os/schema.py +92 -26
  86. agno/os/utils.py +84 -19
  87. agno/reasoning/anthropic.py +2 -2
  88. agno/reasoning/azure_ai_foundry.py +2 -2
  89. agno/reasoning/deepseek.py +2 -2
  90. agno/reasoning/default.py +6 -7
  91. agno/reasoning/gemini.py +2 -2
  92. agno/reasoning/helpers.py +6 -7
  93. agno/reasoning/manager.py +4 -10
  94. agno/reasoning/ollama.py +2 -2
  95. agno/reasoning/openai.py +2 -2
  96. agno/reasoning/vertexai.py +2 -2
  97. agno/registry/__init__.py +3 -0
  98. agno/registry/registry.py +68 -0
  99. agno/run/agent.py +59 -0
  100. agno/run/base.py +7 -0
  101. agno/run/team.py +57 -0
  102. agno/skills/agent_skills.py +10 -3
  103. agno/team/__init__.py +3 -1
  104. agno/team/team.py +1165 -330
  105. agno/tools/duckduckgo.py +25 -71
  106. agno/tools/exa.py +0 -21
  107. agno/tools/function.py +35 -83
  108. agno/tools/knowledge.py +9 -4
  109. agno/tools/mem0.py +11 -10
  110. agno/tools/memory.py +47 -46
  111. agno/tools/parallel.py +0 -7
  112. agno/tools/reasoning.py +30 -23
  113. agno/tools/tavily.py +4 -1
  114. agno/tools/websearch.py +93 -0
  115. agno/tools/website.py +1 -1
  116. agno/tools/wikipedia.py +1 -1
  117. agno/tools/workflow.py +48 -47
  118. agno/utils/agent.py +42 -5
  119. agno/utils/events.py +160 -2
  120. agno/utils/print_response/agent.py +0 -31
  121. agno/utils/print_response/team.py +0 -2
  122. agno/utils/print_response/workflow.py +0 -2
  123. agno/utils/team.py +61 -11
  124. agno/vectordb/lancedb/lance_db.py +4 -1
  125. agno/vectordb/mongodb/mongodb.py +1 -1
  126. agno/vectordb/pgvector/pgvector.py +3 -3
  127. agno/vectordb/qdrant/qdrant.py +4 -4
  128. agno/workflow/__init__.py +3 -1
  129. agno/workflow/condition.py +0 -21
  130. agno/workflow/loop.py +0 -21
  131. agno/workflow/parallel.py +0 -21
  132. agno/workflow/router.py +0 -21
  133. agno/workflow/step.py +117 -24
  134. agno/workflow/steps.py +0 -21
  135. agno/workflow/workflow.py +427 -63
  136. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/METADATA +49 -76
  137. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/RECORD +140 -126
  138. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/WHEEL +1 -1
  139. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/licenses/LICENSE +0 -0
  140. {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,204 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Optional
4
+
5
+ from pydantic import BaseModel
6
+
7
+ if TYPE_CHECKING:
8
+ from agno.knowledge.remote_content.remote_content import (
9
+ GCSContent,
10
+ GitHubContent,
11
+ S3Content,
12
+ SharePointContent,
13
+ )
14
+
15
+
16
+ class RemoteContentConfig(BaseModel):
17
+ """Base configuration for remote content sources."""
18
+
19
+ id: str
20
+ name: str
21
+ metadata: Optional[dict] = None
22
+
23
+ class Config:
24
+ extra = "allow"
25
+
26
+
27
+ class S3Config(RemoteContentConfig):
28
+ """Configuration for AWS S3 content source."""
29
+
30
+ bucket_name: str
31
+ region: Optional[str] = None
32
+ aws_access_key_id: Optional[str] = None
33
+ aws_secret_access_key: Optional[str] = None
34
+ prefix: Optional[str] = None
35
+
36
+ def file(self, key: str) -> "S3Content":
37
+ """Create a content reference for a specific file.
38
+
39
+ Args:
40
+ key: The S3 object key (path to file).
41
+
42
+ Returns:
43
+ S3Content configured with this source's credentials.
44
+ """
45
+ from agno.knowledge.remote_content.remote_content import S3Content
46
+
47
+ return S3Content(
48
+ bucket_name=self.bucket_name,
49
+ key=key,
50
+ config_id=self.id,
51
+ )
52
+
53
+ def folder(self, prefix: str) -> "S3Content":
54
+ """Create a content reference for a folder (prefix).
55
+
56
+ Args:
57
+ prefix: The S3 prefix (folder path).
58
+
59
+ Returns:
60
+ S3Content configured with this source's credentials.
61
+ """
62
+ from agno.knowledge.remote_content.remote_content import S3Content
63
+
64
+ return S3Content(
65
+ bucket_name=self.bucket_name,
66
+ prefix=prefix,
67
+ config_id=self.id,
68
+ )
69
+
70
+
71
+ class GcsConfig(RemoteContentConfig):
72
+ """Configuration for Google Cloud Storage content source."""
73
+
74
+ bucket_name: str
75
+ project: Optional[str] = None
76
+ credentials_path: Optional[str] = None
77
+ prefix: Optional[str] = None
78
+
79
+ def file(self, blob_name: str) -> "GCSContent":
80
+ """Create a content reference for a specific file.
81
+
82
+ Args:
83
+ blob_name: The GCS blob name (path to file).
84
+
85
+ Returns:
86
+ GCSContent configured with this source's credentials.
87
+ """
88
+ from agno.knowledge.remote_content.remote_content import GCSContent
89
+
90
+ return GCSContent(
91
+ bucket_name=self.bucket_name,
92
+ blob_name=blob_name,
93
+ config_id=self.id,
94
+ )
95
+
96
+ def folder(self, prefix: str) -> "GCSContent":
97
+ """Create a content reference for a folder (prefix).
98
+
99
+ Args:
100
+ prefix: The GCS prefix (folder path).
101
+
102
+ Returns:
103
+ GCSContent configured with this source's credentials.
104
+ """
105
+ from agno.knowledge.remote_content.remote_content import GCSContent
106
+
107
+ return GCSContent(
108
+ bucket_name=self.bucket_name,
109
+ prefix=prefix,
110
+ config_id=self.id,
111
+ )
112
+
113
+
114
+ class SharePointConfig(RemoteContentConfig):
115
+ """Configuration for SharePoint content source."""
116
+
117
+ tenant_id: str
118
+ client_id: str
119
+ client_secret: str
120
+ hostname: str
121
+ site_path: Optional[str] = None
122
+ site_id: Optional[str] = None # Full site ID (e.g., "contoso.sharepoint.com,guid1,guid2")
123
+ folder_path: Optional[str] = None
124
+
125
+ def file(self, file_path: str, site_path: Optional[str] = None) -> "SharePointContent":
126
+ """Create a content reference for a specific file.
127
+
128
+ Args:
129
+ file_path: Path to the file in SharePoint.
130
+ site_path: Optional site path override.
131
+
132
+ Returns:
133
+ SharePointContent configured with this source's credentials.
134
+ """
135
+ from agno.knowledge.remote_content.remote_content import SharePointContent
136
+
137
+ return SharePointContent(
138
+ config_id=self.id,
139
+ file_path=file_path,
140
+ site_path=site_path or self.site_path,
141
+ )
142
+
143
+ def folder(self, folder_path: str, site_path: Optional[str] = None) -> "SharePointContent":
144
+ """Create a content reference for a folder.
145
+
146
+ Args:
147
+ folder_path: Path to the folder in SharePoint.
148
+ site_path: Optional site path override.
149
+
150
+ Returns:
151
+ SharePointContent configured with this source's credentials.
152
+ """
153
+ from agno.knowledge.remote_content.remote_content import SharePointContent
154
+
155
+ return SharePointContent(
156
+ config_id=self.id,
157
+ folder_path=folder_path,
158
+ site_path=site_path or self.site_path,
159
+ )
160
+
161
+
162
+ class GitHubConfig(RemoteContentConfig):
163
+ """Configuration for GitHub content source."""
164
+
165
+ repo: str
166
+ token: Optional[str] = None
167
+ branch: Optional[str] = None
168
+ path: Optional[str] = None
169
+
170
+ def file(self, file_path: str, branch: Optional[str] = None) -> "GitHubContent":
171
+ """Create a content reference for a specific file.
172
+
173
+ Args:
174
+ file_path: Path to the file in the repository.
175
+ branch: Optional branch override.
176
+
177
+ Returns:
178
+ GitHubContent configured with this source's credentials.
179
+ """
180
+ from agno.knowledge.remote_content.remote_content import GitHubContent
181
+
182
+ return GitHubContent(
183
+ config_id=self.id,
184
+ file_path=file_path,
185
+ branch=branch or self.branch,
186
+ )
187
+
188
+ def folder(self, folder_path: str, branch: Optional[str] = None) -> "GitHubContent":
189
+ """Create a content reference for a folder.
190
+
191
+ Args:
192
+ folder_path: Path to the folder in the repository.
193
+ branch: Optional branch override.
194
+
195
+ Returns:
196
+ GitHubContent configured with this source's credentials.
197
+ """
198
+ from agno.knowledge.remote_content.remote_content import GitHubContent
199
+
200
+ return GitHubContent(
201
+ config_id=self.id,
202
+ folder_path=folder_path,
203
+ branch=branch or self.branch,
204
+ )
@@ -14,21 +14,23 @@ class S3Content:
14
14
  key: Optional[str] = None,
15
15
  object: Optional[S3Object] = None,
16
16
  prefix: Optional[str] = None,
17
+ config_id: Optional[str] = None,
17
18
  ):
18
19
  self.bucket_name = bucket_name
19
20
  self.bucket = bucket
20
21
  self.key = key
21
22
  self.object = object
22
23
  self.prefix = prefix
24
+ self.config_id = config_id
23
25
 
24
26
  if bucket_name is None and bucket is None:
25
27
  raise ValueError("Either bucket_name or bucket must be provided")
26
- if key is None and object is None:
27
- raise ValueError("Either key or object must be provided")
28
+ if key is None and object is None and prefix is None:
29
+ raise ValueError("Either key, object, or prefix must be provided")
28
30
  if bucket_name is not None and bucket is not None:
29
31
  raise ValueError("Either bucket_name or bucket must be provided, not both")
30
- if key is not None and object is not None:
31
- raise ValueError("Either key or object must be provided, not both")
32
+ if sum(x is not None for x in [key, object, prefix]) > 1:
33
+ raise ValueError("Only one of key, object, or prefix should be provided")
32
34
 
33
35
  if self.bucket_name is not None:
34
36
  self.bucket = S3Bucket(name=self.bucket_name)
@@ -40,6 +42,7 @@ class S3Content:
40
42
  "key": self.key,
41
43
  "object": self.object,
42
44
  "prefix": self.prefix,
45
+ "config_id": self.config_id,
43
46
  }
44
47
 
45
48
 
@@ -51,19 +54,13 @@ class GCSContent:
51
54
  bucket_name: Optional[str] = None,
52
55
  blob_name: Optional[str] = None,
53
56
  prefix: Optional[str] = None,
57
+ config_id: Optional[str] = None,
54
58
  ):
55
- # Import Google Cloud Storage only when actually needed
56
- try:
57
- from google.cloud import storage # type: ignore
58
- except ImportError:
59
- raise ImportError(
60
- "The `google-cloud-storage` package is not installed. Please install it via `pip install google-cloud-storage`."
61
- )
62
-
63
59
  self.bucket = bucket
64
60
  self.bucket_name = bucket_name
65
61
  self.blob_name = blob_name
66
62
  self.prefix = prefix
63
+ self.config_id = config_id
67
64
 
68
65
  if self.bucket is None and self.bucket_name is None:
69
66
  raise ValueError("No bucket or bucket_name provided")
@@ -72,17 +69,77 @@ class GCSContent:
72
69
  if self.blob_name is None and self.prefix is None:
73
70
  raise ValueError("Either blob_name or prefix must be provided")
74
71
 
75
- if self.bucket is None:
76
- client = storage.Client()
77
- self.bucket = client.bucket(self.bucket_name)
78
-
79
72
  def get_config(self):
80
73
  return {
81
74
  "bucket": self.bucket,
82
75
  "bucket_name": self.bucket_name,
83
76
  "blob_name": self.blob_name,
84
77
  "prefix": self.prefix,
78
+ "config_id": self.config_id,
79
+ }
80
+
81
+
82
+ @dataclass
83
+ class SharePointContent:
84
+ """Content reference for SharePoint files."""
85
+
86
+ def __init__(
87
+ self,
88
+ config_id: str,
89
+ file_path: Optional[str] = None,
90
+ folder_path: Optional[str] = None,
91
+ site_path: Optional[str] = None,
92
+ drive_id: Optional[str] = None,
93
+ ):
94
+ self.config_id = config_id
95
+ self.file_path = file_path
96
+ self.folder_path = folder_path
97
+ self.site_path = site_path
98
+ self.drive_id = drive_id
99
+
100
+ if self.file_path is None and self.folder_path is None:
101
+ raise ValueError("Either file_path or folder_path must be provided")
102
+ if self.file_path is not None and self.folder_path is not None:
103
+ raise ValueError("Provide either file_path or folder_path, not both")
104
+
105
+ def get_config(self):
106
+ return {
107
+ "config_id": self.config_id,
108
+ "file_path": self.file_path,
109
+ "folder_path": self.folder_path,
110
+ "site_path": self.site_path,
111
+ "drive_id": self.drive_id,
112
+ }
113
+
114
+
115
+ @dataclass
116
+ class GitHubContent:
117
+ """Content reference for GitHub files."""
118
+
119
+ def __init__(
120
+ self,
121
+ config_id: str,
122
+ file_path: Optional[str] = None,
123
+ folder_path: Optional[str] = None,
124
+ branch: Optional[str] = None,
125
+ ):
126
+ self.config_id = config_id
127
+ self.file_path = file_path
128
+ self.folder_path = folder_path
129
+ self.branch = branch
130
+
131
+ if self.file_path is None and self.folder_path is None:
132
+ raise ValueError("Either file_path or folder_path must be provided")
133
+ if self.file_path is not None and self.folder_path is not None:
134
+ raise ValueError("Provide either file_path or folder_path, not both")
135
+
136
+ def get_config(self):
137
+ return {
138
+ "config_id": self.config_id,
139
+ "file_path": self.file_path,
140
+ "folder_path": self.folder_path,
141
+ "branch": self.branch,
85
142
  }
86
143
 
87
144
 
88
- RemoteContent = Union[S3Content, GCSContent]
145
+ RemoteContent = Union[S3Content, GCSContent, SharePointContent, GitHubContent]
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."""