agno 2.1.2__py3-none-any.whl → 2.3.13__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 +5540 -2273
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +689 -6
- agno/db/dynamo/dynamo.py +933 -37
- agno/db/dynamo/schemas.py +174 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +831 -9
- agno/db/firestore/schemas.py +51 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +660 -12
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +287 -14
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +590 -14
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +43 -13
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2760 -0
- agno/db/mongo/mongo.py +879 -11
- agno/db/mongo/schemas.py +42 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +946 -68
- agno/db/mysql/schemas.py +72 -10
- agno/db/mysql/utils.py +198 -7
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +942 -57
- agno/db/postgres/schemas.py +81 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +671 -7
- agno/db/redis/schemas.py +50 -0
- agno/db/redis/utils.py +65 -7
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +17 -2
- agno/db/singlestore/schemas.py +63 -0
- agno/db/singlestore/singlestore.py +949 -83
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +62 -0
- agno/db/sqlite/sqlite.py +965 -46
- agno/db/sqlite/utils.py +169 -8
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +2 -0
- agno/eval/__init__.py +10 -0
- agno/eval/accuracy.py +75 -55
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +16 -7
- agno/eval/reliability.py +28 -16
- agno/eval/utils.py +35 -17
- agno/exceptions.py +27 -2
- agno/filters.py +354 -0
- agno/guardrails/prompt_injection.py +1 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +1 -1
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/semantic.py +9 -4
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +8 -0
- agno/knowledge/embedder/openai.py +8 -8
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/knowledge.py +1618 -318
- agno/knowledge/reader/base.py +6 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +17 -19
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +32 -3
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +22 -10
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/knowledge/reader/wikipedia_reader.py +33 -1
- agno/knowledge/types.py +1 -0
- agno/knowledge/utils.py +72 -7
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +544 -83
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +515 -40
- agno/models/aws/bedrock.py +102 -21
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +41 -19
- agno/models/azure/openai_chat.py +39 -8
- agno/models/base.py +1249 -525
- agno/models/cerebras/cerebras.py +91 -21
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +40 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +877 -80
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +51 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +44 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +28 -5
- agno/models/meta/llama.py +47 -14
- agno/models/meta/llama_openai.py +22 -17
- agno/models/mistral/mistral.py +8 -4
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/chat.py +24 -8
- agno/models/openai/chat.py +104 -29
- agno/models/openai/responses.py +101 -81
- agno/models/openrouter/openrouter.py +60 -3
- agno/models/perplexity/perplexity.py +17 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +24 -4
- agno/models/response.py +73 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +549 -152
- agno/os/auth.py +190 -3
- agno/os/config.py +23 -0
- agno/os/interfaces/a2a/router.py +8 -11
- agno/os/interfaces/a2a/utils.py +1 -1
- agno/os/interfaces/agui/router.py +18 -3
- agno/os/interfaces/agui/utils.py +152 -39
- agno/os/interfaces/slack/router.py +55 -37
- agno/os/interfaces/slack/slack.py +9 -1
- agno/os/interfaces/whatsapp/router.py +0 -1
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/mcp.py +110 -52
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/jwt.py +676 -112
- agno/os/router.py +40 -1478
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/evals.py +96 -39
- agno/os/routers/evals/schemas.py +65 -33
- agno/os/routers/evals/utils.py +80 -10
- agno/os/routers/health.py +10 -4
- agno/os/routers/knowledge/knowledge.py +196 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +279 -52
- agno/os/routers/memory/schemas.py +46 -17
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +462 -34
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +256 -693
- agno/os/scopes.py +469 -0
- agno/os/utils.py +514 -36
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +155 -32
- agno/run/base.py +55 -3
- agno/run/requirement.py +181 -0
- agno/run/team.py +125 -38
- agno/run/workflow.py +72 -18
- agno/session/agent.py +102 -89
- agno/session/summary.py +56 -15
- agno/session/team.py +164 -90
- agno/session/workflow.py +405 -40
- agno/table.py +10 -0
- agno/team/team.py +3974 -1903
- agno/tools/dalle.py +2 -4
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +16 -10
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +193 -38
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +271 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +3 -3
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +76 -36
- agno/tools/redshift.py +406 -0
- agno/tools/scrapegraph.py +1 -1
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +18 -3
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +146 -0
- agno/tools/toolkit.py +25 -0
- agno/tools/workflow.py +8 -1
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +151 -3
- agno/utils/gemini.py +15 -5
- agno/utils/hooks.py +118 -4
- agno/utils/http.py +113 -2
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +187 -1
- agno/utils/merge_dict.py +3 -3
- agno/utils/message.py +60 -0
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +49 -14
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/print_response/agent.py +109 -16
- agno/utils/print_response/team.py +223 -30
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/streamlit.py +1 -1
- agno/utils/team.py +98 -9
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +39 -7
- agno/vectordb/cassandra/cassandra.py +21 -5
- agno/vectordb/chroma/chromadb.py +43 -12
- agno/vectordb/clickhouse/clickhousedb.py +21 -5
- agno/vectordb/couchbase/couchbase.py +29 -5
- agno/vectordb/lancedb/lance_db.py +92 -181
- agno/vectordb/langchaindb/langchaindb.py +24 -4
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/llamaindexdb.py +25 -5
- agno/vectordb/milvus/milvus.py +50 -37
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +36 -30
- agno/vectordb/pgvector/pgvector.py +201 -77
- agno/vectordb/pineconedb/pineconedb.py +41 -23
- agno/vectordb/qdrant/qdrant.py +67 -54
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/singlestore.py +50 -29
- agno/vectordb/surrealdb/surrealdb.py +31 -41
- agno/vectordb/upstashdb/upstashdb.py +34 -6
- agno/vectordb/weaviate/weaviate.py +53 -14
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +120 -18
- agno/workflow/loop.py +77 -10
- agno/workflow/parallel.py +231 -143
- agno/workflow/router.py +118 -17
- agno/workflow/step.py +609 -170
- agno/workflow/steps.py +73 -6
- agno/workflow/types.py +96 -21
- agno/workflow/workflow.py +2039 -262
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
- agno-2.3.13.dist-info/RECORD +613 -0
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -679
- agno/tools/memori.py +0 -339
- agno-2.1.2.dist-info/RECORD +0 -543
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/db/gcs_json/utils.py
CHANGED
|
@@ -5,33 +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.
|
|
9
|
-
from agno.run.agent import RunOutput
|
|
10
|
-
from agno.run.team import TeamRunOutput
|
|
11
|
-
from agno.session.summary import SessionSummary
|
|
8
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
12
9
|
from agno.utils.log import log_debug
|
|
13
10
|
|
|
14
11
|
|
|
15
|
-
def hydrate_session(session: dict) -> dict:
|
|
16
|
-
"""Convert nested dictionaries to their corresponding object types.
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
session (dict): The session dictionary to hydrate.
|
|
20
|
-
|
|
21
|
-
Returns:
|
|
22
|
-
dict: The hydrated session dictionary.
|
|
23
|
-
"""
|
|
24
|
-
if session.get("summary") is not None:
|
|
25
|
-
session["summary"] = SessionSummary.from_dict(session["summary"])
|
|
26
|
-
if session.get("runs") is not None:
|
|
27
|
-
if session["session_type"] == SessionType.AGENT:
|
|
28
|
-
session["runs"] = [RunOutput.from_dict(run) for run in session["runs"]]
|
|
29
|
-
elif session["session_type"] == SessionType.TEAM:
|
|
30
|
-
session["runs"] = [TeamRunOutput.from_dict(run) for run in session["runs"]]
|
|
31
|
-
|
|
32
|
-
return session
|
|
33
|
-
|
|
34
|
-
|
|
35
12
|
def apply_sorting(
|
|
36
13
|
data: List[Dict[str, Any]], sort_by: Optional[str] = None, sort_order: Optional[str] = None
|
|
37
14
|
) -> List[Dict[str, Any]]:
|
|
@@ -101,7 +78,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
101
78
|
all_user_ids = set()
|
|
102
79
|
|
|
103
80
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
104
|
-
sessions = sessions_data.get(session_type, [])
|
|
81
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
105
82
|
metrics[sessions_count_key] = len(sessions)
|
|
106
83
|
|
|
107
84
|
for session in sessions:
|
|
@@ -122,7 +99,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
122
99
|
|
|
123
100
|
model_metrics = []
|
|
124
101
|
for model, count in model_counts.items():
|
|
125
|
-
model_id, model_provider = model.
|
|
102
|
+
model_id, model_provider = model.rsplit(":", 1)
|
|
126
103
|
model_metrics.append({"model_id": model_id, "model_provider": model_provider, "count": count})
|
|
127
104
|
|
|
128
105
|
metrics["users_count"] = len(all_user_ids)
|
|
@@ -192,3 +169,60 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
|
|
|
192
169
|
if days_diff <= 0:
|
|
193
170
|
return []
|
|
194
171
|
return [starting_date + timedelta(days=x) for x in range(days_diff)]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# -- Cultural Knowledge util methods --
|
|
175
|
+
def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
|
|
176
|
+
"""Serialize a CulturalKnowledge object for database storage.
|
|
177
|
+
|
|
178
|
+
Converts the model's separate content, categories, and notes fields
|
|
179
|
+
into a single dict for the database content column.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Dict[str, Any]: A dictionary with the content field as a dict containing content, categories, and notes.
|
|
186
|
+
"""
|
|
187
|
+
content_dict: Dict[str, Any] = {}
|
|
188
|
+
if cultural_knowledge.content is not None:
|
|
189
|
+
content_dict["content"] = cultural_knowledge.content
|
|
190
|
+
if cultural_knowledge.categories is not None:
|
|
191
|
+
content_dict["categories"] = cultural_knowledge.categories
|
|
192
|
+
if cultural_knowledge.notes is not None:
|
|
193
|
+
content_dict["notes"] = cultural_knowledge.notes
|
|
194
|
+
|
|
195
|
+
return content_dict if content_dict else {}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
|
|
199
|
+
"""Deserialize a database row to a CulturalKnowledge object.
|
|
200
|
+
|
|
201
|
+
The database stores content as a dict containing content, categories, and notes.
|
|
202
|
+
This method extracts those fields and converts them back to the model format.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
db_row (Dict[str, Any]): The database row as a dictionary.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
CulturalKnowledge: The cultural knowledge object.
|
|
209
|
+
"""
|
|
210
|
+
# Extract content, categories, and notes from the content field
|
|
211
|
+
content_json = db_row.get("content", {}) or {}
|
|
212
|
+
|
|
213
|
+
return CulturalKnowledge.from_dict(
|
|
214
|
+
{
|
|
215
|
+
"id": db_row.get("id"),
|
|
216
|
+
"name": db_row.get("name"),
|
|
217
|
+
"summary": db_row.get("summary"),
|
|
218
|
+
"content": content_json.get("content"),
|
|
219
|
+
"categories": content_json.get("categories"),
|
|
220
|
+
"notes": content_json.get("notes"),
|
|
221
|
+
"metadata": db_row.get("metadata"),
|
|
222
|
+
"input": db_row.get("input"),
|
|
223
|
+
"created_at": db_row.get("created_at"),
|
|
224
|
+
"updated_at": db_row.get("updated_at"),
|
|
225
|
+
"agent_id": db_row.get("agent_id"),
|
|
226
|
+
"team_id": db_row.get("team_id"),
|
|
227
|
+
}
|
|
228
|
+
)
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from copy import deepcopy
|
|
3
3
|
from datetime import date, datetime, timedelta, timezone
|
|
4
|
-
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
|
5
5
|
from uuid import uuid4
|
|
6
6
|
|
|
7
7
|
from agno.db.base import BaseDb, SessionType
|
|
8
8
|
from agno.db.in_memory.utils import (
|
|
9
9
|
apply_sorting,
|
|
10
10
|
calculate_date_metrics,
|
|
11
|
+
deserialize_cultural_knowledge_from_db,
|
|
11
12
|
fetch_all_sessions_data,
|
|
12
13
|
get_dates_to_calculate_metrics_for,
|
|
14
|
+
serialize_cultural_knowledge_for_db,
|
|
13
15
|
)
|
|
16
|
+
from agno.db.schemas.culture import CulturalKnowledge
|
|
14
17
|
from agno.db.schemas.evals import EvalFilterType, EvalRunRecord, EvalType
|
|
15
18
|
from agno.db.schemas.knowledge import KnowledgeRow
|
|
16
19
|
from agno.db.schemas.memory import UserMemory
|
|
17
20
|
from agno.session import AgentSession, Session, TeamSession, WorkflowSession
|
|
18
21
|
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
19
22
|
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from agno.tracing.schemas import Span, Trace
|
|
25
|
+
|
|
20
26
|
|
|
21
27
|
class InMemoryDb(BaseDb):
|
|
22
28
|
def __init__(self):
|
|
@@ -29,9 +35,21 @@ class InMemoryDb(BaseDb):
|
|
|
29
35
|
self._metrics: List[Dict[str, Any]] = []
|
|
30
36
|
self._eval_runs: List[Dict[str, Any]] = []
|
|
31
37
|
self._knowledge: List[Dict[str, Any]] = []
|
|
38
|
+
self._cultural_knowledge: List[Dict[str, Any]] = []
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
def table_exists(self, table_name: str) -> bool:
|
|
41
|
+
"""In-memory implementation, always returns True."""
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
def get_latest_schema_version(self):
|
|
45
|
+
"""Get the latest version of the database schema."""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def upsert_schema_version(self, version: str) -> None:
|
|
49
|
+
"""Upsert the schema version into the database."""
|
|
50
|
+
pass
|
|
34
51
|
|
|
52
|
+
# -- Session methods --
|
|
35
53
|
def delete_session(self, session_id: str) -> bool:
|
|
36
54
|
"""Delete a session from in-memory storage.
|
|
37
55
|
|
|
@@ -104,9 +122,6 @@ class InMemoryDb(BaseDb):
|
|
|
104
122
|
if session_data.get("session_id") == session_id:
|
|
105
123
|
if user_id is not None and session_data.get("user_id") != user_id:
|
|
106
124
|
continue
|
|
107
|
-
session_type_value = session_type.value if isinstance(session_type, SessionType) else session_type
|
|
108
|
-
if session_data.get("session_type") != session_type_value:
|
|
109
|
-
continue
|
|
110
125
|
|
|
111
126
|
session_data_copy = deepcopy(session_data)
|
|
112
127
|
|
|
@@ -309,7 +324,7 @@ class InMemoryDb(BaseDb):
|
|
|
309
324
|
return False
|
|
310
325
|
|
|
311
326
|
def upsert_sessions(
|
|
312
|
-
self, sessions: List[Session], deserialize: Optional[bool] = True
|
|
327
|
+
self, sessions: List[Session], deserialize: Optional[bool] = True, preserve_updated_at: bool = False
|
|
313
328
|
) -> List[Union[Session, Dict[str, Any]]]:
|
|
314
329
|
"""
|
|
315
330
|
Bulk upsert multiple sessions for improved performance on large datasets.
|
|
@@ -359,8 +374,7 @@ class InMemoryDb(BaseDb):
|
|
|
359
374
|
# If user_id is provided, verify ownership before deleting
|
|
360
375
|
if user_id is not None:
|
|
361
376
|
self._memories = [
|
|
362
|
-
m for m in self._memories
|
|
363
|
-
if not (m.get("memory_id") == memory_id and m.get("user_id") == user_id)
|
|
377
|
+
m for m in self._memories if not (m.get("memory_id") == memory_id and m.get("user_id") == user_id)
|
|
364
378
|
]
|
|
365
379
|
else:
|
|
366
380
|
self._memories = [m for m in self._memories if m.get("memory_id") != memory_id]
|
|
@@ -388,8 +402,7 @@ class InMemoryDb(BaseDb):
|
|
|
388
402
|
# If user_id is provided, verify ownership before deleting
|
|
389
403
|
if user_id is not None:
|
|
390
404
|
self._memories = [
|
|
391
|
-
m for m in self._memories
|
|
392
|
-
if not (m.get("memory_id") in memory_ids and m.get("user_id") == user_id)
|
|
405
|
+
m for m in self._memories if not (m.get("memory_id") in memory_ids and m.get("user_id") == user_id)
|
|
393
406
|
]
|
|
394
407
|
else:
|
|
395
408
|
self._memories = [m for m in self._memories if m.get("memory_id") not in memory_ids]
|
|
@@ -510,13 +523,14 @@ class InMemoryDb(BaseDb):
|
|
|
510
523
|
raise e
|
|
511
524
|
|
|
512
525
|
def get_user_memory_stats(
|
|
513
|
-
self, limit: Optional[int] = None, page: Optional[int] = None
|
|
526
|
+
self, limit: Optional[int] = None, page: Optional[int] = None, user_id: Optional[str] = None
|
|
514
527
|
) -> Tuple[List[Dict[str, Any]], int]:
|
|
515
528
|
"""Get user memory statistics.
|
|
516
529
|
|
|
517
530
|
Args:
|
|
518
531
|
limit (Optional[int]): Maximum number of stats to return.
|
|
519
532
|
page (Optional[int]): Page number for pagination.
|
|
533
|
+
user_id (Optional[str]): User ID for filtering.
|
|
520
534
|
|
|
521
535
|
Returns:
|
|
522
536
|
Tuple[List[Dict[str, Any]], int]: List of user memory statistics and total count.
|
|
@@ -529,10 +543,16 @@ class InMemoryDb(BaseDb):
|
|
|
529
543
|
|
|
530
544
|
for memory in self._memories:
|
|
531
545
|
memory_user_id = memory.get("user_id")
|
|
532
|
-
|
|
546
|
+
# filter by user_id if provided
|
|
547
|
+
if user_id is not None and memory_user_id != user_id:
|
|
548
|
+
continue
|
|
533
549
|
if memory_user_id:
|
|
534
550
|
if memory_user_id not in user_stats:
|
|
535
|
-
user_stats[memory_user_id] = {
|
|
551
|
+
user_stats[memory_user_id] = {
|
|
552
|
+
"user_id": memory_user_id,
|
|
553
|
+
"total_memories": 0,
|
|
554
|
+
"last_memory_updated_at": 0,
|
|
555
|
+
}
|
|
536
556
|
user_stats[memory_user_id]["total_memories"] += 1
|
|
537
557
|
updated_at = memory.get("updated_at", 0)
|
|
538
558
|
if updated_at > user_stats[memory_user_id]["last_memory_updated_at"]:
|
|
@@ -588,7 +608,7 @@ class InMemoryDb(BaseDb):
|
|
|
588
608
|
raise e
|
|
589
609
|
|
|
590
610
|
def upsert_memories(
|
|
591
|
-
self, memories: List[UserMemory], deserialize: Optional[bool] = True
|
|
611
|
+
self, memories: List[UserMemory], deserialize: Optional[bool] = True, preserve_updated_at: bool = False
|
|
592
612
|
) -> List[Union[UserMemory, Dict[str, Any]]]:
|
|
593
613
|
"""
|
|
594
614
|
Bulk upsert multiple user memories for improved performance on large datasets.
|
|
@@ -1037,3 +1057,256 @@ class InMemoryDb(BaseDb):
|
|
|
1037
1057
|
except Exception as e:
|
|
1038
1058
|
log_error(f"Error renaming eval run {eval_run_id}: {e}")
|
|
1039
1059
|
raise e
|
|
1060
|
+
|
|
1061
|
+
# -- Culture methods --
|
|
1062
|
+
|
|
1063
|
+
def clear_cultural_knowledge(self) -> None:
|
|
1064
|
+
"""Delete all cultural knowledge from in-memory storage."""
|
|
1065
|
+
try:
|
|
1066
|
+
self._cultural_knowledge = []
|
|
1067
|
+
except Exception as e:
|
|
1068
|
+
log_error(f"Error clearing cultural knowledge: {e}")
|
|
1069
|
+
raise e
|
|
1070
|
+
|
|
1071
|
+
def delete_cultural_knowledge(self, id: str) -> None:
|
|
1072
|
+
"""Delete a cultural knowledge entry from in-memory storage."""
|
|
1073
|
+
try:
|
|
1074
|
+
self._cultural_knowledge = [ck for ck in self._cultural_knowledge if ck.get("id") != id]
|
|
1075
|
+
except Exception as e:
|
|
1076
|
+
log_error(f"Error deleting cultural knowledge: {e}")
|
|
1077
|
+
raise e
|
|
1078
|
+
|
|
1079
|
+
def get_cultural_knowledge(
|
|
1080
|
+
self, id: str, deserialize: Optional[bool] = True
|
|
1081
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
1082
|
+
"""Get a cultural knowledge entry from in-memory storage."""
|
|
1083
|
+
try:
|
|
1084
|
+
for ck_data in self._cultural_knowledge:
|
|
1085
|
+
if ck_data.get("id") == id:
|
|
1086
|
+
ck_data_copy = deepcopy(ck_data)
|
|
1087
|
+
if not deserialize:
|
|
1088
|
+
return ck_data_copy
|
|
1089
|
+
return deserialize_cultural_knowledge_from_db(ck_data_copy)
|
|
1090
|
+
return None
|
|
1091
|
+
except Exception as e:
|
|
1092
|
+
log_error(f"Error getting cultural knowledge: {e}")
|
|
1093
|
+
raise e
|
|
1094
|
+
|
|
1095
|
+
def get_all_cultural_knowledge(
|
|
1096
|
+
self,
|
|
1097
|
+
name: Optional[str] = None,
|
|
1098
|
+
agent_id: Optional[str] = None,
|
|
1099
|
+
team_id: Optional[str] = None,
|
|
1100
|
+
limit: Optional[int] = None,
|
|
1101
|
+
page: Optional[int] = None,
|
|
1102
|
+
sort_by: Optional[str] = None,
|
|
1103
|
+
sort_order: Optional[str] = None,
|
|
1104
|
+
deserialize: Optional[bool] = True,
|
|
1105
|
+
) -> Union[List[CulturalKnowledge], Tuple[List[Dict[str, Any]], int]]:
|
|
1106
|
+
"""Get all cultural knowledge from in-memory storage."""
|
|
1107
|
+
try:
|
|
1108
|
+
filtered_ck = []
|
|
1109
|
+
for ck_data in self._cultural_knowledge:
|
|
1110
|
+
if name and ck_data.get("name") != name:
|
|
1111
|
+
continue
|
|
1112
|
+
if agent_id and ck_data.get("agent_id") != agent_id:
|
|
1113
|
+
continue
|
|
1114
|
+
if team_id and ck_data.get("team_id") != team_id:
|
|
1115
|
+
continue
|
|
1116
|
+
filtered_ck.append(ck_data)
|
|
1117
|
+
|
|
1118
|
+
# Apply sorting
|
|
1119
|
+
if sort_by:
|
|
1120
|
+
filtered_ck = apply_sorting(filtered_ck, sort_by, sort_order)
|
|
1121
|
+
|
|
1122
|
+
total_count = len(filtered_ck)
|
|
1123
|
+
|
|
1124
|
+
# Apply pagination
|
|
1125
|
+
if limit and page:
|
|
1126
|
+
start = (page - 1) * limit
|
|
1127
|
+
filtered_ck = filtered_ck[start : start + limit]
|
|
1128
|
+
elif limit:
|
|
1129
|
+
filtered_ck = filtered_ck[:limit]
|
|
1130
|
+
|
|
1131
|
+
if not deserialize:
|
|
1132
|
+
return [deepcopy(ck) for ck in filtered_ck], total_count
|
|
1133
|
+
|
|
1134
|
+
return [deserialize_cultural_knowledge_from_db(deepcopy(ck)) for ck in filtered_ck]
|
|
1135
|
+
except Exception as e:
|
|
1136
|
+
log_error(f"Error getting all cultural knowledge: {e}")
|
|
1137
|
+
raise e
|
|
1138
|
+
|
|
1139
|
+
def upsert_cultural_knowledge(
|
|
1140
|
+
self, cultural_knowledge: CulturalKnowledge, deserialize: Optional[bool] = True
|
|
1141
|
+
) -> Optional[Union[CulturalKnowledge, Dict[str, Any]]]:
|
|
1142
|
+
"""Upsert a cultural knowledge entry into in-memory storage."""
|
|
1143
|
+
try:
|
|
1144
|
+
if not cultural_knowledge.id:
|
|
1145
|
+
cultural_knowledge.id = str(uuid4())
|
|
1146
|
+
|
|
1147
|
+
# Serialize content, categories, and notes into a dict for DB storage
|
|
1148
|
+
content_dict = serialize_cultural_knowledge_for_db(cultural_knowledge)
|
|
1149
|
+
|
|
1150
|
+
# Create the item dict with serialized content
|
|
1151
|
+
ck_dict = {
|
|
1152
|
+
"id": cultural_knowledge.id,
|
|
1153
|
+
"name": cultural_knowledge.name,
|
|
1154
|
+
"summary": cultural_knowledge.summary,
|
|
1155
|
+
"content": content_dict if content_dict else None,
|
|
1156
|
+
"metadata": cultural_knowledge.metadata,
|
|
1157
|
+
"input": cultural_knowledge.input,
|
|
1158
|
+
"created_at": cultural_knowledge.created_at,
|
|
1159
|
+
"updated_at": int(time.time()),
|
|
1160
|
+
"agent_id": cultural_knowledge.agent_id,
|
|
1161
|
+
"team_id": cultural_knowledge.team_id,
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
# Remove existing entry with same id
|
|
1165
|
+
self._cultural_knowledge = [ck for ck in self._cultural_knowledge if ck.get("id") != cultural_knowledge.id]
|
|
1166
|
+
|
|
1167
|
+
# Add new entry
|
|
1168
|
+
self._cultural_knowledge.append(ck_dict)
|
|
1169
|
+
|
|
1170
|
+
return self.get_cultural_knowledge(cultural_knowledge.id, deserialize=deserialize)
|
|
1171
|
+
except Exception as e:
|
|
1172
|
+
log_error(f"Error upserting cultural knowledge: {e}")
|
|
1173
|
+
raise e
|
|
1174
|
+
|
|
1175
|
+
# --- Traces ---
|
|
1176
|
+
def upsert_trace(self, trace: "Trace") -> None:
|
|
1177
|
+
"""Create or update a single trace record in the database.
|
|
1178
|
+
|
|
1179
|
+
Args:
|
|
1180
|
+
trace: The Trace object to store (one per trace_id).
|
|
1181
|
+
"""
|
|
1182
|
+
raise NotImplementedError
|
|
1183
|
+
|
|
1184
|
+
def get_trace(
|
|
1185
|
+
self,
|
|
1186
|
+
trace_id: Optional[str] = None,
|
|
1187
|
+
run_id: Optional[str] = None,
|
|
1188
|
+
):
|
|
1189
|
+
"""Get a single trace by trace_id or other filters.
|
|
1190
|
+
|
|
1191
|
+
Args:
|
|
1192
|
+
trace_id: The unique trace identifier.
|
|
1193
|
+
run_id: Filter by run ID (returns first match).
|
|
1194
|
+
|
|
1195
|
+
Returns:
|
|
1196
|
+
Optional[Trace]: The trace if found, None otherwise.
|
|
1197
|
+
|
|
1198
|
+
Note:
|
|
1199
|
+
If multiple filters are provided, trace_id takes precedence.
|
|
1200
|
+
For other filters, the most recent trace is returned.
|
|
1201
|
+
"""
|
|
1202
|
+
raise NotImplementedError
|
|
1203
|
+
|
|
1204
|
+
def get_traces(
|
|
1205
|
+
self,
|
|
1206
|
+
run_id: Optional[str] = None,
|
|
1207
|
+
session_id: Optional[str] = None,
|
|
1208
|
+
user_id: Optional[str] = None,
|
|
1209
|
+
agent_id: Optional[str] = None,
|
|
1210
|
+
team_id: Optional[str] = None,
|
|
1211
|
+
workflow_id: Optional[str] = None,
|
|
1212
|
+
status: Optional[str] = None,
|
|
1213
|
+
start_time: Optional[datetime] = None,
|
|
1214
|
+
end_time: Optional[datetime] = None,
|
|
1215
|
+
limit: Optional[int] = 20,
|
|
1216
|
+
page: Optional[int] = 1,
|
|
1217
|
+
) -> tuple[List, int]:
|
|
1218
|
+
"""Get traces matching the provided filters.
|
|
1219
|
+
|
|
1220
|
+
Args:
|
|
1221
|
+
run_id: Filter by run ID.
|
|
1222
|
+
session_id: Filter by session ID.
|
|
1223
|
+
user_id: Filter by user ID.
|
|
1224
|
+
agent_id: Filter by agent ID.
|
|
1225
|
+
team_id: Filter by team ID.
|
|
1226
|
+
workflow_id: Filter by workflow ID.
|
|
1227
|
+
status: Filter by status (OK, ERROR, UNSET).
|
|
1228
|
+
start_time: Filter traces starting after this datetime.
|
|
1229
|
+
end_time: Filter traces ending before this datetime.
|
|
1230
|
+
limit: Maximum number of traces to return per page.
|
|
1231
|
+
page: Page number (1-indexed).
|
|
1232
|
+
|
|
1233
|
+
Returns:
|
|
1234
|
+
tuple[List[Trace], int]: Tuple of (list of matching traces, total count).
|
|
1235
|
+
"""
|
|
1236
|
+
raise NotImplementedError
|
|
1237
|
+
|
|
1238
|
+
def get_trace_stats(
|
|
1239
|
+
self,
|
|
1240
|
+
user_id: Optional[str] = None,
|
|
1241
|
+
agent_id: Optional[str] = None,
|
|
1242
|
+
team_id: Optional[str] = None,
|
|
1243
|
+
workflow_id: Optional[str] = None,
|
|
1244
|
+
start_time: Optional[datetime] = None,
|
|
1245
|
+
end_time: Optional[datetime] = None,
|
|
1246
|
+
limit: Optional[int] = 20,
|
|
1247
|
+
page: Optional[int] = 1,
|
|
1248
|
+
) -> tuple[List[Dict[str, Any]], int]:
|
|
1249
|
+
"""Get trace statistics grouped by session.
|
|
1250
|
+
|
|
1251
|
+
Args:
|
|
1252
|
+
user_id: Filter by user ID.
|
|
1253
|
+
agent_id: Filter by agent ID.
|
|
1254
|
+
team_id: Filter by team ID.
|
|
1255
|
+
workflow_id: Filter by workflow ID.
|
|
1256
|
+
start_time: Filter sessions with traces created after this datetime.
|
|
1257
|
+
end_time: Filter sessions with traces created before this datetime.
|
|
1258
|
+
limit: Maximum number of sessions to return per page.
|
|
1259
|
+
page: Page number (1-indexed).
|
|
1260
|
+
|
|
1261
|
+
Returns:
|
|
1262
|
+
tuple[List[Dict], int]: Tuple of (list of session stats dicts, total count).
|
|
1263
|
+
Each dict contains: session_id, user_id, agent_id, team_id, workflow_id, total_traces,
|
|
1264
|
+
first_trace_at, last_trace_at.
|
|
1265
|
+
"""
|
|
1266
|
+
raise NotImplementedError
|
|
1267
|
+
|
|
1268
|
+
# --- Spans ---
|
|
1269
|
+
def create_span(self, span: "Span") -> None:
|
|
1270
|
+
"""Create a single span in the database.
|
|
1271
|
+
|
|
1272
|
+
Args:
|
|
1273
|
+
span: The Span object to store.
|
|
1274
|
+
"""
|
|
1275
|
+
raise NotImplementedError
|
|
1276
|
+
|
|
1277
|
+
def create_spans(self, spans: List) -> None:
|
|
1278
|
+
"""Create multiple spans in the database as a batch.
|
|
1279
|
+
|
|
1280
|
+
Args:
|
|
1281
|
+
spans: List of Span objects to store.
|
|
1282
|
+
"""
|
|
1283
|
+
raise NotImplementedError
|
|
1284
|
+
|
|
1285
|
+
def get_span(self, span_id: str):
|
|
1286
|
+
"""Get a single span by its span_id.
|
|
1287
|
+
|
|
1288
|
+
Args:
|
|
1289
|
+
span_id: The unique span identifier.
|
|
1290
|
+
|
|
1291
|
+
Returns:
|
|
1292
|
+
Optional[Span]: The span if found, None otherwise.
|
|
1293
|
+
"""
|
|
1294
|
+
raise NotImplementedError
|
|
1295
|
+
|
|
1296
|
+
def get_spans(
|
|
1297
|
+
self,
|
|
1298
|
+
trace_id: Optional[str] = None,
|
|
1299
|
+
parent_span_id: Optional[str] = None,
|
|
1300
|
+
limit: Optional[int] = 1000,
|
|
1301
|
+
) -> List:
|
|
1302
|
+
"""Get spans matching the provided filters.
|
|
1303
|
+
|
|
1304
|
+
Args:
|
|
1305
|
+
trace_id: Filter by trace ID.
|
|
1306
|
+
parent_span_id: Filter by parent span ID.
|
|
1307
|
+
limit: Maximum number of spans to return.
|
|
1308
|
+
|
|
1309
|
+
Returns:
|
|
1310
|
+
List[Span]: List of matching spans.
|
|
1311
|
+
"""
|
|
1312
|
+
raise NotImplementedError
|
agno/db/in_memory/utils.py
CHANGED
|
@@ -5,6 +5,7 @@ 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.schemas.culture import CulturalKnowledge
|
|
8
9
|
from agno.utils.log import log_debug
|
|
9
10
|
|
|
10
11
|
|
|
@@ -77,7 +78,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
77
78
|
all_user_ids = set()
|
|
78
79
|
|
|
79
80
|
for session_type, sessions_count_key, runs_count_key in session_types:
|
|
80
|
-
sessions = sessions_data.get(session_type, [])
|
|
81
|
+
sessions = sessions_data.get(session_type, []) or []
|
|
81
82
|
metrics[sessions_count_key] = len(sessions)
|
|
82
83
|
|
|
83
84
|
for session in sessions:
|
|
@@ -98,7 +99,7 @@ def calculate_date_metrics(date_to_process: date, sessions_data: dict) -> dict:
|
|
|
98
99
|
|
|
99
100
|
model_metrics = []
|
|
100
101
|
for model, count in model_counts.items():
|
|
101
|
-
model_id, model_provider = model.
|
|
102
|
+
model_id, model_provider = model.rsplit(":", 1)
|
|
102
103
|
model_metrics.append({"model_id": model_id, "model_provider": model_provider, "count": count})
|
|
103
104
|
|
|
104
105
|
metrics["users_count"] = len(all_user_ids)
|
|
@@ -170,3 +171,60 @@ def get_dates_to_calculate_metrics_for(starting_date: date) -> list[date]:
|
|
|
170
171
|
if days_diff <= 0:
|
|
171
172
|
return []
|
|
172
173
|
return [starting_date + timedelta(days=x) for x in range(days_diff)]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# -- Cultural Knowledge util methods --
|
|
177
|
+
def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
|
|
178
|
+
"""Serialize a CulturalKnowledge object for database storage.
|
|
179
|
+
|
|
180
|
+
Converts the model's separate content, categories, and notes fields
|
|
181
|
+
into a single dict for the database content column.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Dict[str, Any]: A dictionary with the content field as a dict containing content, categories, and notes.
|
|
188
|
+
"""
|
|
189
|
+
content_dict: Dict[str, Any] = {}
|
|
190
|
+
if cultural_knowledge.content is not None:
|
|
191
|
+
content_dict["content"] = cultural_knowledge.content
|
|
192
|
+
if cultural_knowledge.categories is not None:
|
|
193
|
+
content_dict["categories"] = cultural_knowledge.categories
|
|
194
|
+
if cultural_knowledge.notes is not None:
|
|
195
|
+
content_dict["notes"] = cultural_knowledge.notes
|
|
196
|
+
|
|
197
|
+
return content_dict if content_dict else {}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
|
|
201
|
+
"""Deserialize a database row to a CulturalKnowledge object.
|
|
202
|
+
|
|
203
|
+
The database stores content as a dict containing content, categories, and notes.
|
|
204
|
+
This method extracts those fields and converts them back to the model format.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
db_row (Dict[str, Any]): The database row as a dictionary.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
CulturalKnowledge: The cultural knowledge object.
|
|
211
|
+
"""
|
|
212
|
+
# Extract content, categories, and notes from the content field
|
|
213
|
+
content_json = db_row.get("content", {}) or {}
|
|
214
|
+
|
|
215
|
+
return CulturalKnowledge.from_dict(
|
|
216
|
+
{
|
|
217
|
+
"id": db_row.get("id"),
|
|
218
|
+
"name": db_row.get("name"),
|
|
219
|
+
"summary": db_row.get("summary"),
|
|
220
|
+
"content": content_json.get("content"),
|
|
221
|
+
"categories": content_json.get("categories"),
|
|
222
|
+
"notes": content_json.get("notes"),
|
|
223
|
+
"metadata": db_row.get("metadata"),
|
|
224
|
+
"input": db_row.get("input"),
|
|
225
|
+
"created_at": db_row.get("created_at"),
|
|
226
|
+
"updated_at": db_row.get("updated_at"),
|
|
227
|
+
"agent_id": db_row.get("agent_id"),
|
|
228
|
+
"team_id": db_row.get("team_id"),
|
|
229
|
+
}
|
|
230
|
+
)
|