agno 2.0.0rc2__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. agno/agent/agent.py +6009 -2874
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +595 -187
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +3 -0
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +339 -266
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +1011 -566
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +110 -37
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +143 -4
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +60 -6
  142. agno/models/openai/chat.py +102 -43
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +81 -5
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -175
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +266 -112
  205. agno/run/base.py +53 -24
  206. agno/run/team.py +252 -111
  207. agno/run/workflow.py +156 -45
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1692
  213. agno/tools/brightdata.py +3 -3
  214. agno/tools/cartesia.py +3 -5
  215. agno/tools/dalle.py +9 -8
  216. agno/tools/decorator.py +4 -2
  217. agno/tools/desi_vocal.py +2 -2
  218. agno/tools/duckduckgo.py +15 -11
  219. agno/tools/e2b.py +20 -13
  220. agno/tools/eleven_labs.py +26 -28
  221. agno/tools/exa.py +21 -16
  222. agno/tools/fal.py +4 -4
  223. agno/tools/file.py +153 -23
  224. agno/tools/file_generation.py +350 -0
  225. agno/tools/firecrawl.py +4 -4
  226. agno/tools/function.py +257 -37
  227. agno/tools/giphy.py +2 -2
  228. agno/tools/gmail.py +238 -14
  229. agno/tools/google_drive.py +270 -0
  230. agno/tools/googlecalendar.py +36 -8
  231. agno/tools/googlesheets.py +20 -5
  232. agno/tools/jira.py +20 -0
  233. agno/tools/knowledge.py +3 -3
  234. agno/tools/lumalab.py +3 -3
  235. agno/tools/mcp/__init__.py +10 -0
  236. agno/tools/mcp/mcp.py +331 -0
  237. agno/tools/mcp/multi_mcp.py +347 -0
  238. agno/tools/mcp/params.py +24 -0
  239. agno/tools/mcp_toolbox.py +284 -0
  240. agno/tools/mem0.py +11 -17
  241. agno/tools/memori.py +1 -53
  242. agno/tools/memory.py +419 -0
  243. agno/tools/models/azure_openai.py +2 -2
  244. agno/tools/models/gemini.py +3 -3
  245. agno/tools/models/groq.py +3 -5
  246. agno/tools/models/nebius.py +7 -7
  247. agno/tools/models_labs.py +25 -15
  248. agno/tools/notion.py +204 -0
  249. agno/tools/openai.py +4 -9
  250. agno/tools/opencv.py +3 -3
  251. agno/tools/parallel.py +314 -0
  252. agno/tools/replicate.py +7 -7
  253. agno/tools/scrapegraph.py +58 -31
  254. agno/tools/searxng.py +2 -2
  255. agno/tools/serper.py +2 -2
  256. agno/tools/slack.py +18 -3
  257. agno/tools/spider.py +2 -2
  258. agno/tools/tavily.py +146 -0
  259. agno/tools/whatsapp.py +1 -1
  260. agno/tools/workflow.py +278 -0
  261. agno/tools/yfinance.py +12 -11
  262. agno/utils/agent.py +820 -0
  263. agno/utils/audio.py +27 -0
  264. agno/utils/common.py +90 -1
  265. agno/utils/events.py +222 -7
  266. agno/utils/gemini.py +181 -23
  267. agno/utils/hooks.py +57 -0
  268. agno/utils/http.py +111 -0
  269. agno/utils/knowledge.py +12 -5
  270. agno/utils/log.py +1 -0
  271. agno/utils/mcp.py +95 -5
  272. agno/utils/media.py +188 -10
  273. agno/utils/merge_dict.py +22 -1
  274. agno/utils/message.py +60 -0
  275. agno/utils/models/claude.py +40 -11
  276. agno/utils/models/cohere.py +1 -1
  277. agno/utils/models/watsonx.py +1 -1
  278. agno/utils/openai.py +1 -1
  279. agno/utils/print_response/agent.py +105 -21
  280. agno/utils/print_response/team.py +103 -38
  281. agno/utils/print_response/workflow.py +251 -34
  282. agno/utils/reasoning.py +22 -1
  283. agno/utils/serialize.py +32 -0
  284. agno/utils/streamlit.py +16 -10
  285. agno/utils/string.py +41 -0
  286. agno/utils/team.py +98 -9
  287. agno/utils/tools.py +1 -1
  288. agno/vectordb/base.py +23 -4
  289. agno/vectordb/cassandra/cassandra.py +65 -9
  290. agno/vectordb/chroma/chromadb.py +182 -38
  291. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  292. agno/vectordb/couchbase/couchbase.py +105 -10
  293. agno/vectordb/lancedb/lance_db.py +183 -135
  294. agno/vectordb/langchaindb/langchaindb.py +25 -7
  295. agno/vectordb/lightrag/lightrag.py +17 -3
  296. agno/vectordb/llamaindex/__init__.py +3 -0
  297. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  298. agno/vectordb/milvus/milvus.py +126 -9
  299. agno/vectordb/mongodb/__init__.py +7 -1
  300. agno/vectordb/mongodb/mongodb.py +112 -7
  301. agno/vectordb/pgvector/pgvector.py +142 -21
  302. agno/vectordb/pineconedb/pineconedb.py +80 -8
  303. agno/vectordb/qdrant/qdrant.py +125 -39
  304. agno/vectordb/redis/__init__.py +9 -0
  305. agno/vectordb/redis/redisdb.py +694 -0
  306. agno/vectordb/singlestore/singlestore.py +111 -25
  307. agno/vectordb/surrealdb/surrealdb.py +31 -5
  308. agno/vectordb/upstashdb/upstashdb.py +76 -8
  309. agno/vectordb/weaviate/weaviate.py +86 -15
  310. agno/workflow/__init__.py +2 -0
  311. agno/workflow/agent.py +299 -0
  312. agno/workflow/condition.py +112 -18
  313. agno/workflow/loop.py +69 -10
  314. agno/workflow/parallel.py +266 -118
  315. agno/workflow/router.py +110 -17
  316. agno/workflow/step.py +645 -136
  317. agno/workflow/steps.py +65 -6
  318. agno/workflow/types.py +71 -33
  319. agno/workflow/workflow.py +2113 -300
  320. agno-2.3.0.dist-info/METADATA +618 -0
  321. agno-2.3.0.dist-info/RECORD +577 -0
  322. agno-2.3.0.dist-info/licenses/LICENSE +201 -0
  323. agno/knowledge/reader/url_reader.py +0 -128
  324. agno/tools/googlesearch.py +0 -98
  325. agno/tools/mcp.py +0 -610
  326. agno/utils/models/aws_claude.py +0 -170
  327. agno-2.0.0rc2.dist-info/METADATA +0 -355
  328. agno-2.0.0rc2.dist-info/RECORD +0 -515
  329. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  330. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  331. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,147 @@
