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,22 @@
1
+ from cryptography.hazmat.primitives import serialization
2
+ from cryptography.hazmat.primitives.asymmetric import rsa
3
+
4
+
5
+ def generate_rsa_keys():
6
+ """Generate RSA key pair for RS256 JWT signing/verification."""
7
+ private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
8
+
9
+ # Private key PEM (used by auth server to sign tokens)
10
+ private_pem = private_key.private_bytes(
11
+ encoding=serialization.Encoding.PEM,
12
+ format=serialization.PrivateFormat.PKCS8,
13
+ encryption_algorithm=serialization.NoEncryption(),
14
+ )
15
+
16
+ # Public key PEM (used by AgentOS to verify tokens)
17
+ public_pem = private_key.public_key().public_bytes(
18
+ encoding=serialization.Encoding.PEM,
19
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
20
+ )
21
+
22
+ return private_pem.decode("utf-8"), public_pem.decode("utf-8")
agno/utils/dttm.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from datetime import datetime, timezone
2
+ from typing import Union
2
3
 
3
4
 
4
5
  def current_datetime() -> datetime:
@@ -11,3 +12,35 @@ def current_datetime_utc() -> datetime:
11
12
 
12
13
  def current_datetime_utc_str() -> str:
13
14
  return current_datetime_utc().strftime("%Y-%m-%dT%H:%M:%S")
15
+
16
+
17
+ def now_epoch_s() -> int:
18
+ return int(datetime.now(timezone.utc).timestamp())
19
+
20
+
21
+ def to_epoch_s(value: Union[int, float, str, datetime]) -> int:
22
+ """Normalize various datetime representations to epoch seconds (UTC)."""
23
+
24
+ if isinstance(value, (int, float)):
25
+ # assume value is already in seconds
26
+ return int(value)
27
+
28
+ if isinstance(value, datetime):
29
+ dt = value
30
+ if dt.tzinfo is None:
31
+ dt = dt.replace(tzinfo=timezone.utc)
32
+ return int(dt.timestamp())
33
+
34
+ if isinstance(value, str):
35
+ s = value.strip()
36
+ if s.endswith("Z"):
37
+ s = s[:-1] + "+00:00"
38
+ try:
39
+ dt = datetime.fromisoformat(s)
40
+ except ValueError as e:
41
+ raise ValueError(f"Unsupported datetime string: {value!r}") from e
42
+ if dt.tzinfo is None:
43
+ dt = dt.replace(tzinfo=timezone.utc)
44
+ return int(dt.timestamp())
45
+
46
+ raise TypeError(f"Unsupported datetime value: {type(value)}")
agno/utils/events.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict, List, Optional
1
+ from typing import Any, Dict, List, Optional, Union
2
2
 
3
3
  from agno.media import Audio, Image
4
4
  from agno.models.message import Citations
