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.
Files changed (314) hide show
  1. agno/agent/agent.py +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,334 @@
1
+ from dataclasses import asdict
2
+ from datetime import date, datetime, timezone
3
+ from textwrap import dedent
4
+ from typing import Any, Dict, List, Literal, Optional, Sequence
5
+
6
+ from surrealdb import RecordID
7
+
8
+ from agno.db.base import SessionType
9
+ from agno.db.schemas.culture import CulturalKnowledge
10
+ from agno.db.schemas.evals import EvalRunRecord
11
+ from agno.db.schemas.knowledge import KnowledgeRow
12
+ from agno.db.schemas.memory import UserMemory
13
+ from agno.session import Session
14
+ from agno.session.agent import AgentSession
15
+ from agno.session.team import TeamSession
16
+ from agno.session.workflow import WorkflowSession
17
+
18
+ TableType = Literal[
19
+ "agents",
20
+ "culture",
21
+ "evals",
22
+ "knowledge",
23
+ "memories",
24
+ "metrics",
25
+ "sessions",
26
+ "spans",
27
+ "teams",
28
+ "traces",
29
+ "users",
30
+ "workflows",
31
+ ]
32
+
33
+
34
+ def deserialize_record_id(record: dict, agno_field: str, surreal_field: Optional[str] = None) -> dict:
35
+ if surreal_field is None:
36
+ surreal_field = agno_field
37
+ x = record.get(surreal_field)
38
+ if isinstance(x, RecordID):
39
+ record[agno_field] = x.id
40
+ if agno_field != surreal_field:
41
+ del record[surreal_field]
42
+ return record
43
+
44
+
45
+ def surrealize_dates(record: dict) -> dict:
46
+ copy = record.copy()
47
+ for key, value in copy.items():
48
+ if isinstance(value, date):
49
+ copy[key] = datetime.combine(value, datetime.min.time()).replace(tzinfo=timezone.utc)
50
+ elif key in ["created_at", "updated_at"] and isinstance(value, (int, float)):
51
+ copy[key] = datetime.fromtimestamp(value).replace(tzinfo=timezone.utc)
52
+ elif key in ["created_at", "updated_at"] and isinstance(value, str):
53
+ # Handle ISO string format - convert back to datetime object for SurrealDB
54
+ try:
55
+ dt = datetime.fromisoformat(value)
56
+ if dt.tzinfo is None:
57
+ dt = dt.replace(tzinfo=timezone.utc)
58
+ copy[key] = dt
59
+ except ValueError:
60
+ # If it's not a valid ISO format, leave it as is
61
+ pass
62
+ elif key in ["created_at", "updated_at"] and value is None:
63
+ # Set current time for None datetime fields
64
+ copy[key] = datetime.now(timezone.utc)
65
+ elif isinstance(value, datetime):
66
+ copy[key] = value.replace(tzinfo=timezone.utc)
67
+ return copy
68
+
69
+
70
+ def desurrealize_dates(record: dict) -> dict:
71
+ copy = record.copy()
72
+ for key, value in copy.items():
73
+ if isinstance(value, datetime):
74
+ copy[key] = int(value.timestamp())
75
+ return copy
76
+
77
+
78
+ def serialize_session(session: Session, table_names: dict[TableType, str]) -> dict:
79
+ _dict = session.to_dict()
80
+
81
+ if session.session_id is not None:
82
+ _dict["id"] = RecordID(table_names["sessions"], session.session_id)
83
+ del _dict["session_id"]
84
+
85
+ if isinstance(session, AgentSession):
86
+ _dict["agent"] = RecordID(table_names["agents"], session.agent_id)
87
+ del _dict["agent_id"]
88
+ elif isinstance(session, TeamSession):
89
+ _dict["team"] = RecordID(table_names["teams"], session.team_id)
90
+ del _dict["team_id"]
91
+ elif isinstance(session, WorkflowSession):
92
+ _dict["workflow"] = RecordID(table_names["workflows"], session.workflow_id)
93
+ del _dict["workflow_id"]
94
+
95
+ # surrealize dates
96
+ _dict = surrealize_dates(_dict)
97
+
98
+ return _dict
99
+
100
+
101
+ def desurrealize_session(session_raw: dict, session_type: Optional[SessionType] = None) -> dict:
102
+ session_raw = deserialize_record_id(session_raw, "session_id", "id")
103
+ if session_type == SessionType.AGENT:
104
+ session_raw = deserialize_record_id(session_raw, "agent_id", "agent")
105
+ elif session_type == SessionType.TEAM:
106
+ session_raw = deserialize_record_id(session_raw, "team_id", "team")
107
+ elif session_type == SessionType.WORKFLOW:
108
+ session_raw = deserialize_record_id(session_raw, "workflow_id", "workflow")
109
+
110
+ session_raw = desurrealize_dates(session_raw)
111
+
112
+ if session_raw.get("agent_id"):
113
+ session_raw["session_type"] = SessionType.AGENT
114
+ elif session_raw.get("team_id"):
115
+ session_raw["session_type"] = SessionType.TEAM
116
+ elif session_raw.get("workflow_id"):
117
+ session_raw["session_type"] = SessionType.WORKFLOW
118
+
119
+ return session_raw
120
+
121
+
122
+ def deserialize_session(session_type: SessionType, session_raw: dict) -> Optional[Session]:
123
+ session_raw = desurrealize_session(session_raw, session_type)
124
+
125
+ if session_type == SessionType.AGENT:
126
+ return AgentSession.from_dict(session_raw)
127
+ elif session_type == SessionType.TEAM:
128
+ return TeamSession.from_dict(session_raw)
129
+ elif session_type == SessionType.WORKFLOW:
130
+ return WorkflowSession.from_dict(session_raw)
131
+ else:
132
+ raise ValueError(f"Invalid session type: {session_type}")
133
+
134
+
135
+ def deserialize_sessions(session_type: SessionType, sessions_raw: List[dict]) -> List[Session]:
136
+ return [x for x in [deserialize_session(session_type, x) for x in sessions_raw] if x is not None]
137
+
138
+
139
+ def get_session_type(session: Session) -> SessionType:
140
+ if isinstance(session, AgentSession):
141
+ return SessionType.AGENT
142
+ elif isinstance(session, TeamSession):
143
+ return SessionType.TEAM
144
+ elif isinstance(session, WorkflowSession):
145
+ return SessionType.WORKFLOW
146
+ else:
147
+ raise ValueError(f"Invalid session instance: {type(session)}")
148
+
149
+
150
+ def desurrealize_user_memory(memory_raw: dict) -> dict:
151
+ copy = memory_raw.copy()
152
+
153
+ copy = deserialize_record_id(copy, "memory_id", "id")
154
+ copy = deserialize_record_id(copy, "user_id", "user")
155
+ copy = deserialize_record_id(copy, "agent_id", "agent")
156
+ copy = deserialize_record_id(copy, "team_id", "team")
157
+ copy = deserialize_record_id(copy, "workflow_id", "workflow")
158
+
159
+ # TODO: is this ok? or should we cast datetimes to int? Like in desurrealize_session
160
+ # copy = desurrealize_dates(copy)
161
+ updated_at = copy.get("updated_at")
162
+ if not isinstance(updated_at, str):
163
+ copy["updated_at"] = str(updated_at)
164
+
165
+ return copy
166
+
167
+
168
+ def deserialize_user_memory(memory_raw: dict) -> UserMemory:
169
+ return UserMemory.from_dict(desurrealize_user_memory(memory_raw))
170
+
171
+
172
+ def deserialize_user_memories(memories_raw: Sequence[dict]) -> List[UserMemory]:
173
+ return [deserialize_user_memory(x) for x in memories_raw]
174
+
175
+
176
+ def serialize_user_memory(memory: UserMemory, memory_table_name: str, user_table_name: str) -> dict:
177
+ dict_ = asdict(memory)
178
+ if memory.memory_id is not None:
179
+ dict_["id"] = RecordID(memory_table_name, memory.memory_id)
180
+ del dict_["memory_id"]
181
+ if memory.user_id is not None:
182
+ dict_["user"] = RecordID(user_table_name, memory.user_id)
183
+ del dict_["user_id"]
184
+
185
+ # surrealize dates
186
+ dict_ = surrealize_dates(dict_)
187
+
188
+ return dict_
189
+
190
+
191
+ def deserialize_knowledge_row(knowledge_row_raw: dict) -> KnowledgeRow:
192
+ copy = knowledge_row_raw.copy()
193
+
194
+ copy = deserialize_record_id(copy, "id")
195
+ copy = desurrealize_dates(copy)
196
+
197
+ return KnowledgeRow.model_validate(copy)
198
+
199
+
200
+ def serialize_knowledge_row(knowledge_row: KnowledgeRow, knowledge_table_name: str) -> dict:
201
+ dict_ = knowledge_row.model_dump()
202
+ if knowledge_row.id is not None:
203
+ dict_["id"] = RecordID(knowledge_table_name, knowledge_row.id)
204
+
205
+ # surrealize dates
206
+ dict_ = surrealize_dates(dict_)
207
+
208
+ return dict_
209
+
210
+
211
+ def deserialize_cultural_knowledge(cultural_knowledge_raw: dict) -> CulturalKnowledge:
212
+ copy = cultural_knowledge_raw.copy()
213
+
214
+ copy = deserialize_record_id(copy, "id")
215
+ copy = desurrealize_dates(copy)
216
+
217
+ # Extract content, categories, and notes from the content field
218
+ content_json = copy.get("content", {}) or {}
219
+ if isinstance(content_json, dict):
220
+ copy["content"] = content_json.get("content")
221
+ copy["categories"] = content_json.get("categories")
222
+ copy["notes"] = content_json.get("notes")
223
+
224
+ return CulturalKnowledge.from_dict(copy)
225
+
226
+
227
+ def serialize_cultural_knowledge(cultural_knowledge: CulturalKnowledge, culture_table_name: str) -> dict:
228
+ dict_ = asdict(cultural_knowledge)
229
+ if cultural_knowledge.id is not None:
230
+ dict_["id"] = RecordID(culture_table_name, cultural_knowledge.id)
231
+
232
+ # Serialize content, categories, and notes into a single content dict for DB storage
233
+ content_dict: Dict[str, Any] = {}
234
+ if cultural_knowledge.content is not None:
235
+ content_dict["content"] = cultural_knowledge.content
236
+ if cultural_knowledge.categories is not None:
237
+ content_dict["categories"] = cultural_knowledge.categories
238
+ if cultural_knowledge.notes is not None:
239
+ content_dict["notes"] = cultural_knowledge.notes
240
+
241
+ # Replace the separate fields with the combined content field
242
+ dict_["content"] = content_dict if content_dict else None
243
+ # Remove the now-redundant fields since they're in content
244
+ dict_.pop("categories", None)
245
+ dict_.pop("notes", None)
246
+
247
+ # surrealize dates
248
+ dict_ = surrealize_dates(dict_)
249
+
250
+ return dict_
251
+
252
+
253
+ def desurrealize_eval_run_record(eval_run_record_raw: dict) -> dict:
254
+ copy = eval_run_record_raw.copy()
255
+
256
+ copy = deserialize_record_id(copy, "run_id", "id")
257
+ copy = deserialize_record_id(copy, "agent_id", "agent")
258
+ copy = deserialize_record_id(copy, "team_id", "team")
259
+ copy = deserialize_record_id(copy, "workflow_id", "workflow")
260
+
261
+ return copy
262
+
263
+
264
+ def deserialize_eval_run_record(eval_run_record_raw: dict) -> EvalRunRecord:
265
+ return EvalRunRecord.model_validate(desurrealize_eval_run_record(eval_run_record_raw))
266
+
267
+
268
+ def serialize_eval_run_record(eval_run_record: EvalRunRecord, table_names: dict[TableType, str]) -> dict:
269
+ dict_ = eval_run_record.model_dump()
270
+ if eval_run_record.run_id is not None:
271
+ dict_["id"] = RecordID(table_names["evals"], eval_run_record.run_id)
272
+ del dict_["run_id"]
273
+ if eval_run_record.agent_id is not None:
274
+ dict_["agent"] = RecordID(table_names["agents"], eval_run_record.agent_id)
275
+ del dict_["agent_id"]
276
+ if eval_run_record.team_id is not None:
277
+ dict_["team"] = RecordID(table_names["teams"], eval_run_record.team_id)
278
+ del dict_["team_id"]
279
+ if eval_run_record.workflow_id is not None:
280
+ dict_["workflow"] = RecordID(table_names["workflows"], eval_run_record.workflow_id)
281
+ del dict_["workflow_id"]
282
+ return dict_
283
+
284
+
285
+ def get_schema(table_type: TableType, table_name: str) -> str:
286
+ define_table = f"DEFINE TABLE {table_name} SCHEMALESS;"
287
+ if table_type == "memories":
288
+ return dedent(f"""
289
+ {define_table}
290
+ DEFINE FIELD OVERWRITE updated_at ON {table_name} TYPE datetime VALUE time::now();
291
+ """)
292
+ elif table_type == "knowledge":
293
+ return dedent(f"""
294
+ {define_table}
295
+ DEFINE FIELD OVERWRITE created_at ON {table_name} TYPE datetime VALUE time::now();
296
+ DEFINE FIELD OVERWRITE updated_at ON {table_name} TYPE datetime VALUE time::now();
297
+ """)
298
+ elif table_type == "culture":
299
+ return dedent(f"""
300
+ {define_table}
301
+ DEFINE FIELD OVERWRITE created_at ON {table_name} TYPE datetime VALUE time::now();
302
+ DEFINE FIELD OVERWRITE updated_at ON {table_name} TYPE datetime VALUE time::now();
303
+ """)
304
+ elif table_type == "sessions":
305
+ return dedent(f"""
306
+ {define_table}
307
+ DEFINE FIELD OVERWRITE created_at ON {table_name} TYPE datetime VALUE time::now();
308
+ DEFINE FIELD OVERWRITE updated_at ON {table_name} TYPE datetime VALUE time::now();
309
+ """)
310
+ elif table_type == "traces":
311
+ return dedent(f"""
312
+ {define_table}
313
+ DEFINE FIELD OVERWRITE created_at ON {table_name} TYPE datetime VALUE time::now();
314
+ DEFINE INDEX idx_trace_id ON {table_name} FIELDS trace_id UNIQUE;
315
+ DEFINE INDEX idx_run_id ON {table_name} FIELDS run_id;
316
+ DEFINE INDEX idx_session_id ON {table_name} FIELDS session_id;
317
+ DEFINE INDEX idx_user_id ON {table_name} FIELDS user_id;
318
+ DEFINE INDEX idx_agent_id ON {table_name} FIELDS agent_id;
319
+ DEFINE INDEX idx_team_id ON {table_name} FIELDS team_id;
320
+ DEFINE INDEX idx_workflow_id ON {table_name} FIELDS workflow_id;
321
+ DEFINE INDEX idx_status ON {table_name} FIELDS status;
322
+ DEFINE INDEX idx_start_time ON {table_name} FIELDS start_time;
323
+ """)
324
+ elif table_type == "spans":
325
+ return dedent(f"""
326
+ {define_table}
327
+ DEFINE FIELD OVERWRITE created_at ON {table_name} TYPE datetime VALUE time::now();
328
+ DEFINE INDEX idx_span_id ON {table_name} FIELDS span_id UNIQUE;
329
+ DEFINE INDEX idx_trace_id ON {table_name} FIELDS trace_id;
330
+ DEFINE INDEX idx_parent_span_id ON {table_name} FIELDS parent_span_id;
331
+ DEFINE INDEX idx_start_time ON {table_name} FIELDS start_time;
332
+ """)
333
+ else:
334
+ return define_table
@@ -0,0 +1,71 @@
1
+ from textwrap import dedent
2
+ from typing import Any, Final, Literal, Optional
3
+
4
+ OPERATOR = Literal["=", "!=", "<=", ">=", "~", "IN", "CONTAINSANY"]
5
+
6
+ COUNT_QUERY: Final[str] = dedent("""
7
+ RETURN (
8
+ SELECT count(id) AS count
9
+ {group_fields}
10
+ FROM {table}
11
+ {where_clause}
12
+ {group_clause}
13
+ )[0] OR {{count: 0}}
14
+ """)
15
+
16
+
17
+ class WhereClause:
18
+ def __init__(self):
19
+ self._conditions = []
20
+ self._params = {}
21
+ self._param_count = 0
22
+
23
+ def _add_filter(self, field: str, operator: str, value: Any):
24
+ param_name = f"p{self._param_count}"
25
+ self._params[param_name] = value
26
+ self._param_count += 1
27
+
28
+ condition = f"{field} {operator} ${param_name}"
29
+ if not self._conditions:
30
+ self._conditions.append(condition)
31
+ else:
32
+ self._conditions.append("AND")
33
+ self._conditions.append(condition)
34
+ return self
35
+
36
+ def and_(self, field: str, value: Any, operator: OPERATOR = "="):
37
+ return self._add_filter(field, operator, value)
38
+
39
+ def build(self) -> tuple[str, dict[str, Any]]:
40
+ if not self._conditions:
41
+ return "", {}
42
+ return "WHERE " + " ".join(self._conditions), self._params
43
+
44
+
45
+ def order_limit_start(
46
+ sort_by: Optional[str] = None,
47
+ sort_order: Optional[str] = None,
48
+ limit: Optional[int] = None,
49
+ page: Optional[int] = None,
50
+ ) -> str:
51
+ if sort_order is not None:
52
+ if "desc" in sort_order.lower():
53
+ sort_order = "DESC"
54
+ else:
55
+ sort_order = "ASC"
56
+
57
+ order_clause = f"ORDER BY {sort_by} {sort_order or ''}" if sort_by is not None else ""
58
+
59
+ if limit is not None:
60
+ limit_clause = f"LIMIT {limit}"
61
+ if page is not None:
62
+ offset = (page - 1) * limit
63
+ start_clause = f"START {offset}"
64
+ else:
65
+ start_clause = ""
66
+ else:
67
+ limit_clause = ""
68
+ start_clause = ""
69
+
70
+ clauses = [order_clause, limit_clause, start_clause]
71
+ return " ".join(clause for clause in clauses if clause)