agno 2.3.24__py3-none-any.whl → 2.3.25__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 +297 -11
- 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/knowledge/knowledge.py +7 -0
- agno/tools/browserbase.py +78 -6
- agno/tools/google_bigquery.py +11 -2
- agno/utils/agent.py +30 -1
- {agno-2.3.24.dist-info → agno-2.3.25.dist-info}/METADATA +24 -2
- {agno-2.3.24.dist-info → agno-2.3.25.dist-info}/RECORD +64 -50
- {agno-2.3.24.dist-info → agno-2.3.25.dist-info}/WHEEL +0 -0
- {agno-2.3.24.dist-info → agno-2.3.25.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.24.dist-info → agno-2.3.25.dist-info}/top_level.txt +0 -0
|
@@ -58,6 +58,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
58
58
|
traces_table: Optional[str] = None,
|
|
59
59
|
spans_table: Optional[str] = None,
|
|
60
60
|
versions_table: Optional[str] = None,
|
|
61
|
+
learnings_table: Optional[str] = None,
|
|
61
62
|
create_schema: bool = True,
|
|
62
63
|
db_id: Optional[str] = None, # Deprecated, use id instead.
|
|
63
64
|
):
|
|
@@ -92,6 +93,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
92
93
|
traces_table (Optional[str]): Name of the table to store run traces.
|
|
93
94
|
spans_table (Optional[str]): Name of the table to store span events.
|
|
94
95
|
versions_table (Optional[str]): Name of the table to store schema versions.
|
|
96
|
+
learnings_table (Optional[str]): Name of the table to store learnings.
|
|
95
97
|
create_schema (bool): Whether to automatically create the database schema if it doesn't exist.
|
|
96
98
|
Set to False if schema is managed externally (e.g., via migrations). Defaults to True.
|
|
97
99
|
db_id: Deprecated, use id instead.
|
|
@@ -118,6 +120,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
118
120
|
traces_table=traces_table,
|
|
119
121
|
spans_table=spans_table,
|
|
120
122
|
versions_table=versions_table,
|
|
123
|
+
learnings_table=learnings_table,
|
|
121
124
|
)
|
|
122
125
|
|
|
123
126
|
_engine: Optional[AsyncEngine] = db_engine
|
|
@@ -173,6 +176,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
173
176
|
(self.eval_table_name, "evals"),
|
|
174
177
|
(self.knowledge_table_name, "knowledge"),
|
|
175
178
|
(self.versions_table_name, "versions"),
|
|
179
|
+
(self.learnings_table_name, "learnings"),
|
|
176
180
|
]
|
|
177
181
|
|
|
178
182
|
for table_name, table_type in tables_to_create:
|
|
@@ -372,6 +376,15 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
372
376
|
)
|
|
373
377
|
return self.spans_table
|
|
374
378
|
|
|
379
|
+
if table_type == "learnings":
|
|
380
|
+
if not hasattr(self, "learnings_table"):
|
|
381
|
+
self.learnings_table = await self._get_or_create_table(
|
|
382
|
+
table_name=self.learnings_table_name,
|
|
383
|
+
table_type="learnings",
|
|
384
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
385
|
+
)
|
|
386
|
+
return self.learnings_table
|
|
387
|
+
|
|
375
388
|
raise ValueError(f"Unknown table type: {table_type}")
|
|
376
389
|
|
|
377
390
|
async def _get_or_create_table(
|
|
@@ -2716,3 +2729,221 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
2716
2729
|
except Exception as e:
|
|
2717
2730
|
log_error(f"Error getting spans: {e}")
|
|
2718
2731
|
return []
|
|
2732
|
+
|
|
2733
|
+
# -- Learning methods --
|
|
2734
|
+
async def get_learning(
|
|
2735
|
+
self,
|
|
2736
|
+
learning_type: str,
|
|
2737
|
+
user_id: Optional[str] = None,
|
|
2738
|
+
agent_id: Optional[str] = None,
|
|
2739
|
+
team_id: Optional[str] = None,
|
|
2740
|
+
session_id: Optional[str] = None,
|
|
2741
|
+
namespace: Optional[str] = None,
|
|
2742
|
+
entity_id: Optional[str] = None,
|
|
2743
|
+
entity_type: Optional[str] = None,
|
|
2744
|
+
) -> Optional[Dict[str, Any]]:
|
|
2745
|
+
"""Async retrieve a learning record.
|
|
2746
|
+
|
|
2747
|
+
Args:
|
|
2748
|
+
learning_type: Type of learning ('user_profile', 'session_context', etc.)
|
|
2749
|
+
user_id: Filter by user ID.
|
|
2750
|
+
agent_id: Filter by agent ID.
|
|
2751
|
+
team_id: Filter by team ID.
|
|
2752
|
+
session_id: Filter by session ID.
|
|
2753
|
+
namespace: Filter by namespace ('user', 'global', or custom).
|
|
2754
|
+
entity_id: Filter by entity ID (for entity-specific learnings).
|
|
2755
|
+
entity_type: Filter by entity type ('person', 'company', etc.).
|
|
2756
|
+
|
|
2757
|
+
Returns:
|
|
2758
|
+
Dict with 'content' key containing the learning data, or None.
|
|
2759
|
+
"""
|
|
2760
|
+
try:
|
|
2761
|
+
table = await self._get_table(table_type="learnings")
|
|
2762
|
+
if table is None:
|
|
2763
|
+
return None
|
|
2764
|
+
|
|
2765
|
+
async with self.async_session_factory() as sess:
|
|
2766
|
+
stmt = select(table).where(table.c.learning_type == learning_type)
|
|
2767
|
+
|
|
2768
|
+
if user_id is not None:
|
|
2769
|
+
stmt = stmt.where(table.c.user_id == user_id)
|
|
2770
|
+
if agent_id is not None:
|
|
2771
|
+
stmt = stmt.where(table.c.agent_id == agent_id)
|
|
2772
|
+
if team_id is not None:
|
|
2773
|
+
stmt = stmt.where(table.c.team_id == team_id)
|
|
2774
|
+
if session_id is not None:
|
|
2775
|
+
stmt = stmt.where(table.c.session_id == session_id)
|
|
2776
|
+
if namespace is not None:
|
|
2777
|
+
stmt = stmt.where(table.c.namespace == namespace)
|
|
2778
|
+
if entity_id is not None:
|
|
2779
|
+
stmt = stmt.where(table.c.entity_id == entity_id)
|
|
2780
|
+
if entity_type is not None:
|
|
2781
|
+
stmt = stmt.where(table.c.entity_type == entity_type)
|
|
2782
|
+
|
|
2783
|
+
result = await sess.execute(stmt)
|
|
2784
|
+
row = result.fetchone()
|
|
2785
|
+
if row is None:
|
|
2786
|
+
return None
|
|
2787
|
+
|
|
2788
|
+
row_dict = dict(row._mapping)
|
|
2789
|
+
return {"content": row_dict.get("content")}
|
|
2790
|
+
|
|
2791
|
+
except Exception as e:
|
|
2792
|
+
log_debug(f"Error retrieving learning: {e}")
|
|
2793
|
+
return None
|
|
2794
|
+
|
|
2795
|
+
async def upsert_learning(
|
|
2796
|
+
self,
|
|
2797
|
+
id: str,
|
|
2798
|
+
learning_type: str,
|
|
2799
|
+
content: Dict[str, Any],
|
|
2800
|
+
user_id: Optional[str] = None,
|
|
2801
|
+
agent_id: Optional[str] = None,
|
|
2802
|
+
team_id: Optional[str] = None,
|
|
2803
|
+
session_id: Optional[str] = None,
|
|
2804
|
+
namespace: Optional[str] = None,
|
|
2805
|
+
entity_id: Optional[str] = None,
|
|
2806
|
+
entity_type: Optional[str] = None,
|
|
2807
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
2808
|
+
) -> None:
|
|
2809
|
+
"""Async insert or update a learning record.
|
|
2810
|
+
|
|
2811
|
+
Args:
|
|
2812
|
+
id: Unique identifier for the learning.
|
|
2813
|
+
learning_type: Type of learning ('user_profile', 'session_context', etc.)
|
|
2814
|
+
content: The learning content as a dict.
|
|
2815
|
+
user_id: Associated user ID.
|
|
2816
|
+
agent_id: Associated agent ID.
|
|
2817
|
+
team_id: Associated team ID.
|
|
2818
|
+
session_id: Associated session ID.
|
|
2819
|
+
namespace: Namespace for scoping ('user', 'global', or custom).
|
|
2820
|
+
entity_id: Associated entity ID (for entity-specific learnings).
|
|
2821
|
+
entity_type: Entity type ('person', 'company', etc.).
|
|
2822
|
+
metadata: Optional metadata.
|
|
2823
|
+
"""
|
|
2824
|
+
try:
|
|
2825
|
+
table = await self._get_table(table_type="learnings", create_table_if_not_found=True)
|
|
2826
|
+
if table is None:
|
|
2827
|
+
return
|
|
2828
|
+
|
|
2829
|
+
current_time = int(time.time())
|
|
2830
|
+
|
|
2831
|
+
async with self.async_session_factory() as sess, sess.begin():
|
|
2832
|
+
stmt = postgresql.insert(table).values(
|
|
2833
|
+
learning_id=id,
|
|
2834
|
+
learning_type=learning_type,
|
|
2835
|
+
namespace=namespace,
|
|
2836
|
+
user_id=user_id,
|
|
2837
|
+
agent_id=agent_id,
|
|
2838
|
+
team_id=team_id,
|
|
2839
|
+
session_id=session_id,
|
|
2840
|
+
entity_id=entity_id,
|
|
2841
|
+
entity_type=entity_type,
|
|
2842
|
+
content=content,
|
|
2843
|
+
metadata=metadata,
|
|
2844
|
+
created_at=current_time,
|
|
2845
|
+
updated_at=current_time,
|
|
2846
|
+
)
|
|
2847
|
+
stmt = stmt.on_conflict_do_update(
|
|
2848
|
+
index_elements=["learning_id"],
|
|
2849
|
+
set_=dict(
|
|
2850
|
+
content=content,
|
|
2851
|
+
metadata=metadata,
|
|
2852
|
+
updated_at=current_time,
|
|
2853
|
+
),
|
|
2854
|
+
)
|
|
2855
|
+
await sess.execute(stmt)
|
|
2856
|
+
|
|
2857
|
+
log_debug(f"Upserted learning: {id}")
|
|
2858
|
+
|
|
2859
|
+
except Exception as e:
|
|
2860
|
+
log_debug(f"Error upserting learning: {e}")
|
|
2861
|
+
|
|
2862
|
+
async def delete_learning(self, id: str) -> bool:
|
|
2863
|
+
"""Async delete a learning record.
|
|
2864
|
+
|
|
2865
|
+
Args:
|
|
2866
|
+
id: The learning ID to delete.
|
|
2867
|
+
|
|
2868
|
+
Returns:
|
|
2869
|
+
True if deleted, False otherwise.
|
|
2870
|
+
"""
|
|
2871
|
+
try:
|
|
2872
|
+
table = await self._get_table(table_type="learnings")
|
|
2873
|
+
if table is None:
|
|
2874
|
+
return False
|
|
2875
|
+
|
|
2876
|
+
async with self.async_session_factory() as sess, sess.begin():
|
|
2877
|
+
stmt = table.delete().where(table.c.learning_id == id)
|
|
2878
|
+
result = await sess.execute(stmt)
|
|
2879
|
+
return result.rowcount > 0
|
|
2880
|
+
|
|
2881
|
+
except Exception as e:
|
|
2882
|
+
log_debug(f"Error deleting learning: {e}")
|
|
2883
|
+
return False
|
|
2884
|
+
|
|
2885
|
+
async def get_learnings(
|
|
2886
|
+
self,
|
|
2887
|
+
learning_type: Optional[str] = None,
|
|
2888
|
+
user_id: Optional[str] = None,
|
|
2889
|
+
agent_id: Optional[str] = None,
|
|
2890
|
+
team_id: Optional[str] = None,
|
|
2891
|
+
session_id: Optional[str] = None,
|
|
2892
|
+
namespace: Optional[str] = None,
|
|
2893
|
+
entity_id: Optional[str] = None,
|
|
2894
|
+
entity_type: Optional[str] = None,
|
|
2895
|
+
limit: Optional[int] = None,
|
|
2896
|
+
) -> List[Dict[str, Any]]:
|
|
2897
|
+
"""Async get multiple learning records.
|
|
2898
|
+
|
|
2899
|
+
Args:
|
|
2900
|
+
learning_type: Filter by learning type.
|
|
2901
|
+
user_id: Filter by user ID.
|
|
2902
|
+
agent_id: Filter by agent ID.
|
|
2903
|
+
team_id: Filter by team ID.
|
|
2904
|
+
session_id: Filter by session ID.
|
|
2905
|
+
namespace: Filter by namespace ('user', 'global', or custom).
|
|
2906
|
+
entity_id: Filter by entity ID (for entity-specific learnings).
|
|
2907
|
+
entity_type: Filter by entity type ('person', 'company', etc.).
|
|
2908
|
+
limit: Maximum number of records to return.
|
|
2909
|
+
|
|
2910
|
+
Returns:
|
|
2911
|
+
List of learning records.
|
|
2912
|
+
"""
|
|
2913
|
+
try:
|
|
2914
|
+
table = await self._get_table(table_type="learnings")
|
|
2915
|
+
if table is None:
|
|
2916
|
+
return []
|
|
2917
|
+
|
|
2918
|
+
async with self.async_session_factory() as sess:
|
|
2919
|
+
stmt = select(table)
|
|
2920
|
+
|
|
2921
|
+
if learning_type is not None:
|
|
2922
|
+
stmt = stmt.where(table.c.learning_type == learning_type)
|
|
2923
|
+
if user_id is not None:
|
|
2924
|
+
stmt = stmt.where(table.c.user_id == user_id)
|
|
2925
|
+
if agent_id is not None:
|
|
2926
|
+
stmt = stmt.where(table.c.agent_id == agent_id)
|
|
2927
|
+
if team_id is not None:
|
|
2928
|
+
stmt = stmt.where(table.c.team_id == team_id)
|
|
2929
|
+
if session_id is not None:
|
|
2930
|
+
stmt = stmt.where(table.c.session_id == session_id)
|
|
2931
|
+
if namespace is not None:
|
|
2932
|
+
stmt = stmt.where(table.c.namespace == namespace)
|
|
2933
|
+
if entity_id is not None:
|
|
2934
|
+
stmt = stmt.where(table.c.entity_id == entity_id)
|
|
2935
|
+
if entity_type is not None:
|
|
2936
|
+
stmt = stmt.where(table.c.entity_type == entity_type)
|
|
2937
|
+
|
|
2938
|
+
stmt = stmt.order_by(table.c.updated_at.desc())
|
|
2939
|
+
|
|
2940
|
+
if limit is not None:
|
|
2941
|
+
stmt = stmt.limit(limit)
|
|
2942
|
+
|
|
2943
|
+
result = await sess.execute(stmt)
|
|
2944
|
+
rows = result.fetchall()
|
|
2945
|
+
return [dict(row._mapping) for row in rows]
|
|
2946
|
+
|
|
2947
|
+
except Exception as e:
|
|
2948
|
+
log_debug(f"Error getting learnings: {e}")
|
|
2949
|
+
return []
|
agno/db/postgres/postgres.py
CHANGED
|
@@ -57,6 +57,7 @@ class PostgresDb(BaseDb):
|
|
|
57
57
|
traces_table: Optional[str] = None,
|
|
58
58
|
spans_table: Optional[str] = None,
|
|
59
59
|
versions_table: Optional[str] = None,
|
|
60
|
+
learnings_table: Optional[str] = None,
|
|
60
61
|
id: Optional[str] = None,
|
|
61
62
|
create_schema: bool = True,
|
|
62
63
|
):
|
|
@@ -81,6 +82,7 @@ class PostgresDb(BaseDb):
|
|
|
81
82
|
traces_table (Optional[str]): Name of the table to store run traces.
|
|
82
83
|
spans_table (Optional[str]): Name of the table to store span events.
|
|
83
84
|
versions_table (Optional[str]): Name of the table to store schema versions.
|
|
85
|
+
learnings_table (Optional[str]): Name of the table to store learnings.
|
|
84
86
|
id (Optional[str]): ID of the database.
|
|
85
87
|
create_schema (bool): Whether to automatically create the database schema if it doesn't exist.
|
|
86
88
|
Set to False if schema is managed externally (e.g., via migrations). Defaults to True.
|
|
@@ -119,6 +121,7 @@ class PostgresDb(BaseDb):
|
|
|
119
121
|
traces_table=traces_table,
|
|
120
122
|
spans_table=spans_table,
|
|
121
123
|
versions_table=versions_table,
|
|
124
|
+
learnings_table=learnings_table,
|
|
122
125
|
)
|
|
123
126
|
|
|
124
127
|
self.db_schema: str = db_schema if db_schema is not None else "ai"
|
|
@@ -159,6 +162,7 @@ class PostgresDb(BaseDb):
|
|
|
159
162
|
(self.eval_table_name, "evals"),
|
|
160
163
|
(self.knowledge_table_name, "knowledge"),
|
|
161
164
|
(self.versions_table_name, "versions"),
|
|
165
|
+
(self.learnings_table_name, "learnings"),
|
|
162
166
|
]
|
|
163
167
|
|
|
164
168
|
for table_name, table_type in tables_to_create:
|
|
@@ -344,6 +348,14 @@ class PostgresDb(BaseDb):
|
|
|
344
348
|
)
|
|
345
349
|
return self.spans_table
|
|
346
350
|
|
|
351
|
+
if table_type == "learnings":
|
|
352
|
+
self.learnings_table = self._get_or_create_table(
|
|
353
|
+
table_name=self.learnings_table_name,
|
|
354
|
+
table_type="learnings",
|
|
355
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
356
|
+
)
|
|
357
|
+
return self.learnings_table
|
|
358
|
+
|
|
347
359
|
raise ValueError(f"Unknown table type: {table_type}")
|
|
348
360
|
|
|
349
361
|
def _get_or_create_table(
|
|
@@ -3025,3 +3037,230 @@ class PostgresDb(BaseDb):
|
|
|
3025
3037
|
except Exception as e:
|
|
3026
3038
|
log_error(f"Error getting spans: {e}")
|
|
3027
3039
|
return []
|
|
3040
|
+
|
|
3041
|
+
# -- Learning methods --
|
|
3042
|
+
def get_learning(
|
|
3043
|
+
self,
|
|
3044
|
+
learning_type: str,
|
|
3045
|
+
user_id: Optional[str] = None,
|
|
3046
|
+
agent_id: Optional[str] = None,
|
|
3047
|
+
team_id: Optional[str] = None,
|
|
3048
|
+
workflow_id: Optional[str] = None,
|
|
3049
|
+
session_id: Optional[str] = None,
|
|
3050
|
+
namespace: Optional[str] = None,
|
|
3051
|
+
entity_id: Optional[str] = None,
|
|
3052
|
+
entity_type: Optional[str] = None,
|
|
3053
|
+
) -> Optional[Dict[str, Any]]:
|
|
3054
|
+
"""Retrieve a learning record.
|
|
3055
|
+
|
|
3056
|
+
Args:
|
|
3057
|
+
learning_type: Type of learning ('user_profile', 'session_context', etc.)
|
|
3058
|
+
user_id: Filter by user ID.
|
|
3059
|
+
agent_id: Filter by agent ID.
|
|
3060
|
+
team_id: Filter by team ID.
|
|
3061
|
+
workflow_id: Filter by workflow ID.
|
|
3062
|
+
session_id: Filter by session ID.
|
|
3063
|
+
namespace: Filter by namespace ('user', 'global', or custom).
|
|
3064
|
+
entity_id: Filter by entity ID (for entity-specific learnings).
|
|
3065
|
+
entity_type: Filter by entity type ('person', 'company', etc.).
|
|
3066
|
+
|
|
3067
|
+
Returns:
|
|
3068
|
+
Dict with 'content' key containing the learning data, or None.
|
|
3069
|
+
"""
|
|
3070
|
+
try:
|
|
3071
|
+
table = self._get_table(table_type="learnings")
|
|
3072
|
+
if table is None:
|
|
3073
|
+
return None
|
|
3074
|
+
|
|
3075
|
+
with self.Session() as sess:
|
|
3076
|
+
stmt = select(table).where(table.c.learning_type == learning_type)
|
|
3077
|
+
|
|
3078
|
+
if user_id is not None:
|
|
3079
|
+
stmt = stmt.where(table.c.user_id == user_id)
|
|
3080
|
+
if agent_id is not None:
|
|
3081
|
+
stmt = stmt.where(table.c.agent_id == agent_id)
|
|
3082
|
+
if team_id is not None:
|
|
3083
|
+
stmt = stmt.where(table.c.team_id == team_id)
|
|
3084
|
+
if workflow_id is not None:
|
|
3085
|
+
stmt = stmt.where(table.c.workflow_id == workflow_id)
|
|
3086
|
+
if session_id is not None:
|
|
3087
|
+
stmt = stmt.where(table.c.session_id == session_id)
|
|
3088
|
+
if namespace is not None:
|
|
3089
|
+
stmt = stmt.where(table.c.namespace == namespace)
|
|
3090
|
+
if entity_id is not None:
|
|
3091
|
+
stmt = stmt.where(table.c.entity_id == entity_id)
|
|
3092
|
+
if entity_type is not None:
|
|
3093
|
+
stmt = stmt.where(table.c.entity_type == entity_type)
|
|
3094
|
+
|
|
3095
|
+
result = sess.execute(stmt).fetchone()
|
|
3096
|
+
if result is None:
|
|
3097
|
+
return None
|
|
3098
|
+
|
|
3099
|
+
row = dict(result._mapping)
|
|
3100
|
+
return {"content": row.get("content")}
|
|
3101
|
+
|
|
3102
|
+
except Exception as e:
|
|
3103
|
+
log_debug(f"Error retrieving learning: {e}")
|
|
3104
|
+
return None
|
|
3105
|
+
|
|
3106
|
+
def upsert_learning(
|
|
3107
|
+
self,
|
|
3108
|
+
id: str,
|
|
3109
|
+
learning_type: str,
|
|
3110
|
+
content: Dict[str, Any],
|
|
3111
|
+
user_id: Optional[str] = None,
|
|
3112
|
+
agent_id: Optional[str] = None,
|
|
3113
|
+
team_id: Optional[str] = None,
|
|
3114
|
+
workflow_id: Optional[str] = None,
|
|
3115
|
+
session_id: Optional[str] = None,
|
|
3116
|
+
namespace: Optional[str] = None,
|
|
3117
|
+
entity_id: Optional[str] = None,
|
|
3118
|
+
entity_type: Optional[str] = None,
|
|
3119
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
3120
|
+
) -> None:
|
|
3121
|
+
"""Insert or update a learning record.
|
|
3122
|
+
|
|
3123
|
+
Args:
|
|
3124
|
+
id: Unique identifier for the learning.
|
|
3125
|
+
learning_type: Type of learning ('user_profile', 'session_context', etc.)
|
|
3126
|
+
content: The learning content as a dict.
|
|
3127
|
+
user_id: Associated user ID.
|
|
3128
|
+
agent_id: Associated agent ID.
|
|
3129
|
+
team_id: Associated team ID.
|
|
3130
|
+
workflow_id: Associated workflow ID.
|
|
3131
|
+
session_id: Associated session ID.
|
|
3132
|
+
namespace: Namespace for scoping ('user', 'global', or custom).
|
|
3133
|
+
entity_id: Associated entity ID (for entity-specific learnings).
|
|
3134
|
+
entity_type: Entity type ('person', 'company', etc.).
|
|
3135
|
+
metadata: Optional metadata.
|
|
3136
|
+
"""
|
|
3137
|
+
try:
|
|
3138
|
+
table = self._get_table(table_type="learnings", create_table_if_not_found=True)
|
|
3139
|
+
if table is None:
|
|
3140
|
+
return
|
|
3141
|
+
|
|
3142
|
+
current_time = int(time.time())
|
|
3143
|
+
|
|
3144
|
+
with self.Session() as sess, sess.begin():
|
|
3145
|
+
stmt = postgresql.insert(table).values(
|
|
3146
|
+
learning_id=id,
|
|
3147
|
+
learning_type=learning_type,
|
|
3148
|
+
namespace=namespace,
|
|
3149
|
+
user_id=user_id,
|
|
3150
|
+
agent_id=agent_id,
|
|
3151
|
+
team_id=team_id,
|
|
3152
|
+
workflow_id=workflow_id,
|
|
3153
|
+
session_id=session_id,
|
|
3154
|
+
entity_id=entity_id,
|
|
3155
|
+
entity_type=entity_type,
|
|
3156
|
+
content=content,
|
|
3157
|
+
metadata=metadata,
|
|
3158
|
+
created_at=current_time,
|
|
3159
|
+
updated_at=current_time,
|
|
3160
|
+
)
|
|
3161
|
+
stmt = stmt.on_conflict_do_update(
|
|
3162
|
+
index_elements=["learning_id"],
|
|
3163
|
+
set_=dict(
|
|
3164
|
+
content=content,
|
|
3165
|
+
metadata=metadata,
|
|
3166
|
+
updated_at=current_time,
|
|
3167
|
+
),
|
|
3168
|
+
)
|
|
3169
|
+
sess.execute(stmt)
|
|
3170
|
+
|
|
3171
|
+
log_debug(f"Upserted learning: {id}")
|
|
3172
|
+
|
|
3173
|
+
except Exception as e:
|
|
3174
|
+
log_debug(f"Error upserting learning: {e}")
|
|
3175
|
+
|
|
3176
|
+
def delete_learning(self, id: str) -> bool:
|
|
3177
|
+
"""Delete a learning record.
|
|
3178
|
+
|
|
3179
|
+
Args:
|
|
3180
|
+
id: The learning ID to delete.
|
|
3181
|
+
|
|
3182
|
+
Returns:
|
|
3183
|
+
True if deleted, False otherwise.
|
|
3184
|
+
"""
|
|
3185
|
+
try:
|
|
3186
|
+
table = self._get_table(table_type="learnings")
|
|
3187
|
+
if table is None:
|
|
3188
|
+
return False
|
|
3189
|
+
|
|
3190
|
+
with self.Session() as sess, sess.begin():
|
|
3191
|
+
stmt = table.delete().where(table.c.learning_id == id)
|
|
3192
|
+
result = sess.execute(stmt)
|
|
3193
|
+
return result.rowcount > 0
|
|
3194
|
+
|
|
3195
|
+
except Exception as e:
|
|
3196
|
+
log_debug(f"Error deleting learning: {e}")
|
|
3197
|
+
return False
|
|
3198
|
+
|
|
3199
|
+
def get_learnings(
|
|
3200
|
+
self,
|
|
3201
|
+
learning_type: Optional[str] = None,
|
|
3202
|
+
user_id: Optional[str] = None,
|
|
3203
|
+
agent_id: Optional[str] = None,
|
|
3204
|
+
team_id: Optional[str] = None,
|
|
3205
|
+
workflow_id: Optional[str] = None,
|
|
3206
|
+
session_id: Optional[str] = None,
|
|
3207
|
+
namespace: Optional[str] = None,
|
|
3208
|
+
entity_id: Optional[str] = None,
|
|
3209
|
+
entity_type: Optional[str] = None,
|
|
3210
|
+
limit: Optional[int] = None,
|
|
3211
|
+
) -> List[Dict[str, Any]]:
|
|
3212
|
+
"""Get multiple learning records.
|
|
3213
|
+
|
|
3214
|
+
Args:
|
|
3215
|
+
learning_type: Filter by learning type.
|
|
3216
|
+
user_id: Filter by user ID.
|
|
3217
|
+
agent_id: Filter by agent ID.
|
|
3218
|
+
team_id: Filter by team ID.
|
|
3219
|
+
workflow_id: Filter by workflow ID.
|
|
3220
|
+
session_id: Filter by session ID.
|
|
3221
|
+
namespace: Filter by namespace ('user', 'global', or custom).
|
|
3222
|
+
entity_id: Filter by entity ID (for entity-specific learnings).
|
|
3223
|
+
entity_type: Filter by entity type ('person', 'company', etc.).
|
|
3224
|
+
limit: Maximum number of records to return.
|
|
3225
|
+
|
|
3226
|
+
Returns:
|
|
3227
|
+
List of learning records.
|
|
3228
|
+
"""
|
|
3229
|
+
try:
|
|
3230
|
+
table = self._get_table(table_type="learnings")
|
|
3231
|
+
if table is None:
|
|
3232
|
+
return []
|
|
3233
|
+
|
|
3234
|
+
with self.Session() as sess:
|
|
3235
|
+
stmt = select(table)
|
|
3236
|
+
|
|
3237
|
+
if learning_type is not None:
|
|
3238
|
+
stmt = stmt.where(table.c.learning_type == learning_type)
|
|
3239
|
+
if user_id is not None:
|
|
3240
|
+
stmt = stmt.where(table.c.user_id == user_id)
|
|
3241
|
+
if agent_id is not None:
|
|
3242
|
+
stmt = stmt.where(table.c.agent_id == agent_id)
|
|
3243
|
+
if team_id is not None:
|
|
3244
|
+
stmt = stmt.where(table.c.team_id == team_id)
|
|
3245
|
+
if workflow_id is not None:
|
|
3246
|
+
stmt = stmt.where(table.c.workflow_id == workflow_id)
|
|
3247
|
+
if session_id is not None:
|
|
3248
|
+
stmt = stmt.where(table.c.session_id == session_id)
|
|
3249
|
+
if namespace is not None:
|
|
3250
|
+
stmt = stmt.where(table.c.namespace == namespace)
|
|
3251
|
+
if entity_id is not None:
|
|
3252
|
+
stmt = stmt.where(table.c.entity_id == entity_id)
|
|
3253
|
+
if entity_type is not None:
|
|
3254
|
+
stmt = stmt.where(table.c.entity_type == entity_type)
|
|
3255
|
+
|
|
3256
|
+
stmt = stmt.order_by(table.c.updated_at.desc())
|
|
3257
|
+
|
|
3258
|
+
if limit is not None:
|
|
3259
|
+
stmt = stmt.limit(limit)
|
|
3260
|
+
|
|
3261
|
+
result = sess.execute(stmt).fetchall()
|
|
3262
|
+
return [dict(row._mapping) for row in result]
|
|
3263
|
+
|
|
3264
|
+
except Exception as e:
|
|
3265
|
+
log_debug(f"Error getting learnings: {e}")
|
|
3266
|
+
return []
|
agno/db/postgres/schemas.py
CHANGED
|
@@ -169,6 +169,24 @@ def _get_span_table_schema(traces_table_name: str = "agno_traces", db_schema: st
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
|
|
172
|
+
LEARNINGS_TABLE_SCHEMA = {
|
|
173
|
+
"learning_id": {"type": String, "primary_key": True, "nullable": False},
|
|
174
|
+
"learning_type": {"type": String, "nullable": False, "index": True},
|
|
175
|
+
"namespace": {"type": String, "nullable": True, "index": True},
|
|
176
|
+
"user_id": {"type": String, "nullable": True, "index": True},
|
|
177
|
+
"agent_id": {"type": String, "nullable": True, "index": True},
|
|
178
|
+
"team_id": {"type": String, "nullable": True, "index": True},
|
|
179
|
+
"workflow_id": {"type": String, "nullable": True, "index": True},
|
|
180
|
+
"session_id": {"type": String, "nullable": True, "index": True},
|
|
181
|
+
"entity_id": {"type": String, "nullable": True, "index": True},
|
|
182
|
+
"entity_type": {"type": String, "nullable": True, "index": True},
|
|
183
|
+
"content": {"type": JSONB, "nullable": False},
|
|
184
|
+
"metadata": {"type": JSONB, "nullable": True},
|
|
185
|
+
"created_at": {"type": BigInteger, "nullable": False, "index": True},
|
|
186
|
+
"updated_at": {"type": BigInteger, "nullable": True},
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
172
190
|
def get_table_schema_definition(
|
|
173
191
|
table_type: str, traces_table_name: str = "agno_traces", db_schema: str = "agno"
|
|
174
192
|
) -> dict[str, Any]:
|
|
@@ -196,6 +214,7 @@ def get_table_schema_definition(
|
|
|
196
214
|
"culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
|
|
197
215
|
"versions": VERSIONS_TABLE_SCHEMA,
|
|
198
216
|
"traces": TRACE_TABLE_SCHEMA,
|
|
217
|
+
"learnings": LEARNINGS_TABLE_SCHEMA,
|
|
199
218
|
}
|
|
200
219
|
|
|
201
220
|
schema = schemas.get(table_type, {})
|
agno/db/redis/redis.py
CHANGED
|
@@ -2139,3 +2139,50 @@ class RedisDb(BaseDb):
|
|
|
2139
2139
|
except Exception as e:
|
|
2140
2140
|
log_error(f"Error getting spans: {e}")
|
|
2141
2141
|
return []
|
|
2142
|
+
|
|
2143
|
+
# -- Learning methods (stubs) --
|
|
2144
|
+
def get_learning(
|
|
2145
|
+
self,
|
|
2146
|
+
learning_type: str,
|
|
2147
|
+
user_id: Optional[str] = None,
|
|
2148
|
+
agent_id: Optional[str] = None,
|
|
2149
|
+
team_id: Optional[str] = None,
|
|
2150
|
+
session_id: Optional[str] = None,
|
|
2151
|
+
namespace: Optional[str] = None,
|
|
2152
|
+
entity_id: Optional[str] = None,
|
|
2153
|
+
entity_type: Optional[str] = None,
|
|
2154
|
+
) -> Optional[Dict[str, Any]]:
|
|
2155
|
+
raise NotImplementedError("Learning methods not yet implemented for RedisDb")
|
|
2156
|
+
|
|
2157
|
+
def upsert_learning(
|
|
2158
|
+
self,
|
|
2159
|
+
id: str,
|
|
2160
|
+
learning_type: str,
|
|
2161
|
+
content: Dict[str, Any],
|
|
2162
|
+
user_id: Optional[str] = None,
|
|
2163
|
+
agent_id: Optional[str] = None,
|
|
2164
|
+
team_id: Optional[str] = None,
|
|
2165
|
+
session_id: Optional[str] = None,
|
|
2166
|
+
namespace: Optional[str] = None,
|
|
2167
|
+
entity_id: Optional[str] = None,
|
|
2168
|
+
entity_type: Optional[str] = None,
|
|
2169
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
2170
|
+
) -> None:
|
|
2171
|
+
raise NotImplementedError("Learning methods not yet implemented for RedisDb")
|
|
2172
|
+
|
|
2173
|
+
def delete_learning(self, id: str) -> bool:
|
|
2174
|
+
raise NotImplementedError("Learning methods not yet implemented for RedisDb")
|
|
2175
|
+
|
|
2176
|
+
def get_learnings(
|
|
2177
|
+
self,
|
|
2178
|
+
learning_type: Optional[str] = None,
|
|
2179
|
+
user_id: Optional[str] = None,
|
|
2180
|
+
agent_id: Optional[str] = None,
|
|
2181
|
+
team_id: Optional[str] = None,
|
|
2182
|
+
session_id: Optional[str] = None,
|
|
2183
|
+
namespace: Optional[str] = None,
|
|
2184
|
+
entity_id: Optional[str] = None,
|
|
2185
|
+
entity_type: Optional[str] = None,
|
|
2186
|
+
limit: Optional[int] = None,
|
|
2187
|
+
) -> List[Dict[str, Any]]:
|
|
2188
|
+
raise NotImplementedError("Learning methods not yet implemented for RedisDb")
|
|
@@ -2886,3 +2886,50 @@ class SingleStoreDb(BaseDb):
|
|
|
2886
2886
|
except Exception as e:
|
|
2887
2887
|
log_error(f"Error getting spans: {e}")
|
|
2888
2888
|
return []
|
|
2889
|
+
|
|
2890
|
+
# -- Learning methods (stubs) --
|
|
2891
|
+
def get_learning(
|
|
2892
|
+
self,
|
|
2893
|
+
learning_type: str,
|
|
2894
|
+
user_id: Optional[str] = None,
|
|
2895
|
+
agent_id: Optional[str] = None,
|
|
2896
|
+
team_id: Optional[str] = None,
|
|
2897
|
+
session_id: Optional[str] = None,
|
|
2898
|
+
namespace: Optional[str] = None,
|
|
2899
|
+
entity_id: Optional[str] = None,
|
|
2900
|
+
entity_type: Optional[str] = None,
|
|
2901
|
+
) -> Optional[Dict[str, Any]]:
|
|
2902
|
+
raise NotImplementedError("Learning methods not yet implemented for SingleStoreDb")
|
|
2903
|
+
|
|
2904
|
+
def upsert_learning(
|
|
2905
|
+
self,
|
|
2906
|
+
id: str,
|
|
2907
|
+
learning_type: str,
|
|
2908
|
+
content: Dict[str, Any],
|
|
2909
|
+
user_id: Optional[str] = None,
|
|
2910
|
+
agent_id: Optional[str] = None,
|
|
2911
|
+
team_id: Optional[str] = None,
|
|
2912
|
+
session_id: Optional[str] = None,
|
|
2913
|
+
namespace: Optional[str] = None,
|
|
2914
|
+
entity_id: Optional[str] = None,
|
|
2915
|
+
entity_type: Optional[str] = None,
|
|
2916
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
2917
|
+
) -> None:
|
|
2918
|
+
raise NotImplementedError("Learning methods not yet implemented for SingleStoreDb")
|
|
2919
|
+
|
|
2920
|
+
def delete_learning(self, id: str) -> bool:
|
|
2921
|
+
raise NotImplementedError("Learning methods not yet implemented for SingleStoreDb")
|
|
2922
|
+
|
|
2923
|
+
def get_learnings(
|
|
2924
|
+
self,
|
|
2925
|
+
learning_type: Optional[str] = None,
|
|
2926
|
+
user_id: Optional[str] = None,
|
|
2927
|
+
agent_id: Optional[str] = None,
|
|
2928
|
+
team_id: Optional[str] = None,
|
|
2929
|
+
session_id: Optional[str] = None,
|
|
2930
|
+
namespace: Optional[str] = None,
|
|
2931
|
+
entity_id: Optional[str] = None,
|
|
2932
|
+
entity_type: Optional[str] = None,
|
|
2933
|
+
limit: Optional[int] = None,
|
|
2934
|
+
) -> List[Dict[str, Any]]:
|
|
2935
|
+
raise NotImplementedError("Learning methods not yet implemented for SingleStoreDb")
|