1
+ import dataclasses
2
+ from typing import Any, Dict, Optional, Sequence, TypeVar, Union, cast
3
+
4
+ from surrealdb import BlockingHttpSurrealConnection, BlockingWsSurrealConnection, Surreal
5
+
6
+ from agno.db.schemas.culture import CulturalKnowledge
7
+ from agno.utils.log import logger
8
+
9
+ RecordType = TypeVar("RecordType")
10
+
11
+
12
+ def build_client(
13
+ url: str, creds: dict[str, str], ns: str, db: str
14
+ ) -> Union[BlockingWsSurrealConnection, BlockingHttpSurrealConnection]:
15
+ client = Surreal(url=url)
16
+ client.signin(creds)
17
+ client.use(namespace=ns, database=db)
18
+ return client
19
+
20
+
21
+ def _query_aux(
22
+ client: Union[BlockingWsSurrealConnection, BlockingHttpSurrealConnection],
23
+ query: str,
24
+ vars: dict[str, Any],
25
+ ) -> Union[list, dict, str, int]:
26
+ try:
27
+ response = client.query(query, vars)
28
+ except Exception as e:
29
+ msg = f"!! Query execution error: {query} with {vars}, Error: {e}"
30
+ logger.error(msg)
31
+ raise RuntimeError(msg)
32
+ return response
33
+
34
+
35
+ def query(
36
+ client: Union[BlockingWsSurrealConnection, BlockingHttpSurrealConnection],
37
+ query: str,
38
+ vars: dict[str, Any],
39
+ record_type: type[RecordType],
40
+ ) -> Sequence[RecordType]:
41
+ response = _query_aux(client, query, vars)
42
+ if isinstance(response, list):
43
+ if dataclasses.is_dataclass(record_type) and hasattr(record_type, "from_dict"):
44
+ return [getattr(record_type, "from_dict").__call__(x) for x in response]
45
+ else:
46
+ result: list[RecordType] = []
47
+ for x in response:
48
+ if isinstance(x, dict):
49
+ result.append(record_type(**x))
50
+ else:
51
+ result.append(record_type.__call__(x))
52
+ return result
53
+ else:
54
+ raise ValueError(f"Unexpected response type: {type(response)}")
55
+
56
+
57
+ def query_one(
58
+ client: Union[BlockingWsSurrealConnection, BlockingHttpSurrealConnection],
59
+ query: str,
60
+ vars: dict[str, Any],
61
+ record_type: type[RecordType],
62
+ ) -> Optional[RecordType]:
63
+ response = _query_aux(client, query, vars)
64
+ if response is None:
65
+ return None
66
+ elif not isinstance(response, list):
67
+ if dataclasses.is_dataclass(record_type) and hasattr(record_type, "from_dict"):
68
+ return getattr(record_type, "from_dict").__call__(response)
69
+ elif isinstance(response, dict):
70
+ return record_type(**response)
71
+ else:
72
+ return record_type.__call__(response)
73
+ elif isinstance(response, list):
74
+ # Handle list responses - SurrealDB might return a list with a single element
75
+ if len(response) == 1 and isinstance(response[0], dict):
76
+ result = response[0]
77
+ if dataclasses.is_dataclass(record_type) and hasattr(record_type, "from_dict"):
78
+ return getattr(record_type, "from_dict").__call__(result)
79
+ elif record_type is dict:
80
+ return cast(RecordType, result)
81
+ else:
82
+ return record_type(**result)
83
+ elif len(response) == 0:
84
+ return None
85
+ else:
86
+ raise ValueError(f"Expected single record, got {len(response)} records: {response}")
87
+ else:
88
+ raise ValueError(f"Unexpected response type: {type(response)}")
89
+
90
+
91
+ # -- Cultural Knowledge util methods --
92
+
93
+
94
+ def serialize_cultural_knowledge_for_db(cultural_knowledge: CulturalKnowledge) -> Dict[str, Any]:
95
+ """Serialize a CulturalKnowledge object for database storage.
96
+
97
+ Converts the model's separate content, categories, and notes fields
98
+ into a single dict for the database content field.
99
+
100
+ Args:
101
+ cultural_knowledge (CulturalKnowledge): The cultural knowledge object to serialize.
102
+
103
+ Returns:
104
+ Dict[str, Any]: A dictionary with content, categories, and notes.
105
+ """
106
+ content_dict: Dict[str, Any] = {}
107
+ if cultural_knowledge.content is not None:
108
+ content_dict["content"] = cultural_knowledge.content
109
+ if cultural_knowledge.categories is not None:
110
+ content_dict["categories"] = cultural_knowledge.categories
111
+ if cultural_knowledge.notes is not None:
112
+ content_dict["notes"] = cultural_knowledge.notes
113
+
114
+ return content_dict if content_dict else {}
115
+
116
+
117
+ def deserialize_cultural_knowledge_from_db(db_row: Dict[str, Any]) -> CulturalKnowledge:
118
+ """Deserialize a database row to a CulturalKnowledge object.
119
+
120
+ The database stores content as a dict containing content, categories, and notes.
121
+ This method extracts those fields and converts them back to the model format.
122
+
123
+ Args:
124
+ db_row (Dict[str, Any]): The database row as a dictionary.
125
+
126
+ Returns:
127
+ CulturalKnowledge: The cultural knowledge object.
128
+ """
129
+ # Extract content, categories, and notes from the content field
130
+ content_json = db_row.get("content", {}) or {}
131
+
132
+ return CulturalKnowledge.from_dict(
133
+ {
134
+ "id": db_row.get("id"),
135
+ "name": db_row.get("name"),
136
+ "summary": db_row.get("summary"),
137
+ "content": content_json.get("content"),
138
+ "categories": content_json.get("categories"),
139
+ "notes": content_json.get("notes"),
140
+ "metadata": db_row.get("metadata"),
141
+ "input": db_row.get("input"),
142
+ "created_at": db_row.get("created_at"),
143
+ "updated_at": db_row.get("updated_at"),
144
+ "agent_id": db_row.get("agent_id"),
145
+ "team_id": db_row.get("team_id"),
146
+ }
147
+ )
agno/db/utils.py CHANGED
@@ -4,7 +4,6 @@ import json
4
4
  from datetime import date, datetime