@@ -11,6 +11,8 @@ from agno.run.agent import (
11
11
  OutputModelResponseStartedEvent,
12
12
  ParserModelResponseCompletedEvent,
13
13
  ParserModelResponseStartedEvent,
14
+ PostHookCompletedEvent,
15
+ PostHookStartedEvent,
14
16
  PreHookCompletedEvent,
15
17
  PreHookStartedEvent,
16
18
  ReasoningCompletedEvent,
@@ -18,22 +20,30 @@ from agno.run.agent import (
18
20
  ReasoningStepEvent,
19
21
  RunCancelledEvent,
20
22
  RunCompletedEvent,
23
+ RunContentCompletedEvent,
21
24
  RunContentEvent,
22
25
  RunContinuedEvent,
23
26
  RunErrorEvent,
27
+ RunEvent,
24
28
  RunInput,
25
29
  RunOutput,
30
+ RunOutputEvent,
26
31
  RunPausedEvent,
27
32
  RunStartedEvent,
33
+ SessionSummaryCompletedEvent,
34
+ SessionSummaryStartedEvent,
28
35
  ToolCallCompletedEvent,
29
36
  ToolCallStartedEvent,
30
37
  )
38
+ from agno.run.requirement import RunRequirement
31
39
  from agno.run.team import MemoryUpdateCompletedEvent as TeamMemoryUpdateCompletedEvent
32
40
  from agno.run.team import MemoryUpdateStartedEvent as TeamMemoryUpdateStartedEvent
33
41
  from agno.run.team import OutputModelResponseCompletedEvent as TeamOutputModelResponseCompletedEvent
34
42
  from agno.run.team import OutputModelResponseStartedEvent as TeamOutputModelResponseStartedEvent
35
43
  from agno.run.team import ParserModelResponseCompletedEvent as TeamParserModelResponseCompletedEvent
36
44
  from agno.run.team import ParserModelResponseStartedEvent as TeamParserModelResponseStartedEvent
45
+ from agno.run.team import PostHookCompletedEvent as TeamPostHookCompletedEvent
46
+ from agno.run.team import PostHookStartedEvent as TeamPostHookStartedEvent
37
47
  from agno.run.team import PreHookCompletedEvent as TeamPreHookCompletedEvent
38
48
  from agno.run.team import PreHookStartedEvent as TeamPreHookStartedEvent
39
49
  from agno.run.team import ReasoningCompletedEvent as TeamReasoningCompletedEvent
@@ -41,12 +51,16 @@ from agno.run.team import ReasoningStartedEvent as TeamReasoningStartedEvent
41
51
  from agno.run.team import ReasoningStepEvent as TeamReasoningStepEvent
42
52
  from agno.run.team import RunCancelledEvent as TeamRunCancelledEvent
43
53
  from agno.run.team import RunCompletedEvent as TeamRunCompletedEvent
54
+ from agno.run.team import RunContentCompletedEvent as TeamRunContentCompletedEvent
44
55
  from agno.run.team import RunContentEvent as TeamRunContentEvent
45
56
  from agno.run.team import RunErrorEvent as TeamRunErrorEvent
46
57
  from agno.run.team import RunStartedEvent as TeamRunStartedEvent
47
- from agno.run.team import TeamRunInput, TeamRunOutput
58
+ from agno.run.team import SessionSummaryCompletedEvent as TeamSessionSummaryCompletedEvent
59
+ from agno.run.team import SessionSummaryStartedEvent as TeamSessionSummaryStartedEvent
60
+ from agno.run.team import TeamRunEvent, TeamRunInput, TeamRunOutput, TeamRunOutputEvent
48
61
  from agno.run.team import ToolCallCompletedEvent as TeamToolCallCompletedEvent
49
62
  from agno.run.team import ToolCallStartedEvent as TeamToolCallStartedEvent
63
+ from agno.session.summary import SessionSummary
50
64
 
51
65
 
52
66
  def create_team_run_started_event(from_run_response: TeamRunOutput) -> TeamRunStartedEvent:
@@ -93,6 +107,7 @@ def create_team_run_completed_event(from_run_response: TeamRunOutput) -> TeamRun
93
107
  member_responses=from_run_response.member_responses, # type: ignore
94
108
  metadata=from_run_response.metadata, # type: ignore
95
109
  metrics=from_run_response.metrics, # type: ignore
110
+ session_state=from_run_response.session_state, # type: ignore
96
111
  )
97
112
 
98
113
 
@@ -117,11 +132,14 @@ def create_run_completed_event(from_run_response: RunOutput) -> RunCompletedEven
117
132
  reasoning_messages=from_run_response.reasoning_messages, # type: ignore
118
133
  metadata=from_run_response.metadata, # type: ignore
119
134
  metrics=from_run_response.metrics, # type: ignore
135
+ session_state=from_run_response.session_state, # type: ignore
120
136
  )
121
137
 
122
138
 
