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.
- agno/agent/__init__.py +4 -0
- agno/agent/agent.py +1368 -541
- agno/agent/remote.py +13 -0
- agno/db/base.py +339 -0
- agno/db/postgres/async_postgres.py +116 -12
- agno/db/postgres/postgres.py +1242 -25
- agno/db/postgres/schemas.py +48 -1
- agno/db/sqlite/async_sqlite.py +119 -4
- agno/db/sqlite/schemas.py +51 -0
- agno/db/sqlite/sqlite.py +1186 -13
- agno/db/utils.py +37 -1
- agno/integrations/discord/client.py +12 -1
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +1 -1
- agno/knowledge/chunking/semantic.py +1 -1
- agno/knowledge/chunking/strategy.py +4 -0
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +3722 -2182
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +2 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +236 -13
- agno/knowledge/reader/docx_reader.py +2 -2
- agno/knowledge/reader/field_labeled_csv_reader.py +169 -5
- agno/knowledge/reader/firecrawl_reader.py +2 -2
- agno/knowledge/reader/json_reader.py +2 -2
- agno/knowledge/reader/markdown_reader.py +2 -2
- agno/knowledge/reader/pdf_reader.py +5 -4
- agno/knowledge/reader/pptx_reader.py +2 -2
- agno/knowledge/reader/reader_factory.py +118 -1
- agno/knowledge/reader/s3_reader.py +2 -2
- agno/knowledge/reader/tavily_reader.py +2 -2
- agno/knowledge/reader/text_reader.py +2 -2
- agno/knowledge/reader/web_search_reader.py +2 -2
- agno/knowledge/reader/website_reader.py +5 -3
- agno/knowledge/reader/wikipedia_reader.py +2 -2
- agno/knowledge/reader/youtube_reader.py +2 -2
- agno/knowledge/remote_content/__init__.py +29 -0
- agno/knowledge/remote_content/config.py +204 -0
- agno/knowledge/remote_content/remote_content.py +74 -17
- agno/knowledge/utils.py +37 -29
- agno/learn/__init__.py +6 -0
- agno/learn/machine.py +35 -0
- agno/learn/schemas.py +82 -11
- agno/learn/stores/__init__.py +3 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/learned_knowledge.py +6 -6
- agno/models/anthropic/claude.py +24 -0
- agno/models/aws/bedrock.py +20 -0
- agno/models/base.py +60 -6
- agno/models/cerebras/cerebras.py +34 -2
- agno/models/cohere/chat.py +25 -0
- agno/models/google/gemini.py +50 -5
- agno/models/litellm/chat.py +38 -0
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/openai/chat.py +25 -1
- agno/models/openrouter/openrouter.py +46 -0
- agno/models/perplexity/perplexity.py +2 -0
- agno/models/response.py +16 -0
- agno/os/app.py +83 -44
- agno/os/interfaces/slack/router.py +10 -1
- agno/os/interfaces/whatsapp/router.py +6 -0
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +1 -0
- agno/os/routers/agents/router.py +29 -16
- agno/os/routers/agents/schema.py +6 -4
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/evals/schemas.py +4 -3
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +128 -3
- agno/os/routers/knowledge/schemas.py +12 -0
- agno/os/routers/memory/schemas.py +4 -2
- agno/os/routers/metrics/metrics.py +9 -11
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/teams/router.py +20 -8
- agno/os/routers/teams/schema.py +6 -4
- agno/os/routers/traces/traces.py +5 -5
- agno/os/routers/workflows/router.py +38 -11
- agno/os/routers/workflows/schema.py +1 -1
- agno/os/schema.py +92 -26
- agno/os/utils.py +84 -19
- agno/reasoning/anthropic.py +2 -2
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +2 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +4 -10
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +2 -2
- agno/reasoning/vertexai.py +2 -2
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/run/agent.py +59 -0
- agno/run/base.py +7 -0
- agno/run/team.py +57 -0
- agno/skills/agent_skills.py +10 -3
- agno/team/__init__.py +3 -1
- agno/team/team.py +1165 -330
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/function.py +35 -83
- agno/tools/knowledge.py +9 -4
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/parallel.py +0 -7
- agno/tools/reasoning.py +30 -23
- agno/tools/tavily.py +4 -1
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +48 -47
- agno/utils/agent.py +42 -5
- agno/utils/events.py +160 -2
- agno/utils/print_response/agent.py +0 -31
- agno/utils/print_response/team.py +0 -2
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/team.py +61 -11
- agno/vectordb/lancedb/lance_db.py +4 -1
- agno/vectordb/mongodb/mongodb.py +1 -1
- agno/vectordb/pgvector/pgvector.py +3 -3
- agno/vectordb/qdrant/qdrant.py +4 -4
- agno/workflow/__init__.py +3 -1
- agno/workflow/condition.py +0 -21
- agno/workflow/loop.py +0 -21
- agno/workflow/parallel.py +0 -21
- agno/workflow/router.py +0 -21
- agno/workflow/step.py +117 -24
- agno/workflow/steps.py +0 -21
- agno/workflow/workflow.py +427 -63
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/METADATA +49 -76
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/RECORD +140 -126
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/WHEEL +1 -1
- {agno-2.3.26.dist-info → agno-2.4.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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
|
|
31
|
-
raise ValueError("
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
# Get the reader CLASS without instantiation
|
|
59
|
+
reader_class = ReaderFactory.get_reader_class(reader_key)
|
|
56
60
|
|
|
57
|
-
#
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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": ""
|
|
67
|
-
"description":
|
|
68
|
-
"chunking_strategies": [
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
127
|
-
|
|
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."""
|