5
5
  from uuid import UUID
6
6
 
7
- from agno.db.base import SessionType
8
7
  from agno.models.message import Message
9
8
  from agno.models.metrics import Metrics
10
9
 
@@ -55,34 +54,63 @@ def serialize_session_json_fields(session: dict) -> dict:
55
54
 
56
55
 
57
56
  def deserialize_session_json_fields(session: dict) -> dict:
58
- """Deserialize all JSON fields in the given Session dictionary.
57
+ """Deserialize JSON fields in the given Session dictionary.
59
58
 
60
59
  Args:
61
60
  session (dict): The dictionary to deserialize.
62
61
 
63
62
  Returns:
64
- dict: The dictionary with JSON fields deserialized.
63
+ dict: The dictionary with JSON string fields deserialized to objects.
65
64
  """
66
- if session.get("agent_data") is not None:
67
- session["agent_data"] = json.loads(session["agent_data"])
68
- if session.get("team_data") is not None:
69
- session["team_data"] = json.loads(session["team_data"])
70
- if session.get("workflow_data") is not None:
71
- session["workflow_data"] = json.loads(session["workflow_data"])
72
- if session.get("metadata") is not None:
73
- session["metadata"] = json.loads(session["metadata"])
74
- if session.get("chat_history") is not None:
75
- session["chat_history"] = json.loads(session["chat_history"])
76
- if session.get("summary") is not None:
77
- session["summary"] = json.loads(session["summary"])
65
+ from agno.utils.log import log_warning
66
+
67
+ if session.get("agent_data") is not None and isinstance(session["agent_data"], str):
68
+ try:
69
+ session["agent_data"] = json.loads(session["agent_data"])
70
+ except (json.JSONDecodeError, TypeError) as e:
71
+ log_warning(f"Warning: Could not parse agent_data as JSON, keeping as string: {e}")
72
+
73
+ if session.get("team_data") is not None and isinstance(session["team_data"], str):
74
+ try:
75
+ session["team_data"] = json.loads(session["team_data"])
76
+ except (json.JSONDecodeError, TypeError) as e:
77
+ log_warning(f"Warning: Could not parse team_data as JSON, keeping as string: {e}")
78
+
79
+ if session.get("workflow_data") is not None and isinstance(session["workflow_data"], str):
80
+ try:
81
+ session["workflow_data"] = json.loads(session["workflow_data"])
82
+ except (json.JSONDecodeError, TypeError) as e:
83
+ log_warning(f"Warning: Could not parse workflow_data as JSON, keeping as string: {e}")
84
+
85
+ if session.get("metadata") is not None and isinstance(session["metadata"], str):
86
+ try:
87
+ session["metadata"] = json.loads(session["metadata"])
88
+ except (json.JSONDecodeError, TypeError) as e:
89
+ log_warning(f"Warning: Could not parse metadata as JSON, keeping as string: {e}")
90
+
91
+ if session.get("chat_history") is not None and isinstance(session["chat_history"], str):
92
+ try:
93
+ session["chat_history"] = json.loads(session["chat_history"])
94
+ except (json.JSONDecodeError, TypeError) as e:
95
+ log_warning(f"Warning: Could not parse chat_history as JSON, keeping as string: {e}")
96
+
97
+ if session.get("summary") is not None and isinstance(session["summary"], str):
98
+ try:
99
+ session["summary"] = json.loads(session["summary"])
100
+ except (json.JSONDecodeError, TypeError) as e:
101
+ log_warning(f"Warning: Could not parse summary as JSON, keeping as string: {e}")
102
+
78
103
  if session.get("session_data") is not None and isinstance(session["session_data"], str):
79
- session["session_data"] = json.loads(session["session_data"])
80
- if session.get("runs") is not None:
81
- if session["session_type"] == SessionType.AGENT.value:
82
- session["runs"] = json.loads(session["runs"])
83
- if session["session_type"] == SessionType.TEAM.value:
84
- session["runs"] = json.loads(session["runs"])
85
- if session["session_type"] == SessionType.WORKFLOW.value:
104
+ try:
105
+ session["session_data"] = json.loads(session["session_data"])
106
+ except (json.JSONDecodeError, TypeError) as e:
107
+ log_warning(f"Warning: Could not parse session_data as JSON, keeping as string: {e}")
108
+
109
+ # Handle runs field with session type checking
110
+ if session.get("runs") is not None and isinstance(session["runs"], str):
111
+ try:
86
112
  session["runs"] = json.loads(session["runs"])
113
+ except (json.JSONDecodeError, TypeError) as e:
114
+ log_warning(f"Warning: Could not parse runs as JSON, keeping as string: {e}")
87
115
 
88
116
  return session
agno/eval/accuracy.py CHANGED
@@ -7,13 +7,13 @@ from uuid import uuid4
7
7
  from pydantic import BaseModel, Field
8
8
 
9
9
  from agno.agent import Agent
10
- from agno.db.base import BaseDb
10
+ from agno.db.base import AsyncBaseDb, BaseDb
11
11
  from agno.db.schemas.evals import EvalType
12
12
  from agno.eval.utils import async_log_eval, log_eval_run, store_result_in_file
13
13
  from agno.exceptions import EvalError
14
14
  from agno.models.base import Model
15
15
  from agno.team.team import Team
16
- from agno.utils.log import logger, set_log_level_to_debug, set_log_level_to_info
16
+ from agno.utils.log import log_error, logger, set_log_level_to_debug, set_log_level_to_info
17
17
 
18
18
  if TYPE_CHECKING:
19
19
  from rich.console import Console
@@ -176,7 +176,7 @@ class AccuracyEval:
176
176
  # Enable debug logs
177
177
  debug_mode: bool = getenv("AGNO_DEBUG", "false").lower() == "true"
178
178
  # The database to store Evaluation results
179
- db: Optional[BaseDb] = None
179
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None
180
180
 
181
181
  # Telemetry settings
182
182
  # telemetry=True logs minimal telemetry for analytics
@@ -327,6 +327,9 @@ Remember: You must only compare the agent_output to the expected_output. The exp
327
327
  print_summary: bool = True,
328
328
  print_results: bool = True,
329
329
  ) -> Optional[AccuracyResult]:
330
+ if isinstance(self.db, AsyncBaseDb):
331
+ raise ValueError("run() is not supported with an async DB. Please use arun() instead.")
332
+
330
333
  if self.agent is None and self.team is None:
331
334
  logger.error("You need to provide one of 'agent' or 'team' to run the evaluation.")
332
335
  return None
@@ -661,47 +664,51 @@ Remember: You must only compare the agent_output to the expected_output. The exp
661
664
  )
662
665
  # Log results to the Agno DB if requested
663
666
  if self.db:
664
- if self.agent is not None:
665
- agent_id = self.agent.id
666
- team_id = None
667
- model_id = self.agent.model.id if self.agent.model is not None else None
668
- model_provider = self.agent.model.provider if self.agent.model is not None else None
669
- evaluated_component_name = self.agent.name
670
- elif self.team is not None:
671
- agent_id = None
672
- team_id = self.team.id
673
- model_id = self.team.model.id if self.team.model is not None else None
674
- model_provider = self.team.model.provider if self.team.model is not None else None
675
- evaluated_component_name = self.team.name
676
- else:
677
- agent_id = None
678
- team_id = None
679
- model_id = None
680
- model_provider = None
681
- evaluated_component_name = None
667
+ if isinstance(self.db, AsyncBaseDb):
668
+ log_error("You are using an async DB in a non-async method. The evaluation won't be stored in the DB.")
682
669
 
683
- log_eval_input = {
684
- "additional_guidelines": self.additional_guidelines,
685
- "additional_context": self.additional_context,
686
- "num_iterations": self.num_iterations,
687
- "expected_output": self.expected_output,
688
- "input": self.input,
689
- }
690
-
691
- log_eval_run(
692
- db=self.db,
693
- run_id=self.eval_id, # type: ignore
694
- run_data=asdict(self.result),
695
- eval_type=EvalType.ACCURACY,
696
- name=self.name if self.name is not None else None,
697
- agent_id=agent_id,
698
- team_id=team_id,
699
- model_id=model_id,
700
- model_provider=model_provider,
701
- evaluated_component_name=evaluated_component_name,
702
- workflow_id=None,
703
- eval_input=log_eval_input,
704
- )
670
+ else:
671
+ if self.agent is not None:
672
+ agent_id = self.agent.id
673
+ team_id = None
674
+ model_id = self.agent.model.id if self.agent.model is not None else None
675
+ model_provider = self.agent.model.provider if self.agent.model is not None else None
676
+ evaluated_component_name = self.agent.name
677
+ elif self.team is not None:
678
+ agent_id = None
679
+ team_id = self.team.id
680
+ model_id = self.team.model.id if self.team.model is not None else None
681
+ model_provider = self.team.model.provider if self.team.model is not None else None
682
+ evaluated_component_name = self.team.name
683
+ else:
684
+ agent_id = None
685
+ team_id = None
686
+ model_id = None
687
+ model_provider = None
688
+ evaluated_component_name = None
689
+
690
+ log_eval_input = {
691
+ "additional_guidelines": self.additional_guidelines,
692
+ "additional_context": self.additional_context,
693
+ "num_iterations": self.num_iterations,
694
+ "expected_output": self.expected_output,
695
+ "input": self.input,
696
+ }
697
+
698
+ log_eval_run(
699
+ db=self.db,
700
+ run_id=self.eval_id, # type: ignore
701
+ run_data=asdict(self.result),
702
+ eval_type=EvalType.ACCURACY,
703
+ name=self.name if self.name is not None else None,
704
+ agent_id=agent_id,
705
+ team_id=team_id,
706
+ model_id=model_id,
707
+ model_provider=model_provider,
708
+ evaluated_component_name=evaluated_component_name,
709
+ workflow_id=None,
710
+ eval_input=log_eval_input,
711
+ )
705
712
 
706
713
  if self.telemetry:
707
714
  from agno.api.evals import EvalRunCreate, create_eval_run_telemetry
agno/eval/performance.py CHANGED
@@ -3,10 +3,10 @@ import gc
3
3
  import tracemalloc
4
4
  from dataclasses import dataclass, field
5
5
  from os import getenv
6
- from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional
6
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
7
7
  from uuid import uuid4
8
8
 
9
- from agno.db.base import BaseDb
9
+ from agno.db.base import AsyncBaseDb, BaseDb
10
10
  from agno.db.schemas.evals import EvalType
11
11
  from agno.eval.utils import async_log_eval, log_eval_run, store_result_in_file
12
12
  from agno.utils.log import log_debug, set_log_level_to_debug, set_log_level_to_info
@@ -222,7 +222,7 @@ class PerformanceEval:
222
222
  # Enable debug logs
223
223
  debug_mode: bool = getenv("AGNO_DEBUG", "false").lower() == "true"
224
224
  # The database to store Evaluation results
225
- db: Optional[BaseDb] = None
225
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None
226
226
 
227
227
  # Telemetry settings
228
228
  # telemetry=True logs minimal telemetry for analytics
@@ -491,6 +491,9 @@ class PerformanceEval:
491
491
  6. Print results as requested
492
492
  7. Log results to the Agno platform if requested
493
493
  """
