agno 2.2.13__py3-none-any.whl → 2.4.3__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 (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/utils/http.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import logging
3
+ import threading
3
4
  from time import sleep
4
5
  from typing import Optional
5
6
 
@@ -10,6 +11,164 @@ logger = logging.getLogger(__name__)
10
11
  DEFAULT_MAX_RETRIES = 3
11
12
  DEFAULT_BACKOFF_FACTOR = 2 # Exponential backoff: 1, 2, 4, 8...
12
13
 
14
+ # Global httpx clients for resource efficiency
15
+ # These are shared across all models to reuse connection pools and avoid resource leaks.
16
+ # Consumers can override these at application startup using set_default_sync_client()
17
+ # and set_default_async_client() to customize limits, timeouts, proxies, etc.
18
+ _global_sync_client: Optional[httpx.Client] = None
19
+ _global_async_client: Optional[httpx.AsyncClient] = None
20
+
21
+ # Locks for thread-safe lazy initialization
22
+ _sync_client_lock = threading.Lock()
23
+ _async_client_lock = threading.Lock()
24
+
25
+
26
+ def get_default_sync_client() -> httpx.Client:
27
+ """Get or create the global synchronous httpx client.
28
+
29
+ Thread-safe lazy initialization using double-checked locking.
30
+
31
+ Note: HTTP/2 is disabled for the sync client because HTTP/2's stream
32
+ multiplexing is not thread-safe when sharing a client across threads
33
+ (e.g., when using ThreadPoolExecutor). HTTP/1.1 uses connection pooling
34
+ where each connection handles one request at a time, which is thread-safe.
35
+
36
+ Returns:
37
+ A singleton httpx.Client instance with default limits.
38
+ """
39
+ global _global_sync_client
40
+
41
+ if _global_sync_client is not None and not _global_sync_client.is_closed:
42
+ return _global_sync_client
43
+
44
+ with _sync_client_lock:
45
+ if _global_sync_client is None or _global_sync_client.is_closed:
46
+ _global_sync_client = httpx.Client(
47
+ limits=httpx.Limits(max_connections=1000, max_keepalive_connections=200),
48
+ http2=False, # Disabled for thread safety in multi-threaded contexts
49
+ follow_redirects=True,
50
+ )
51
+ return _global_sync_client
52
+
53
+
54
+ def get_default_async_client() -> httpx.AsyncClient:
55
+ """Get or create the global asynchronous httpx client.
56
+
57
+ Thread-safe lazy initialization using double-checked locking.
58
+
59
+ Note: HTTP/2 is enabled for the async client because asyncio runs in a
60
+ single-threaded event loop where HTTP/2 stream multiplexing is safe.
61
+
62
+ Returns:
63
+ A singleton httpx.AsyncClient instance with default limits.
64
+ """
65
+ global _global_async_client
66
+
67
+ if _global_async_client is not None and not _global_async_client.is_closed:
68
+ return _global_async_client
69
+
70
+ with _async_client_lock:
71
+ if _global_async_client is None or _global_async_client.is_closed:
72
+ _global_async_client = httpx.AsyncClient(
73
+ limits=httpx.Limits(max_connections=1000, max_keepalive_connections=200),
74
+ http2=True, # Safe in async context (single-threaded event loop)
75
+ follow_redirects=True,
76
+ )
77
+ return _global_async_client
78
+
79
+
80
+ def close_sync_client() -> None:
81
+ """Closes the global sync httpx client.
82
+
83
+ Thread-safe. Should be called during application shutdown.
84
+ """
85
+ global _global_sync_client
86
+ with _sync_client_lock:
87
+ if _global_sync_client is not None and not _global_sync_client.is_closed:
88
+ _global_sync_client.close()
89
+ _global_sync_client = None
90
+
91
+
92
+ async def aclose_default_clients() -> None:
93
+ """Asynchronously close the global httpx clients.
94
+
95
+ Thread-safe. Should be called during application shutdown in async contexts.
96
+ """
97
+ global _global_sync_client, _global_async_client
98
+
99
+ with _sync_client_lock:
100
+ if _global_sync_client is not None and not _global_sync_client.is_closed:
101
+ _global_sync_client.close()
102
+ _global_sync_client = None
103
+
104
+ with _async_client_lock:
105
+ if _global_async_client is not None and not _global_async_client.is_closed:
106
+ await _global_async_client.aclose()
107
+ _global_async_client = None
108
+
109
+
110
+ def set_default_sync_client(client: httpx.Client) -> None:
111
+ """Set the global synchronous httpx client.
112
+
113
+ Thread-safe. Call before creating any model instances for best results,
114
+ though this can be called at any time.
115
+
116
+ Allows consumers to override the default httpx client with custom configuration
117
+ (e.g., custom limits, timeouts, proxies, SSL verification, etc.).
118
+ This is useful at application startup to customize how all models connect.
119
+
120
+ Warning: If using this client in multi-threaded contexts (e.g., ThreadPoolExecutor),
121
+ consider disabling HTTP/2 (http2=False) to avoid thread-safety issues with
122
+ HTTP/2 stream multiplexing.
123
+
124
+ Example:
125
+ >>> import httpx
126
+ >>> from agno.utils.http import set_default_sync_client
127
+ >>> custom_client = httpx.Client(
128
+ ... limits=httpx.Limits(max_connections=500),
129
+ ... timeout=httpx.Timeout(30.0),
130
+ ... http2=False, # Recommended for multi-threaded use
131
+ ... verify=False # for dev environments
132
+ ... )
133
+ >>> set_default_sync_client(custom_client)
134
+ >>> # All models will now use this custom client
135
+
136
+ Args:
137
+ client: An httpx.Client instance to use as the global sync client.
138
+ """
139
+ global _global_sync_client
140
+ with _sync_client_lock:
141
+ _global_sync_client = client
142
+
143
+
144
+ def set_default_async_client(client: httpx.AsyncClient) -> None:
145
+ """Set the global asynchronous httpx client.
146
+
147
+ Thread-safe. Call before creating any model instances for best results,
148
+ though this can be called at any time.
149
+
150
+ Allows consumers to override the default async httpx client with custom configuration
151
+ (e.g., custom limits, timeouts, proxies, SSL verification, etc.).
152
+ This is useful at application startup to customize how all models connect.
153
+
154
+ Example:
155
+ >>> import httpx
156
+ >>> from agno.utils.http import set_default_async_client
157
+ >>> custom_client = httpx.AsyncClient(
158
+ ... limits=httpx.Limits(max_connections=500),
159
+ ... timeout=httpx.Timeout(30.0),
160
+ ... verify=False # for dev environments
161
+ ... )
162
+ >>> set_default_async_client(custom_client)
163
+ >>> # All models will now use this custom client
164
+
165
+ Args:
166
+ client: An httpx.AsyncClient instance to use as the global async client.
167
+ """
168
+ global _global_async_client
169
+ with _async_client_lock:
170
+ _global_async_client = client
171
+
13
172
 
14
173
  def fetch_with_retry(
15
174
  url: str,
@@ -29,7 +188,7 @@ def fetch_with_retry(
29
188
  logger.error(f"Failed to fetch {url} after {max_retries} attempts: {e}")
30
189
  raise
31
190
  wait_time = backoff_factor**attempt
32
- logger.warning(f"Request failed (attempt {attempt + 1}), retrying in {wait_time} seconds...")
191
+ logger.warning("Connection error.")
33
192
  sleep(wait_time)
34
193
  except httpx.HTTPStatusError as e:
35
194
  logger.error(f"HTTP error for {url}: {e.response.status_code} - {e.response.text}")
@@ -65,7 +224,7 @@ async def async_fetch_with_retry(
65
224
  logger.error(f"Failed to fetch {url} after {max_retries} attempts: {e}")
66
225
  raise
67
226
  wait_time = backoff_factor**attempt
68
- logger.warning(f"Request failed (attempt {attempt + 1}), retrying in {wait_time} seconds...")
227
+ logger.warning("Connection error.")
69
228
  await asyncio.sleep(wait_time)
70
229
  except httpx.HTTPStatusError as e:
71
230
  logger.error(f"HTTP error for {url}: {e.response.status_code} - {e.response.text}")
agno/utils/mcp.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  from functools import partial
3
+ from typing import TYPE_CHECKING, Optional, Union
3
4
  from uuid import uuid4
4
5
 
5
6
  from agno.utils.log import log_debug, log_exception
@@ -15,28 +16,68 @@ except (ImportError, ModuleNotFoundError):
15
16
  from agno.media import Image
16
17
  from agno.tools.function import ToolResult
17
18
 
19
+ if TYPE_CHECKING:
20
+ from agno.agent import Agent
21
+ from agno.run import RunContext
22
+ from agno.team.team import Team
23
+ from agno.tools.mcp.mcp import MCPTools
24
+ from agno.tools.mcp.multi_mcp import MultiMCPTools
18
25
 
19
- def get_entrypoint_for_tool(tool: MCPTool, session: ClientSession):
26
+
27
+ def get_entrypoint_for_tool(
28
+ tool: MCPTool,
29
+ session: ClientSession,
30
+ mcp_tools_instance: Optional[Union["MCPTools", "MultiMCPTools"]] = None,
31
+ server_idx: int = 0,
32
+ ):
20
33
  """
21
34
  Return an entrypoint for an MCP tool.
22
35
 
23
36
  Args:
24
37
  tool: The MCP tool to create an entrypoint for
25
- session: The session to use
38
+ session: The MCP ClientSession to use
39
+ mcp_tools_instance: Optional MCPTools or MultiMCPTools instance
40
+ server_idx: Index of the server (for MultiMCPTools)
26
41
 
27
42
  Returns:
28
43
  Callable: The entrypoint function for the tool
29
44
  """
30
45
 
31
- async def call_tool(tool_name: str, **kwargs) -> ToolResult:
46
+ async def call_tool(
47
+ tool_name: str,
48
+ run_context: Optional["RunContext"] = None,
49
+ agent: Optional["Agent"] = None,
50
+ team: Optional["Team"] = None,
51
+ **kwargs,
52
+ ) -> ToolResult:
53
+ # Execute the MCP tool call
32
54
  try:
33
- await session.send_ping()
34
- except Exception as e:
35
- print(e)
55
+ # Get the appropriate session for this run
56
+ # If mcp_tools_instance has header_provider and run_context is provided,
57
+ # this will create/reuse a session with dynamic headers
58
+ if mcp_tools_instance and hasattr(mcp_tools_instance, "get_session_for_run"):
59
+ # Import here to avoid circular imports
60
+ from agno.tools.mcp.multi_mcp import MultiMCPTools
61
+
62
+ # For MultiMCPTools, pass server_idx; for MCPTools, only pass run_context
63
+ if isinstance(mcp_tools_instance, MultiMCPTools):
64
+ active_session = await mcp_tools_instance.get_session_for_run(
65
+ run_context=run_context, server_idx=server_idx, agent=agent, team=team
66
+ )
67
+ else:
68
+ active_session = await mcp_tools_instance.get_session_for_run(
69
+ run_context=run_context, agent=agent, team=team
70
+ )
71
+ else:
72
+ active_session = session
73
+
74
+ try:
75
+ await active_session.send_ping()
76
+ except Exception as e:
77
+ log_exception(e)
36
78
 
37
- try:
38
79
  log_debug(f"Calling MCP Tool '{tool_name}' with args: {kwargs}")
39
- result: CallToolResult = await session.call_tool(tool_name, kwargs) # type: ignore
80
+ result: CallToolResult = await active_session.call_tool(tool_name, kwargs) # type: ignore
40
81
 
41
82
  # Return an error if the tool call failed
42
83
  if result.isError:
agno/utils/media.py CHANGED
@@ -56,6 +56,17 @@ def download_image(url: str, output_path: str) -> bool:
56
56
  return False
57
57
 
58
58
 
59
+ def download_audio(url: str, output_path: str) -> str:
60
+ """Download audio from URL"""
61
+ response = httpx.get(url)
62
+ response.raise_for_status()
63
+
64
+ with open(output_path, "wb") as f:
65
+ for chunk in response.iter_bytes(chunk_size=8192):
66
+ f.write(chunk)
67
+ return output_path
68
+
69
+
59
70
  def download_video(url: str, output_path: str) -> str:
60
71
  """Download video from URL"""
61
72
  response = httpx.get(url)
@@ -280,7 +291,7 @@ def reconstruct_file_from_dict(file_data):
280
291
  if isinstance(file_data, dict):
281
292
  # If content is base64 string, decode it back to bytes
282
293
  if "content" in file_data and isinstance(file_data["content"], str):
283
- return File.from_base64(
294
+ file_obj = File.from_base64(
284
295
  file_data["content"],
285
296
  id=file_data.get("id"),
286
297
  mime_type=file_data.get("mime_type"),
@@ -288,6 +299,16 @@ def reconstruct_file_from_dict(file_data):
288
299
  name=file_data.get("name"),
289
300
  format=file_data.get("format"),
290
301
  )
302
+ # Preserve additional fields that from_base64 doesn't handle
303
+ if file_data.get("size") is not None:
304
+ file_obj.size = file_data.get("size")
305
+ if file_data.get("file_type") is not None:
306
+ file_obj.file_type = file_data.get("file_type")
307
+ if file_data.get("filepath") is not None:
308
+ file_obj.filepath = file_data.get("filepath")
309
+ if file_data.get("url") is not None:
310
+ file_obj.url = file_data.get("url")
311
+ return file_obj
291
312
  else:
292
313
  # Regular file (filepath/url)
293
314
  return File(**file_data)
@@ -5,19 +5,26 @@ from agno.utils.log import log_warning
5
5
  from agno.utils.openai import images_to_message
6
6
 
7
7
 
8
- def format_message(message: Message) -> Dict[str, Any]:
8
+ def format_message(message: Message, compress_tool_results: bool = False) -> Dict[str, Any]:
9
9
  """
10
10
  Format a message into the format expected by OpenAI.
11
11
 
12
12
  Args:
13
13
  message (Message): The message to format.
14
+ compress_tool_results: Whether to compress tool results.
14
15
 
15
16
  Returns:
16
17
  Dict[str, Any]: The formatted message.
17
18
  """
19
+ # Use compressed content for tool messages if compression is active
20
+ content = message.content
21
+
22
+ if message.role == "tool":
23
+ content = message.get_content(use_compressed_content=compress_tool_results)
24
+
18
25
  message_dict: Dict[str, Any] = {
19
26
  "role": message.role,
20
- "content": message.content,
27
+ "content": content,
21
28
  "name": message.name,
22
29
  "tool_call_id": message.tool_call_id,
23
30
  "tool_calls": message.tool_calls,
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  from dataclasses import dataclass, field
3
- from typing import Any, Dict, List, Optional, Tuple
3
+ from typing import Any, Dict, List, Optional, Tuple, Union
4
4
 
5
5
  from agno.media import File, Image
6
6
  from agno.models.message import Message
@@ -221,17 +221,20 @@ def _format_file_for_message(file: File) -> Optional[Dict[str, Any]]:
221
221
  return None
222
222
 
223
223
 
224
- def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]:
224
+ def format_messages(
225
+ messages: List[Message], compress_tool_results: bool = False
226
+ ) -> Tuple[List[Dict[str, Union[str, list]]], str]:
225
227
  """
226
228
  Process the list of messages and separate them into API messages and system messages.
227
229
 
228
230
  Args:
229
231
  messages (List[Message]): The list of messages to process.
232
+ compress_tool_results: Whether to compress tool results.
230
233
 
231
234
  Returns:
232
- Tuple[List[Dict[str, str]], str]: A tuple containing the list of API messages and the concatenated system messages.
235
+ Tuple[List[Dict[str, Union[str, list]]], str]: A tuple containing the list of API messages and the concatenated system messages.
233
236
  """
234
- chat_messages: List[Dict[str, str]] = []
237
+ chat_messages: List[Dict[str, Union[str, list]]] = []
235
238
  system_messages: List[str] = []
236
239
 
237
240
  for message in messages:
@@ -301,11 +304,15 @@ def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]
301
304
  )
302
305
  elif message.role == "tool":
303
306
  content = []
307
+
308
+ # Use compressed content for tool messages if compression is active
309
+ tool_result = message.get_content(use_compressed_content=compress_tool_results)
310
+
304
311
  content.append(
305
312
  {
306
313
  "type": "tool_result",
307
314
  "tool_use_id": message.tool_call_id,
308
- "content": str(message.content),
315
+ "content": str(tool_result),
309
316
  }
310
317
  )
311
318
 
@@ -320,6 +327,7 @@ def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]
320
327
  def format_tools_for_model(tools: Optional[List[Dict[str, Any]]] = None) -> Optional[List[Dict[str, Any]]]:
321
328
  """
322
329
  Transforms function definitions into a format accepted by the Anthropic API.
330
+ Now supports strict mode for structured outputs.
323
331
  """
324
332
  if not tools:
325
333
  return None
@@ -352,7 +360,14 @@ def format_tools_for_model(tools: Optional[List[Dict[str, Any]]] = None) -> Opti
352
360
  "type": parameters.get("type", "object"),
353
361
  "properties": input_properties,
354
362
  "required": required_params,
363
+ "additionalProperties": False,
355
364
  },
356
365
  }
