agno 2.1.9__py3-none-any.whl → 2.1.10__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 +646 -133
- 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/message.py +0 -1
- agno/os/app.py +28 -6
- agno/team/team.py +1 -1
- agno/tools/gmail.py +59 -14
- agno/workflow/router.py +1 -1
- {agno-2.1.9.dist-info → agno-2.1.10.dist-info}/METADATA +1 -1
- {agno-2.1.9.dist-info → agno-2.1.10.dist-info}/RECORD +54 -51
- {agno-2.1.9.dist-info → agno-2.1.10.dist-info}/WHEEL +0 -0
- {agno-2.1.9.dist-info → agno-2.1.10.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.9.dist-info → agno-2.1.10.dist-info}/top_level.txt +0 -0
agno/db/dynamo/dynamo.py
CHANGED
|
@@ -13,6 +13,7 @@ from agno.db.dynamo.utils import (
|
|
|
13
13
|
build_topic_filter_expression,
|
|
14
14
|
calculate_date_metrics,
|
|
15
15
|
create_table_if_not_exists,
|
|
16
|
+
deserialize_cultural_knowledge_from_db,
|
|
16
17
|
deserialize_eval_record,
|
|
17
18
|
deserialize_from_dynamodb_item,
|
|
18
19
|
deserialize_knowledge_row,
|
|
@@ -23,10 +24,12 @@ from agno.db.dynamo.utils import (
|
|
|
23
24
|
get_dates_to_calculate_metrics_for,
|
|
24
25
|
merge_with_existing_session,
|
|
25
26
|
prepare_session_data,
|
|
27
|
+
serialize_cultural_knowledge_for_db,
|
|
26
28
|
serialize_eval_record,
|
|
27
29
|
serialize_knowledge_row,
|
|
28
30
|
serialize_to_dynamo_item,
|
|
29
31
|
)
|
|
32
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
30
33
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
31
34
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
32
35
|
from agno.db.schemas.memory import UserMemory
|
|
@@ -52,6 +55,7 @@ class DynamoDb(BaseDb):
|
|
|
52
55
|
aws_access_key_id: Optional[str] = None,
|
|
53
56
|
aws_secret_access_key: Optional[str] = None,
|
|
54
57
|
session_table: Optional[str] = None,
|
|
58
|
+
culture_table: Optional[str] = None,
|
|
55
59
|
memory_table: Optional[str] = None,
|
|
56
60
|
metrics_table: Optional[str] = None,
|
|
57
61
|
eval_table: Optional[str] = None,
|
|
@@ -67,6 +71,7 @@ class DynamoDb(BaseDb):
|
|
|
67
71
|
aws_access_key_id: AWS access key ID.
|
|
68
72
|
aws_secret_access_key: AWS secret access key.
|
|
69
73
|
session_table: The name of the session table.
|
|
74
|
+
culture_table: The name of the culture table.
|
|
70
75
|
memory_table: The name of the memory table.
|
|
71
76
|
metrics_table: The name of the metrics table.
|
|
72
77
|
eval_table: The name of the eval table.
|
|
@@ -80,6 +85,7 @@ class DynamoDb(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,
|
|
@@ -168,6 +174,8 @@ class DynamoDb(BaseDb):
|
|
|
168
174
|
table_name = self.eval_table_name
|
|
169
175
|
elif table_type == "knowledge":
|
|
170
176
|
table_name = self.knowledge_table_name
|
|
177
|
+
elif table_type == "culture":
|
|
178
|
+
table_name = self.culture_table_name
|
|
171
179
|
else:
|
|
172
180
|
raise ValueError(f"Unknown table type: {table_type}")
|
|
173
181
|
|
|
@@ -1885,3 +1893,157 @@ class DynamoDb(BaseDb):
|
|
|
1885
1893
|
except Exception as e:
|
|
1886
1894
|
log_error(f"Failed to rename eval run {eval_run_id}: {e}")
|
|
1887
1895
|
raise e
|
|
1896
|
+
|
|
1897
|
+
# -- Culture methods --
|
|
1898
|
+
|
|
1899
|
+
def clear_cultural_knowledge(self) -> None:
|
|
1900
|
+
"""Delete all cultural knowledge from the database."""
|
|
1901
|
+
try:
|
|
1902
|
+
table_name = self._get_table("culture")
|
|
1903
|
+
response = self.client.scan(TableName=table_name, ProjectionExpression="id")
|
|
1904
|
+
|
|
1905
|
+
with self.client.batch_writer(table_name) as batch:
|
|
1906
|
+
for item in response.get("Items", []):
|
|
1907
|
+
batch.delete_item(Key={"id": item["id"]})
|
|
1908
|
+
except Exception as e:
|
|
1909
|
+
log_error(f"Failed to clear cultural knowledge: {e}")
|
|
1910
|
+
raise e
|
|
1911
|
+
|
|
1912
|
+
def delete_cultural_knowledge(self, id: str) -> None:
|
|
1913
|
+
"""Delete a cultural knowledge entry from the database."""
|
|
1914
|
+
try:
|
|
1915
|
+
table_name = self._get_table("culture")
|
|
1916
|
+
self.client.delete_item(TableName=table_name, Key={"id": {"S": id}})
|
|
1917
|
+
except Exception as e:
|
|
1918
|
+
log_error(f"Failed to delete cultural knowledge {id}: {e}")
|
|
1919
|
+
raise e
|
|
1920
|
+
|
|
1921
|
+
def get_cultural_knowledge(
|
|
1922
|
+
self, id: str, deserialize: Optional[bool] = True
|
|
1923
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
1924
|
+
"""Get a cultural knowledge entry from the database."""
|
|
1925
|
+
try:
|
|
1926
|
+
table_name = self._get_table("culture")
|
|
1927
|
+
response = self.client.get_item(TableName=table_name, Key={"id": {"S": id}})
|
|
1928
|
+
|
|
1929
|
+
item = response.get("Item")
|
|
1930
|
+
if not item:
|
|
1931
|
+
return None
|
|
1932
|
+
|
|
1933
|
+
db_row = deserialize_from_dynamodb_item(item)
|
|
1934
|
+
if not deserialize:
|
|
1935
|
+
return db_row
|
|
1936
|
+
|
|
1937
|
+
return deserialize_cultural_knowledge_from_db(db_row)
|
|
1938
|
+
except Exception as e:
|
|
1939
|
+
log_error(f"Failed to get cultural knowledge {id}: {e}")
|
|
1940
|
+
raise e
|
|
1941
|
+
|
|
1942
|
+
def get_all_cultural_knowledge(
|
|
1943
|
+
self,
|
|
1944
|
+
name: Optional[str] = None,
|
|
1945
|
+
agent_id: Optional[str] = None,
|
|
1946
|
+
team_id: Optional[str] = None,
|
|
1947
|
+
limit: Optional[int] = None,
|
|
1948
|
+
page: Optional[int] = None,
|
|
1949
|
+
sort_by: Optional[str] = None,
|
|
1950
|
+
sort_order: Optional[str] = None,
|
|
1951
|
+
deserialize: Optional[bool] = True,
|
|
1952
|
+
) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
1953
|
+
"""Get all cultural knowledge from the database."""
|
|
1954
|
+
try:
|
|
1955
|
+
table_name = self._get_table("culture")
|
|
1956
|
+
|
|
1957
|
+
# Build filter expression
|
|
1958
|
+
filter_expressions = []
|
|
1959
|
+
expression_values = {}
|
|
1960
|
+
|
|
1961
|
+
if name:
|
|
1962
|
+
filter_expressions.append("#name = :name")
|
|
1963
|
+
expression_values[":name"] = {"S": name}
|
|
1964
|
+
if agent_id:
|
|
1965
|
+
filter_expressions.append("agent_id = :agent_id")
|
|
1966
|
+
expression_values[":agent_id"] = {"S": agent_id}
|
|
1967
|
+
if team_id:
|
|
1968
|
+
filter_expressions.append("team_id = :team_id")
|
|
1969
|
+
expression_values[":team_id"] = {"S": team_id}
|
|
1970
|
+
|
|
1971
|
+
scan_kwargs: Dict[str, Any] = {"TableName": table_name}
|
|
1972
|
+
if filter_expressions:
|
|
1973
|
+
scan_kwargs["FilterExpression"] = " AND ".join(filter_expressions)
|
|
1974
|
+
scan_kwargs["ExpressionAttributeValues"] = expression_values
|
|
1975
|
+
if name:
|
|
1976
|
+
scan_kwargs["ExpressionAttributeNames"] = {"#name": "name"}
|
|
1977
|
+
|
|
1978
|
+
# Execute scan
|
|
1979
|
+
response = self.client.scan(**scan_kwargs)
|
|
1980
|
+
items = response.get("Items", [])
|
|
1981
|
+
|
|
1982
|
+
# Continue scanning if there's more data
|
|
1983
|
+
while "LastEvaluatedKey" in response:
|
|
1984
|
+
scan_kwargs["ExclusiveStartKey"] = response["LastEvaluatedKey"]
|
|
1985
|
+
response = self.client.scan(**scan_kwargs)
|
|
1986
|
+
items.extend(response.get("Items", []))
|
|
1987
|
+
|
|
1988
|
+
# Deserialize items from DynamoDB format
|
|
1989
|
+
db_rows = [deserialize_from_dynamodb_item(item) for item in items]
|
|
1990
|
+
|
|
1991
|
+
# Apply sorting
|
|
1992
|
+
if sort_by:
|
|
1993
|
+
reverse = sort_order == "desc" if sort_order else False
|
|
1994
|
+
db_rows.sort(key=lambda x: x.get(sort_by, ""), reverse=reverse)
|
|
1995
|
+
|
|
1996
|
+
# Apply pagination
|
|
1997
|
+
total_count = len(db_rows)
|
|
1998
|
+
if limit and page:
|
|
1999
|
+
start = (page - 1) * limit
|
|
2000
|
+
db_rows = db_rows[start : start + limit]
|
|
2001
|
+
elif limit:
|
|
2002
|
+
db_rows = db_rows[:limit]
|
|
2003
|
+
|
|
2004
|
+
if not deserialize:
|
|
2005
|
+
return db_rows, total_count
|
|
2006
|
+
|
|
2007
|
+
return [deserialize_cultural_knowledge_from_db(row) for row in db_rows]
|
|
2008
|
+
except Exception as e:
|
|
2009
|
+
log_error(f"Failed to get all cultural knowledge: {e}")
|
|
2010
|
+
raise e
|
|
2011
|
+
|
|
2012
|
+
def upsert_cultural_knowledge(
|
|
2013
|
+
self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
|
|
2014
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
2015
|
+
"""Upsert a cultural knowledge entry into the database."""
|
|
2016
|
+
try:
|
|
2017
|
+
from uuid import uuid4
|
|
2018
|
+
|
|
2019
|
+
table_name = self._get_table("culture", create_table_if_not_found=True)
|
|
2020
|
+
|
|
2021
|
+
if not cultural_knowledge.id:
|
|
2022
|
+
cultural_knowledge.id = str(uuid4())
|
|
2023
|
+
|
|
2024
|
+
# Serialize content, categories, and notes into a dict for DB storage
|
|
2025
|
+
content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
|
|
2026
|
+
|
|
2027
|
+
# Create the item dict with serialized content
|
|
2028
|
+
item_dict = {
|
|
2029
|
+
"id": cultural_knowledge.id,
|
|
2030
|
+
"name": cultural_knowledge.name,
|
|
2031
|
+
"summary": cultural_knowledge.summary,
|
|
2032
|
+
"content": content_dict if content_dict else None,
|
|
2033
|
+
"metadata": cultural_knowledge.metadata,
|
|
2034
|
+
"input": cultural_knowledge.input,
|
|
2035
|
+
"created_at": cultural_knowledge.created_at,
|
|
2036
|
+
"updated_at": int(time.time()),
|
|
2037
|
+
"agent_id": cultural_knowledge.agent_id,
|
|
2038
|
+
"team_id": cultural_knowledge.team_id,
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
# Convert to DynamoDB format
|
|
2042
|
+
item = serialize_to_dynamo_item(item_dict)
|
|
2043
|
+
self.client.put_item(TableName=table_name, Item=item)
|
|
2044
|
+
|
|
2045
|
+
return self.get_cultural_knowledge(cultural_knowledge.id, deserialize=deserialize)
|
|
2046
|
+
|
|
2047
|
+
except Exception as e:
|
|
2048
|
+
log_error(f"Failed to upsert cultural knowledge: {e}")
|
|
2049
|
+
raise e
|
agno/db/dynamo/schemas.py
CHANGED
|
@@ -243,6 +243,49 @@ METRICS_TABLE_SCHEMA = {
|
|
|
243
243
|
"ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
CULTURAL_KNOWLEDGE_TABLE_SCHEMA = {
|
|
247
|
+
"TableName": "agno_cultural_knowledge",
|
|
248
|
+
"KeySchema": [{"AttributeName": "id", "KeyType": "HASH"}],
|
|
249
|
+
"AttributeDefinitions": [
|
|
250
|
+
{"AttributeName": "id", "AttributeType": "S"},
|
|
251
|
+
{"AttributeName": "name", "AttributeType": "S"},
|
|
252
|
+
{"AttributeName": "agent_id", "AttributeType": "S"},
|
|
253
|
+
{"AttributeName": "team_id", "AttributeType": "S"},
|
|
254
|
+
{"AttributeName": "created_at", "AttributeType": "N"},
|
|
255
|
+
],
|
|
256
|
+
"GlobalSecondaryIndexes": [
|
|
257
|
+
{
|
|
258
|
+
"IndexName": "name-created_at-index",
|
|
259
|
+
"KeySchema": [
|
|
260
|
+
{"AttributeName": "name", "KeyType": "HASH"},
|
|
261
|
+
{"AttributeName": "created_at", "KeyType": "RANGE"},
|
|
262
|
+
],
|
|
263
|
+
"Projection": {"ProjectionType": "ALL"},
|
|
264
|
+
"ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"IndexName": "agent_id-created_at-index",
|
|
268
|
+
"KeySchema": [
|
|
269
|
+
{"AttributeName": "agent_id", "KeyType": "HASH"},
|
|
270
|
+
{"AttributeName": "created_at", "KeyType": "RANGE"},
|
|
271
|
+
],
|
|
272
|
+
"Projection": {"ProjectionType": "ALL"},
|
|
273
|
+
"ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"IndexName": "team_id-created_at-index",
|
|
277
|
+
"KeySchema": [
|
|
278
|
+
{"AttributeName": "team_id", "KeyType": "HASH"},
|
|
279
|
+
{"AttributeName": "created_at", "KeyType": "RANGE"},
|
|
280
|
+
],
|
|
281
|
+
"Projection": {"ProjectionType": "ALL"},
|
|
282
|
+
"ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
"BillingMode": "PROVISIONED",
|
|
286
|
+
"ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
|
|
287
|
+
}
|
|
288
|
+
|
|
246
289
|
|
|
247
290
|
def get_table_schema_definition(table_type: str) -> Dict[str, Any]:
|
|
248
291
|
"""
|
|
@@ -260,6 +303,7 @@ def get_table_schema_definition(table_type: str) -> Dict[str, Any]:
|
|
|
260
303
|
"evals": EVAL_TABLE_SCHEMA,
|
|
261
304
|
"knowledge": KNOWLEDGE_TABLE_SCHEMA,
|
|
262
305
|
"metrics": METRICS_TABLE_SCHEMA,
|
|
306
|
+
"culture": CULTURAL_KNOWLEDGE_TABLE_SCHEMA,
|
|
263
307
|
}
|
|
264
308
|
|
|
265
309
|
schema = schemas.get(table_type, {})
|
agno/db/dynamo/utils.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Any, Callable, Dict, List, Optional, Union
|
|
|
5
5
|
from uuid import uuid4
|
|
6
6
|
|
|
7
7
|
from agno.db.base import SessionType
|
|
8
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
8
9
|
from agno.db.schemas.evals import EvalRunRecord
|
|
9
10
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
10
11
|
from agno.session import Session
|
|
@@ -682,3 +683,61 @@ def process_query_results(
|
|
|
682
683
|
log_error(f"Failed to deserialize item: {e}")
|
|
683
684
|
|
|
684
685
|
return deserialized_items
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
# -- Cultural Knowledge util methods --
|
|
689
|
+
def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
|
|
690
|
+
"""Serialize a CulturalKnowledge object for database storage.
|
|
691
|
+
|
|
692
|
+
Converts the model's separate content, categories, and notes fields
|
|
693
|
+
into a single JSON dict for the database content column.
|
|
694
|
+
DynamoDB supports nested maps/dicts natively.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
Dict[str, Any]: A dictionary with the content field as a dict containing content, categories, and notes.
|
|
701
|
+
"""
|
|
702
|
+
content_dict: Dict[str, Any] = {}
|
|
703
|
+
if cultural_knowledge.content is not None:
|
|
704
|
+
content_dict["content"] = cultural_knowledge.content
|
|
705
|
+
if cultural_knowledge.categories is not None:
|
|
706
|
+
content_dict["categories"] = cultural_knowledge.categories
|
|
707
|
+
if cultural_knowledge.notes is not None:
|
|
708
|
+
content_dict["notes"] = cultural_knowledge.notes
|
|
709
|
+
|
|
710
|
+
return content_dict if content_dict else {}
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
|
|
714
|
+
"""Deserialize a database row to a CulturalKnowledge object.
|
|
715
|
+
|
|
716
|
+
The database stores content as a dict containing content, categories, and notes.
|
|
717
|
+
This method extracts those fields and converts them back to the model format.
|
|
718
|
+
|
|
719
|
+
Args:
|
|
720
|
+
db_row (Dict[str, Any]): The database row as a dictionary.
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
CulturalKnowledge: The cultural knowledge object.
|
|
724
|
+
"""
|
|
725
|
+
# Extract content, categories, and notes from the content field
|
|
726
|
+
content_json = db_row.get("content", {}) or {}
|
|
727
|
+
|
|
728
|
+
return CulturalKnowledge.from_dict(
|
|
729
|
+
{
|
|
730
|
+
"id": db_row.get("id"),
|
|
731
|
+
"name": db_row.get("name"),
|
|
732
|
+
"summary": db_row.get("summary"),
|
|
733
|
+
"content": content_json.get("content"),
|
|
734
|
+
"categories": content_json.get("categories"),
|
|
735
|
+
"notes": content_json.get("notes"),
|
|
736
|
+
"metadata": db_row.get("metadata"),
|
|
737
|
+
"input": db_row.get("input"),
|
|
738
|
+
"created_at": db_row.get("created_at"),
|
|
739
|
+
"updated_at": db_row.get("updated_at"),
|
|
740
|
+
"agent_id": db_row.get("agent_id"),
|
|
741
|
+
"team_id": db_row.get("team_id"),
|
|
742
|
+
}
|
|
743
|
+
)
|
agno/db/firestore/firestore.py
CHANGED
|
@@ -6,13 +6,18 @@ from uuid import uuid4
|
|
|
6
6
|
from agno.db.base import BaseDb, SessionType
|
|
7
7
|
from agno.db.firestore.utils import (
|
|
8
8
|
apply_pagination,
|
|
9
|
+
apply_pagination_to_records,
|
|
9
10
|
apply_sorting,
|
|
11
|
+
apply_sorting_to_records,
|
|
10
12
|
bulk_upsert_metrics,
|
|
11
13
|
calculate_date_metrics,
|
|
12
14
|
create_collection_indexes,
|
|
15
|
+
deserialize_cultural_knowledge_from_db,
|
|
13
16
|
fetch_all_sessions_data,
|
|
14
17
|
get_dates_to_calculate_metrics_for,
|
|
18
|
+
serialize_cultural_knowledge_for_db,
|
|
15
19
|
)
|
|
20
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
16
21
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
17
22
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
18
23
|
from agno.db.schemas.memory import UserMemory
|
|
@@ -39,6 +44,7 @@ class FirestoreDb(BaseDb):
|
|
|
39
44
|
metrics_collection: Optional[str] = None,
|
|
40
45
|
eval_collection: Optional[str] = None,
|
|
41
46
|
knowledge_collection: Optional[str] = None,
|
|
47
|
+
culture_collection: Optional[str] = None,
|
|
42
48
|
id: Optional[str] = None,
|
|
43
49
|
):
|
|
44
50
|
"""
|
|
@@ -52,6 +58,7 @@ class FirestoreDb(BaseDb):
|
|
|
52
58
|
metrics_collection (Optional[str]): Name of the collection to store metrics.
|
|
53
59
|
eval_collection (Optional[str]): Name of the collection to store evaluation runs.
|
|
54
60
|
knowledge_collection (Optional[str]): Name of the collection to store knowledge documents.
|
|
61
|
+
culture_collection (Optional[str]): Name of the collection to store cultural knowledge.
|
|
55
62
|
id (Optional[str]): ID of the database.
|
|
56
63
|
|
|
57
64
|
Raises:
|
|
@@ -68,6 +75,7 @@ class FirestoreDb(BaseDb):
|
|
|
68
75
|
metrics_table=metrics_collection,
|
|
69
76
|
eval_table=eval_collection,
|
|
70
77
|
knowledge_table=knowledge_collection,
|
|
78
|
+
culture_table=culture_collection,
|
|
71
79
|
)
|
|
72
80
|
|
|
73
81
|
_client: Optional[Client] = db_client
|
|
@@ -146,6 +154,17 @@ class FirestoreDb(BaseDb):
|
|
|
146
154
|
)
|
|
147
155
|
return self.knowledge_collection
|
|
148
156
|
|
|
157
|
+
if table_type == "culture":
|
|
158
|
+
if not hasattr(self, "culture_collection"):
|
|
159
|
+
if self.culture_table_name is None:
|
|
160
|
+
raise ValueError("Culture collection was not provided on initialization")
|
|
161
|
+
self.culture_collection = self._get_or_create_collection(
|
|
162
|
+
collection_name=self.culture_table_name,
|
|
163
|
+
collection_type="culture",
|
|
164
|
+
create_collection_if_not_found=create_collection_if_not_found,
|
|
165
|
+
)
|
|
166
|
+
return self.culture_collection
|
|
167
|
+
|
|
149
168
|
raise ValueError(f"Unknown table type: {table_type}")
|
|
150
169
|
|
|
151
170
|
def _get_or_create_collection(
|
|
@@ -1008,6 +1027,213 @@ class FirestoreDb(BaseDb):
|
|
|
1008
1027
|
log_error(f"Exception deleting all memories: {e}")
|
|
1009
1028
|
raise e
|
|
1010
1029
|
|
|
1030
|
+
# -- Cultural Knowledge methods --
|
|
1031
|
+
def clear_cultural_knowledge(self) -> None:
|
|
1032
|
+
"""Delete all cultural knowledge from the database.
|
|
1033
|
+
|
|
1034
|
+
Raises:
|
|
1035
|
+
Exception: If an error occurs during deletion.
|
|
1036
|
+
"""
|
|
1037
|
+
try:
|
|
1038
|
+
collection_ref = self._get_collection(table_type="culture")
|
|
1039
|
+
|
|
1040
|
+
# Get all documents in the collection
|
|
1041
|
+
docs = collection_ref.stream()
|
|
1042
|
+
|
|
1043
|
+
# Delete all documents in batches
|
|
1044
|
+
batch = self.db_client.batch()
|
|
1045
|
+
batch_count = 0
|
|
1046
|
+
|
|
1047
|
+
for doc in docs:
|
|
1048
|
+
batch.delete(doc.reference)
|
|
1049
|
+
batch_count += 1
|
|
1050
|
+
|
|
1051
|
+
# Firestore batch has a limit of 500 operations
|
|
1052
|
+
if batch_count >= 500:
|
|
1053
|
+
batch.commit()
|
|
1054
|
+
batch = self.db_client.batch()
|
|
1055
|
+
batch_count = 0
|
|
1056
|
+
|
|
1057
|
+
# Commit remaining operations
|
|
1058
|
+
if batch_count > 0:
|
|
1059
|
+
batch.commit()
|
|
1060
|
+
|
|
1061
|
+
except Exception as e:
|
|
1062
|
+
log_error(f"Exception deleting all cultural knowledge: {e}")
|
|
1063
|
+
raise e
|
|
1064
|
+
|
|
1065
|
+
def delete_cultural_knowledge(self, id: str) -> None:
|
|
1066
|
+
"""Delete cultural knowledge by ID.
|
|
1067
|
+
|
|
1068
|
+
Args:
|
|
1069
|
+
id (str): The ID of the cultural knowledge to delete.
|
|
1070
|
+
|
|
1071
|
+
Raises:
|
|
1072
|
+
Exception: If an error occurs during deletion.
|
|
1073
|
+
"""
|
|
1074
|
+
try:
|
|
1075
|
+
collection_ref = self._get_collection(table_type="culture")
|
|
1076
|
+
docs = collection_ref.where(filter=FieldFilter("id", "==", id)).stream()
|
|
1077
|
+
|
|
1078
|
+
for doc in docs:
|
|
1079
|
+
doc.reference.delete()
|
|
1080
|
+
log_debug(f"Deleted cultural knowledge with ID: {id}")
|
|
1081
|
+
|
|
1082
|
+
except Exception as e:
|
|
1083
|
+
log_error(f"Error deleting cultural knowledge: {e}")
|
|
1084
|
+
raise e
|
|
1085
|
+
|
|
1086
|
+
def get_cultural_knowledge(
|
|
1087
|
+
self, id: str, deserialize: Optional[bool] = True
|
|
1088
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
1089
|
+
"""Get cultural knowledge by ID.
|
|
1090
|
+
|
|
1091
|
+
Args:
|
|
1092
|
+
id (str): The ID of the cultural knowledge to retrieve.
|
|
1093
|
+
deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge object. Defaults to True.
|
|
1094
|
+
|
|
1095
|
+
Returns:
|
|
1096
|
+
Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The cultural knowledge if found, None otherwise.
|
|
1097
|
+
|
|
1098
|
+
Raises:
|
|
1099
|
+
Exception: If an error occurs during retrieval.
|
|
1100
|
+
"""
|
|
1101
|
+
try:
|
|
1102
|
+
collection_ref = self._get_collection(table_type="culture")
|
|
1103
|
+
docs = collection_ref.where(filter=FieldFilter("id", "==", id)).limit(1).stream()
|
|
1104
|
+
|
|
1105
|
+
for doc in docs:
|
|
1106
|
+
result = doc.to_dict()
|
|
1107
|
+
if not deserialize:
|
|
1108
|
+
return result
|
|
1109
|
+
return deserialize_cultural_knowledge_from_db(result)
|
|
1110
|
+
|
|
1111
|
+
return None
|
|
1112
|
+
|
|
1113
|
+
except Exception as e:
|
|
1114
|
+
log_error(f"Error getting cultural knowledge: {e}")
|
|
1115
|
+
raise e
|
|
1116
|
+
|
|
1117
|
+
def get_all_cultural_knowledge(
|
|
1118
|
+
self,
|
|
1119
|
+
agent_id: Optional[str] = None,
|
|
1120
|
+
team_id: Optional[str] = None,
|
|
1121
|
+
name: Optional[str] = None,
|
|
1122
|
+
limit: Optional[int] = None,
|
|
1123
|
+
page: Optional[int] = None,
|
|
1124
|
+
sort_by: Optional[str] = None,
|
|
1125
|
+
sort_order: Optional[str] = None,
|
|
1126
|
+
deserialize: Optional[bool] = True,
|
|
1127
|
+
) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
1128
|
+
"""Get all cultural knowledge with filtering and pagination.
|
|
1129
|
+
|
|
1130
|
+
Args:
|
|
1131
|
+
agent_id (Optional[str]): Filter by agent ID.
|
|
1132
|
+
team_id (Optional[str]): Filter by team ID.
|
|
1133
|
+
name (Optional[str]): Filter by name (case-insensitive partial match).
|
|
1134
|
+
limit (Optional[int]): Maximum number of results to return.
|
|
1135
|
+
page (Optional[int]): Page number for pagination.
|
|
1136
|
+
sort_by (Optional[str]): Field to sort by.
|
|
1137
|
+
sort_order (Optional[str]): Sort order ('asc' or 'desc').
|
|
1138
|
+
deserialize (Optional[bool]): Whether to deserialize to CulturalKnowledge objects. Defaults to True.
|
|
1139
|
+
|
|
1140
|
+
Returns:
|
|
1141
|
+
Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
1142
|
+
- When deserialize=True: List of CulturalKnowledge objects
|
|
1143
|
+
- When deserialize=False: Tuple with list of dictionaries and total count
|
|
1144
|
+
|
|
1145
|
+
Raises:
|
|
1146
|
+
Exception: If an error occurs during retrieval.
|
|
1147
|
+
"""
|
|
1148
|
+
try:
|
|
1149
|
+
collection_ref = self._get_collection(table_type="culture")
|
|
1150
|
+
|
|
1151
|
+
# Build query with filters
|
|
1152
|
+
query = collection_ref
|
|
1153
|
+
if agent_id is not None:
|
|
1154
|
+
query = query.where(filter=FieldFilter("agent_id", "==", agent_id))
|
|
1155
|
+
if team_id is not None:
|
|
1156
|
+
query = query.where(filter=FieldFilter("team_id", "==", team_id))
|
|
1157
|
+
|
|
1158
|
+
# Get all matching documents
|
|
1159
|
+
docs = query.stream()
|
|
1160
|
+
results = [doc.to_dict() for doc in docs]
|
|
1161
|
+
|
|
1162
|
+
# Apply name filter (Firestore doesn't support regex in queries)
|
|
1163
|
+
if name is not None:
|
|
1164
|
+
results = [r for r in results if name.lower() in r.get("name", "").lower()]
|
|
1165
|
+
|
|
1166
|
+
total_count = len(results)
|
|
1167
|
+
|
|
1168
|
+
# Apply sorting and pagination to in-memory results
|
|
1169
|
+
sorted_results = apply_sorting_to_records(records=results, sort_by=sort_by, sort_order=sort_order)
|
|
1170
|
+
paginated_results = apply_pagination_to_records(records=sorted_results, limit=limit, page=page)
|
|
1171
|
+
|
|
1172
|
+
if not deserialize:
|
|
1173
|
+
return paginated_results, total_count
|
|
1174
|
+
|
|
1175
|
+
return [deserialize_cultural_knowledge_from_db(item) for item in paginated_results]
|
|
1176
|
+
|
|
1177
|
+
except Exception as e:
|
|
1178
|
+
log_error(f"Error getting all cultural knowledge: {e}")
|
|
1179
|
+
raise e
|
|
1180
|
+
|
|
1181
|
+
def upsert_cultural_knowledge(
|
|
1182
|
+
self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
|
|
1183
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
1184
|
+
"""Upsert cultural knowledge in Firestore.
|
|
1185
|
+
|
|
1186
|
+
Args:
|
|
1187
|
+
cultural_knowledge (CulturalKnowledge): The cultural knowledge to upsert.
|
|
1188
|
+
deserialize (Optional[bool]): Whether to deserialize the result. Defaults to True.
|
|
1189
|
+
|
|
1190
|
+
Returns:
|
|
1191
|
+
Optional[Union[CulturalKnowledge, Dict[str, Any]]]: The upserted cultural knowledge.
|
|
1192
|
+
|
|
1193
|
+
Raises:
|
|
1194
|
+
Exception: If an error occurs during upsert.
|
|
1195
|
+
"""
|
|
1196
|
+
try:
|
|
1197
|
+
collection_ref = self._get_collection(table_type="culture", create_collection_if_not_found=True)
|
|
1198
|
+
|
|
1199
|
+
# Serialize content, categories, and notes into a dict for DB storage
|
|
1200
|
+
content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
|
|
1201
|
+
|
|
1202
|
+
# Create the update document with serialized content
|
|
1203
|
+
update_doc = {
|
|
1204
|
+
"id": cultural_knowledge.id,
|
|
1205
|
+
"name": cultural_knowledge.name,
|
|
1206
|
+
"summary": cultural_knowledge.summary,
|
|
1207
|
+
"content": content_dict if content_dict else None,
|
|
1208
|
+
"metadata": cultural_knowledge.metadata,
|
|
1209
|
+
"input": cultural_knowledge.input,
|
|
1210
|
+
"created_at": cultural_knowledge.created_at,
|
|
1211
|
+
"updated_at": int(time.time()),
|
|
1212
|
+
"agent_id": cultural_knowledge.agent_id,
|
|
1213
|
+
"team_id": cultural_knowledge.team_id,
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
# Find and update or create new document
|
|
1217
|
+
docs = collection_ref.where(filter=FieldFilter("id", "==", cultural_knowledge.id)).limit(1).stream()
|
|
1218
|
+
|
|
1219
|
+
doc_found = False
|
|
1220
|
+
for doc in docs:
|
|
1221
|
+
doc.reference.set(update_doc)
|
|
1222
|
+
doc_found = True
|
|
1223
|
+
break
|
|
1224
|
+
|
|
1225
|
+
if not doc_found:
|
|
1226
|
+
collection_ref.add(update_doc)
|
|
1227
|
+
|
|
1228
|
+
if not deserialize:
|
|
1229
|
+
return update_doc
|
|
1230
|
+
|
|
1231
|
+
return deserialize_cultural_knowledge_from_db(update_doc)
|
|
1232
|
+
|
|
1233
|
+
except Exception as e:
|
|
1234
|
+
log_error(f"Error upserting cultural knowledge: {e}")
|
|
1235
|
+
raise e
|
|
1236
|
+
|
|
1011
1237
|
# -- Metrics methods --
|
|
1012
1238
|
|
|
1013
1239
|
def _get_all_sessions_for_metrics_calculation(
|
|
@@ -1388,6 +1614,9 @@ class FirestoreDb(BaseDb):
|
|
|
1388
1614
|
"""
|
|
1389
1615
|
try:
|
|
1390
1616
|
collection_ref = self._get_collection(table_type="evals")
|
|
1617
|
+
if not collection_ref:
|
|
1618
|
+
return None
|
|
1619
|
+
|
|
1391
1620
|
docs = collection_ref.where(filter=FieldFilter("run_id", "==", eval_run_id)).stream()
|
|
1392
1621
|
|
|
1393
1622
|
eval_run_raw = None
|
|
@@ -1528,6 +1757,8 @@ class FirestoreDb(BaseDb):
|
|
|
1528
1757
|
"""
|
|
1529
1758
|
try:
|
|
1530
1759
|
collection_ref = self._get_collection(table_type="evals")
|
|
1760
|
+
if not collection_ref:
|
|
1761
|
+
return None
|
|
1531
1762
|
|
|
1532
1763
|
docs = collection_ref.where(filter=FieldFilter("run_id", "==", eval_run_id)).stream()
|
|
1533
1764
|
doc_ref = next((doc.reference for doc in docs), None)
|
agno/db/firestore/schemas.py
CHANGED
|
@@ -112,6 +112,15 @@ METRICS_COLLECTION_SCHEMA = [
|
|
|
112
112
|
{"key": [("date", "ASCENDING"), ("aggregation_period", "ASCENDING")], "collection_group": False, "unique": True},
|
|
113
113
|
]
|
|
114
114
|
|
|
115
|
+
CULTURAL_KNOWLEDGE_COLLECTION_SCHEMA = [
|
|
116
|
+
{"key": "id", "unique": True},
|
|
117
|
+
{"key": "name"},
|
|
118
|
+
{"key": "agent_id"},
|
|
119
|
+
{"key": "team_id"},
|
|
120
|
+
{"key": "created_at"},
|
|
121
|
+
{"key": "updated_at"},
|
|
122
|
+
]
|
|
123
|
+
|
|
115
124
|
|
|
116
125
|
def get_collection_indexes(collection_type: str) -> List[Dict[str, Any]]:
|
|
117
126
|
"""Get the index definitions for a specific collection type."""
|
|
@@ -121,6 +130,7 @@ def get_collection_indexes(collection_type: str) -> List[Dict[str, Any]]:
|
|
|
121
130
|
"metrics": METRICS_COLLECTION_SCHEMA,
|
|
122
131
|
"evals": EVAL_COLLECTION_SCHEMA,
|
|
123
132
|
"knowledge": KNOWLEDGE_COLLECTION_SCHEMA,
|
|
133
|
+
"culture": CULTURAL_KNOWLEDGE_COLLECTION_SCHEMA,
|
|
124
134
|
}
|
|
125
135
|
|
|
126
136
|
indexes = index_definitions.get(collection_type)
|