agno 2.1.9__py3-none-any.whl → 2.2.0__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 +2048 -1204
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +954 -0
- agno/db/async_postgres/async_postgres.py +232 -0
- agno/db/async_postgres/schemas.py +15 -0
- agno/db/async_postgres/utils.py +58 -0
- agno/db/base.py +83 -6
- agno/db/dynamo/dynamo.py +162 -0
- agno/db/dynamo/schemas.py +44 -0
- agno/db/dynamo/utils.py +59 -0
- agno/db/firestore/firestore.py +231 -0
- agno/db/firestore/schemas.py +10 -0
- agno/db/firestore/utils.py +96 -0
- agno/db/gcs_json/gcs_json_db.py +190 -0
- agno/db/gcs_json/utils.py +58 -0
- agno/db/in_memory/in_memory_db.py +118 -0
- agno/db/in_memory/utils.py +58 -0
- agno/db/json/json_db.py +129 -0
- agno/db/json/utils.py +58 -0
- agno/db/mongo/mongo.py +222 -0
- agno/db/mongo/schemas.py +10 -0
- agno/db/mongo/utils.py +59 -0
- agno/db/mysql/mysql.py +232 -1
- agno/db/mysql/schemas.py +14 -0
- agno/db/mysql/utils.py +58 -0
- agno/db/postgres/postgres.py +242 -0
- agno/db/postgres/schemas.py +15 -0
- agno/db/postgres/utils.py +58 -0
- agno/db/redis/redis.py +181 -0
- agno/db/redis/schemas.py +14 -0
- agno/db/redis/utils.py +58 -0
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/singlestore/schemas.py +14 -0
- agno/db/singlestore/singlestore.py +231 -0
- agno/db/singlestore/utils.py +58 -0
- agno/db/sqlite/schemas.py +14 -0
- agno/db/sqlite/sqlite.py +274 -7
- agno/db/sqlite/utils.py +62 -0
- agno/db/surrealdb/models.py +51 -1
- agno/db/surrealdb/surrealdb.py +154 -0
- agno/db/surrealdb/utils.py +61 -1
- agno/knowledge/reader/field_labeled_csv_reader.py +0 -2
- agno/memory/manager.py +28 -11
- agno/models/anthropic/claude.py +2 -2
- agno/models/message.py +0 -1
- agno/models/ollama/chat.py +7 -2
- agno/os/app.py +29 -7
- agno/os/interfaces/a2a/router.py +2 -2
- agno/os/interfaces/agui/router.py +2 -2
- agno/os/router.py +7 -7
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/health.py +6 -2
- agno/os/routers/knowledge/schemas.py +49 -47
- agno/os/routers/memory/schemas.py +16 -16
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +382 -7
- agno/os/schema.py +254 -231
- agno/os/utils.py +1 -1
- agno/run/agent.py +49 -1
- agno/run/team.py +43 -0
- agno/session/summary.py +45 -13
- agno/session/team.py +90 -5
- agno/team/team.py +1118 -857
- agno/tools/gmail.py +59 -14
- agno/utils/agent.py +372 -0
- agno/utils/events.py +144 -2
- agno/utils/print_response/agent.py +10 -6
- agno/utils/print_response/team.py +6 -4
- agno/utils/print_response/workflow.py +7 -5
- agno/utils/team.py +9 -8
- agno/workflow/condition.py +17 -9
- agno/workflow/loop.py +18 -10
- agno/workflow/parallel.py +14 -6
- agno/workflow/router.py +17 -9
- agno/workflow/step.py +14 -6
- agno/workflow/steps.py +14 -6
- agno/workflow/workflow.py +245 -122
- {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/METADATA +60 -23
- {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/RECORD +83 -79
- {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/WHEEL +0 -0
- {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.9.dist-info → agno-2.2.0.dist-info}/top_level.txt +0 -0
agno/db/mysql/mysql.py
CHANGED
|
@@ -12,16 +12,19 @@ from agno.db.mysql.utils import (
|
|
|
12
12
|
bulk_upsert_metrics,
|
|
13
13
|
calculate_date_metrics,
|
|
14
14
|
create_schema,
|
|
15
|
+
deserialize_cultural_knowledge_from_db,
|
|
15
16
|
fetch_all_sessions_data,
|
|
16
17
|
get_dates_to_calculate_metrics_for,
|
|
17
18
|
is_table_available,
|
|
18
19
|
is_valid_table,
|
|
20
|
+
serialize_cultural_knowledge_for_db,
|
|
19
21
|
)
|
|
22
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
20
23
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
21
24
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
22
25
|
from agno.db.schemas.memory import UserMemory
|
|
23
26
|
from agno.session import AgentSession, Session, TeamSession, WorkflowSession
|
|
24
|
-
from agno.utils.log import log_debug, log_error, log_info
|
|
27
|
+
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
25
28
|
from agno.utils.string import generate_id
|
|
26
29
|
|
|
27
30
|
try:
|
|
@@ -42,6 +45,7 @@ class MySQLDb(BaseDb):
|
|
|
42
45
|
db_schema: Optional[str] = None,
|
|
43
46
|
db_url: Optional[str] = None,
|
|
44
47
|
session_table: Optional[str] = None,
|
|
48
|
+
culture_table: Optional[str] = None,
|
|
45
49
|
memory_table: Optional[str] = None,
|
|
46
50
|
metrics_table: Optional[str] = None,
|
|
47
51
|
eval_table: Optional[str] = None,
|
|
@@ -61,6 +65,7 @@ class MySQLDb(BaseDb):
|
|
|
61
65
|
db_engine (Optional[Engine]): The SQLAlchemy database engine to use.
|
|
62
66
|
db_schema (Optional[str]): The database schema to use.
|
|
63
67
|
session_table (Optional[str]): Name of the table to store Agent, Team and Workflow sessions.
|
|
68
|
+
culture_table (Optional[str]): Name of the table to store cultural knowledge.
|
|
64
69
|
memory_table (Optional[str]): Name of the table to store memories.
|
|
65
70
|
metrics_table (Optional[str]): Name of the table to store metrics.
|
|
66
71
|
eval_table (Optional[str]): Name of the table to store evaluation runs data.
|
|
@@ -80,6 +85,7 @@ class MySQLDb(BaseDb):
|
|
|
80
85
|
super().__init__(
|
|
81
86
|
id=id,
|
|
82
87
|
session_table=session_table,
|
|
88
|
+
culture_table=culture_table,
|
|
83
89
|
memory_table=memory_table,
|
|
84
90
|
metrics_table=metrics_table,
|
|
85
91
|
eval_table=eval_table,
|
|
@@ -238,6 +244,15 @@ class MySQLDb(BaseDb):
|
|
|
238
244
|
)
|
|
239
245
|
return self.knowledge_table
|
|
240
246
|
|
|
247
|
+
if table_type == "culture":
|
|
248
|
+
self.culture_table = self._get_or_create_table(
|
|
249
|
+
table_name=self.culture_table_name,
|
|
250
|
+
table_type="culture",
|
|
251
|
+
db_schema=self.db_schema,
|
|
252
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
253
|
+
)
|
|
254
|
+
return self.culture_table
|
|
255
|
+
|
|
241
256
|
raise ValueError(f"Unknown table type: {table_type}")
|
|
242
257
|
|
|
243
258
|
def _get_or_create_table(
|
|
@@ -1996,6 +2011,222 @@ class MySQLDb(BaseDb):
|
|
|
1996
2011
|
log_error(f"Error upserting eval run name {eval_run_id}: {e}")
|
|
1997
2012
|
return None
|
|
1998
2013
|
|
|
2014
|
+
# -- Culture methods --
|
|
2015
|
+
|
|
2016
|
+
def clear_cultural_knowledge(self) -> None:
|
|
2017
|
+
"""Delete all cultural knowledge from the database.
|
|
2018
|
+
|
|
2019
|
+
Raises:
|
|
2020
|
+
Exception: If an error occurs during deletion.
|
|
2021
|
+
"""
|
|
2022
|
+
try:
|
|
2023
|
+
table = self._get_table(table_type="culture")
|
|
2024
|
+
if table is None:
|
|
2025
|
+
return
|
|
2026
|
+
|
|
2027
|
+
with self.Session() as sess, sess.begin():
|
|
2028
|
+
sess.execute(table.delete())
|
|
2029
|
+
|
|
2030
|
+
except Exception as e:
|
|
2031
|
+
log_warning(f"Exception deleting all cultural knowledge: {e}")
|
|
2032
|
+
raise e
|
|
2033
|
+
|
|
2034
|
+
def delete_cultural_knowledge(self, id: str) -> None:
|
|
2035
|
+
"""Delete a cultural knowledge entry from the database.
|
|
2036
|
+
|
|
2037
|
+
Args:
|
|
2038
|
+
id (str): The ID of the cultural knowledge to delete.
|
|
2039
|
+
|
|
2040
|
+
Raises:
|
|
2041
|
+
Exception: If an error occurs during deletion.
|
|
2042
|
+
"""
|
|
2043
|
+
try:
|
|
2044
|
+
table = self._get_table(table_type="culture")
|
|
2045
|
+
if table is None:
|
|
2046
|
+
return
|
|
2047
|
+
|
|
2048
|
+
with self.Session() as sess, sess.begin():
|
|
2049
|
+
delete_stmt = table.delete().where(table.c.id == id)
|
|
2050
|
+
result = sess.execute(delete_stmt)
|
|
2051
|
+
|
|
2052
|
+
success = result.rowcount > 0
|
|
2053
|
+
if success:
|
|
2054
|
+
log_debug(f"Successfully deleted cultural knowledge id: {id}")
|
|
2055
|
+
else:
|
|
2056
|
+
log_debug(f"No cultural knowledge found with id: {id}")
|
|
2057
|
+
|
|
2058
|
+
except Exception as e:
|
|
2059
|
+
log_error(f"Error deleting cultural knowledge: {e}")
|
|
2060
|
+
raise e
|
|
2061
|
+
|
|
2062
|
+
def get_cultural_knowledge(
|
|
2063
|
+
self, id: str, deserialize: Optional[bool] = True
|
|
2064
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
2065
|
+
"""Get a cultural knowledge entry from the database.
|
|
2066
|
+
|
|
2067
|
+
Args:
|
|
2068
|
+
id (str): The ID of the cultural knowledge to get.
|
|
2069
|
+
deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
|
|
2070
|
+
|
|
2071
|
+
Returns:
|
|
2072
|
+
Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge entry, or None if it doesn't exist.
|
|
2073
|
+
|
|
2074
|
+
Raises:
|
|
2075
|
+
Exception: If an error occurs during retrieval.
|
|
2076
|
+
"""
|
|
2077
|
+
try:
|
|
2078
|
+
table = self._get_table(table_type="culture")
|
|
2079
|
+
if table is None:
|
|
2080
|
+
return None
|
|
2081
|
+
|
|
2082
|
+
with self.Session() as sess, sess.begin():
|
|
2083
|
+
stmt = select(table).where(table.c.id == id)
|
|
2084
|
+
result = sess.execute(stmt).fetchone()
|
|
2085
|
+
if result is None:
|
|
2086
|
+
return None
|
|
2087
|
+
|
|
2088
|
+
db_row = dict(result._mapping)
|
|
2089
|
+
if not db_row or not deserialize:
|
|
2090
|
+
return db_row
|
|
2091
|
+
|
|
2092
|
+
return deserialize_cultural_knowledge_from_db(db_row)
|
|
2093
|
+
|
|
2094
|
+
except Exception as e:
|
|
2095
|
+
log_error(f"Exception reading from cultural knowledge table: {e}")
|
|
2096
|
+
raise e
|
|
2097
|
+
|
|
2098
|
+
def get_all_cultural_knowledge(
|
|
2099
|
+
self,
|
|
2100
|
+
name: Optional[str] = None,
|
|
2101
|
+
agent_id: Optional[str] = None,
|
|
2102
|
+
team_id: Optional[str] = None,
|
|
2103
|
+
limit: Optional[int] = None,
|
|
2104
|
+
page: Optional[int] = None,
|
|
2105
|
+
sort_by: Optional[str] = None,
|
|
2106
|
+
sort_order: Optional[str] = None,
|
|
2107
|
+
deserialize: Optional[bool] = True,
|
|
2108
|
+
) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
2109
|
+
"""Get all cultural knowledge from the database as CulturalKnowledge objects.
|
|
2110
|
+
|
|
2111
|
+
Args:
|
|
2112
|
+
name (Optional[str]): The name of the cultural knowledge to filter by.
|
|
2113
|
+
agent_id (Optional[str]): The ID of the agent to filter by.
|
|
2114
|
+
team_id (Optional[str]): The ID of the team to filter by.
|
|
2115
|
+
limit (Optional[int]): The maximum number of cultural knowledge entries to return.
|
|
2116
|
+
page (Optional[int]): The page number.
|
|
2117
|
+
sort_by (Optional[str]): The column to sort by.
|
|
2118
|
+
sort_order (Optional[str]): The order to sort by.
|
|
2119
|
+
deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
|
|
2120
|
+
|
|
2121
|
+
Returns:
|
|
2122
|
+
Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
2123
|
+
- When deserialize=True: List of CulturalKnowledge objects
|
|
2124
|
+
- When deserialize=False: List of CulturalKnowledge dictionaries and total count
|
|
2125
|
+
|
|
2126
|
+
Raises:
|
|
2127
|
+
Exception: If an error occurs during retrieval.
|
|
2128
|
+
"""
|
|
2129
|
+
try:
|
|
2130
|
+
table = self._get_table(table_type="culture")
|
|
2131
|
+
if table is None:
|
|
2132
|
+
return [] if deserialize else ([], 0)
|
|
2133
|
+
|
|
2134
|
+
with self.Session() as sess, sess.begin():
|
|
2135
|
+
stmt = select(table)
|
|
2136
|
+
|
|
2137
|
+
# Filtering
|
|
2138
|
+
if name is not None:
|
|
2139
|
+
stmt = stmt.where(table.c.name == name)
|
|
2140
|
+
if agent_id is not None:
|
|
2141
|
+
stmt = stmt.where(table.c.agent_id == agent_id)
|
|
2142
|
+
if team_id is not None:
|
|
2143
|
+
stmt = stmt.where(table.c.team_id == team_id)
|
|
2144
|
+
|
|
2145
|
+
# Get total count after applying filtering
|
|
2146
|
+
count_stmt = select(func.count()).select_from(stmt.alias())
|
|
2147
|
+
total_count = sess.execute(count_stmt).scalar()
|
|
2148
|
+
|
|
2149
|
+
# Sorting
|
|
2150
|
+
stmt = apply_sorting(stmt, table, sort_by, sort_order)
|
|
2151
|
+
# Paginating
|
|
2152
|
+
if limit is not None:
|
|
2153
|
+
stmt = stmt.limit(limit)
|
|
2154
|
+
if page is not None:
|
|
2155
|
+
stmt = stmt.offset((page - 1) * limit)
|
|
2156
|
+
|
|
2157
|
+
result = sess.execute(stmt).fetchall()
|
|
2158
|
+
if not result:
|
|
2159
|
+
return [] if deserialize else ([], 0)
|
|
2160
|
+
|
|
2161
|
+
db_rows = [dict(record._mapping) for record in result]
|
|
2162
|
+
|
|
2163
|
+
if not deserialize:
|
|
2164
|
+
return db_rows, total_count
|
|
2165
|
+
|
|
2166
|
+
return [deserialize_cultural_knowledge_from_db(row) for row in db_rows]
|
|
2167
|
+
|
|
2168
|
+
except Exception as e:
|
|
2169
|
+
log_error(f"Error reading from cultural knowledge table: {e}")
|
|
2170
|
+
raise e
|
|
2171
|
+
|
|
2172
|
+
def upsert_cultural_knowledge(
|
|
2173
|
+
self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
|
|
2174
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
2175
|
+
"""Upsert a cultural knowledge entry into the database.
|
|
2176
|
+
|
|
2177
|
+
Args:
|
|
2178
|
+
cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
|
|
2179
|
+
deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
|
|
2180
|
+
|
|
2181
|
+
Returns:
|
|
2182
|
+
Optional[CulturalKnowledge]: The upserted cultural knowledge entry.
|
|
2183
|
+
|
|
2184
|
+
Raises:
|
|
2185
|
+
Exception: If an error occurs during upsert.
|
|
2186
|
+
"""
|
|
2187
|
+
try:
|
|
2188
|
+
table = self._get_table(table_type="culture", create_table_if_not_found=True)
|
|
2189
|
+
if table is None:
|
|
2190
|
+
return None
|
|
2191
|
+
|
|
2192
|
+
if cultural_knowledge.id is None:
|
|
2193
|
+
cultural_knowledge.id = str(uuid4())
|
|
2194
|
+
|
|
2195
|
+
# Serialize content, categories, and notes into a JSON dict for DB storage
|
|
2196
|
+
content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
|
|
2197
|
+
|
|
2198
|
+
with self.Session() as sess, sess.begin():
|
|
2199
|
+
stmt = mysql.insert(table).values(
|
|
2200
|
+
id=cultural_knowledge.id,
|
|
2201
|
+
name=cultural_knowledge.name,
|
|
2202
|
+
summary=cultural_knowledge.summary,
|
|
2203
|
+
content=content_dict if content_dict else None,
|
|
2204
|
+
metadata=cultural_knowledge.metadata,
|
|
2205
|
+
input=cultural_knowledge.input,
|
|
2206
|
+
created_at=cultural_knowledge.created_at,
|
|
2207
|
+
updated_at=int(time.time()),
|
|
2208
|
+
agent_id=cultural_knowledge.agent_id,
|
|
2209
|
+
team_id=cultural_knowledge.team_id,
|
|
2210
|
+
)
|
|
2211
|
+
stmt = stmt.on_duplicate_key_update(
|
|
2212
|
+
name=cultural_knowledge.name,
|
|
2213
|
+
summary=cultural_knowledge.summary,
|
|
2214
|
+
content=content_dict if content_dict else None,
|
|
2215
|
+
metadata=cultural_knowledge.metadata,
|
|
2216
|
+
input=cultural_knowledge.input,
|
|
2217
|
+
updated_at=int(time.time()),
|
|
2218
|
+
agent_id=cultural_knowledge.agent_id,
|
|
2219
|
+
team_id=cultural_knowledge.team_id,
|
|
2220
|
+
)
|
|
2221
|
+
sess.execute(stmt)
|
|
2222
|
+
|
|
2223
|
+
# Fetch the inserted/updated row
|
|
2224
|
+
return self.get_cultural_knowledge(id=cultural_knowledge.id, deserialize=deserialize)
|
|
2225
|
+
|
|
2226
|
+
except Exception as e:
|
|
2227
|
+
log_error(f"Error upserting cultural knowledge: {e}")
|
|
2228
|
+
raise e
|
|
2229
|
+
|
|
1999
2230
|
# -- Migrations --
|
|
2000
2231
|
|
|
2001
2232
|
def migrate_table_from_v1_to_v2(self, v1_db_schema: str, v1_table_name: str, v1_table_type: str):
|
agno/db/mysql/schemas.py
CHANGED
|
@@ -98,6 +98,19 @@ METRICS_TABLE_SCHEMA = {
|
|
|
98
98
|
],
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
CULTURAL_KNOWLEDGE_TABLE_SCHEMA = {
|
|
102
|
+
"id": {"type": lambda: String(128), "primary_key": True, "nullable": False},
|
|
103
|
+
"name": {"type": lambda: String(255), "nullable": False, "index": True},
|
|
104
|
+
"summary": {"type": Text, "nullable": True},
|
|
105
|
+
"content": {"type": JSON, "nullable": True},
|
|
106
|
+
"metadata": {"type": JSON, "nullable": True},
|
|
107
|
+
"input": {"type": Text, "nullable": True},
|
|
108
|
+
"created_at": {"type": BigInteger, "nullable": True},
|
|
109
|
+
"updated_at": {"type": BigInteger, "nullable": True},
|
|
110
|
+
"agent_id": {"type": lambda: String(128), "nullable": True},
|
|
111
|
+
"team_id": {"type": lambda: String(128), "nullable": True},
|
|
112
|
+
}
|
|
113
|
+
|
|
101
114
|
|
|
102
115
|
def get_table_schema_definition(table_type: str) -> dict[str, Any]:
|
|
103
116
|
"""
|
|
@@ -115,6 +128,7 @@ def get_table_schema_definition(table_type: str) -> dict[str, Any]:
|
|
|
115
128
|
"metrics": METRICS_TABLE_SCHEMA,
|
|
116
129
|
"memories": USER_MEMORY_TABLE_SCHEMA,
|
|
117
130
|
"knowledge": KNOWLEDGE_TABLE_SCHEMA,
|
|
131
|
+
"culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
schema = schemas.get(table_type, {})
|
agno/db/mysql/utils.py
CHANGED
|
@@ -8,6 +8,7 @@ from uuid import uuid4
|
|
|
8
8
|
from sqlalchemy import Engine
|
|
9
9
|
|
|
10
10
|
from agno.db.mysql.schemas import get_table_schema_definition
|
|
11
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
11
12
|
from agno.utils.log import log_debug, log_error, log_warning
|
|
12
13
|
|
|
13
14
|
try:
|
|
@@ -295,3 +296,60 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
|
|
|
295
296
|
if days_diff <= 0:
|
|
296
297
|
return []
|
|
297
298
|
return [starting_date + timedelta(days=x) for x in range(days_diff)]
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# -- Cultural Knowledge util methods --
|
|
302
|
+
def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
|
|
303
|
+
"""Serialize a CulturalKnowledge object for database storage.
|
|
304
|
+
|
|
305
|
+
Converts the model's separate content, categories, and notes fields
|
|
306
|
+
into a single JSON dict for the database content column.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Dict[str, Any]: A dictionary with the content field as JSON containing content, categories, and notes.
|
|
313
|
+
"""
|
|
314
|
+
content_dict: Dict[str, Any] = {}
|
|
315
|
+
if cultural_knowledge.content is not None:
|
|
316
|
+
content_dict["content"] = cultural_knowledge.content
|
|
317
|
+
if cultural_knowledge.categories is not None:
|
|
318
|
+
content_dict["categories"] = cultural_knowledge.categories
|
|
319
|
+
if cultural_knowledge.notes is not None:
|
|
320
|
+
content_dict["notes"] = cultural_knowledge.notes
|
|
321
|
+
|
|
322
|
+
return content_dict if content_dict else {}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
|
|
326
|
+
"""Deserialize a database row to a CulturalKnowledge object.
|
|
327
|
+
|
|
328
|
+
The database stores content as a JSON dict containing content, categories, and notes.
|
|
329
|
+
This method extracts those fields and converts them back to the model format.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
db_row (Dict[str, Any]): The database row as a dictionary.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
CulturalKnowledge: The cultural knowledge object.
|
|
336
|
+
"""
|
|
337
|
+
# Extract content, categories, and notes from the JSON content field
|
|
338
|
+
content_json = db_row.get("content", {}) or {}
|
|
339
|
+
|
|
340
|
+
return CulturalKnowledge.from_dict(
|
|
341
|
+
{
|
|
342
|
+
"id": db_row.get("id"),
|
|
343
|
+
"name": db_row.get("name"),
|
|
344
|
+
"summary": db_row.get("summary"),
|
|
345
|
+
"content": content_json.get("content"),
|
|
346
|
+
"categories": content_json.get("categories"),
|
|
347
|
+
"notes": content_json.get("notes"),
|
|
348
|
+
"metadata": db_row.get("metadata"),
|
|
349
|
+
"input": db_row.get("input"),
|
|
350
|
+
"created_at": db_row.get("created_at"),
|
|
351
|
+
"updated_at": db_row.get("updated_at"),
|
|
352
|
+
"agent_id": db_row.get("agent_id"),
|
|
353
|
+
"team_id": db_row.get("team_id"),
|
|
354
|
+
}
|
|
355
|
+
)
|
agno/db/postgres/postgres.py
CHANGED
|
@@ -10,11 +10,14 @@ from agno.db.postgres.utils import (
|
|
|
10
10
|
bulk_upsert_metrics,
|
|
11
11
|
calculate_date_metrics,
|
|
12
12
|
create_schema,
|
|
13
|
+
deserialize_cultural_knowledge_from_db,
|
|
13
14
|
fetch_all_sessions_data,
|
|
14
15
|
get_dates_to_calculate_metrics_for,
|
|
15
16
|
is_table_available,
|
|
16
17
|
is_valid_table,
|
|
18
|
+
serialize_cultural_knowledge_for_db,
|
|
17
19
|
)
|
|
20
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
18
21
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
19
22
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
20
23
|
from agno.db.schemas.memory import UserMemory
|
|
@@ -40,6 +43,7 @@ class PostgresDb(BaseDb):
|
|
|
40
43
|
db_engine: Optional[Engine] = None,
|
|
41
44
|
db_schema: Optional[str] = None,
|
|
42
45
|
session_table: Optional[str] = None,
|
|
46
|
+
culture_table: Optional[str] = None,
|
|
43
47
|
memory_table: Optional[str] = None,
|
|
44
48
|
metrics_table: Optional[str] = None,
|
|
45
49
|
eval_table: Optional[str] = None,
|
|
@@ -63,6 +67,7 @@ class PostgresDb(BaseDb):
|
|
|
63
67
|
metrics_table (Optional[str]): Name of the table to store metrics.
|
|
64
68
|
eval_table (Optional[str]): Name of the table to store evaluation runs data.
|
|
65
69
|
knowledge_table (Optional[str]): Name of the table to store knowledge content.
|
|
70
|
+
culture_table (Optional[str]): Name of the table to store cultural knowledge.
|
|
66
71
|
id (Optional[str]): ID of the database.
|
|
67
72
|
|
|
68
73
|
Raises:
|
|
@@ -91,6 +96,7 @@ class PostgresDb(BaseDb):
|
|
|
91
96
|
metrics_table=metrics_table,
|
|
92
97
|
eval_table=eval_table,
|
|
93
98
|
knowledge_table=knowledge_table,
|
|
99
|
+
culture_table=culture_table,
|
|
94
100
|
)
|
|
95
101
|
|
|
96
102
|
self.db_schema: str = db_schema if db_schema is not None else "ai"
|
|
@@ -231,6 +237,15 @@ class PostgresDb(BaseDb):
|
|
|
231
237
|
)
|
|
232
238
|
return self.knowledge_table
|
|
233
239
|
|
|
240
|
+
if table_type == "culture":
|
|
241
|
+
self.culture_table = self._get_or_create_table(
|
|
242
|
+
table_name=self.culture_table_name,
|
|
243
|
+
table_type="culture",
|
|
244
|
+
db_schema=self.db_schema,
|
|
245
|
+
create_table_if_not_found=create_table_if_not_found,
|
|
246
|
+
)
|
|
247
|
+
return self.culture_table
|
|
248
|
+
|
|
234
249
|
raise ValueError(f"Unknown table type: {table_type}")
|
|
235
250
|
|
|
236
251
|
def _get_or_create_table(
|
|
@@ -1937,6 +1952,233 @@ class PostgresDb(BaseDb):
|
|
|
1937
1952
|
log_error(f"Error upserting eval run name {eval_run_id}: {e}")
|
|
1938
1953
|
raise e
|
|
1939
1954
|
|
|
1955
|
+
# -- Culture methods --
|
|
1956
|
+
|
|
1957
|
+
def clear_cultural_knowledge(self) -> None:
|
|
1958
|
+
"""Delete all cultural knowledge from the database.
|
|
1959
|
+
|
|
1960
|
+
Raises:
|
|
1961
|
+
Exception: If an error occurs during deletion.
|
|
1962
|
+
"""
|
|
1963
|
+
try:
|
|
1964
|
+
table = self._get_table(table_type="culture")
|
|
1965
|
+
if table is None:
|
|
1966
|
+
return
|
|
1967
|
+
|
|
1968
|
+
with self.Session() as sess, sess.begin():
|
|
1969
|
+
sess.execute(table.delete())
|
|
1970
|
+
|
|
1971
|
+
except Exception as e:
|
|
1972
|
+
log_warning(f"Exception deleting all cultural knowledge: {e}")
|
|
1973
|
+
raise e
|
|
1974
|
+
|
|
1975
|
+
def delete_cultural_knowledge(self, id: str) -> None:
|
|
1976
|
+
"""Delete a cultural knowledge entry from the database.
|
|
1977
|
+
|
|
1978
|
+
Args:
|
|
1979
|
+
id (str): The ID of the cultural knowledge to delete.
|
|
1980
|
+
|
|
1981
|
+
Raises:
|
|
1982
|
+
Exception: If an error occurs during deletion.
|
|
1983
|
+
"""
|
|
1984
|
+
try:
|
|
1985
|
+
table = self._get_table(table_type="culture")
|
|
1986
|
+
if table is None:
|
|
1987
|
+
return
|
|
1988
|
+
|
|
1989
|
+
with self.Session() as sess, sess.begin():
|
|
1990
|
+
delete_stmt = table.delete().where(table.c.id == id)
|
|
1991
|
+
result = sess.execute(delete_stmt)
|
|
1992
|
+
|
|
1993
|
+
success = result.rowcount > 0
|
|
1994
|
+
if success:
|
|
1995
|
+
log_debug(f"Successfully deleted cultural knowledge id: {id}")
|
|
1996
|
+
else:
|
|
1997
|
+
log_debug(f"No cultural knowledge found with id: {id}")
|
|
1998
|
+
|
|
1999
|
+
except Exception as e:
|
|
2000
|
+
log_error(f"Error deleting cultural knowledge: {e}")
|
|
2001
|
+
raise e
|
|
2002
|
+
|
|
2003
|
+
def get_cultural_knowledge(
|
|
2004
|
+
self, id: str, deserialize: Optional[bool] = True
|
|
2005
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
2006
|
+
"""Get a cultural knowledge entry from the database.
|
|
2007
|
+
|
|
2008
|
+
Args:
|
|
2009
|
+
id (str): The ID of the cultural knowledge to get.
|
|
2010
|
+
deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
|
|
2011
|
+
|
|
2012
|
+
Returns:
|
|
2013
|
+
Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge entry, or None if it doesn't exist.
|
|
2014
|
+
|
|
2015
|
+
Raises:
|
|
2016
|
+
Exception: If an error occurs during retrieval.
|
|
2017
|
+
"""
|
|
2018
|
+
try:
|
|
2019
|
+
table = self._get_table(table_type="culture")
|
|
2020
|
+
if table is None:
|
|
2021
|
+
return None
|
|
2022
|
+
|
|
2023
|
+
with self.Session() as sess, sess.begin():
|
|
2024
|
+
stmt = select(table).where(table.c.id == id)
|
|
2025
|
+
result = sess.execute(stmt).fetchone()
|
|
2026
|
+
if result is None:
|
|
2027
|
+
return None
|
|
2028
|
+
|
|
2029
|
+
db_row = dict(result._mapping)
|
|
2030
|
+
if not db_row or not deserialize:
|
|
2031
|
+
return db_row
|
|
2032
|
+
|
|
2033
|
+
return deserialize_cultural_knowledge_from_db(db_row)
|
|
2034
|
+
|
|
2035
|
+
except Exception as e:
|
|
2036
|
+
log_error(f"Exception reading from cultural knowledge table: {e}")
|
|
2037
|
+
raise e
|
|
2038
|
+
|
|
2039
|
+
def get_all_cultural_knowledge(
|
|
2040
|
+
self,
|
|
2041
|
+
name: Optional[str] = None,
|
|
2042
|
+
agent_id: Optional[str] = None,
|
|
2043
|
+
team_id: Optional[str] = None,
|
|
2044
|
+
limit: Optional[int] = None,
|
|
2045
|
+
page: Optional[int] = None,
|
|
2046
|
+
sort_by: Optional[str] = None,
|
|
2047
|
+
sort_order: Optional[str] = None,
|
|
2048
|
+
deserialize: Optional[bool] = True,
|
|
2049
|
+
) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
2050
|
+
"""Get all cultural knowledge from the database as CulturalKnowledge objects.
|
|
2051
|
+
|
|
2052
|
+
Args:
|
|
2053
|
+
name (Optional[str]): The name of the cultural knowledge to filter by.
|
|
2054
|
+
agent_id (Optional[str]): The ID of the agent to filter by.
|
|
2055
|
+
team_id (Optional[str]): The ID of the team to filter by.
|
|
2056
|
+
limit (Optional[int]): The maximum number of cultural knowledge entries to return.
|
|
2057
|
+
page (Optional[int]): The page number.
|
|
2058
|
+
sort_by (Optional[str]): The column to sort by.
|
|
2059
|
+
sort_order (Optional[str]): The order to sort by.
|
|
2060
|
+
deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
|
|
2061
|
+
|
|
2062
|
+
Returns:
|
|
2063
|
+
Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
2064
|
+
- When deserialize=True: List of CulturalKnowledge objects
|
|
2065
|
+
- When deserialize=False: List of CulturalKnowledge dictionaries and total count
|
|
2066
|
+
|
|
2067
|
+
Raises:
|
|
2068
|
+
Exception: If an error occurs during retrieval.
|
|
2069
|
+
"""
|
|
2070
|
+
try:
|
|
2071
|
+
table = self._get_table(table_type="culture")
|
|
2072
|
+
if table is None:
|
|
2073
|
+
return [] if deserialize else ([], 0)
|
|
2074
|
+
|
|
2075
|
+
with self.Session() as sess, sess.begin():
|
|
2076
|
+
stmt = select(table)
|
|
2077
|
+
|
|
2078
|
+
# Filtering
|
|
2079
|
+
if name is not None:
|
|
2080
|
+
stmt = stmt.where(table.c.name == name)
|
|
2081
|
+
if agent_id is not None:
|
|
2082
|
+
stmt = stmt.where(table.c.agent_id == agent_id)
|
|
2083
|
+
if team_id is not None:
|
|
2084
|
+
stmt = stmt.where(table.c.team_id == team_id)
|
|
2085
|
+
|
|
2086
|
+
# Get total count after applying filtering
|
|
2087
|
+
count_stmt = select(func.count()).select_from(stmt.alias())
|
|
2088
|
+
total_count = sess.execute(count_stmt).scalar()
|
|
2089
|
+
|
|
2090
|
+
# Sorting
|
|
2091
|
+
stmt = apply_sorting(stmt, table, sort_by, sort_order)
|
|
2092
|
+
# Paginating
|
|
2093
|
+
if limit is not None:
|
|
2094
|
+
stmt = stmt.limit(limit)
|
|
2095
|
+
if page is not None:
|
|
2096
|
+
stmt = stmt.offset((page - 1) * limit)
|
|
2097
|
+
|
|
2098
|
+
result = sess.execute(stmt).fetchall()
|
|
2099
|
+
if not result:
|
|
2100
|
+
return [] if deserialize else ([], 0)
|
|
2101
|
+
|
|
2102
|
+
db_rows = [dict(record._mapping) for record in result]
|
|
2103
|
+
|
|
2104
|
+
if not deserialize:
|
|
2105
|
+
return db_rows, total_count
|
|
2106
|
+
|
|
2107
|
+
return [deserialize_cultural_knowledge_from_db(row) for row in db_rows]
|
|
2108
|
+
|
|
2109
|
+
except Exception as e:
|
|
2110
|
+
log_error(f"Error reading from cultural knowledge table: {e}")
|
|
2111
|
+
raise e
|
|
2112
|
+
|
|
2113
|
+
def upsert_cultural_knowledge(
|
|
2114
|
+
self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
|
|
2115
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
2116
|
+
"""Upsert a cultural knowledge entry into the database.
|
|
2117
|
+
|
|
2118
|
+
Args:
|
|
2119
|
+
cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
|
|
2120
|
+
deserialize (Optional[bool]): Whether to deserialize the cultural knowledge. Defaults to True.
|
|
2121
|
+
|
|
2122
|
+
Returns:
|
|
2123
|
+
Optional[CulturalKnowledge]: The upserted cultural knowledge entry.
|
|
2124
|
+
|
|
2125
|
+
Raises:
|
|
2126
|
+
Exception: If an error occurs during upsert.
|
|
2127
|
+
"""
|
|
2128
|
+
try:
|
|
2129
|
+
table = self._get_table(table_type="culture", create_table_if_not_found=True)
|
|
2130
|
+
if table is None:
|
|
2131
|
+
return None
|
|
2132
|
+
|
|
2133
|
+
if cultural_knowledge.id is None:
|
|
2134
|
+
cultural_knowledge.id = str(uuid4())
|
|
2135
|
+
|
|
2136
|
+
# Serialize content, categories, and notes into a JSON dict for DB storage
|
|
2137
|
+
content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
|
|
2138
|
+
|
|
2139
|
+
with self.Session() as sess, sess.begin():
|
|
2140
|
+
stmt = postgresql.insert(table).values(
|
|
2141
|
+
id=cultural_knowledge.id,
|
|
2142
|
+
name=cultural_knowledge.name,
|
|
2143
|
+
summary=cultural_knowledge.summary,
|
|
2144
|
+
content=content_dict if content_dict else None,
|
|
2145
|
+
metadata=cultural_knowledge.metadata,
|
|
2146
|
+
input=cultural_knowledge.input,
|
|
2147
|
+
created_at=cultural_knowledge.created_at,
|
|
2148
|
+
updated_at=int(time.time()),
|
|
2149
|
+
agent_id=cultural_knowledge.agent_id,
|
|
2150
|
+
team_id=cultural_knowledge.team_id,
|
|
2151
|
+
)
|
|
2152
|
+
stmt = stmt.on_conflict_do_update(
|
|
2153
|
+
index_elements=["id"],
|
|
2154
|
+
set_=dict(
|
|
2155
|
+
name=cultural_knowledge.name,
|
|
2156
|
+
summary=cultural_knowledge.summary,
|
|
2157
|
+
content=content_dict if content_dict else None,
|
|
2158
|
+
metadata=cultural_knowledge.metadata,
|
|
2159
|
+
input=cultural_knowledge.input,
|
|
2160
|
+
updated_at=int(time.time()),
|
|
2161
|
+
agent_id=cultural_knowledge.agent_id,
|
|
2162
|
+
team_id=cultural_knowledge.team_id,
|
|
2163
|
+
),
|
|
2164
|
+
).returning(table)
|
|
2165
|
+
|
|
2166
|
+
result = sess.execute(stmt)
|
|
2167
|
+
row = result.fetchone()
|
|
2168
|
+
|
|
2169
|
+
if row is None:
|
|
2170
|
+
return None
|
|
2171
|
+
|
|
2172
|
+
db_row = dict(row._mapping)
|
|
2173
|
+
if not db_row or not deserialize:
|
|
2174
|
+
return db_row
|
|
2175
|
+
|
|
2176
|
+
return deserialize_cultural_knowledge_from_db(db_row)
|
|
2177
|
+
|
|
2178
|
+
except Exception as e:
|
|
2179
|
+
log_error(f"Error upserting cultural knowledge: {e}")
|
|
2180
|
+
raise e
|
|
2181
|
+
|
|
1940
2182
|
# -- Migrations --
|
|
1941
2183
|
|
|
1942
2184
|
def migrate_table_from_v1_to_v2(self, v1_db_schema: str, v1_table_name: str, v1_table_type: str):
|