366
+
367
+ # Add strict mode if specified (check both function dict and tool_def top level)
368
+ strict_mode = func_def.get("strict") or tool_def.get("strict")
369
+ if strict_mode is True:
370
+ tool["strict"] = True
371
+
357
372
  parsed_tools.append(tool)
358
373
  return parsed_tools
@@ -46,21 +46,28 @@ def _format_images_for_message(message: Message, images: Sequence[Image]) -> Lis
46
46
  return message_content_with_image
47
47
 
48
48
 
49
- def format_messages(messages: List[Message]) -> List[Dict[str, Any]]:
49
+ def format_messages(messages: List[Message], compress_tool_results: bool = False) -> List[Dict[str, Any]]:
50
50
  """
51
51
  Format messages for the Cohere API.
52
52
 
53
53
  Args:
54
54
  messages (List[Message]): The list of messages.
55
+ compress_tool_results: Whether to compress tool results.
55
56
 
56
57
  Returns:
57
58
  List[Dict[str, Any]]: The formatted messages.
58
59
  """
59
60
  formatted_messages = []
60
61
  for message in messages:
62
+ # Use compressed content for tool messages if compression is active
63
+ content = message.content
64
+
65
+ if message.role == "tool":
66
+ content = message.get_content(use_compressed_content=compress_tool_results)
67
+
61
68
  message_dict = {
62
69
  "role": message.role,
63
- "content": message.content,
70
+ "content": content,
64
71
  "name": message.name,
65
72
  "tool_call_id": message.tool_call_id,
66
73
  "tool_calls": message.tool_calls,
@@ -19,13 +19,17 @@ TOOL_CALL_ROLE_MAP = {
19
19
  }
20
20
 
21
21
 
22
- def format_message(message: Message, openai_like: bool = False, tool_calls: bool = False) -> Dict[str, Any]:
22
+ def format_message(
23
+ message: Message, openai_like: bool = False, tool_calls: bool = False, compress_tool_results: bool = False
24
+ ) -> Dict[str, Any]:
23
25
  """
24
26
  Format a message into the format expected by Llama API.
25
27
 
26
28
  Args:
27
29
  message (Message): The message to format.
28
30
  openai_like (bool): Whether to format the message as an OpenAI-like message.
31
+ tool_calls (bool): Whether tool calls are present.
32
+ compress_tool_results: Whether to compress tool results.
29
33
 
30
34
  Returns:
31
35
  Dict[str, Any]: The formatted message.
@@ -52,10 +56,13 @@ def format_message(message: Message, openai_like: bool = False, tool_calls: bool
52
56
  log_warning("Audio input is currently unsupported.")
53
57
 
54
58
  if message.role == "tool":
59
+ # Use compressed content if compression is active
60
+ content = message.get_content(use_compressed_content=compress_tool_results)
61
+
55
62
  message_dict = {
56
63
  "role": "tool",
57
64
  "tool_call_id": message.tool_call_id,
58
- "content": message.content,
65
+ "content": content,
59
66
  }
60
67
 
61
68
  if message.role == "assistant":
@@ -48,7 +48,7 @@ def _format_image_for_message(image: Image) -> Optional[ImageURLChunk]:
48
48
  return None
49
49
 
50
50
 
51
- def format_messages(messages: List[Message]) -> List[MistralMessage]:
51
+ def format_messages(messages: List[Message], compress_tool_results: bool = False) -> List[MistralMessage]:
52
52
  mistral_messages: List[MistralMessage] = []
53
53
 
54
54
  for message in messages:
@@ -84,7 +84,9 @@ def format_messages(messages: List[Message]) -> List[MistralMessage]:
84
84
  elif message.role == "system":
85
85
  mistral_message = SystemMessage(role="system", content=message.content)
86
86
  elif message.role == "tool":
87
- mistral_message = ToolMessage(name="tool", content=message.content, tool_call_id=message.tool_call_id)
87
+ # Get compressed content if compression is active
88
+ tool_content = message.get_content(use_compressed_content=compress_tool_results)
89
+ mistral_message = ToolMessage(name="tool", content=tool_content, tool_call_id=message.tool_call_id)
88
90
  else:
89
91
  raise ValueError(f"Unknown role: {message.role}")
90
92
 
agno/utils/os.py ADDED
File without changes