agno 2.3.24__py3-none-any.whl → 2.3.26__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/agent.py +357 -28
- agno/db/base.py +214 -0
- agno/db/dynamo/dynamo.py +47 -0
- agno/db/firestore/firestore.py +47 -0
- agno/db/gcs_json/gcs_json_db.py +47 -0
- agno/db/in_memory/in_memory_db.py +47 -0
- agno/db/json/json_db.py +47 -0
- agno/db/mongo/async_mongo.py +229 -0
- agno/db/mongo/mongo.py +47 -0
- agno/db/mongo/schemas.py +16 -0
- agno/db/mysql/async_mysql.py +47 -0
- agno/db/mysql/mysql.py +47 -0
- agno/db/postgres/async_postgres.py +231 -0
- agno/db/postgres/postgres.py +239 -0
- agno/db/postgres/schemas.py +19 -0
- agno/db/redis/redis.py +47 -0
- agno/db/singlestore/singlestore.py +47 -0
- agno/db/sqlite/async_sqlite.py +242 -0
- agno/db/sqlite/schemas.py +18 -0
- agno/db/sqlite/sqlite.py +239 -0
- agno/db/surrealdb/surrealdb.py +47 -0
- agno/knowledge/chunking/code.py +90 -0
- agno/knowledge/chunking/document.py +62 -2
- agno/knowledge/chunking/strategy.py +14 -0
- agno/knowledge/knowledge.py +7 -1
- agno/knowledge/reader/arxiv_reader.py +1 -0
- agno/knowledge/reader/csv_reader.py +1 -0
- agno/knowledge/reader/docx_reader.py +1 -0
- agno/knowledge/reader/firecrawl_reader.py +1 -0
- agno/knowledge/reader/json_reader.py +1 -0
- agno/knowledge/reader/markdown_reader.py +1 -0
- agno/knowledge/reader/pdf_reader.py +1 -0
- agno/knowledge/reader/pptx_reader.py +1 -0
- agno/knowledge/reader/s3_reader.py +1 -0
- agno/knowledge/reader/tavily_reader.py +1 -0
- agno/knowledge/reader/text_reader.py +1 -0
- agno/knowledge/reader/web_search_reader.py +1 -0
- agno/knowledge/reader/website_reader.py +1 -0
- agno/knowledge/reader/wikipedia_reader.py +1 -0
- agno/knowledge/reader/youtube_reader.py +1 -0
- agno/knowledge/utils.py +1 -0
- agno/learn/__init__.py +65 -0
- agno/learn/config.py +463 -0
- agno/learn/curate.py +185 -0
- agno/learn/machine.py +690 -0
- agno/learn/schemas.py +1043 -0
- agno/learn/stores/__init__.py +35 -0
- agno/learn/stores/entity_memory.py +3275 -0
- agno/learn/stores/learned_knowledge.py +1583 -0
- agno/learn/stores/protocol.py +117 -0
- agno/learn/stores/session_context.py +1217 -0
- agno/learn/stores/user_memory.py +1495 -0
- agno/learn/stores/user_profile.py +1220 -0
- agno/learn/utils.py +209 -0
- agno/models/base.py +59 -0
- agno/os/routers/agents/router.py +4 -4
- agno/os/routers/knowledge/knowledge.py +7 -0
- agno/os/routers/teams/router.py +3 -3
- agno/os/routers/workflows/router.py +5 -5
- agno/os/utils.py +55 -3
- agno/team/team.py +131 -0
- agno/tools/browserbase.py +78 -6
- agno/tools/google_bigquery.py +11 -2
- agno/utils/agent.py +30 -1
- agno/workflow/workflow.py +198 -0
- {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/METADATA +24 -2
- {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/RECORD +70 -56
- {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/WHEEL +0 -0
- {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/top_level.txt +0 -0
agno/learn/machine.py
ADDED
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Learning Machine
|
|
3
|
+
================
|
|
4
|
+
Unified learning system for agents.
|
|
5
|
+
|
|
6
|
+
Coordinates multiple learning stores to give agents:
|
|
7
|
+
- User memory (who they're talking to)
|
|
8
|
+
- Session context (what's happened so far)
|
|
9
|
+
- Entity memory (knowledge about external things)
|
|
10
|
+
- Learned knowledge (reusable insights)
|
|
11
|
+
|
|
12
|
+
Plus maintenance via the Curator for keeping memories healthy.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from os import getenv
|
|
17
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
18
|
+
|
|
19
|
+
from agno.learn.config import (
|
|
20
|
+
EntityMemoryConfig,
|
|
21
|
+
LearnedKnowledgeConfig,
|
|
22
|
+
LearningMode,
|
|
23
|
+
SessionContextConfig,
|
|
24
|
+
UserMemoryConfig,
|
|
25
|
+
UserProfileConfig,
|
|
26
|
+
)
|
|
27
|
+
from agno.learn.curate import Curator
|
|
28
|
+
from agno.learn.stores.protocol import LearningStore
|
|
29
|
+
from agno.utils.log import (
|
|
30
|
+
log_debug,
|
|
31
|
+
log_warning,
|
|
32
|
+
set_log_level_to_debug,
|
|
33
|
+
set_log_level_to_info,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from agno.db.base import AsyncBaseDb, BaseDb
|
|
38
|
+
from agno.models.base import Model
|
|
39
|
+
except ImportError:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
# Type aliases for cleaner signatures
|
|
43
|
+
UserProfileInput = Union[bool, UserProfileConfig, LearningStore, None]
|
|
44
|
+
UserMemoryInput = Union[bool, UserMemoryConfig, LearningStore, None]
|
|
45
|
+
EntityMemoryInput = Union[bool, EntityMemoryConfig, LearningStore, None]
|
|
46
|
+
SessionContextInput = Union[bool, SessionContextConfig, LearningStore, None]
|
|
47
|
+
LearnedKnowledgeInput = Union[bool, LearnedKnowledgeConfig, LearningStore, None]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class LearningMachine:
|
|
52
|
+
"""Central orchestrator for agent learning.
|
|
53
|
+
|
|
54
|
+
Coordinates all learning stores and provides unified interface
|
|
55
|
+
for recall, processing, tool generation, and maintenance.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
db: Database backend for persistence.
|
|
59
|
+
model: Model for learning extraction.
|
|
60
|
+
knowledge: Knowledge base for learned knowledge store.
|
|
61
|
+
|
|
62
|
+
user_profile: Enable user profile. Accepts bool, Config, or Store.
|
|
63
|
+
user_memory: Enable user memory. Accepts bool, Config, or Store.
|
|
64
|
+
session_context: Enable session context. Accepts bool, Config, or Store.
|
|
65
|
+
entity_memory: Enable entity memory. Accepts bool, Config, or Store.
|
|
66
|
+
learned_knowledge: Enable learned knowledge. Auto-enabled when knowledge provided.
|
|
67
|
+
|
|
68
|
+
namespace: Default namespace for entity_memory and learned_knowledge.
|
|
69
|
+
custom_stores: Additional stores implementing LearningStore protocol.
|
|
70
|
+
debug_mode: Enable debug logging.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
db: Optional[Union["BaseDb", "AsyncBaseDb"]] = None
|
|
74
|
+
model: Optional["Model"] = None
|
|
75
|
+
knowledge: Optional[Any] = None
|
|
76
|
+
|
|
77
|
+
# Store configurations (accepts bool, Config, or Store instance)
|
|
78
|
+
user_profile: UserProfileInput = False
|
|
79
|
+
user_memory: UserMemoryInput = False
|
|
80
|
+
session_context: SessionContextInput = False
|
|
81
|
+
entity_memory: EntityMemoryInput = False
|
|
82
|
+
learned_knowledge: LearnedKnowledgeInput = False
|
|
83
|
+
|
|
84
|
+
# Namespace for entity_memory and learned_knowledge
|
|
85
|
+
namespace: str = "global"
|
|
86
|
+
|
|
87
|
+
# Custom stores
|
|
88
|
+
custom_stores: Optional[Dict[str, LearningStore]] = None
|
|
89
|
+
|
|
90
|
+
# Debug mode
|
|
91
|
+
debug_mode: bool = False
|
|
92
|
+
|
|
93
|
+
# Internal state (lazy initialization)
|
|
94
|
+
_stores: Optional[Dict[str, LearningStore]] = field(default=None, init=False)
|
|
95
|
+
_curator: Optional[Any] = field(default=None, init=False)
|
|
96
|
+
|
|
97
|
+
# =========================================================================
|
|
98
|
+
# Initialization (Lazy)
|
|
99
|
+
# =========================================================================
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def stores(self) -> Dict[str, LearningStore]:
|
|
103
|
+
"""All registered stores, keyed by name. Lazily initialized."""
|
|
104
|
+
if self._stores is None:
|
|
105
|
+
self._initialize_stores()
|
|
106
|
+
return self._stores # type: ignore
|
|
107
|
+
|
|
108
|
+
def _initialize_stores(self) -> None:
|
|
109
|
+
"""Initialize all configured stores."""
|
|
110
|
+
self._stores = {}
|
|
111
|
+
|
|
112
|
+
# User Profile
|
|
113
|
+
if self.user_profile:
|
|
114
|
+
self._stores["user_profile"] = self._resolve_store(
|
|
115
|
+
input_value=self.user_profile,
|
|
116
|
+
store_type="user_profile",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# User Memory
|
|
120
|
+
if self.user_memory:
|
|
121
|
+
self._stores["user_memory"] = self._resolve_store(
|
|
122
|
+
input_value=self.user_memory,
|
|
123
|
+
store_type="user_memory",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Session Context
|
|
127
|
+
if self.session_context:
|
|
128
|
+
self._stores["session_context"] = self._resolve_store(
|
|
129
|
+
input_value=self.session_context,
|
|
130
|
+
store_type="session_context",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Entity Memory
|
|
134
|
+
if self.entity_memory:
|
|
135
|
+
self._stores["entity_memory"] = self._resolve_store(
|
|
136
|
+
input_value=self.entity_memory,
|
|
137
|
+
store_type="entity_memory",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Learned Knowledge (auto-enable if knowledge provided)
|
|
141
|
+
if self.learned_knowledge or self.knowledge is not None:
|
|
142
|
+
self._stores["learned_knowledge"] = self._resolve_store(
|
|
143
|
+
input_value=self.learned_knowledge if self.learned_knowledge else True,
|
|
144
|
+
store_type="learned_knowledge",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Custom stores
|
|
148
|
+
if self.custom_stores:
|
|
149
|
+
for name, store in self.custom_stores.items():
|
|
150
|
+
self._stores[name] = store
|
|
151
|
+
|
|
152
|
+
log_debug(f"LearningMachine initialized with stores: {list(self._stores.keys())}")
|
|
153
|
+
|
|
154
|
+
def _resolve_store(
|
|
155
|
+
self,
|
|
156
|
+
input_value: Any,
|
|
157
|
+
store_type: str,
|
|
158
|
+
) -> LearningStore:
|
|
159
|
+
"""Resolve input to a store instance.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
input_value: bool, Config, or Store instance
|
|
163
|
+
store_type: One of "user_profile", "user_memory", "session_context", "entity_memory", "learned_knowledge"
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Initialized store instance.
|
|
167
|
+
"""
|
|
168
|
+
# Already a store instance
|
|
169
|
+
if isinstance(input_value, LearningStore):
|
|
170
|
+
return input_value
|
|
171
|
+
|
|
172
|
+
# Create store based on type
|
|
173
|
+
if store_type == "user_profile":
|
|
174
|
+
return self._create_user_profile_store(config=input_value)
|
|
175
|
+
elif store_type == "user_memory":
|
|
176
|
+
return self._create_user_memory_store(config=input_value)
|
|
177
|
+
elif store_type == "session_context":
|
|
178
|
+
return self._create_session_context_store(config=input_value)
|
|
179
|
+
elif store_type == "entity_memory":
|
|
180
|
+
return self._create_entity_memory_store(config=input_value)
|
|
181
|
+
elif store_type == "learned_knowledge":
|
|
182
|
+
return self._create_learned_knowledge_store(config=input_value)
|
|
183
|
+
else:
|
|
184
|
+
raise ValueError(f"Unknown store type: {store_type}")
|
|
185
|
+
|
|
186
|
+
def _create_user_profile_store(self, config: Any) -> LearningStore:
|
|
187
|
+
"""Create UserProfileStore with resolved config."""
|
|
188
|
+
from agno.learn.stores import UserProfileStore
|
|
189
|
+
|
|
190
|
+
if isinstance(config, UserProfileConfig):
|
|
191
|
+
if config.db is None:
|
|
192
|
+
config.db = self.db
|
|
193
|
+
if config.model is None:
|
|
194
|
+
config.model = self.model
|
|
195
|
+
else:
|
|
196
|
+
config = UserProfileConfig(
|
|
197
|
+
db=self.db,
|
|
198
|
+
model=self.model,
|
|
199
|
+
mode=LearningMode.ALWAYS,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return UserProfileStore(config=config, debug_mode=self.debug_mode)
|
|
203
|
+
|
|
204
|
+
def _create_user_memory_store(self, config: Any) -> LearningStore:
|
|
205
|
+
"""Create UserMemoryStore with resolved config."""
|
|
206
|
+
from agno.learn.stores import UserMemoryStore
|
|
207
|
+
|
|
208
|
+
if isinstance(config, UserMemoryConfig):
|
|
209
|
+
if config.db is None:
|
|
210
|
+
config.db = self.db
|
|
211
|
+
if config.model is None:
|
|
212
|
+
config.model = self.model
|
|
213
|
+
else:
|
|
214
|
+
config = UserMemoryConfig(
|
|
215
|
+
db=self.db,
|
|
216
|
+
model=self.model,
|
|
217
|
+
mode=LearningMode.ALWAYS,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return UserMemoryStore(config=config, debug_mode=self.debug_mode)
|
|
221
|
+
|
|
222
|
+
def _create_session_context_store(self, config: Any) -> LearningStore:
|
|
223
|
+
"""Create SessionContextStore with resolved config."""
|
|
224
|
+
from agno.learn.stores import SessionContextStore
|
|
225
|
+
|
|
226
|
+
if isinstance(config, SessionContextConfig):
|
|
227
|
+
if config.db is None:
|
|
228
|
+
config.db = self.db
|
|
229
|
+
if config.model is None:
|
|
230
|
+
config.model = self.model
|
|
231
|
+
else:
|
|
232
|
+
config = SessionContextConfig(
|
|
233
|
+
db=self.db,
|
|
234
|
+
model=self.model,
|
|
235
|
+
enable_planning=False,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return SessionContextStore(config=config, debug_mode=self.debug_mode)
|
|
239
|
+
|
|
240
|
+
def _create_entity_memory_store(self, config: Any) -> LearningStore:
|
|
241
|
+
"""Create EntityMemoryStore with resolved config."""
|
|
242
|
+
from agno.learn.stores import EntityMemoryStore
|
|
243
|
+
|
|
244
|
+
if isinstance(config, EntityMemoryConfig):
|
|
245
|
+
if config.db is None:
|
|
246
|
+
config.db = self.db
|
|
247
|
+
if config.model is None:
|
|
248
|
+
config.model = self.model
|
|
249
|
+
else:
|
|
250
|
+
config = EntityMemoryConfig(
|
|
251
|
+
db=self.db,
|
|
252
|
+
model=self.model,
|
|
253
|
+
namespace=self.namespace,
|
|
254
|
+
mode=LearningMode.ALWAYS,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return EntityMemoryStore(config=config, debug_mode=self.debug_mode)
|
|
258
|
+
|
|
259
|
+
def _create_learned_knowledge_store(self, config: Any) -> LearningStore:
|
|
260
|
+
"""Create LearnedKnowledgeStore with resolved config."""
|
|
261
|
+
from agno.learn.stores import LearnedKnowledgeStore
|
|
262
|
+
|
|
263
|
+
if isinstance(config, LearnedKnowledgeConfig):
|
|
264
|
+
if config.model is None:
|
|
265
|
+
config.model = self.model
|
|
266
|
+
if config.knowledge is None and self.knowledge is not None:
|
|
267
|
+
config.knowledge = self.knowledge
|
|
268
|
+
else:
|
|
269
|
+
config = LearnedKnowledgeConfig(
|
|
270
|
+
model=self.model,
|
|
271
|
+
knowledge=self.knowledge,
|
|
272
|
+
mode=LearningMode.AGENTIC,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
return LearnedKnowledgeStore(config=config, debug_mode=self.debug_mode)
|
|
276
|
+
|
|
277
|
+
# =========================================================================
|
|
278
|
+
# Store Accessors (Type-Safe)
|
|
279
|
+
# =========================================================================
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def user_profile_store(self) -> Optional[LearningStore]:
|
|
283
|
+
"""Get user profile store if enabled."""
|
|
284
|
+
return self.stores.get("user_profile")
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def user_memory_store(self) -> Optional[LearningStore]:
|
|
288
|
+
"""Get user memory store if enabled."""
|
|
289
|
+
return self.stores.get("user_memory")
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def session_context_store(self) -> Optional[LearningStore]:
|
|
293
|
+
"""Get session context store if enabled."""
|
|
294
|
+
return self.stores.get("session_context")
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def entity_memory_store(self) -> Optional[LearningStore]:
|
|
298
|
+
"""Get entity memory store if enabled."""
|
|
299
|
+
return self.stores.get("entity_memory")
|
|
300
|
+
|
|
301
|
+
@property
|
|
302
|
+
def learned_knowledge_store(self) -> Optional[LearningStore]:
|
|
303
|
+
"""Get learned knowledge store if enabled."""
|
|
304
|
+
return self.stores.get("learned_knowledge")
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def was_updated(self) -> bool:
|
|
308
|
+
"""True if any store was updated in the last operation."""
|
|
309
|
+
return any(getattr(store, "was_updated", False) for store in self.stores.values())
|
|
310
|
+
|
|
311
|
+
# =========================================================================
|
|
312
|
+
# Main API
|
|
313
|
+
# =========================================================================
|
|
314
|
+
|
|
315
|
+
def build_context(
|
|
316
|
+
self,
|
|
317
|
+
user_id: Optional[str] = None,
|
|
318
|
+
session_id: Optional[str] = None,
|
|
319
|
+
message: Optional[str] = None,
|
|
320
|
+
entity_id: Optional[str] = None,
|
|
321
|
+
entity_type: Optional[str] = None,
|
|
322
|
+
namespace: Optional[str] = None,
|
|
323
|
+
agent_id: Optional[str] = None,
|
|
324
|
+
team_id: Optional[str] = None,
|
|
325
|
+
**kwargs,
|
|
326
|
+
) -> str:
|
|
327
|
+
"""Build memory context for the agent's system prompt.
|
|
328
|
+
|
|
329
|
+
Call before generating a response to give the agent relevant context.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
user_id: User identifier (for user profile lookup).
|
|
333
|
+
session_id: Session identifier (for session context lookup).
|
|
334
|
+
message: Current message (for semantic search of learnings).
|
|
335
|
+
entity_id: Entity to retrieve (for entity memory).
|
|
336
|
+
entity_type: Type of entity to retrieve.
|
|
337
|
+
namespace: Namespace filter for entity_memory and learned_knowledge.
|
|
338
|
+
agent_id: Optional agent context.
|
|
339
|
+
team_id: Optional team context.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
Context string to inject into the agent's system prompt.
|
|
343
|
+
"""
|
|
344
|
+
results = self.recall(
|
|
345
|
+
user_id=user_id,
|
|
346
|
+
session_id=session_id,
|
|
347
|
+
message=message,
|
|
348
|
+
entity_id=entity_id,
|
|
349
|
+
entity_type=entity_type,
|
|
350
|
+
namespace=namespace or self.namespace,
|
|
351
|
+
agent_id=agent_id,
|
|
352
|
+
team_id=team_id,
|
|
353
|
+
**kwargs,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
return self._format_results(results=results)
|
|
357
|
+
|
|
358
|
+
async def abuild_context(
|
|
359
|
+
self,
|
|
360
|
+
user_id: Optional[str] = None,
|
|
361
|
+
session_id: Optional[str] = None,
|
|
362
|
+
message: Optional[str] = None,
|
|
363
|
+
entity_id: Optional[str] = None,
|
|
364
|
+
entity_type: Optional[str] = None,
|
|
365
|
+
namespace: Optional[str] = None,
|
|
366
|
+
agent_id: Optional[str] = None,
|
|
367
|
+
team_id: Optional[str] = None,
|
|
368
|
+
**kwargs,
|
|
369
|
+
) -> str:
|
|
370
|
+
"""Async version of build_context."""
|
|
371
|
+
results = await self.arecall(
|
|
372
|
+
user_id=user_id,
|
|
373
|
+
session_id=session_id,
|
|
374
|
+
message=message,
|
|
375
|
+
entity_id=entity_id,
|
|
376
|
+
entity_type=entity_type,
|
|
377
|
+
namespace=namespace or self.namespace,
|
|
378
|
+
agent_id=agent_id,
|
|
379
|
+
team_id=team_id,
|
|
380
|
+
**kwargs,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return self._format_results(results=results)
|
|
384
|
+
|
|
385
|
+
def get_tools(
|
|
386
|
+
self,
|
|
387
|
+
user_id: Optional[str] = None,
|
|
388
|
+
session_id: Optional[str] = None,
|
|
389
|
+
namespace: Optional[str] = None,
|
|
390
|
+
agent_id: Optional[str] = None,
|
|
391
|
+
team_id: Optional[str] = None,
|
|
392
|
+
**kwargs,
|
|
393
|
+
) -> List[Callable]:
|
|
394
|
+
"""Get learning tools to expose to the agent.
|
|
395
|
+
|
|
396
|
+
Returns tools based on which stores are enabled:
|
|
397
|
+
- user_profile: update_user_memory
|
|
398
|
+
- entity_memory: search_entities, create_entity, update_entity, add_fact, etc.
|
|
399
|
+
- learned_knowledge: search_learnings, save_learning
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
user_id: User identifier (required for user profile tools).
|
|
403
|
+
session_id: Session identifier.
|
|
404
|
+
namespace: Default namespace for entity/learning operations.
|
|
405
|
+
agent_id: Optional agent context.
|
|
406
|
+
team_id: Optional team context.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
List of callable tools.
|
|
410
|
+
"""
|
|
411
|
+
tools = []
|
|
412
|
+
context = {
|
|
413
|
+
"user_id": user_id,
|
|
414
|
+
"session_id": session_id,
|
|
415
|
+
"namespace": namespace or self.namespace,
|
|
416
|
+
"agent_id": agent_id,
|
|
417
|
+
"team_id": team_id,
|
|
418
|
+
**kwargs,
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
for name, store in self.stores.items():
|
|
422
|
+
try:
|
|
423
|
+
store_tools = store.get_tools(**context)
|
|
424
|
+
if store_tools:
|
|
425
|
+
tools.extend(store_tools)
|
|
426
|
+
log_debug(f"Got {len(store_tools)} tools from {name}")
|
|
427
|
+
except Exception as e:
|
|
428
|
+
log_warning(f"Error getting tools from {name}: {e}")
|
|
429
|
+
|
|
430
|
+
return tools
|
|
431
|
+
|
|
432
|
+
async def aget_tools(
|
|
433
|
+
self,
|
|
434
|
+
user_id: Optional[str] = None,
|
|
435
|
+
session_id: Optional[str] = None,
|
|
436
|
+
namespace: Optional[str] = None,
|
|
437
|
+
agent_id: Optional[str] = None,
|
|
438
|
+
team_id: Optional[str] = None,
|
|
439
|
+
**kwargs,
|
|
440
|
+
) -> List[Callable]:
|
|
441
|
+
"""Async version of get_tools."""
|
|
442
|
+
tools = []
|
|
443
|
+
context = {
|
|
444
|
+
"user_id": user_id,
|
|
445
|
+
"session_id": session_id,
|
|
446
|
+
"namespace": namespace or self.namespace,
|
|
447
|
+
"agent_id": agent_id,
|
|
448
|
+
"team_id": team_id,
|
|
449
|
+
**kwargs,
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
for name, store in self.stores.items():
|
|
453
|
+
try:
|
|
454
|
+
store_tools = await store.aget_tools(**context)
|
|
455
|
+
if store_tools:
|
|
456
|
+
tools.extend(store_tools)
|
|
457
|
+
log_debug(f"Got {len(store_tools)} tools from {name}")
|
|
458
|
+
except Exception as e:
|
|
459
|
+
log_warning(f"Error getting tools from {name}: {e}")
|
|
460
|
+
|
|
461
|
+
return tools
|
|
462
|
+
|
|
463
|
+
def process(
|
|
464
|
+
self,
|
|
465
|
+
messages: List[Any],
|
|
466
|
+
user_id: Optional[str] = None,
|
|
467
|
+
session_id: Optional[str] = None,
|
|
468
|
+
namespace: Optional[str] = None,
|
|
469
|
+
agent_id: Optional[str] = None,
|
|
470
|
+
team_id: Optional[str] = None,
|
|
471
|
+
**kwargs,
|
|
472
|
+
) -> None:
|
|
473
|
+
"""Extract and save learnings from a conversation.
|
|
474
|
+
|
|
475
|
+
Call after a conversation to extract learnings. Each store
|
|
476
|
+
processes based on its mode (ALWAYS stores extract automatically).
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
messages: Conversation messages to analyze.
|
|
480
|
+
user_id: User identifier (for user profile extraction).
|
|
481
|
+
session_id: Session identifier (for session context extraction).
|
|
482
|
+
namespace: Namespace for entity/learning saves.
|
|
483
|
+
agent_id: Optional agent context.
|
|
484
|
+
team_id: Optional team context.
|
|
485
|
+
"""
|
|
486
|
+
context = {
|
|
487
|
+
"messages": messages,
|
|
488
|
+
"user_id": user_id,
|
|
489
|
+
"session_id": session_id,
|
|
490
|
+
"namespace": namespace or self.namespace,
|
|
491
|
+
"agent_id": agent_id,
|
|
492
|
+
"team_id": team_id,
|
|
493
|
+
**kwargs,
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
for name, store in self.stores.items():
|
|
497
|
+
try:
|
|
498
|
+
store.process(**context)
|
|
499
|
+
if getattr(store, "was_updated", False):
|
|
500
|
+
log_debug(f"Store {name} was updated")
|
|
501
|
+
except Exception as e:
|
|
502
|
+
log_warning(f"Error processing through {name}: {e}")
|
|
503
|
+
|
|
504
|
+
async def aprocess(
|
|
505
|
+
self,
|
|
506
|
+
messages: List[Any],
|
|
507
|
+
user_id: Optional[str] = None,
|
|
508
|
+
session_id: Optional[str] = None,
|
|
509
|
+
namespace: Optional[str] = None,
|
|
510
|
+
agent_id: Optional[str] = None,
|
|
511
|
+
team_id: Optional[str] = None,
|
|
512
|
+
**kwargs,
|
|
513
|
+
) -> None:
|
|
514
|
+
"""Async version of process."""
|
|
515
|
+
context = {
|
|
516
|
+
"messages": messages,
|
|
517
|
+
"user_id": user_id,
|
|
518
|
+
"session_id": session_id,
|
|
519
|
+
"namespace": namespace or self.namespace,
|
|
520
|
+
"agent_id": agent_id,
|
|
521
|
+
"team_id": team_id,
|
|
522
|
+
**kwargs,
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
for name, store in self.stores.items():
|
|
526
|
+
try:
|
|
527
|
+
await store.aprocess(**context)
|
|
528
|
+
if getattr(store, "was_updated", False):
|
|
529
|
+
log_debug(f"Store {name} was updated")
|
|
530
|
+
except Exception as e:
|
|
531
|
+
log_warning(f"Error processing through {name}: {e}")
|
|
532
|
+
|
|
533
|
+
# =========================================================================
|
|
534
|
+
# Lower-Level API
|
|
535
|
+
# =========================================================================
|
|
536
|
+
|
|
537
|
+
def recall(
|
|
538
|
+
self,
|
|
539
|
+
user_id: Optional[str] = None,
|
|
540
|
+
session_id: Optional[str] = None,
|
|
541
|
+
message: Optional[str] = None,
|
|
542
|
+
entity_id: Optional[str] = None,
|
|
543
|
+
entity_type: Optional[str] = None,
|
|
544
|
+
namespace: Optional[str] = None,
|
|
545
|
+
agent_id: Optional[str] = None,
|
|
546
|
+
team_id: Optional[str] = None,
|
|
547
|
+
**kwargs,
|
|
548
|
+
) -> Dict[str, Any]:
|
|
549
|
+
"""Retrieve raw data from all stores.
|
|
550
|
+
|
|
551
|
+
Most users should use `build_context()` instead.
|
|
552
|
+
|
|
553
|
+
Returns:
|
|
554
|
+
Dict mapping store names to their recalled data.
|
|
555
|
+
"""
|
|
556
|
+
results = {}
|
|
557
|
+
context = {
|
|
558
|
+
"user_id": user_id,
|
|
559
|
+
"session_id": session_id,
|
|
560
|
+
"message": message,
|
|
561
|
+
"query": message, # For learned_knowledge
|
|
562
|
+
"entity_id": entity_id,
|
|
563
|
+
"entity_type": entity_type,
|
|
564
|
+
"namespace": namespace or self.namespace,
|
|
565
|
+
"agent_id": agent_id,
|
|
566
|
+
"team_id": team_id,
|
|
567
|
+
**kwargs,
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
for name, store in self.stores.items():
|
|
571
|
+
try:
|
|
572
|
+
result = store.recall(**context)
|
|
573
|
+
results[name] = result
|
|
574
|
+
try:
|
|
575
|
+
log_debug(f"Recalled from {name}: {result}")
|
|
576
|
+
except Exception:
|
|
577
|
+
pass
|
|
578
|
+
except Exception as e:
|
|
579
|
+
log_warning(f"Error recalling from {name}: {e}")
|
|
580
|
+
|
|
581
|
+
return results
|
|
582
|
+
|
|
583
|
+
async def arecall(
|
|
584
|
+
self,
|
|
585
|
+
user_id: Optional[str] = None,
|
|
586
|
+
session_id: Optional[str] = None,
|
|
587
|
+
message: Optional[str] = None,
|
|
588
|
+
entity_id: Optional[str] = None,
|
|
589
|
+
entity_type: Optional[str] = None,
|
|
590
|
+
namespace: Optional[str] = None,
|
|
591
|
+
agent_id: Optional[str] = None,
|
|
592
|
+
team_id: Optional[str] = None,
|
|
593
|
+
**kwargs,
|
|
594
|
+
) -> Dict[str, Any]:
|
|
595
|
+
"""Async version of recall."""
|
|
596
|
+
results = {}
|
|
597
|
+
context = {
|
|
598
|
+
"user_id": user_id,
|
|
599
|
+
"session_id": session_id,
|
|
600
|
+
"message": message,
|
|
601
|
+
"query": message,
|
|
602
|
+
"entity_id": entity_id,
|
|
603
|
+
"entity_type": entity_type,
|
|
604
|
+
"namespace": namespace or self.namespace,
|
|
605
|
+
"agent_id": agent_id,
|
|
606
|
+
"team_id": team_id,
|
|
607
|
+
**kwargs,
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
for name, store in self.stores.items():
|
|
611
|
+
try:
|
|
612
|
+
result = await store.arecall(**context)
|
|
613
|
+
if result is not None:
|
|
614
|
+
results[name] = result
|
|
615
|
+
try:
|
|
616
|
+
log_debug(f"Recalled from {name}: {result}")
|
|
617
|
+
except Exception:
|
|
618
|
+
pass
|
|
619
|
+
except Exception as e:
|
|
620
|
+
log_warning(f"Error recalling from {name}: {e}")
|
|
621
|
+
|
|
622
|
+
return results
|
|
623
|
+
|
|
624
|
+
def _format_results(self, results: Dict[str, Any]) -> str:
|
|
625
|
+
"""Format recalled data into context string."""
|
|
626
|
+
parts = []
|
|
627
|
+
|
|
628
|
+
for name, data in results.items():
|
|
629
|
+
store = self.stores.get(name)
|
|
630
|
+
if store:
|
|
631
|
+
try:
|
|
632
|
+
formatted = store.build_context(data=data)
|
|
633
|
+
if formatted:
|
|
634
|
+
parts.append(formatted)
|
|
635
|
+
except Exception as e:
|
|
636
|
+
log_warning(f"Error building context from {name}: {e}")
|
|
637
|
+
|
|
638
|
+
return "\n\n".join(parts)
|
|
639
|
+
|
|
640
|
+
# =========================================================================
|
|
641
|
+
# Curation
|
|
642
|
+
# =========================================================================
|
|
643
|
+
|
|
644
|
+
@property
|
|
645
|
+
def curator(self) -> "Curator":
|
|
646
|
+
"""Get the curator for memory maintenance.
|
|
647
|
+
|
|
648
|
+
Lazily creates the curator on first access.
|
|
649
|
+
|
|
650
|
+
Example:
|
|
651
|
+
>>> learning.curator.prune(user_id="alice", max_age_days=90)
|
|
652
|
+
>>> learning.curator.deduplicate(user_id="alice")
|
|
653
|
+
"""
|
|
654
|
+
if self._curator is None:
|
|
655
|
+
from agno.learn.curate import Curator
|
|
656
|
+
|
|
657
|
+
self._curator = Curator(machine=self)
|
|
658
|
+
return self._curator
|
|
659
|
+
|
|
660
|
+
# =========================================================================
|
|
661
|
+
# Debug
|
|
662
|
+
# =========================================================================
|
|
663
|
+
|
|
664
|
+
def set_log_level(self) -> None:
|
|
665
|
+
"""Set log level based on debug_mode or AGNO_DEBUG env var."""
|
|
666
|
+
if self.debug_mode or getenv("AGNO_DEBUG", "false").lower() == "true":
|
|
667
|
+
self.debug_mode = True
|
|
668
|
+
set_log_level_to_debug()
|
|
669
|
+
else:
|
|
670
|
+
set_log_level_to_info()
|
|
671
|
+
|
|
672
|
+
# =========================================================================
|
|
673
|
+
# Representation
|
|
674
|
+
# =========================================================================
|
|
675
|
+
|
|
676
|
+
def __repr__(self) -> str:
|
|
677
|
+
"""String representation for debugging."""
|
|
678
|
+
store_names = list(self.stores.keys()) if self._stores is not None else "[not initialized]"
|
|
679
|
+
db_name = self.db.__class__.__name__ if self.db else None
|
|
680
|
+
model_name = self.model.id if self.model and hasattr(self.model, "id") else None
|
|
681
|
+
has_knowledge = self.knowledge is not None
|
|
682
|
+
|
|
683
|
+
return (
|
|
684
|
+
f"LearningMachine("
|
|
685
|
+
f"stores={store_names}, "
|
|
686
|
+
f"db={db_name}, "
|
|
687
|
+
f"model={model_name}, "
|
|
688
|
+
f"knowledge={has_knowledge}, "
|
|
689
|
+
f"namespace={self.namespace!r})"
|
|
690
|
+
)
|