494
+ if isinstance(self.db, AsyncBaseDb):
495
+ raise ValueError("run() is not supported with an async DB. Please use arun() instead.")
496
+
494
497
  from rich.console import Console
495
498
  from rich.live import Live
496
499
  from rich.status import Status
agno/eval/reliability.py CHANGED
@@ -1,9 +1,9 @@
1
1
  from dataclasses import asdict, dataclass, field
2
2
  from os import getenv
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
4
4
  from uuid import uuid4
5
5
 
6
- from agno.db.base import BaseDb
6
+ from agno.db.base import AsyncBaseDb, BaseDb
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from rich.console import Console
@@ -63,7 +63,7 @@ class ReliabilityEval:
63
63
  # Enable debug logs
64
64
  debug_mode: bool = getenv("AGNO_DEBUG", "false").lower() == "true"
65
65
  # The database to store Evaluation results
66
- db: Optional[BaseDb] = None
66
+ db: Optional[Union[BaseDb, AsyncBaseDb]] = None
67
67
 
68
68
  # Telemetry settings
69
69
  # telemetry=True logs minimal telemetry for analytics
@@ -71,6 +71,9 @@ class ReliabilityEval:
71
71
  telemetry: bool = True
72
72
 
73
73
  def run(self, *, print_results: bool = False) -> Optional[ReliabilityResult]:
