agno 2.2.0__py3-none-any.whl → 2.2.2__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 +751 -575
- agno/culture/manager.py +22 -24
- agno/db/async_postgres/__init__.py +1 -1
- agno/db/dynamo/dynamo.py +0 -2
- agno/db/firestore/firestore.py +0 -2
- agno/db/gcs_json/gcs_json_db.py +0 -4
- agno/db/gcs_json/utils.py +0 -24
- agno/db/in_memory/in_memory_db.py +0 -3
- agno/db/json/json_db.py +4 -10
- agno/db/json/utils.py +0 -24
- agno/db/mongo/mongo.py +0 -2
- agno/db/mysql/mysql.py +0 -3
- agno/db/postgres/__init__.py +1 -1
- agno/db/{async_postgres → postgres}/async_postgres.py +19 -22
- agno/db/postgres/postgres.py +7 -10
- agno/db/postgres/utils.py +106 -2
- agno/db/redis/redis.py +0 -2
- agno/db/singlestore/singlestore.py +0 -3
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2269 -0
- agno/db/sqlite/sqlite.py +0 -2
- agno/db/sqlite/utils.py +96 -0
- agno/db/surrealdb/surrealdb.py +0 -6
- agno/knowledge/knowledge.py +14 -3
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +30 -0
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/types.py +1 -0
- agno/memory/manager.py +28 -25
- agno/models/anthropic/claude.py +63 -6
- agno/models/base.py +255 -36
- agno/models/response.py +69 -0
- agno/os/router.py +7 -5
- agno/os/routers/memory/memory.py +2 -1
- agno/os/routers/memory/schemas.py +5 -2
- agno/os/schema.py +26 -20
- agno/os/utils.py +9 -2
- agno/run/agent.py +28 -30
- agno/run/base.py +17 -1
- agno/run/team.py +28 -29
- agno/run/workflow.py +32 -17
- agno/session/agent.py +3 -0
- agno/session/summary.py +4 -1
- agno/session/team.py +1 -1
- agno/team/team.py +620 -374
- agno/tools/dalle.py +2 -4
- agno/tools/eleven_labs.py +23 -25
- agno/tools/function.py +40 -0
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +324 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/slack.py +18 -3
- agno/tools/tavily.py +146 -0
- agno/utils/agent.py +366 -1
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +166 -1
- agno/utils/message.py +60 -0
- agno/utils/print_response/workflow.py +17 -1
- agno/utils/team.py +89 -1
- agno/workflow/step.py +0 -1
- agno/workflow/types.py +10 -15
- agno/workflow/workflow.py +86 -1
- {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/METADATA +31 -25
- {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/RECORD +68 -64
- agno/db/async_postgres/schemas.py +0 -139
- agno/db/async_postgres/utils.py +0 -347
- agno/tools/mcp.py +0 -679
- {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/WHEEL +0 -0
- {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/top_level.txt +0 -0
agno/culture/manager.py
CHANGED
|
@@ -78,8 +78,6 @@ class CultureManager:
|
|
|
78
78
|
self.delete_knowledge = delete_knowledge
|
|
79
79
|
self.clear_knowledge = clear_knowledge
|
|
80
80
|
self.debug_mode = debug_mode
|
|
81
|
-
self._tools_for_model: Optional[List[Dict[str, Any]]] = None
|
|
82
|
-
self._functions_for_model: Optional[Dict[str, Function]] = None
|
|
83
81
|
|
|
84
82
|
def get_model(self) -> Model:
|
|
85
83
|
if self.model is None:
|
|
@@ -316,23 +314,27 @@ class CultureManager:
|
|
|
316
314
|
return response
|
|
317
315
|
|
|
318
316
|
# -*- Utility Functions -*-
|
|
319
|
-
def _determine_tools_for_model(self, tools: List[Callable]) ->
|
|
317
|
+
def _determine_tools_for_model(self, tools: List[Callable]) -> List[Union[Function, dict]]:
|
|
320
318
|
# Have to reset each time, because of different user IDs
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
|
|
320
|
+
_function_names = []
|
|
321
|
+
_functions: List[Union[Function, dict]] = []
|
|
323
322
|
|
|
324
323
|
for tool in tools:
|
|
325
324
|
try:
|
|
326
325
|
function_name = tool.__name__
|
|
327
|
-
if function_name
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
326
|
+
if function_name in _function_names:
|
|
327
|
+
continue
|
|
328
|
+
_function_names.append(function_name)
|
|
329
|
+
func = Function.from_callable(tool, strict=True) # type: ignore
|
|
330
|
+
func.strict = True
|
|
331
|
+
_functions.append(func)
|
|
332
|
+
log_debug(f"Added function {func.name}")
|
|
333
333
|
except Exception as e:
|
|
334
334
|
log_warning(f"Could not add function {tool}: {e}")
|
|
335
335
|
|
|
336
|
+
return _functions
|
|
337
|
+
|
|
336
338
|
def get_system_message(
|
|
337
339
|
self,
|
|
338
340
|
existing_knowledge: Optional[List[Dict[str, Any]]] = None,
|
|
@@ -460,7 +462,7 @@ class CultureManager:
|
|
|
460
462
|
|
|
461
463
|
model_copy = deepcopy(self.model)
|
|
462
464
|
# Update the Model (set defaults, add logit etc.)
|
|
463
|
-
self._determine_tools_for_model(
|
|
465
|
+
_tools = self._determine_tools_for_model(
|
|
464
466
|
self._get_db_tools(
|
|
465
467
|
db,
|
|
466
468
|
enable_add_knowledge=add_knowledge,
|
|
@@ -485,8 +487,7 @@ class CultureManager:
|
|
|
485
487
|
# Generate a response from the Model (includes running function calls)
|
|
486
488
|
response = model_copy.response(
|
|
487
489
|
messages=messages_for_model,
|
|
488
|
-
tools=
|
|
489
|
-
functions=self._functions_for_model,
|
|
490
|
+
tools=_tools,
|
|
490
491
|
)
|
|
491
492
|
|
|
492
493
|
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
|
@@ -513,7 +514,7 @@ class CultureManager:
|
|
|
513
514
|
model_copy = deepcopy(self.model)
|
|
514
515
|
db = cast(AsyncBaseDb, db)
|
|
515
516
|
|
|
516
|
-
self._determine_tools_for_model(
|
|
517
|
+
_tools = self._determine_tools_for_model(
|
|
517
518
|
await self._aget_db_tools(
|
|
518
519
|
db,
|
|
519
520
|
enable_update_knowledge=update_knowledge,
|
|
@@ -535,8 +536,7 @@ class CultureManager:
|
|
|
535
536
|
# Generate a response from the Model (includes running function calls)
|
|
536
537
|
response = await model_copy.aresponse(
|
|
537
538
|
messages=messages_for_model,
|
|
538
|
-
tools=
|
|
539
|
-
functions=self._functions_for_model,
|
|
539
|
+
tools=_tools,
|
|
540
540
|
)
|
|
541
541
|
|
|
542
542
|
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
|
@@ -564,7 +564,7 @@ class CultureManager:
|
|
|
564
564
|
|
|
565
565
|
model_copy = deepcopy(self.model)
|
|
566
566
|
# Update the Model (set defaults, add logit etc.)
|
|
567
|
-
self._determine_tools_for_model(
|
|
567
|
+
_tools = self._determine_tools_for_model(
|
|
568
568
|
self._get_db_tools(
|
|
569
569
|
db,
|
|
570
570
|
enable_delete_knowledge=delete_knowledge,
|
|
@@ -590,8 +590,7 @@ class CultureManager:
|
|
|
590
590
|
# Generate a response from the Model (includes running function calls)
|
|
591
591
|
response = model_copy.response(
|
|
592
592
|
messages=messages_for_model,
|
|
593
|
-
tools=
|
|
594
|
-
functions=self._functions_for_model,
|
|
593
|
+
tools=_tools,
|
|
595
594
|
)
|
|
596
595
|
|
|
597
596
|
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
|
@@ -620,7 +619,7 @@ class CultureManager:
|
|
|
620
619
|
model_copy = deepcopy(self.model)
|
|
621
620
|
# Update the Model (set defaults, add logit etc.)
|
|
622
621
|
if isinstance(db, AsyncBaseDb):
|
|
623
|
-
self._determine_tools_for_model(
|
|
622
|
+
_tools = self._determine_tools_for_model(
|
|
624
623
|
await self._aget_db_tools(
|
|
625
624
|
db,
|
|
626
625
|
enable_delete_knowledge=delete_knowledge,
|
|
@@ -630,7 +629,7 @@ class CultureManager:
|
|
|
630
629
|
),
|
|
631
630
|
)
|
|
632
631
|
else:
|
|
633
|
-
self._determine_tools_for_model(
|
|
632
|
+
_tools = self._determine_tools_for_model(
|
|
634
633
|
self._get_db_tools(
|
|
635
634
|
db,
|
|
636
635
|
enable_delete_knowledge=delete_knowledge,
|
|
@@ -656,8 +655,7 @@ class CultureManager:
|
|
|
656
655
|
# Generate a response from the Model (includes running function calls)
|
|
657
656
|
response = await model_copy.aresponse(
|
|
658
657
|
messages=messages_for_model,
|
|
659
|
-
tools=
|
|
660
|
-
functions=self._functions_for_model,
|
|
658
|
+
tools=_tools,
|
|
661
659
|
)
|
|
662
660
|
|
|
663
661
|
if response.tool_calls is not None and len(response.tool_calls) > 0:
|
agno/db/dynamo/dynamo.py
CHANGED
agno/db/firestore/firestore.py
CHANGED
|
@@ -280,8 +280,6 @@ class FirestoreDb(BaseDb):
|
|
|
280
280
|
|
|
281
281
|
if user_id is not None:
|
|
282
282
|
query = query.where(filter=FieldFilter("user_id", "==", user_id))
|
|
283
|
-
if session_type is not None:
|
|
284
|
-
query = query.where(filter=FieldFilter("session_type", "==", session_type.value))
|
|
285
283
|
|
|
286
284
|
docs = query.stream()
|
|
287
285
|
result = None
|
agno/db/gcs_json/gcs_json_db.py
CHANGED
|
@@ -220,10 +220,6 @@ class GcsJsonDb(BaseDb):
|
|
|
220
220
|
if user_id is not None and session_data.get("user_id") != user_id:
|
|
221
221
|
continue
|
|
222
222
|
|
|
223
|
-
session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
|
|
224
|
-
if session_data.get("session_type") != session_type_value:
|
|
225
|
-
continue
|
|
226
|
-
|
|
227
223
|
if not deserialize:
|
|
228
224
|
return session_data
|
|
229
225
|
|
agno/db/gcs_json/utils.py
CHANGED
|
@@ -5,34 +5,10 @@ from datetime import date, datetime, timedelta, timezone
|
|
|
5
5
|
from typing import Any, Dict, List, Optional
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
|
|
8
|
-
from agno.db.base import SessionType
|
|
9
8
|
from agno.db.schemas.culture import CulturalKnowledge
|
|
10
|
-
from agno.run.agent import RunOutput
|
|
11
|
-
from agno.run.team import TeamRunOutput
|
|
12
|
-
from agno.session.summary import SessionSummary
|
|
13
9
|
from agno.utils.log import log_debug
|
|
14
10
|
|
|
15
11
|
|
|
16
|
-
def hydrate_session(session: dict) -> dict:
|
|
17
|
-
"""Convert nested dictionaries to their corresponding object types.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
session (dict): The session dictionary to hydrate.
|
|
21
|
-
|
|
22
|
-
Returns:
|
|
23
|
-
dict: The hydrated session dictionary.
|
|
24
|
-
"""
|
|
25
|
-
if session.get("summary") is not None:
|
|
26
|
-
session["summary"] = SessionSummary.from_dict(session["summary"])
|
|
27
|
-
if session.get("runs") is not None:
|
|
28
|
-
if session["session_type"] == SessionType.AGENT:
|
|
29
|
-
session["runs"] = [RunOutput.from_dict(run) for run in session["runs"]]
|
|
30
|
-
elif session["session_type"] == SessionType.TEAM:
|
|
31
|
-
session["runs"] = [TeamRunOutput.from_dict(run) for run in session["runs"]]
|
|
32
|
-
|
|
33
|
-
return session
|
|
34
|
-
|
|
35
|
-
|
|
36
12
|
def apply_sorting(
|
|
37
13
|
data: List[Dict[str, Any]], sort_by: Optional[str] = None, sort_order: Optional[str] = None
|
|
38
14
|
) -> List[Dict[str, Any]]:
|
|
@@ -108,9 +108,6 @@ class InMemoryDb(BaseDb):
|
|
|
108
108
|
if session_data.get("session_id") == session_id:
|
|
109
109
|
if user_id is not None and session_data.get("user_id") != user_id:
|
|
110
110
|
continue
|
|
111
|
-
session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
|
|
112
|
-
if session_data.get("session_type") != session_type_value:
|
|
113
|
-
continue
|
|
114
111
|
|
|
115
112
|
session_data_copy = deepcopy(session_data)
|
|
116
113
|
|
agno/db/json/json_db.py
CHANGED
|
@@ -13,7 +13,6 @@ from agno.db.json.utils import (
|
|
|
13
13
|
deserialize_cultural_knowledge_from_db,
|
|
14
14
|
fetch_all_sessions_data,
|
|
15
15
|
get_dates_to_calculate_metrics_for,
|
|
16
|
-
hydrate_session,
|
|
17
16
|
serialize_cultural_knowledge_for_db,
|
|
18
17
|
)
|
|
19
18
|
from agno.db.schemas.culture import CulturalKnowledge
|
|
@@ -202,21 +201,16 @@ class JsonDb(BaseDb):
|
|
|
202
201
|
if session_data.get("session_id") == session_id:
|
|
203
202
|
if user_id is not None and session_data.get("user_id") != user_id:
|
|
204
203
|
continue
|
|
205
|
-
session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
|
|
206
|
-
if session_data.get("session_type") != session_type_value:
|
|
207
|
-
continue
|
|
208
|
-
|
|
209
|
-
session = hydrate_session(session_data)
|
|
210
204
|
|
|
211
205
|
if not deserialize:
|
|
212
|
-
return
|
|
206
|
+
return session_data
|
|
213
207
|
|
|
214
208
|
if session_type == SessionType.AGENT:
|
|
215
|
-
return AgentSession.from_dict(
|
|
209
|
+
return AgentSession.from_dict(session_data)
|
|
216
210
|
elif session_type == SessionType.TEAM:
|
|
217
|
-
return TeamSession.from_dict(
|
|
211
|
+
return TeamSession.from_dict(session_data)
|
|
218
212
|
elif session_type == SessionType.WORKFLOW:
|
|
219
|
-
return WorkflowSession.from_dict(
|
|
213
|
+
return WorkflowSession.from_dict(session_data)
|
|
220
214
|
else:
|
|
221
215
|
raise ValueError(f"Invalid session type: {session_type}")
|
|
222
216
|
|
agno/db/json/utils.py
CHANGED
|
@@ -5,34 +5,10 @@ from datetime import date, datetime, timedelta, timezone
|
|
|
5
5
|
from typing import Any, Dict, List, Optional
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
|
|
8
|
-
from agno.db.base import SessionType
|
|
9
8
|
from agno.db.schemas.culture import CulturalKnowledge
|
|
10
|
-
from agno.run.agent import RunOutput
|
|
11
|
-
from agno.run.team import TeamRunOutput
|
|
12
|
-
from agno.session.summary import SessionSummary
|
|
13
9
|
from agno.utils.log import log_debug
|
|
14
10
|
|
|
15
11
|
|
|
16
|
-
def hydrate_session(session: dict) -> dict:
|
|
17
|
-
"""Convert nested dictionaries to their corresponding object types.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
session (dict): The session dictionary to hydrate.
|
|
21
|
-
|
|
22
|
-
Returns:
|
|
23
|
-
dict: The hydrated session dictionary.
|
|
24
|
-
"""
|
|
25
|
-
if session.get("summary") is not None:
|
|
26
|
-
session["summary"] = SessionSummary.from_dict(session["summary"])
|
|
27
|
-
if session.get("runs") is not None:
|
|
28
|
-
if session["session_type"] == SessionType.AGENT:
|
|
29
|
-
session["runs"] = [RunOutput.from_dict(run) for run in session["runs"]]
|
|
30
|
-
elif session["session_type"] == SessionType.TEAM:
|
|
31
|
-
session["runs"] = [TeamRunOutput.from_dict(run) for run in session["runs"]]
|
|
32
|
-
|
|
33
|
-
return session
|
|
34
|
-
|
|
35
|
-
|
|
36
12
|
def apply_sorting(
|
|
37
13
|
data: List[Dict[str, Any]], sort_by: Optional[str] = None, sort_order: Optional[str] = None
|
|
38
14
|
) -> List[Dict[str, Any]]:
|
agno/db/mongo/mongo.py
CHANGED
|
@@ -291,8 +291,6 @@ class MongoDb(BaseDb):
|
|
|
291
291
|
query = {"session_id": session_id}
|
|
292
292
|
if user_id is not None:
|
|
293
293
|
query["user_id"] = user_id
|
|
294
|
-
if session_type is not None:
|
|
295
|
-
query["session_type"] = session_type
|
|
296
294
|
|
|
297
295
|
result = collection.find_one(query)
|
|
298
296
|
if result is None:
|
agno/db/mysql/mysql.py
CHANGED
|
@@ -387,9 +387,6 @@ class MySQLDb(BaseDb):
|
|
|
387
387
|
|
|
388
388
|
if user_id is not None:
|
|
389
389
|
stmt = stmt.where(table.c.user_id == user_id)
|
|
390
|
-
if session_type is not None:
|
|
391
|
-
session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
|
|
392
|
-
stmt = stmt.where(table.c.session_type == session_type_value)
|
|
393
390
|
result = sess.execute(stmt).fetchone()
|
|
394
391
|
if result is None:
|
|
395
392
|
return None
|
agno/db/postgres/__init__.py
CHANGED
|
@@ -3,20 +3,20 @@ from datetime import date, datetime, timedelta, timezone
|
|
|
3
3
|
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
|
4
4
|
from uuid import uuid4
|
|
5
5
|
|
|
6
|
-
from agno.db.
|
|
7
|
-
from agno.db.
|
|
6
|
+
from agno.db.base import AsyncBaseDb, SessionType
|
|
7
|
+
from agno.db.postgres.schemas import get_table_schema_definition
|
|
8
|
+
from agno.db.postgres.utils import (
|
|
9
|
+
abulk_upsert_metrics,
|
|
10
|
+
acreate_schema,
|
|
11
|
+
ais_table_available,
|
|
12
|
+
ais_valid_table,
|
|
8
13
|
apply_sorting,
|
|
9
|
-
bulk_upsert_metrics,
|
|
10
14
|
calculate_date_metrics,
|
|
11
|
-
create_schema,
|
|
12
15
|
deserialize_cultural_knowledge,
|
|
13
16
|
fetch_all_sessions_data,
|
|
14
17
|
get_dates_to_calculate_metrics_for,
|
|
15
|
-
is_table_available,
|
|
16
|
-
is_valid_table,
|
|
17
18
|
serialize_cultural_knowledge,
|
|
18
19
|
)
|
|
19
|
-
from agno.db.base import AsyncBaseDb, SessionType
|
|
20
20
|
from agno.db.schemas.culture import CulturalKnowledge
|
|
21
21
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
22
22
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
@@ -148,7 +148,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
148
148
|
table.append_constraint(Index(idx_name, idx_col))
|
|
149
149
|
|
|
150
150
|
async with self.async_session_factory() as sess, sess.begin():
|
|
151
|
-
await
|
|
151
|
+
await acreate_schema(session=sess, db_schema=db_schema)
|
|
152
152
|
|
|
153
153
|
# Create table
|
|
154
154
|
async with self.db_engine.begin() as conn:
|
|
@@ -241,12 +241,12 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
241
241
|
"""
|
|
242
242
|
|
|
243
243
|
async with self.async_session_factory() as sess, sess.begin():
|
|
244
|
-
table_is_available = await
|
|
244
|
+
table_is_available = await ais_table_available(session=sess, table_name=table_name, db_schema=db_schema)
|
|
245
245
|
|
|
246
246
|
if not table_is_available:
|
|
247
247
|
return await self._create_table(table_name=table_name, table_type=table_type, db_schema=db_schema)
|
|
248
248
|
|
|
249
|
-
if not await
|
|
249
|
+
if not await ais_valid_table(
|
|
250
250
|
db_engine=self.db_engine,
|
|
251
251
|
table_name=table_name,
|
|
252
252
|
table_type=table_type,
|
|
@@ -288,7 +288,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
288
288
|
delete_stmt = table.delete().where(table.c.session_id == session_id)
|
|
289
289
|
result = await sess.execute(delete_stmt)
|
|
290
290
|
|
|
291
|
-
if result.rowcount == 0:
|
|
291
|
+
if result.rowcount == 0: # type: ignore
|
|
292
292
|
log_debug(f"No session found to delete with session_id: {session_id} in table {table.name}")
|
|
293
293
|
return False
|
|
294
294
|
|
|
@@ -317,7 +317,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
317
317
|
delete_stmt = table.delete().where(table.c.session_id.in_(session_ids))
|
|
318
318
|
result = await sess.execute(delete_stmt)
|
|
319
319
|
|
|
320
|
-
log_debug(f"Successfully deleted {result.rowcount} sessions")
|
|
320
|
+
log_debug(f"Successfully deleted {result.rowcount} sessions") # type: ignore
|
|
321
321
|
|
|
322
322
|
except Exception as e:
|
|
323
323
|
log_error(f"Error deleting sessions: {e}")
|
|
@@ -354,9 +354,6 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
354
354
|
|
|
355
355
|
if user_id is not None:
|
|
356
356
|
stmt = stmt.where(table.c.user_id == user_id)
|
|
357
|
-
if session_type is not None:
|
|
358
|
-
session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
|
|
359
|
-
stmt = stmt.where(table.c.session_type == session_type_value)
|
|
360
357
|
result = await sess.execute(stmt)
|
|
361
358
|
row = result.fetchone()
|
|
362
359
|
if row is None:
|
|
@@ -712,7 +709,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
712
709
|
delete_stmt = table.delete().where(table.c.memory_id == memory_id)
|
|
713
710
|
result = await sess.execute(delete_stmt)
|
|
714
711
|
|
|
715
|
-
success = result.rowcount > 0
|
|
712
|
+
success = result.rowcount > 0 # type: ignore
|
|
716
713
|
if success:
|
|
717
714
|
log_debug(f"Successfully deleted user memory id: {memory_id}")
|
|
718
715
|
else:
|
|
@@ -737,10 +734,10 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
737
734
|
delete_stmt = table.delete().where(table.c.memory_id.in_(memory_ids))
|
|
738
735
|
result = await sess.execute(delete_stmt)
|
|
739
736
|
|
|
740
|
-
if result.rowcount == 0:
|
|
737
|
+
if result.rowcount == 0: # type: ignore
|
|
741
738
|
log_debug(f"No user memories found with ids: {memory_ids}")
|
|
742
739
|
else:
|
|
743
|
-
log_debug(f"Successfully deleted {result.rowcount} user memories")
|
|
740
|
+
log_debug(f"Successfully deleted {result.rowcount} user memories") # type: ignore
|
|
744
741
|
|
|
745
742
|
except Exception as e:
|
|
746
743
|
log_error(f"Error deleting user memories: {e}")
|
|
@@ -1387,7 +1384,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
1387
1384
|
|
|
1388
1385
|
if metrics_records:
|
|
1389
1386
|
async with self.async_session_factory() as sess, sess.begin():
|
|
1390
|
-
results = await
|
|
1387
|
+
results = await abulk_upsert_metrics(session=sess, table=table, metrics_records=metrics_records)
|
|
1391
1388
|
|
|
1392
1389
|
log_debug("Updated metrics calculations")
|
|
1393
1390
|
|
|
@@ -1649,7 +1646,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
1649
1646
|
stmt = table.delete().where(table.c.run_id == eval_run_id)
|
|
1650
1647
|
result = await sess.execute(stmt)
|
|
1651
1648
|
|
|
1652
|
-
if result.rowcount == 0:
|
|
1649
|
+
if result.rowcount == 0: # type: ignore
|
|
1653
1650
|
log_warning(f"No eval run found with ID: {eval_run_id}")
|
|
1654
1651
|
else:
|
|
1655
1652
|
log_debug(f"Deleted eval run with ID: {eval_run_id}")
|
|
@@ -1670,10 +1667,10 @@ class AsyncPostgresDb(AsyncBaseDb):
|
|
|
1670
1667
|
stmt = table.delete().where(table.c.run_id.in_(eval_run_ids))
|
|
1671
1668
|
result = await sess.execute(stmt)
|
|
1672
1669
|
|
|
1673
|
-
if result.rowcount == 0:
|
|
1670
|
+
if result.rowcount == 0: # type: ignore
|
|
1674
1671
|
log_warning(f"No eval runs found with IDs: {eval_run_ids}")
|
|
1675
1672
|
else:
|
|
1676
|
-
log_debug(f"Deleted {result.rowcount} eval runs")
|
|
1673
|
+
log_debug(f"Deleted {result.rowcount} eval runs") # type: ignore
|
|
1677
1674
|
|
|
1678
1675
|
except Exception as e:
|
|
1679
1676
|
log_error(f"Error deleting eval runs {eval_run_ids}: {e}")
|
agno/db/postgres/postgres.py
CHANGED
|
@@ -10,12 +10,12 @@ from agno.db.postgres.utils import (
|
|
|
10
10
|
bulk_upsert_metrics,
|
|
11
11
|
calculate_date_metrics,
|
|
12
12
|
create_schema,
|
|
13
|
-
|
|
13
|
+
deserialize_cultural_knowledge,
|
|
14
14
|
fetch_all_sessions_data,
|
|
15
15
|
get_dates_to_calculate_metrics_for,
|
|
16
16
|
is_table_available,
|
|
17
17
|
is_valid_table,
|
|
18
|
-
|
|
18
|
+
serialize_cultural_knowledge,
|
|
19
19
|
)
|
|
20
20
|
from agno.db.schemas.culture import CulturalKnowledge
|
|
21
21
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
@@ -383,9 +383,6 @@ class PostgresDb(BaseDb):
|
|
|
383
383
|
|
|
384
384
|
if user_id is not None:
|
|
385
385
|
stmt = stmt.where(table.c.user_id == user_id)
|
|
386
|
-
if session_type is not None:
|
|
387
|
-
session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
|
|
388
|
-
stmt = stmt.where(table.c.session_type == session_type_value)
|
|
389
386
|
result = sess.execute(stmt).fetchone()
|
|
390
387
|
if result is None:
|
|
391
388
|
return None
|
|
@@ -2030,7 +2027,7 @@ class PostgresDb(BaseDb):
|
|
|
2030
2027
|
if not db_row or not deserialize:
|
|
2031
2028
|
return db_row
|
|
2032
2029
|
|
|
2033
|
-
return
|
|
2030
|
+
return deserialize_cultural_knowledge(db_row)
|
|
2034
2031
|
|
|
2035
2032
|
except Exception as e:
|
|
2036
2033
|
log_error(f"Exception reading from cultural knowledge table: {e}")
|
|
@@ -2104,7 +2101,7 @@ class PostgresDb(BaseDb):
|
|
|
2104
2101
|
if not deserialize:
|
|
2105
2102
|
return db_rows, total_count
|
|
2106
2103
|
|
|
2107
|
-
return [
|
|
2104
|
+
return [deserialize_cultural_knowledge(row) for row in db_rows]
|
|
2108
2105
|
|
|
2109
2106
|
except Exception as e:
|
|
2110
2107
|
log_error(f"Error reading from cultural knowledge table: {e}")
|
|
@@ -2134,7 +2131,7 @@ class PostgresDb(BaseDb):
|
|
|
2134
2131
|
cultural_knowledge.id = str(uuid4())
|
|
2135
2132
|
|
|
2136
2133
|
# Serialize content, categories, and notes into a JSON dict for DB storage
|
|
2137
|
-
content_dict =
|
|
2134
|
+
content_dict = serialize_cultural_knowledge(cultural_knowledge)
|
|
2138
2135
|
|
|
2139
2136
|
with self.Session() as sess, sess.begin():
|
|
2140
2137
|
stmt = postgresql.insert(table).values(
|
|
@@ -2149,7 +2146,7 @@ class PostgresDb(BaseDb):
|
|
|
2149
2146
|
agent_id=cultural_knowledge.agent_id,
|
|
2150
2147
|
team_id=cultural_knowledge.team_id,
|
|
2151
2148
|
)
|
|
2152
|
-
stmt = stmt.on_conflict_do_update(
|
|
2149
|
+
stmt = stmt.on_conflict_do_update( # type: ignore
|
|
2153
2150
|
index_elements=["id"],
|
|
2154
2151
|
set_=dict(
|
|
2155
2152
|
name=cultural_knowledge.name,
|
|
@@ -2173,7 +2170,7 @@ class PostgresDb(BaseDb):
|
|
|
2173
2170
|
if not db_row or not deserialize:
|
|
2174
2171
|
return db_row
|
|
2175
2172
|
|
|
2176
|
-
return
|
|
2173
|
+
return deserialize_cultural_knowledge(db_row)
|
|
2177
2174
|
|
|
2178
2175
|
except Exception as e:
|
|
2179
2176
|
log_error(f"Error upserting cultural knowledge: {e}")
|
agno/db/postgres/utils.py
CHANGED
|
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional
|
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
|
|
8
8
|
from sqlalchemy import Engine
|
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
|
9
10
|
|
|
10
11
|
from agno.db.postgres.schemas import get_table_schema_definition
|
|
11
12
|
from agno.db.schemas.culture import CulturalKnowledge
|
|
@@ -63,6 +64,20 @@ def create_schema(session: Session, db_schema: str) -> None:
|
|
|
63
64
|
log_warning(f"Could not create schema {db_schema}: {e}")
|
|
64
65
|
|
|
65
66
|
|
|
67
|
+
async def acreate_schema(session: AsyncSession, db_schema: str) -> None:
|
|
68
|
+
"""Create the database schema if it doesn't exist.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
session: The SQLAlchemy session to use
|
|
72
|
+
db_schema (str): The definition of the database schema to create
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
log_debug(f"Creating schema if not exists: {db_schema}")
|
|
76
|
+
await session.execute(text(f"CREATE SCHEMA IF NOT EXISTS {db_schema};"))
|
|
77
|
+
except Exception as e:
|
|
78
|
+
log_warning(f"Could not create schema {db_schema}: {e}")
|
|
79
|
+
|
|
80
|
+
|
|
66
81
|
def is_table_available(session: Session, table_name: str, db_schema: str) -> bool:
|
|
67
82
|
"""
|
|
68
83
|
Check if a table with the given name exists in the given schema.
|
|
@@ -82,6 +97,24 @@ def is_table_available(session: Session, table_name: str, db_schema: str) -> boo
|
|
|
82
97
|
return False
|
|
83
98
|
|
|
84
99
|
|
|
100
|
+
async def ais_table_available(session: AsyncSession, table_name: str, db_schema: str) -> bool:
|
|
101
|
+
"""
|
|
102
|
+
Check if a table with the given name exists in the given schema.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
bool: True if the table exists, False otherwise.
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
exists_query = text(
|
|
109
|
+
"SELECT 1 FROM information_schema.tables WHERE table_schema = :schema AND table_name = :table"
|
|
110
|
+
)
|
|
111
|
+
exists = (await session.execute(exists_query, {"schema": db_schema, "table": table_name})).scalar() is not None
|
|
112
|
+
return exists
|
|
113
|
+
except Exception as e:
|
|
114
|
+
log_error(f"Error checking if table exists: {e}")
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
|
|
85
118
|
def is_valid_table(db_engine: Engine, table_name: str, table_type: str, db_schema: str) -> bool:
|
|
86
119
|
"""
|
|
87
120
|
Check if the existing table has the expected column names.
|
|
@@ -114,6 +147,44 @@ def is_valid_table(db_engine: Engine, table_name: str, table_type: str, db_schem
|
|
|
114
147
|
return False
|
|
115
148
|
|
|
116
149
|
|
|
150
|
+
async def ais_valid_table(db_engine: AsyncEngine, table_name: str, table_type: str, db_schema: str) -> bool:
|
|
151
|
+
"""
|
|
152
|
+
Check if the existing table has the expected column names.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
table_name (str): Name of the table to validate
|
|
156
|
+
schema (str): Database schema name
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
bool: True if table has all expected columns, False otherwise
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
expected_table_schema = get_table_schema_definition(table_type)
|
|
163
|
+
expected_columns = {col_name for col_name in expected_table_schema.keys() if not col_name.startswith("_")}
|
|
164
|
+
|
|
165
|
+
# Get existing columns from the async engine
|
|
166
|
+
async with db_engine.connect() as conn:
|
|
167
|
+
existing_columns = await conn.run_sync(_get_table_columns, table_name, db_schema)
|
|
168
|
+
|
|
169
|
+
# Check if all expected columns exist
|
|
170
|
+
missing_columns = expected_columns - existing_columns
|
|
171
|
+
if missing_columns:
|
|
172
|
+
log_warning(f"Missing columns {missing_columns} in table {db_schema}.{table_name}")
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
return True
|
|
176
|
+
except Exception as e:
|
|
177
|
+
log_error(f"Error validating table schema for {db_schema}.{table_name}: {e}")
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _get_table_columns(conn, table_name: str, db_schema: str) -> set[str]:
|
|
182
|
+
"""Helper function to get table columns using sync inspector."""
|
|
183
|
+
inspector = inspect(conn)
|
|
184
|
+
columns_info = inspector.get_columns(table_name, schema=db_schema)
|
|
185
|
+
return {col["name"] for col in columns_info}
|
|
186
|
+
|
|
187
|
+
|
|
117
188
|
# -- Metrics util methods --
|
|
118
189
|
def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[dict]) -> list[dict]:
|
|
119
190
|
"""Bulk upsert metrics into the database.
|
|
@@ -148,6 +219,39 @@ def bulk_upsert_metrics(session: Session, table: Table, metrics_records: list[di
|
|
|
148
219
|
return results # type: ignore
|
|
149
220
|
|
|
150
221
|
|
|
222
|
+
async def abulk_upsert_metrics(session: AsyncSession, table: Table, metrics_records: list[dict]) -> list[dict]:
|
|
223
|
+
"""Bulk upsert metrics into the database.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
table (Table): The table to upsert into.
|
|
227
|
+
metrics_records (list[dict]): The metrics records to upsert.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
list[dict]: The upserted metrics records.
|
|
231
|
+
"""
|
|
232
|
+
if not metrics_records:
|
|
233
|
+
return []
|
|
234
|
+
|
|
235
|
+
results = []
|
|
236
|
+
stmt = postgresql.insert(table)
|
|
237
|
+
|
|
238
|
+
# Columns to update in case of conflict
|
|
239
|
+
update_columns = {
|
|
240
|
+
col.name: stmt.excluded[col.name]
|
|
241
|
+
for col in table.columns
|
|
242
|
+
if col.name not in ["id", "date", "created_at", "aggregation_period"]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
stmt = stmt.on_conflict_do_update(index_elements=["date", "aggregation_period"], set_=update_columns).returning( # type: ignore
|
|
246
|
+
table
|
|
247
|
+
)
|
|
248
|
+
result = await session.execute(stmt, metrics_records)
|
|
249
|
+
results = [row._mapping for row in result.fetchall()]
|
|
250
|
+
await session.commit()
|
|
251
|
+
|
|
252
|
+
return results # type: ignore
|
|
253
|
+
|
|
254
|
+
|
|
151
255
|
def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
152
256
|
"""Calculate metrics for the given single date.
|
|
153
257
|
|
|
@@ -282,7 +386,7 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
|
|
|
282
386
|
|
|
283
387
|
|
|
284
388
|
# -- Cultural Knowledge util methods --
|
|
285
|
-
def
|
|
389
|
+
def serialize_cultural_knowledge(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
|
|
286
390
|
"""Serialize a CulturalKnowledge object for database storage.
|
|
287
391
|
|
|
288
392
|
Converts the model's separate content, categories, and notes fields
|
|
@@ -305,7 +409,7 @@ def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -
|
|
|
305
409
|
return content_dict if content_dict else {}
|
|
306
410
|
|
|
307
411
|
|
|
308
|
-
def
|
|
412
|
+
def deserialize_cultural_knowledge(db_row: Dict[str, Any]) -> CulturalKnowledge:
|
|
309
413
|
"""Deserialize a database row to a CulturalKnowledge object.
|
|
310
414
|
|
|
311
415
|
The database stores content as a JSON dict containing content, categories, and notes.
|