123
139
  def create_run_paused_event(
124
- from_run_response: RunOutput, tools: Optional[List[ToolExecution]] = None
140
+ from_run_response: RunOutput,
141
+ tools: Optional[List[ToolExecution]] = None,
142
+ requirements: Optional[List[RunRequirement]] = None,
125
143
  ) -> RunPausedEvent:
126
144
  return RunPausedEvent(
127
145
  session_id=from_run_response.session_id,
@@ -129,6 +147,7 @@ def create_run_paused_event(
129
147
  agent_name=from_run_response.agent_name, # type: ignore
130
148
  run_id=from_run_response.run_id,
131
149
  tools=tools,
150
+ requirements=requirements,
132
151
  content=from_run_response.content,
133
152
  )
134
153
 
@@ -242,6 +261,54 @@ def create_team_pre_hook_completed_event(
242
261
  )
243
262
 
244
263
 
264
+ def create_post_hook_started_event(
265
+ from_run_response: RunOutput, post_hook_name: Optional[str] = None
266
+ ) -> PostHookStartedEvent:
267
+ return PostHookStartedEvent(
268
+ session_id=from_run_response.session_id,
269
+ agent_id=from_run_response.agent_id, # type: ignore
270
+ agent_name=from_run_response.agent_name, # type: ignore
271
+ run_id=from_run_response.run_id,
272
+ post_hook_name=post_hook_name,
273
+ )
274
+
275
+
276
+ def create_team_post_hook_started_event(
277
+ from_run_response: TeamRunOutput, post_hook_name: Optional[str] = None
278
+ ) -> TeamPostHookStartedEvent:
279
+ return TeamPostHookStartedEvent(
280
+ session_id=from_run_response.session_id,
281
+ team_id=from_run_response.team_id, # type: ignore
282
+ team_name=from_run_response.team_name, # type: ignore
283
+ run_id=from_run_response.run_id,
284
+ post_hook_name=post_hook_name,
285
+ )
286
+
287
+
288
+ def create_post_hook_completed_event(
289
+ from_run_response: RunOutput, post_hook_name: Optional[str] = None
290
+ ) -> PostHookCompletedEvent:
291
+ return PostHookCompletedEvent(
292
+ session_id=from_run_response.session_id,
293
+ agent_id=from_run_response.agent_id, # type: ignore
294
+ agent_name=from_run_response.agent_name, # type: ignore
295
+ run_id=from_run_response.run_id,
296
+ post_hook_name=post_hook_name,
297
+ )
298
+
299
+
300
+ def create_team_post_hook_completed_event(
301
+ from_run_response: TeamRunOutput, post_hook_name: Optional[str] = None
302
+ ) -> TeamPostHookCompletedEvent:
303
+ return TeamPostHookCompletedEvent(
304
+ session_id=from_run_response.session_id,
305
+ team_id=from_run_response.team_id, # type: ignore
306
+ team_name=from_run_response.team_name, # type: ignore
307
+ run_id=from_run_response.run_id,
308
+ post_hook_name=post_hook_name,
309
+ )
310
+
311
+
245
312
  def create_memory_update_started_event(from_run_response: RunOutput) -> MemoryUpdateStartedEvent:
246
313
  return MemoryUpdateStartedEvent(
247
314
  session_id=from_run_response.session_id,
@@ -278,6 +345,50 @@ def create_team_memory_update_completed_event(from_run_response: TeamRunOutput)
278
345
  )
279
346
 
280
347
 
348
+ def create_team_session_summary_started_event(
349
+ from_run_response: TeamRunOutput,
350
+ ) -> TeamSessionSummaryStartedEvent:
351
+ return TeamSessionSummaryStartedEvent(
352
+ session_id=from_run_response.session_id,
353
+ team_id=from_run_response.team_id, # type: ignore
354
+ team_name=from_run_response.team_name, # type: ignore
355
+ run_id=from_run_response.run_id,
356
+ )
357
+
358
+
359
+ def create_team_session_summary_completed_event(
360
+ from_run_response: TeamRunOutput, session_summary: Optional[SessionSummary] = None
361
+ ) -> TeamSessionSummaryCompletedEvent:
362
+ return TeamSessionSummaryCompletedEvent(
363
+ session_id=from_run_response.session_id,
364
+ team_id=from_run_response.team_id, # type: ignore
365
+ team_name=from_run_response.team_name, # type: ignore
366
+ run_id=from_run_response.run_id,
367
+ session_summary=session_summary,
368
+ )
369
+
370
+
371
+ def create_session_summary_started_event(from_run_response: RunOutput) -> SessionSummaryStartedEvent:
372
+ return SessionSummaryStartedEvent(
373
+ session_id=from_run_response.session_id,
374
+ agent_id=from_run_response.agent_id, # type: ignore
375
+ agent_name=from_run_response.agent_name, # type: ignore
376
+ run_id=from_run_response.run_id,
377
+ )
378
+
379
+
380
+ def create_session_summary_completed_event(
381
+ from_run_response: RunOutput, session_summary: Optional[SessionSummary] = None
382
+ ) -> SessionSummaryCompletedEvent:
383
+ return SessionSummaryCompletedEvent(
384
+ session_id=from_run_response.session_id,
385
+ agent_id=from_run_response.agent_id, # type: ignore
386
+ agent_name=from_run_response.agent_name, # type: ignore
387
+ run_id=from_run_response.run_id,
388
+ session_summary=session_summary,
389
+ )
390
+
391
+
281
392
  def create_reasoning_started_event(from_run_response: RunOutput) -> ReasoningStartedEvent:
282
393
  return ReasoningStartedEvent(
283
394
  session_id=from_run_response.session_id,
@@ -468,6 +579,28 @@ def create_team_run_output_content_event(
468
579
  )
469
580
 
470
581
 
582
+ def create_run_content_completed_event(
583
+ from_run_response: RunOutput,
584
+ ) -> RunContentCompletedEvent:
585
+ return RunContentCompletedEvent(
586
+ session_id=from_run_response.session_id,
587
+ agent_id=from_run_response.agent_id, # type: ignore
588
+ agent_name=from_run_response.agent_name, # type: ignore
589
+ run_id=from_run_response.run_id,
590
+ )
591
+
592
+
593
+ def create_team_run_content_completed_event(
594
+ from_run_response: TeamRunOutput,
595
+ ) -> TeamRunContentCompletedEvent:
596
+ return TeamRunContentCompletedEvent(
597
+ session_id=from_run_response.session_id,
598
+ team_id=from_run_response.team_id, # type: ignore
599
+ team_name=from_run_response.team_name, # type: ignore
600
+ run_id=from_run_response.run_id,
601
+ )
602
+
603
+
471
604
  def create_parser_model_response_started_event(
472
605
  from_run_response: RunOutput,
473
606
  ) -> ParserModelResponseStartedEvent:
@@ -550,3 +683,18 @@ def create_team_output_model_response_completed_event(
550
683
  team_name=from_run_response.team_name, # type: ignore
551
684
  run_id=from_run_response.run_id,
552
685
  )
686
+
687
+
688
+ def handle_event(
689
+ event: Union[RunOutputEvent, TeamRunOutputEvent],
690
+ run_response: Union[RunOutput, TeamRunOutput],
691
+ events_to_skip: Optional[List[Union[RunEvent, TeamRunEvent]]] = None,
692
+ store_events: bool = False,
693
+ ) -> Union[RunOutputEvent, TeamRunOutputEvent]:
694
+ # We only store events that are not run_response_content events
695
+ events_to_skip = [event.value for event in events_to_skip] if events_to_skip else []
696
+ if store_events and event.event not in events_to_skip:
697
+ if run_response.events is None:
698
+ run_response.events = []
699
+ run_response.events.append(event) # type: ignore
700
+ return event
agno/utils/gemini.py CHANGED
@@ -225,12 +225,13 @@ def convert_schema(
225
225
  if schema_type is None or schema_type == "null":
226
226
  return None
227
227
  description = schema_dict.get("description", None)
228
+ title = schema_dict.get("title", None)
228
229
  default = schema_dict.get("default", None)
229
230
 
230
231
  # Handle enum types
231
232
  if "enum" in schema_dict:
232
233
  enum_values = schema_dict["enum"]
233
- return Schema(type=GeminiType.STRING, enum=enum_values, description=description, default=default)
234
+ return Schema(type=GeminiType.STRING, enum=enum_values, description=description, default=default, title=title)
234
235
 
235
236
  if schema_type == "object":
236
237
  # Handle regular objects with properties
@@ -250,6 +251,10 @@ def convert_schema(
250
251
  if is_nullable:
251
252
  converted_schema.nullable = True
252
253
  properties[key] = converted_schema
254
+ else:
255
+ properties[key] = Schema(
256
+ title=prop_def.get("title", None), description=prop_def.get("description", None)
257
+ )
253
258
 
254
259
  required = schema_dict.get("required", [])
255
260
 
@@ -260,9 +265,10 @@ def convert_schema(
260
265
  required=required,
261
266
  description=description,
262
267
  default=default,
268
+ title=title,
263
269
  )
264
270
  else:
265
- return Schema(type=GeminiType.OBJECT, description=description, default=default)
271
+ return Schema(type=GeminiType.OBJECT, description=description, default=default, title=title)
266
272
 
267
273
  # Handle Dict types (objects with additionalProperties but no properties)
268
274
  elif "additionalProperties" in schema_dict:
@@ -305,11 +311,11 @@ def convert_schema(
305
311
  )
306
312
  else:
307
313
  # additionalProperties is false or true
308
- return Schema(type=GeminiType.OBJECT, description=description, default=default)
314
+ return Schema(type=GeminiType.OBJECT, description=description, default=default, title=title)
309
315
 
310
316
  # Handle empty objects
311
317
  else:
312
- return Schema(type=GeminiType.OBJECT, description=description, default=default)
318
+ return Schema(type=GeminiType.OBJECT, description=description, default=default, title=title)
313
319
 
314
320
  elif schema_type == "array" and "items" in schema_dict:
315
321
  if not schema_dict["items"]: # Handle empty {}
@@ -325,6 +331,7 @@ def convert_schema(
325
331
  items=items,
326
332
  min_items=min_items,
327
333
  max_items=max_items,
334
+ title=title,
328
335
  )
329
336
 
330
337
  elif schema_type == "string":
@@ -332,6 +339,7 @@ def convert_schema(
332
339
  "type": GeminiType.STRING,
333
340
  "description": description,
334
341
  "default": default,
342
+ "title": title,
335
343
  }
336
344
  if "format" in schema_dict:
337
345
  schema_kwargs["format"] = schema_dict["format"]
@@ -342,6 +350,7 @@ def convert_schema(
342
350
  "type": schema_type.upper(),
343
351
  "description": description,
344
352
  "default": default,
353
+ "title": title,
345
354
  }
346
355
  if "maximum" in schema_dict:
347
356
  schema_kwargs["maximum"] = schema_dict["maximum"]
@@ -373,6 +382,7 @@ def convert_schema(
373
382
  any_of=any_of,
374
383
  description=description,
375
384
  default=default,
385
+ title=title,
376
386
  )
377
387
  else:
378
388
  if isinstance(schema_type, list):
@@ -384,7 +394,7 @@ def convert_schema(
384
394
  # Only convert to uppercase if schema_type is not empty
385
395
  if schema_type:
386
396
  schema_type = schema_type.upper()
387
- return Schema(type=schema_type, description=description, default=default)
397
+ return Schema(type=schema_type, description=description, default=default, title=title)
388
398
  else:
389
399
  # If we get here with an empty type and no other handlers matched,
390
400
  # something is wrong with the schema
agno/utils/hooks.py CHANGED
@@ -1,14 +1,73 @@
1
- from typing import Any, Callable, Dict, List, Optional, Union
1
+ from copy import deepcopy
2
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
3
+
4
+ if TYPE_CHECKING:
5
+ from agno.eval.base import BaseEval
2
6
 
3
7
  from agno.guardrails.base import BaseGuardrail
8
+ from agno.hooks.decorator import HOOK_RUN_IN_BACKGROUND_ATTR
4
9
  from agno.utils.log import log_warning
5
10
 
11
+ # Keys that should be deep copied for background hooks to prevent race conditions
12
+ BACKGROUND_HOOK_COPY_KEYS = frozenset(
13
+ {"run_input", "run_context", "run_output", "session_state", "dependencies", "metadata"}
14
+ )
15
+
16
+
17
+ def copy_args_for_background(args: Dict[str, Any]) -> Dict[str, Any]:
18
+ """
19
+ Create a copy of hook arguments for background execution.
20
+
21
+ This deep copies run_input, run_context, run_output, session_state, dependencies,
22
+ and metadata to prevent race conditions when hooks run in the background.
23
+
24
+ Args:
25
+ args: The original arguments dictionary
26
+
27
+ Returns:
28
+ A new dictionary with copied values for sensitive keys
29
+ """
30
+ copied_args = {}
31
+ for key, value in args.items():
32
+ if key in BACKGROUND_HOOK_COPY_KEYS and value is not None:
33
+ try:
34
+ copied_args[key] = deepcopy(value)
35
+ except Exception:
36
+ # If deepcopy fails (e.g., for non-copyable objects), use the original
37
+ log_warning(f"Could not deepcopy {key} for background hook, using original reference")
38
+ copied_args[key] = value
39
+ else:
40
+ copied_args[key] = value
41
+ return copied_args
42
+
43
+
44
+ def should_run_hook_in_background(hook: Callable[..., Any]) -> bool:
45
+ """
46
+ Check if a hook function should run in background.
47
+
48
+ This checks for the _agno_run_in_background attribute set by the @hook decorator.
49
+
50
+ Args:
51
+ hook: The hook function to check
52
+
53
+ Returns:
54
+ True if the hook is decorated with @hook(run_in_background=True)
55
+ """
56
+ return getattr(hook, HOOK_RUN_IN_BACKGROUND_ATTR, False)
57
+
6
58
 
7
- def normalize_hooks(
8
- hooks: Optional[Union[List[Callable[..., Any]], List[BaseGuardrail]]],
59
+ def normalize_pre_hooks(
60
+ hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]],
9
61
  async_mode: bool = False,
10
62
  ) -> Optional[List[Callable[..., Any]]]:
11
- """Normalize hooks to a list format"""
63
+ """Normalize pre-hooks to a list format.
64
+
65
+ Args:
66
+ hooks: List of hook functions, guardrails, or eval instances
67
+ async_mode: Whether to use async versions of methods
68
+ """
69
+ from agno.eval.base import BaseEval
70
+
12
71
  result_hooks: List[Callable[..., Any]] = []
13
72
 
14
73
  if hooks is not None:
@@ -18,6 +77,61 @@ def normalize_hooks(
18
77
  result_hooks.append(hook.async_check)
19
78
  else:
20
79
  result_hooks.append(hook.check)
80
+ elif isinstance(hook, BaseEval):
81
+ # Extract pre_check method
82
+ method = hook.async_pre_check if async_mode else hook.pre_check
83
+
84
+ from functools import partial
85
+
86
+ wrapped = partial(method)
87
+ wrapped.__name__ = method.__name__ # type: ignore
88
+ setattr(wrapped, HOOK_RUN_IN_BACKGROUND_ATTR, getattr(hook, "run_in_background", False))
89
+ result_hooks.append(wrapped)
90
+ else:
91
+ # Check if the hook is async and used within sync methods
92
+ if not async_mode:
93
+ import asyncio
94
+
95
+ if asyncio.iscoroutinefunction(hook):
96
+ raise ValueError(
97
+ f"Cannot use {hook.__name__} (an async hook) with `run()`. Use `arun()` instead."
98
+ )
99
+
100
+ result_hooks.append(hook)
101
+ return result_hooks if result_hooks else None
102
+
103
+
104
+ def normalize_post_hooks(
105
+ hooks: Optional[List[Union[Callable[..., Any], BaseGuardrail, "BaseEval"]]],
106
+ async_mode: bool = False,
107
+ ) -> Optional[List[Callable[..., Any]]]:
108
+ """Normalize post-hooks to a list format.
109
+
110
+ Args:
111
+ hooks: List of hook functions, guardrails, or eval instances
112
+ async_mode: Whether to use async versions of methods
113
+ """
114
+ from agno.eval.base import BaseEval
115
+
116
+ result_hooks: List[Callable[..., Any]] = []
117
+
118
+ if hooks is not None:
119
+ for hook in hooks:
120
+ if isinstance(hook, BaseGuardrail):
121
+ if async_mode:
122
+ result_hooks.append(hook.async_check)
123
+ else:
124
+ result_hooks.append(hook.check)
125
+ elif isinstance(hook, BaseEval):
126
+ # Extract post_check method
127
+ method = hook.async_post_check if async_mode else hook.post_check # type: ignore[assignment]
128
+
129
+ from functools import partial
130
+
131
+ wrapped = partial(method)
132
+ wrapped.__name__ = method.__name__ # type: ignore
133
+ setattr(wrapped, HOOK_RUN_IN_BACKGROUND_ATTR, getattr(hook, "run_in_background", False))
134
+ result_hooks.append(wrapped)
21
135
  else:
22
136
  # Check if the hook is async and used within sync methods
23
137
  if not async_mode:
agno/utils/http.py CHANGED
@@ -10,6 +10,117 @@ logger = logging.getLogger(__name__)
10
10
  DEFAULT_MAX_RETRIES = 3
11
11
  DEFAULT_BACKOFF_FACTOR = 2 # Exponential backoff: 1, 2, 4, 8...
12
12
 
13
+ # Global httpx clients for resource efficiency
14
+ # These are shared across all models to reuse connection pools and avoid resource leaks.
15
+ # Consumers can override these at application startup using set_default_sync_client()
16
+ # and set_default_async_client() to customize limits, timeouts, proxies, etc.
17
+ _global_sync_client: Optional[httpx.Client] = None
18
+ _global_async_client: Optional[httpx.AsyncClient] = None
19
+
20
+
21
+ def get_default_sync_client() -> httpx.Client:
22
+ """Get or create the global synchronous httpx client.
23
+
24
+ Returns:
25
+ A singleton httpx.Client instance with default limits.
26
+ """
27
+ global _global_sync_client
28
+ if _global_sync_client is None or _global_sync_client.is_closed:
29
+ _global_sync_client = httpx.Client(
30
+ limits=httpx.Limits(max_connections=1000, max_keepalive_connections=200), http2=True, follow_redirects=True
31
+ )
32
+ return _global_sync_client
33
+
34
+
35
+ def get_default_async_client() -> httpx.AsyncClient:
36
+ """Get or create the global asynchronous httpx client.
37
+
38
+ Returns:
39
+ A singleton httpx.AsyncClient instance with default limits.
40
+ """
41
+ global _global_async_client
42
+ if _global_async_client is None or _global_async_client.is_closed:
43
+ _global_async_client = httpx.AsyncClient(
44
+ limits=httpx.Limits(max_connections=1000, max_keepalive_connections=200), http2=True, follow_redirects=True
45
+ )
46
+ return _global_async_client
47
+
48
+
49
+ def close_sync_client() -> None:
50
+ """Closes the global sync httpx client.
51
+
52
+ Should be called during application shutdown.
53
+ """
54
+ global _global_sync_client
55
+ if _global_sync_client is not None and not _global_sync_client.is_closed:
56
+ _global_sync_client.close()
57
+
58
+
59
+ async def aclose_default_clients() -> None:
60
+ """Asynchronously close the global httpx clients.
61
+
62
+ Should be called during application shutdown in async contexts.
63
+ """
64
+ global _global_sync_client, _global_async_client
65
+ if _global_sync_client is not None and not _global_sync_client.is_closed:
66
+ _global_sync_client.close()
67
+ if _global_async_client is not None and not _global_async_client.is_closed:
68
+ await _global_async_client.aclose()
69
+
70
+
71
+ def set_default_sync_client(client: httpx.Client) -> None:
72
+ """Set the global synchronous httpx client.
73
+
74
+ IMPORTANT: Call before creating any model instances. Models cache clients on first use.
75
+
76
+ Allows consumers to override the default httpx client with custom configuration
77
+ (e.g., custom limits, timeouts, proxies, SSL verification, etc.).
78
+ This is useful at application startup to customize how all models connect.
79
+
80
+ Example:
81
+ >>> import httpx
82
+ >>> from agno.utils.http import set_default_sync_client
83
+ >>> custom_client = httpx.Client(
84
+ ... limits=httpx.Limits(max_connections=500),
85
+ ... timeout=httpx.Timeout(30.0),
86
+ ... verify=False # for dev environments
87
+ ... )
88
+ >>> set_default_sync_client(custom_client)
89
+ >>> # All models will now use this custom client
90
+
91
+ Args:
92
+ client: An httpx.Client instance to use as the global sync client.
93
+ """
94
+ global _global_sync_client
95
+ _global_sync_client = client
96
+
97
+
98
+ def set_default_async_client(client: httpx.AsyncClient) -> None:
99
+ """Set the global asynchronous httpx client.
100
+
101
+ IMPORTANT: Call before creating any model instances. Models cache clients on first use.
102
+
103
+ Allows consumers to override the default async httpx client with custom configuration
104
+ (e.g., custom limits, timeouts, proxies, SSL verification, etc.).
105
+ This is useful at application startup to customize how all models connect.
106
+
107
+ Example:
108
+ >>> import httpx
109
+ >>> from agno.utils.http import set_default_async_client
110
+ >>> custom_client = httpx.AsyncClient(
111
+ ... limits=httpx.Limits(max_connections=500),
112
+ ... timeout=httpx.Timeout(30.0),
113
+ ... verify=False # for dev environments
114
+ ... )
115
+ >>> set_default_async_client(custom_client)
116
+ >>> # All models will now use this custom client
117
+
118
+ Args:
119
+ client: An httpx.AsyncClient instance to use as the global async client.
120
+ """
121
+ global _global_async_client
122
+ _global_async_client = client
123
+
13
124
 
14
125
  def fetch_with_retry(
15
126
  url: str,
@@ -29,7 +140,7 @@ def fetch_with_retry(
29
140
  logger.error(f"Failed to fetch {url} after {max_retries} attempts: {e}")
30
141
  raise
31
142
  wait_time = backoff_factor**attempt
32
- logger.warning(f"Request failed (attempt {attempt + 1}), retrying in {wait_time} seconds...")
143
+ logger.warning("Connection error.")
33
144
  sleep(wait_time)
34
145
  except httpx.HTTPStatusError as e:
35
146
  logger.error(f"HTTP error for {url}: {e.response.status_code} - {e.response.text}")
@@ -65,7 +176,7 @@ async def async_fetch_with_retry(
65
176
  logger.error(f"Failed to fetch {url} after {max_retries} attempts: {e}")
66
177
  raise
67
178
  wait_time = backoff_factor**attempt
68
- logger.warning(f"Request failed (attempt {attempt + 1}), retrying in {wait_time} seconds...")
179
+ logger.warning("Connection error.")
69
180
  await asyncio.sleep(wait_time)
70
181
  except httpx.HTTPStatusError as e:
71
182
  logger.error(f"HTTP error for {url}: {e.response.status_code} - {e.response.text}")