74
+ if isinstance(self.db, AsyncBaseDb):
75
+ raise ValueError("run() is not supported with an async DB. Please use arun() instead.")
76
+
74
77
  if self.agent_response is None and self.team_response is None:
75
78
  raise ValueError("You need to provide 'agent_response' or 'team_response' to run the evaluation.")
76
79
 
agno/eval/utils.py CHANGED
@@ -2,7 +2,7 @@ from dataclasses import asdict
2
2
  from pathlib import Path
3
3
  from typing import TYPE_CHECKING, Optional, Union
4
4
 
5
- from agno.db.base import BaseDb
5
+ from agno.db.base import AsyncBaseDb, BaseDb
6
6
  from agno.db.schemas.evals import EvalRunRecord, EvalType
7
7
  from agno.utils.log import log_debug, logger
8
8
 
@@ -49,7 +49,7 @@ def log_eval_run(
49
49
 
50
50
 
51
51
  async def async_log_eval(
52
- db: BaseDb,
52
+ db: Union[BaseDb, AsyncBaseDb],
53
53
  run_id: str,
54
54
  run_data: dict,
55
55
  eval_type: EvalType,
@@ -65,21 +65,38 @@ async def async_log_eval(
65
65
  """Call the API to create an evaluation run."""
66
66
 
67
67
  try:
68
- db.create_eval_run(
69
- EvalRunRecord(
70
- run_id=run_id,
71
- eval_type=eval_type,
72
- eval_data=run_data,
73
- eval_input=eval_input,
74
- agent_id=agent_id,
75
- model_id=model_id,
76
- model_provider=model_provider,
77
- name=name,
78
- evaluated_component_name=evaluated_component_name,
79
- team_id=team_id,
80
- workflow_id=workflow_id,
68
+ if isinstance(db, AsyncBaseDb):
69
+ await db.create_eval_run(
70
+ EvalRunRecord(
71
+ run_id=run_id,
72
+ eval_type=eval_type,
73
+ eval_data=run_data,
74
+ eval_input=eval_input,
75
+ agent_id=agent_id,
76
+ model_id=model_id,
77
+ model_provider=model_provider,
78
+ name=name,
79
+ evaluated_component_name=evaluated_component_name,
80
+ team_id=team_id,
81
+ workflow_id=workflow_id,
82
+ )
83
+ )
84
+ else:
85
+ db.create_eval_run(
86
+ EvalRunRecord(
87
+ run_id=run_id,
88
+ eval_type=eval_type,
89
+ eval_data=run_data,
90
+ eval_input=eval_input,
91
+ agent_id=agent_id,
92
+ model_id=model_id,
93
+ model_provider=model_provider,
94
+ name=name,
95
+ evaluated_component_name=evaluated_component_name,
96
+ team_id=team_id,
97
+ workflow_id=workflow_id,
98
+ )
81
99
  )
82
- )
83
100
  except Exception as e:
84
101
  log_debug(f"Could not create agent event: {e}")
85
102
 
agno/exceptions.py CHANGED
@@ -1,4 +1,5 @@
1
- from typing import List, Optional, Union
1
+ from enum import Enum
2
+ from typing import Any, Dict, List, Optional, Union
2
3
 
3
4
  from agno.models.message import Message
4
5
 
@@ -17,6 +18,8 @@ class AgentRunException(Exception):
17
18
  self.agent_message = agent_message
18
19
  self.messages = messages
19
20
  self.stop_execution = stop_execution
21
+ self.type = "agent_run_error"
22
+ self.error_id = "agent_run_error"
20
23
 
21
24
 
22
25
  class RetryAgentRun(AgentRunException):
@@ -32,6 +35,7 @@ class RetryAgentRun(AgentRunException):
32
35
  super().__init__(
33
36
  exc, user_message=user_message, agent_message=agent_message, messages=messages, stop_execution=False
34
37
  )
38
+ self.error_id = "retry_agent_run_error"
35
39
 
36
40
 
37
41
  class StopAgentRun(AgentRunException):
@@ -47,6 +51,7 @@ class StopAgentRun(AgentRunException):
47
51
  super().__init__(
48
52
  exc, user_message=user_message, agent_message=agent_message, messages=messages, stop_execution=True
49
53
  )
54
+ self.error_id = "stop_agent_run_error"
50
55
 
51
56
 
52
57
  class RunCancelledException(Exception):
@@ -54,6 +59,8 @@ class RunCancelledException(Exception):
54
59
 
55
60
  def __init__(self, message: str = "Operation cancelled by user"):
56
61
  super().__init__(message)
62
+ self.type = "run_cancelled_error"
63
+ self.error_id = "run_cancelled_error"
57
64
 
58
65
 
59
66
  class AgnoError(Exception):
@@ -63,6 +70,8 @@ class AgnoError(Exception):
63
70
  super().__init__(message)
64
71
  self.message = message
65
72
  self.status_code = status_code
73
+ self.type = "agno_error"
74
+ self.error_id = "agno_error"
66
75
 
67
76
  def __str__(self) -> str:
68
77
  return str(self.message)
@@ -78,6 +87,9 @@ class ModelProviderError(AgnoError):
78
87
  self.model_name = model_name
79
88
  self.model_id = model_id
80
89
 
90
+ self.type = "model_provider_error"
91
+ self.error_id = "model_provider_error"
92
+
81
93
 
82
94
  class ModelRateLimitError(ModelProviderError):
83
95
  """Exception raised when a model provider returns a rate limit error."""
@@ -86,9 +98,64 @@ class ModelRateLimitError(ModelProviderError):
86
98
  self, message: str, status_code: int = 429, model_name: Optional[str] = None, model_id: Optional[str] = None
87
99
  ):
88
100
  super().__init__(message, status_code, model_name, model_id)
101
+ self.error_id = "model_rate_limit_error"
89
102
 
90
103
 
91
104
  class EvalError(Exception):
92
105
  """Exception raised when an evaluation fails."""
93
106
 
94
107
  pass
108
+
109
+
110
+ class CheckTrigger(Enum):
111
+ """Enum for guardrail triggers."""
112
+
113
+ OFF_TOPIC = "off_topic"
114
+ INPUT_NOT_ALLOWED = "input_not_allowed"
115
+ OUTPUT_NOT_ALLOWED = "output_not_allowed"
116
+ VALIDATION_FAILED = "validation_failed"
117
+
118
+ PROMPT_INJECTION = "prompt_injection"
119
+ PII_DETECTED = "pii_detected"
120
+
121
+
122
+ class InputCheckError(Exception):
123
+ """Exception raised when an input check fails."""
124
+
125
+ def __init__(
126
+ self,
127
+ message: str,
128
+ check_trigger: CheckTrigger = CheckTrigger.INPUT_NOT_ALLOWED,
129
+ additional_data: Optional[Dict[str, Any]] = None,
130
+ ):
131
+ super().__init__(message)
132
+ self.type = "input_check_error"
133
+ if isinstance(check_trigger, CheckTrigger):
134
+ self.error_id = check_trigger.value
135
+ else:
136
+ self.error_id = str(check_trigger)
137
+
138
+ self.message = message
139
+ self.check_trigger = check_trigger
140
+ self.additional_data = additional_data
141
+
142
+
143
+ class OutputCheckError(Exception):
144
+ """Exception raised when an output check fails."""
145
+
146
+ def __init__(
147
+ self,
148
+ message: str,
149
+ check_trigger: CheckTrigger = CheckTrigger.OUTPUT_NOT_ALLOWED,
150
+ additional_data: Optional[Dict[str, Any]] = None,
151
+ ):
152
+ super().__init__(message)
153
+ self.type = "output_check_error"
154
+ if isinstance(check_trigger, CheckTrigger):
155
+ self.error_id = check_trigger.value
156
+ else:
157
+ self.error_id = str(check_trigger)
158
+
159
+ self.message = message
160
+ self.check_trigger = check_trigger
161
+ self.additional_data = additional_data