agno 2.2.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 (575) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +51 -0
  3. agno/agent/agent.py +10405 -0
  4. agno/api/__init__.py +0 -0
  5. agno/api/agent.py +28 -0
  6. agno/api/api.py +40 -0
  7. agno/api/evals.py +22 -0
  8. agno/api/os.py +17 -0
  9. agno/api/routes.py +13 -0
  10. agno/api/schemas/__init__.py +9 -0
  11. agno/api/schemas/agent.py +16 -0
  12. agno/api/schemas/evals.py +16 -0
  13. agno/api/schemas/os.py +14 -0
  14. agno/api/schemas/response.py +6 -0
  15. agno/api/schemas/team.py +16 -0
  16. agno/api/schemas/utils.py +21 -0
  17. agno/api/schemas/workflows.py +16 -0
  18. agno/api/settings.py +53 -0
  19. agno/api/team.py +30 -0
  20. agno/api/workflow.py +28 -0
  21. agno/cloud/aws/base.py +214 -0
  22. agno/cloud/aws/s3/__init__.py +2 -0
  23. agno/cloud/aws/s3/api_client.py +43 -0
  24. agno/cloud/aws/s3/bucket.py +195 -0
  25. agno/cloud/aws/s3/object.py +57 -0
  26. agno/culture/__init__.py +3 -0
  27. agno/culture/manager.py +956 -0
  28. agno/db/__init__.py +24 -0
  29. agno/db/async_postgres/__init__.py +3 -0
  30. agno/db/base.py +598 -0
  31. agno/db/dynamo/__init__.py +3 -0
  32. agno/db/dynamo/dynamo.py +2042 -0
  33. agno/db/dynamo/schemas.py +314 -0
  34. agno/db/dynamo/utils.py +743 -0
  35. agno/db/firestore/__init__.py +3 -0
  36. agno/db/firestore/firestore.py +1795 -0
  37. agno/db/firestore/schemas.py +140 -0
  38. agno/db/firestore/utils.py +376 -0
  39. agno/db/gcs_json/__init__.py +3 -0
  40. agno/db/gcs_json/gcs_json_db.py +1335 -0
  41. agno/db/gcs_json/utils.py +228 -0
  42. agno/db/in_memory/__init__.py +3 -0
  43. agno/db/in_memory/in_memory_db.py +1160 -0
  44. agno/db/in_memory/utils.py +230 -0
  45. agno/db/json/__init__.py +3 -0
  46. agno/db/json/json_db.py +1328 -0
  47. agno/db/json/utils.py +230 -0
  48. agno/db/migrations/__init__.py +0 -0
  49. agno/db/migrations/v1_to_v2.py +635 -0
  50. agno/db/mongo/__init__.py +17 -0
  51. agno/db/mongo/async_mongo.py +2026 -0
  52. agno/db/mongo/mongo.py +1982 -0
  53. agno/db/mongo/schemas.py +87 -0
  54. agno/db/mongo/utils.py +259 -0
  55. agno/db/mysql/__init__.py +3 -0
  56. agno/db/mysql/mysql.py +2308 -0
  57. agno/db/mysql/schemas.py +138 -0
  58. agno/db/mysql/utils.py +355 -0
  59. agno/db/postgres/__init__.py +4 -0
  60. agno/db/postgres/async_postgres.py +1927 -0
  61. agno/db/postgres/postgres.py +2260 -0
  62. agno/db/postgres/schemas.py +139 -0
  63. agno/db/postgres/utils.py +442 -0
  64. agno/db/redis/__init__.py +3 -0
  65. agno/db/redis/redis.py +1660 -0
  66. agno/db/redis/schemas.py +123 -0
  67. agno/db/redis/utils.py +346 -0
  68. agno/db/schemas/__init__.py +4 -0
  69. agno/db/schemas/culture.py +120 -0
  70. agno/db/schemas/evals.py +33 -0
  71. agno/db/schemas/knowledge.py +40 -0
  72. agno/db/schemas/memory.py +46 -0
  73. agno/db/schemas/metrics.py +0 -0
  74. agno/db/singlestore/__init__.py +3 -0
  75. agno/db/singlestore/schemas.py +130 -0
  76. agno/db/singlestore/singlestore.py +2272 -0
  77. agno/db/singlestore/utils.py +384 -0
  78. agno/db/sqlite/__init__.py +4 -0
  79. agno/db/sqlite/async_sqlite.py +2293 -0
  80. agno/db/sqlite/schemas.py +133 -0
  81. agno/db/sqlite/sqlite.py +2288 -0
  82. agno/db/sqlite/utils.py +431 -0
  83. agno/db/surrealdb/__init__.py +3 -0
  84. agno/db/surrealdb/metrics.py +292 -0
  85. agno/db/surrealdb/models.py +309 -0
  86. agno/db/surrealdb/queries.py +71 -0
  87. agno/db/surrealdb/surrealdb.py +1353 -0
  88. agno/db/surrealdb/utils.py +147 -0
  89. agno/db/utils.py +116 -0
  90. agno/debug.py +18 -0
  91. agno/eval/__init__.py +14 -0
  92. agno/eval/accuracy.py +834 -0
  93. agno/eval/performance.py +773 -0
  94. agno/eval/reliability.py +306 -0
  95. agno/eval/utils.py +119 -0
  96. agno/exceptions.py +161 -0
  97. agno/filters.py +354 -0
  98. agno/guardrails/__init__.py +6 -0
  99. agno/guardrails/base.py +19 -0
  100. agno/guardrails/openai.py +144 -0
  101. agno/guardrails/pii.py +94 -0
  102. agno/guardrails/prompt_injection.py +52 -0
  103. agno/integrations/__init__.py +0 -0
  104. agno/integrations/discord/__init__.py +3 -0
  105. agno/integrations/discord/client.py +203 -0
  106. agno/knowledge/__init__.py +5 -0
  107. agno/knowledge/chunking/__init__.py +0 -0
  108. agno/knowledge/chunking/agentic.py +79 -0
  109. agno/knowledge/chunking/document.py +91 -0
  110. agno/knowledge/chunking/fixed.py +57 -0
  111. agno/knowledge/chunking/markdown.py +151 -0
  112. agno/knowledge/chunking/recursive.py +63 -0
  113. agno/knowledge/chunking/row.py +39 -0
  114. agno/knowledge/chunking/semantic.py +86 -0
  115. agno/knowledge/chunking/strategy.py +165 -0
  116. agno/knowledge/content.py +74 -0
  117. agno/knowledge/document/__init__.py +5 -0
  118. agno/knowledge/document/base.py +58 -0
  119. agno/knowledge/embedder/__init__.py +5 -0
  120. agno/knowledge/embedder/aws_bedrock.py +343 -0
  121. agno/knowledge/embedder/azure_openai.py +210 -0
  122. agno/knowledge/embedder/base.py +23 -0
  123. agno/knowledge/embedder/cohere.py +323 -0
  124. agno/knowledge/embedder/fastembed.py +62 -0
  125. agno/knowledge/embedder/fireworks.py +13 -0
  126. agno/knowledge/embedder/google.py +258 -0
  127. agno/knowledge/embedder/huggingface.py +94 -0
  128. agno/knowledge/embedder/jina.py +182 -0
  129. agno/knowledge/embedder/langdb.py +22 -0
  130. agno/knowledge/embedder/mistral.py +206 -0
  131. agno/knowledge/embedder/nebius.py +13 -0
  132. agno/knowledge/embedder/ollama.py +154 -0
  133. agno/knowledge/embedder/openai.py +195 -0
  134. agno/knowledge/embedder/sentence_transformer.py +63 -0
  135. agno/knowledge/embedder/together.py +13 -0
  136. agno/knowledge/embedder/vllm.py +262 -0
  137. agno/knowledge/embedder/voyageai.py +165 -0
  138. agno/knowledge/knowledge.py +1988 -0
  139. agno/knowledge/reader/__init__.py +7 -0
  140. agno/knowledge/reader/arxiv_reader.py +81 -0
  141. agno/knowledge/reader/base.py +95 -0
  142. agno/knowledge/reader/csv_reader.py +166 -0
  143. agno/knowledge/reader/docx_reader.py +82 -0
  144. agno/knowledge/reader/field_labeled_csv_reader.py +292 -0
  145. agno/knowledge/reader/firecrawl_reader.py +201 -0
  146. agno/knowledge/reader/json_reader.py +87 -0
  147. agno/knowledge/reader/markdown_reader.py +137 -0
  148. agno/knowledge/reader/pdf_reader.py +431 -0
  149. agno/knowledge/reader/pptx_reader.py +101 -0
  150. agno/knowledge/reader/reader_factory.py +313 -0
  151. agno/knowledge/reader/s3_reader.py +89 -0
  152. agno/knowledge/reader/tavily_reader.py +194 -0
  153. agno/knowledge/reader/text_reader.py +115 -0
  154. agno/knowledge/reader/web_search_reader.py +372 -0
  155. agno/knowledge/reader/website_reader.py +455 -0
  156. agno/knowledge/reader/wikipedia_reader.py +59 -0
  157. agno/knowledge/reader/youtube_reader.py +78 -0
  158. agno/knowledge/remote_content/__init__.py +0 -0
  159. agno/knowledge/remote_content/remote_content.py +88 -0
  160. agno/knowledge/reranker/__init__.py +3 -0
  161. agno/knowledge/reranker/base.py +14 -0
  162. agno/knowledge/reranker/cohere.py +64 -0
  163. agno/knowledge/reranker/infinity.py +195 -0
  164. agno/knowledge/reranker/sentence_transformer.py +54 -0
  165. agno/knowledge/types.py +39 -0
  166. agno/knowledge/utils.py +189 -0
  167. agno/media.py +462 -0
  168. agno/memory/__init__.py +3 -0
  169. agno/memory/manager.py +1327 -0
  170. agno/models/__init__.py +0 -0
  171. agno/models/aimlapi/__init__.py +5 -0
  172. agno/models/aimlapi/aimlapi.py +45 -0
  173. agno/models/anthropic/__init__.py +5 -0
  174. agno/models/anthropic/claude.py +757 -0
  175. agno/models/aws/__init__.py +15 -0
  176. agno/models/aws/bedrock.py +701 -0
  177. agno/models/aws/claude.py +378 -0
  178. agno/models/azure/__init__.py +18 -0
  179. agno/models/azure/ai_foundry.py +485 -0
  180. agno/models/azure/openai_chat.py +131 -0
  181. agno/models/base.py +2175 -0
  182. agno/models/cerebras/__init__.py +12 -0
  183. agno/models/cerebras/cerebras.py +501 -0
  184. agno/models/cerebras/cerebras_openai.py +112 -0
  185. agno/models/cohere/__init__.py +5 -0
  186. agno/models/cohere/chat.py +389 -0
  187. agno/models/cometapi/__init__.py +5 -0
  188. agno/models/cometapi/cometapi.py +57 -0
  189. agno/models/dashscope/__init__.py +5 -0
  190. agno/models/dashscope/dashscope.py +91 -0
  191. agno/models/deepinfra/__init__.py +5 -0
  192. agno/models/deepinfra/deepinfra.py +28 -0
  193. agno/models/deepseek/__init__.py +5 -0
  194. agno/models/deepseek/deepseek.py +61 -0
  195. agno/models/defaults.py +1 -0
  196. agno/models/fireworks/__init__.py +5 -0
  197. agno/models/fireworks/fireworks.py +26 -0
  198. agno/models/google/__init__.py +5 -0
  199. agno/models/google/gemini.py +1085 -0
  200. agno/models/groq/__init__.py +5 -0
  201. agno/models/groq/groq.py +556 -0
  202. agno/models/huggingface/__init__.py +5 -0
  203. agno/models/huggingface/huggingface.py +491 -0
  204. agno/models/ibm/__init__.py +5 -0
  205. agno/models/ibm/watsonx.py +422 -0
  206. agno/models/internlm/__init__.py +3 -0
  207. agno/models/internlm/internlm.py +26 -0
  208. agno/models/langdb/__init__.py +1 -0
  209. agno/models/langdb/langdb.py +48 -0
  210. agno/models/litellm/__init__.py +14 -0
  211. agno/models/litellm/chat.py +468 -0
  212. agno/models/litellm/litellm_openai.py +25 -0
  213. agno/models/llama_cpp/__init__.py +5 -0
  214. agno/models/llama_cpp/llama_cpp.py +22 -0
  215. agno/models/lmstudio/__init__.py +5 -0
  216. agno/models/lmstudio/lmstudio.py +25 -0
  217. agno/models/message.py +434 -0
  218. agno/models/meta/__init__.py +12 -0
  219. agno/models/meta/llama.py +475 -0
  220. agno/models/meta/llama_openai.py +78 -0
  221. agno/models/metrics.py +120 -0
  222. agno/models/mistral/__init__.py +5 -0
  223. agno/models/mistral/mistral.py +432 -0
  224. agno/models/nebius/__init__.py +3 -0
  225. agno/models/nebius/nebius.py +54 -0
  226. agno/models/nexus/__init__.py +3 -0
  227. agno/models/nexus/nexus.py +22 -0
  228. agno/models/nvidia/__init__.py +5 -0
  229. agno/models/nvidia/nvidia.py +28 -0
  230. agno/models/ollama/__init__.py +5 -0
  231. agno/models/ollama/chat.py +441 -0
  232. agno/models/openai/__init__.py +9 -0
  233. agno/models/openai/chat.py +883 -0
  234. agno/models/openai/like.py +27 -0
  235. agno/models/openai/responses.py +1050 -0
  236. agno/models/openrouter/__init__.py +5 -0
  237. agno/models/openrouter/openrouter.py +66 -0
  238. agno/models/perplexity/__init__.py +5 -0
  239. agno/models/perplexity/perplexity.py +187 -0
  240. agno/models/portkey/__init__.py +3 -0
  241. agno/models/portkey/portkey.py +81 -0
  242. agno/models/requesty/__init__.py +5 -0
  243. agno/models/requesty/requesty.py +52 -0
  244. agno/models/response.py +199 -0
  245. agno/models/sambanova/__init__.py +5 -0
  246. agno/models/sambanova/sambanova.py +28 -0
  247. agno/models/siliconflow/__init__.py +5 -0
  248. agno/models/siliconflow/siliconflow.py +25 -0
  249. agno/models/together/__init__.py +5 -0
  250. agno/models/together/together.py +25 -0
  251. agno/models/utils.py +266 -0
  252. agno/models/vercel/__init__.py +3 -0
  253. agno/models/vercel/v0.py +26 -0
  254. agno/models/vertexai/__init__.py +0 -0
  255. agno/models/vertexai/claude.py +70 -0
  256. agno/models/vllm/__init__.py +3 -0
  257. agno/models/vllm/vllm.py +78 -0
  258. agno/models/xai/__init__.py +3 -0
  259. agno/models/xai/xai.py +113 -0
  260. agno/os/__init__.py +3 -0
  261. agno/os/app.py +876 -0
  262. agno/os/auth.py +57 -0
  263. agno/os/config.py +104 -0
  264. agno/os/interfaces/__init__.py +1 -0
  265. agno/os/interfaces/a2a/__init__.py +3 -0
  266. agno/os/interfaces/a2a/a2a.py +42 -0
  267. agno/os/interfaces/a2a/router.py +250 -0
  268. agno/os/interfaces/a2a/utils.py +924 -0
  269. agno/os/interfaces/agui/__init__.py +3 -0
  270. agno/os/interfaces/agui/agui.py +47 -0
  271. agno/os/interfaces/agui/router.py +144 -0
  272. agno/os/interfaces/agui/utils.py +534 -0
  273. agno/os/interfaces/base.py +25 -0
  274. agno/os/interfaces/slack/__init__.py +3 -0
  275. agno/os/interfaces/slack/router.py +148 -0
  276. agno/os/interfaces/slack/security.py +30 -0
  277. agno/os/interfaces/slack/slack.py +47 -0
  278. agno/os/interfaces/whatsapp/__init__.py +3 -0
  279. agno/os/interfaces/whatsapp/router.py +211 -0
  280. agno/os/interfaces/whatsapp/security.py +53 -0
  281. agno/os/interfaces/whatsapp/whatsapp.py +36 -0
  282. agno/os/mcp.py +292 -0
  283. agno/os/middleware/__init__.py +7 -0
  284. agno/os/middleware/jwt.py +233 -0
  285. agno/os/router.py +1763 -0
  286. agno/os/routers/__init__.py +3 -0
  287. agno/os/routers/evals/__init__.py +3 -0
  288. agno/os/routers/evals/evals.py +430 -0
  289. agno/os/routers/evals/schemas.py +142 -0
  290. agno/os/routers/evals/utils.py +162 -0
  291. agno/os/routers/health.py +31 -0
  292. agno/os/routers/home.py +52 -0
  293. agno/os/routers/knowledge/__init__.py +3 -0
  294. agno/os/routers/knowledge/knowledge.py +997 -0
  295. agno/os/routers/knowledge/schemas.py +178 -0
  296. agno/os/routers/memory/__init__.py +3 -0
  297. agno/os/routers/memory/memory.py +515 -0
  298. agno/os/routers/memory/schemas.py +62 -0
  299. agno/os/routers/metrics/__init__.py +3 -0
  300. agno/os/routers/metrics/metrics.py +190 -0
  301. agno/os/routers/metrics/schemas.py +47 -0
  302. agno/os/routers/session/__init__.py +3 -0
  303. agno/os/routers/session/session.py +997 -0
  304. agno/os/schema.py +1055 -0
  305. agno/os/settings.py +43 -0
  306. agno/os/utils.py +630 -0
  307. agno/py.typed +0 -0
  308. agno/reasoning/__init__.py +0 -0
  309. agno/reasoning/anthropic.py +80 -0
  310. agno/reasoning/azure_ai_foundry.py +67 -0
  311. agno/reasoning/deepseek.py +63 -0
  312. agno/reasoning/default.py +97 -0
  313. agno/reasoning/gemini.py +73 -0
  314. agno/reasoning/groq.py +71 -0
  315. agno/reasoning/helpers.py +63 -0
  316. agno/reasoning/ollama.py +67 -0
  317. agno/reasoning/openai.py +86 -0
  318. agno/reasoning/step.py +31 -0
  319. agno/reasoning/vertexai.py +76 -0
  320. agno/run/__init__.py +6 -0
  321. agno/run/agent.py +787 -0
  322. agno/run/base.py +229 -0
  323. agno/run/cancel.py +81 -0
  324. agno/run/messages.py +32 -0
  325. agno/run/team.py +753 -0
  326. agno/run/workflow.py +708 -0
  327. agno/session/__init__.py +10 -0
  328. agno/session/agent.py +295 -0
  329. agno/session/summary.py +265 -0
  330. agno/session/team.py +392 -0
  331. agno/session/workflow.py +205 -0
  332. agno/team/__init__.py +37 -0
  333. agno/team/team.py +8793 -0
  334. agno/tools/__init__.py +10 -0
  335. agno/tools/agentql.py +120 -0
  336. agno/tools/airflow.py +69 -0
  337. agno/tools/api.py +122 -0
  338. agno/tools/apify.py +314 -0
  339. agno/tools/arxiv.py +127 -0
  340. agno/tools/aws_lambda.py +53 -0
  341. agno/tools/aws_ses.py +66 -0
  342. agno/tools/baidusearch.py +89 -0
  343. agno/tools/bitbucket.py +292 -0
  344. agno/tools/brandfetch.py +213 -0
  345. agno/tools/bravesearch.py +106 -0
  346. agno/tools/brightdata.py +367 -0
  347. agno/tools/browserbase.py +209 -0
  348. agno/tools/calcom.py +255 -0
  349. agno/tools/calculator.py +151 -0
  350. agno/tools/cartesia.py +187 -0
  351. agno/tools/clickup.py +244 -0
  352. agno/tools/confluence.py +240 -0
  353. agno/tools/crawl4ai.py +158 -0
  354. agno/tools/csv_toolkit.py +185 -0
  355. agno/tools/dalle.py +110 -0
  356. agno/tools/daytona.py +475 -0
  357. agno/tools/decorator.py +262 -0
  358. agno/tools/desi_vocal.py +108 -0
  359. agno/tools/discord.py +161 -0
  360. agno/tools/docker.py +716 -0
  361. agno/tools/duckdb.py +379 -0
  362. agno/tools/duckduckgo.py +91 -0
  363. agno/tools/e2b.py +703 -0
  364. agno/tools/eleven_labs.py +196 -0
  365. agno/tools/email.py +67 -0
  366. agno/tools/evm.py +129 -0
  367. agno/tools/exa.py +396 -0
  368. agno/tools/fal.py +127 -0
  369. agno/tools/file.py +240 -0
  370. agno/tools/file_generation.py +350 -0
  371. agno/tools/financial_datasets.py +288 -0
  372. agno/tools/firecrawl.py +143 -0
  373. agno/tools/function.py +1187 -0
  374. agno/tools/giphy.py +93 -0
  375. agno/tools/github.py +1760 -0
  376. agno/tools/gmail.py +922 -0
  377. agno/tools/google_bigquery.py +117 -0
  378. agno/tools/google_drive.py +270 -0
  379. agno/tools/google_maps.py +253 -0
  380. agno/tools/googlecalendar.py +674 -0
  381. agno/tools/googlesearch.py +98 -0
  382. agno/tools/googlesheets.py +377 -0
  383. agno/tools/hackernews.py +77 -0
  384. agno/tools/jina.py +101 -0
  385. agno/tools/jira.py +170 -0
  386. agno/tools/knowledge.py +218 -0
  387. agno/tools/linear.py +426 -0
  388. agno/tools/linkup.py +58 -0
  389. agno/tools/local_file_system.py +90 -0
  390. agno/tools/lumalab.py +183 -0
  391. agno/tools/mcp/__init__.py +10 -0
  392. agno/tools/mcp/mcp.py +331 -0
  393. agno/tools/mcp/multi_mcp.py +347 -0
  394. agno/tools/mcp/params.py +24 -0
  395. agno/tools/mcp_toolbox.py +284 -0
  396. agno/tools/mem0.py +193 -0
  397. agno/tools/memori.py +339 -0
  398. agno/tools/memory.py +419 -0
  399. agno/tools/mlx_transcribe.py +139 -0
  400. agno/tools/models/__init__.py +0 -0
  401. agno/tools/models/azure_openai.py +190 -0
  402. agno/tools/models/gemini.py +203 -0
  403. agno/tools/models/groq.py +158 -0
  404. agno/tools/models/morph.py +186 -0
  405. agno/tools/models/nebius.py +124 -0
  406. agno/tools/models_labs.py +195 -0
  407. agno/tools/moviepy_video.py +349 -0
  408. agno/tools/neo4j.py +134 -0
  409. agno/tools/newspaper.py +46 -0
  410. agno/tools/newspaper4k.py +93 -0
  411. agno/tools/notion.py +204 -0
  412. agno/tools/openai.py +202 -0
  413. agno/tools/openbb.py +160 -0
  414. agno/tools/opencv.py +321 -0
  415. agno/tools/openweather.py +233 -0
  416. agno/tools/oxylabs.py +385 -0
  417. agno/tools/pandas.py +102 -0
  418. agno/tools/parallel.py +314 -0
  419. agno/tools/postgres.py +257 -0
  420. agno/tools/pubmed.py +188 -0
  421. agno/tools/python.py +205 -0
  422. agno/tools/reasoning.py +283 -0
  423. agno/tools/reddit.py +467 -0
  424. agno/tools/replicate.py +117 -0
  425. agno/tools/resend.py +62 -0
  426. agno/tools/scrapegraph.py +222 -0
  427. agno/tools/searxng.py +152 -0
  428. agno/tools/serpapi.py +116 -0
  429. agno/tools/serper.py +255 -0
  430. agno/tools/shell.py +53 -0
  431. agno/tools/slack.py +136 -0
  432. agno/tools/sleep.py +20 -0
  433. agno/tools/spider.py +116 -0
  434. agno/tools/sql.py +154 -0
  435. agno/tools/streamlit/__init__.py +0 -0
  436. agno/tools/streamlit/components.py +113 -0
  437. agno/tools/tavily.py +254 -0
  438. agno/tools/telegram.py +48 -0
  439. agno/tools/todoist.py +218 -0
  440. agno/tools/tool_registry.py +1 -0
  441. agno/tools/toolkit.py +146 -0
  442. agno/tools/trafilatura.py +388 -0
  443. agno/tools/trello.py +274 -0
  444. agno/tools/twilio.py +186 -0
  445. agno/tools/user_control_flow.py +78 -0
  446. agno/tools/valyu.py +228 -0
  447. agno/tools/visualization.py +467 -0
  448. agno/tools/webbrowser.py +28 -0
  449. agno/tools/webex.py +76 -0
  450. agno/tools/website.py +54 -0
  451. agno/tools/webtools.py +45 -0
  452. agno/tools/whatsapp.py +286 -0
  453. agno/tools/wikipedia.py +63 -0
  454. agno/tools/workflow.py +278 -0
  455. agno/tools/x.py +335 -0
  456. agno/tools/yfinance.py +257 -0
  457. agno/tools/youtube.py +184 -0
  458. agno/tools/zendesk.py +82 -0
  459. agno/tools/zep.py +454 -0
  460. agno/tools/zoom.py +382 -0
  461. agno/utils/__init__.py +0 -0
  462. agno/utils/agent.py +820 -0
  463. agno/utils/audio.py +49 -0
  464. agno/utils/certs.py +27 -0
  465. agno/utils/code_execution.py +11 -0
  466. agno/utils/common.py +132 -0
  467. agno/utils/dttm.py +13 -0
  468. agno/utils/enum.py +22 -0
  469. agno/utils/env.py +11 -0
  470. agno/utils/events.py +696 -0
  471. agno/utils/format_str.py +16 -0
  472. agno/utils/functions.py +166 -0
  473. agno/utils/gemini.py +426 -0
  474. agno/utils/hooks.py +57 -0
  475. agno/utils/http.py +74 -0
  476. agno/utils/json_schema.py +234 -0
  477. agno/utils/knowledge.py +36 -0
  478. agno/utils/location.py +19 -0
  479. agno/utils/log.py +255 -0
  480. agno/utils/mcp.py +214 -0
  481. agno/utils/media.py +352 -0
  482. agno/utils/merge_dict.py +41 -0
  483. agno/utils/message.py +118 -0
  484. agno/utils/models/__init__.py +0 -0
  485. agno/utils/models/ai_foundry.py +43 -0
  486. agno/utils/models/claude.py +358 -0
  487. agno/utils/models/cohere.py +87 -0
  488. agno/utils/models/llama.py +78 -0
  489. agno/utils/models/mistral.py +98 -0
  490. agno/utils/models/openai_responses.py +140 -0
  491. agno/utils/models/schema_utils.py +153 -0
  492. agno/utils/models/watsonx.py +41 -0
  493. agno/utils/openai.py +257 -0
  494. agno/utils/pickle.py +32 -0
  495. agno/utils/pprint.py +178 -0
  496. agno/utils/print_response/__init__.py +0 -0
  497. agno/utils/print_response/agent.py +842 -0
  498. agno/utils/print_response/team.py +1724 -0
  499. agno/utils/print_response/workflow.py +1668 -0
  500. agno/utils/prompts.py +111 -0
  501. agno/utils/reasoning.py +108 -0
  502. agno/utils/response.py +163 -0
  503. agno/utils/response_iterator.py +17 -0
  504. agno/utils/safe_formatter.py +24 -0
  505. agno/utils/serialize.py +32 -0
  506. agno/utils/shell.py +22 -0
  507. agno/utils/streamlit.py +487 -0
  508. agno/utils/string.py +231 -0
  509. agno/utils/team.py +139 -0
  510. agno/utils/timer.py +41 -0
  511. agno/utils/tools.py +102 -0
  512. agno/utils/web.py +23 -0
  513. agno/utils/whatsapp.py +305 -0
  514. agno/utils/yaml_io.py +25 -0
  515. agno/vectordb/__init__.py +3 -0
  516. agno/vectordb/base.py +127 -0
  517. agno/vectordb/cassandra/__init__.py +5 -0
  518. agno/vectordb/cassandra/cassandra.py +501 -0
  519. agno/vectordb/cassandra/extra_param_mixin.py +11 -0
  520. agno/vectordb/cassandra/index.py +13 -0
  521. agno/vectordb/chroma/__init__.py +5 -0
  522. agno/vectordb/chroma/chromadb.py +929 -0
  523. agno/vectordb/clickhouse/__init__.py +9 -0
  524. agno/vectordb/clickhouse/clickhousedb.py +835 -0
  525. agno/vectordb/clickhouse/index.py +9 -0
  526. agno/vectordb/couchbase/__init__.py +3 -0
  527. agno/vectordb/couchbase/couchbase.py +1442 -0
  528. agno/vectordb/distance.py +7 -0
  529. agno/vectordb/lancedb/__init__.py +6 -0
  530. agno/vectordb/lancedb/lance_db.py +995 -0
  531. agno/vectordb/langchaindb/__init__.py +5 -0
  532. agno/vectordb/langchaindb/langchaindb.py +163 -0
  533. agno/vectordb/lightrag/__init__.py +5 -0
  534. agno/vectordb/lightrag/lightrag.py +388 -0
  535. agno/vectordb/llamaindex/__init__.py +3 -0
  536. agno/vectordb/llamaindex/llamaindexdb.py +166 -0
  537. agno/vectordb/milvus/__init__.py +4 -0
  538. agno/vectordb/milvus/milvus.py +1182 -0
  539. agno/vectordb/mongodb/__init__.py +9 -0
  540. agno/vectordb/mongodb/mongodb.py +1417 -0
  541. agno/vectordb/pgvector/__init__.py +12 -0
  542. agno/vectordb/pgvector/index.py +23 -0
  543. agno/vectordb/pgvector/pgvector.py +1462 -0
  544. agno/vectordb/pineconedb/__init__.py +5 -0
  545. agno/vectordb/pineconedb/pineconedb.py +747 -0
  546. agno/vectordb/qdrant/__init__.py +5 -0
  547. agno/vectordb/qdrant/qdrant.py +1134 -0
  548. agno/vectordb/redis/__init__.py +9 -0
  549. agno/vectordb/redis/redisdb.py +694 -0
  550. agno/vectordb/search.py +7 -0
  551. agno/vectordb/singlestore/__init__.py +10 -0
  552. agno/vectordb/singlestore/index.py +41 -0
  553. agno/vectordb/singlestore/singlestore.py +763 -0
  554. agno/vectordb/surrealdb/__init__.py +3 -0
  555. agno/vectordb/surrealdb/surrealdb.py +699 -0
  556. agno/vectordb/upstashdb/__init__.py +5 -0
  557. agno/vectordb/upstashdb/upstashdb.py +718 -0
  558. agno/vectordb/weaviate/__init__.py +8 -0
  559. agno/vectordb/weaviate/index.py +15 -0
  560. agno/vectordb/weaviate/weaviate.py +1005 -0
  561. agno/workflow/__init__.py +23 -0
  562. agno/workflow/agent.py +299 -0
  563. agno/workflow/condition.py +738 -0
  564. agno/workflow/loop.py +735 -0
  565. agno/workflow/parallel.py +824 -0
  566. agno/workflow/router.py +702 -0
  567. agno/workflow/step.py +1432 -0
  568. agno/workflow/steps.py +592 -0
  569. agno/workflow/types.py +520 -0
  570. agno/workflow/workflow.py +4321 -0
  571. agno-2.2.13.dist-info/METADATA +614 -0
  572. agno-2.2.13.dist-info/RECORD +575 -0
  573. agno-2.2.13.dist-info/WHEEL +5 -0
  574. agno-2.2.13.dist-info/licenses/LICENSE +201 -0
  575. agno-2.2.13.dist-info/top_level.txt +1 -0
agno/utils/mcp.py ADDED
@@ -0,0 +1,214 @@
1
+ import json
2
+ from functools import partial
3
+ from uuid import uuid4
4
+
5
+ from agno.utils.log import log_debug, log_exception
6
+
7
+ try:
8
+ from mcp import ClientSession
9
+ from mcp.types import CallToolResult, EmbeddedResource, ImageContent, TextContent
10
+ from mcp.types import Tool as MCPTool
11
+ except (ImportError, ModuleNotFoundError):
12
+ raise ImportError("`mcp` not installed. Please install using `pip install mcp`")
13
+
14
+
15
+ from agno.media import Image
16
+ from agno.tools.function import ToolResult
17
+
18
+
19
+ def get_entrypoint_for_tool(tool: MCPTool, session: ClientSession):
20
+ """
21
+ Return an entrypoint for an MCP tool.
22
+
23
+ Args:
24
+ tool: The MCP tool to create an entrypoint for
25
+ session: The session to use
26
+
27
+ Returns:
28
+ Callable: The entrypoint function for the tool
29
+ """
30
+
31
+ async def call_tool(tool_name: str, **kwargs) -> ToolResult:
32
+ try:
33
+ await session.send_ping()
34
+ except Exception as e:
35
+ print(e)
36
+
37
+ try:
38
+ log_debug(f"Calling MCP Tool '{tool_name}' with args: {kwargs}")
39
+ result: CallToolResult = await session.call_tool(tool_name, kwargs) # type: ignore
40
+
41
+ # Return an error if the tool call failed
42
+ if result.isError:
43
+ return ToolResult(content=f"Error from MCP tool '{tool_name}': {result.content}")
44
+
45
+ # Process the result content
46
+ response_str = ""
47
+ images = []
48
+
49
+ for content_item in result.content:
50
+ if isinstance(content_item, TextContent):
51
+ text_content = content_item.text
52
+
53
+ # Parse as JSON to check for custom image format
54
+ try:
55
+ parsed_json = json.loads(text_content)
56
+ if (
57
+ isinstance(parsed_json, dict)
58
+ and parsed_json.get("type") == "image"
59
+ and "data" in parsed_json
60
+ ):
61
+ log_debug("Found custom JSON image format in TextContent")
62
+
63
+ # Extract image data
64
+ image_data = parsed_json.get("data")
65
+ mime_type = parsed_json.get("mimeType", "image/png")
66
+
67
+ if image_data and isinstance(image_data, str):
68
+ import base64
69
+
70
+ try:
71
+ image_bytes = base64.b64decode(image_data)
72
+ except Exception as e:
73
+ log_debug(f"Failed to decode base64 image data: {e}")
74
+ image_bytes = None
75
+
76
+ if image_bytes:
77
+ img_artifact = Image(
78
+ id=str(uuid4()),
79
+ url=None,
80
+ content=image_bytes,
81
+ mime_type=mime_type,
82
+ )
83
+ images.append(img_artifact)
84
+ response_str += "Image has been generated and added to the response.\n"
85
+ continue
86
+
87
+ except (json.JSONDecodeError, TypeError):
88
+ pass
89
+
90
+ response_str += text_content + "\n"
91
+
92
+ elif isinstance(content_item, ImageContent):
93
+ # Handle standard MCP ImageContent
94
+ image_data = getattr(content_item, "data", None)
95
+
96
+ if image_data and isinstance(image_data, str):
97
+ import base64
98
+
99
+ try:
100
+ image_data = base64.b64decode(image_data)
101
+ except Exception as e:
102
+ log_debug(f"Failed to decode base64 image data: {e}")
103
+ image_data = None
104
+
105
+ img_artifact = Image(
106
+ id=str(uuid4()),
107
+ url=getattr(content_item, "url", None),
108
+ content=image_data,
109
+ mime_type=getattr(content_item, "mimeType", "image/png"),
110
+ )
111
+ images.append(img_artifact)
112
+ response_str += "Image has been generated and added to the response.\n"
113
+ elif isinstance(content_item, EmbeddedResource):
114
+ # Handle embedded resources
115
+ response_str += f"[Embedded resource: {content_item.resource.model_dump_json()}]\n"
116
+ else:
117
+ # Handle other content types
118
+ response_str += f"[Unsupported content type: {content_item.type}]\n"
119
+
120
+ return ToolResult(
121
+ content=response_str.strip(),
122
+ images=images if images else None,
123
+ )
124
+ except Exception as e:
125
+ log_exception(f"Failed to call MCP tool '{tool_name}': {e}")
126
+ return ToolResult(content=f"Error: {e}")
127
+
128
+ return partial(call_tool, tool_name=tool.name)
129
+
130
+
131
+ def prepare_command(command: str) -> list[str]:
132
+ """Sanitize a command and split it into parts before using it to run a MCP server."""
133
+ import os
134
+ import shutil
135
+ from shlex import split
136
+
137
+ # Block dangerous characters
138
+ if any(char in command for char in ["&", "|", ";", "`", "$", "(", ")"]):
139
+ raise ValueError("MCP command can't contain shell metacharacters")
140
+
141
+ parts = split(command)
142
+ if not parts:
143
+ raise ValueError("MCP command can't be empty")
144
+
145
+ # Only allow specific executables
146
+ ALLOWED_COMMANDS = {
147
+ # Python
148
+ "python",
149
+ "python3",
150
+ "uv",
151
+ "uvx",
152
+ "pipx",
153
+ # Node
154
+ "node",
155
+ "npm",
156
+ "npx",
157
+ "yarn",
158
+ "pnpm",
159
+ "bun",
160
+ # Other runtimes
161
+ "deno",
162
+ "java",
163
+ "ruby",
164
+ "docker",
165
+ }
166
+
167
+ executable = parts[0].split("/")[-1]
168
+
169
+ # Check if it's a relative path starting with ./ or ../
170
+ if executable.startswith("./") or executable.startswith("../"):
171
+ # Allow relative paths to binaries
172
+ return parts
173
+
174
+ # Check if it's an absolute path to a binary
175
+ if executable.startswith("/") and os.path.isfile(executable):
176
+ # Allow absolute paths to existing files
177
+ return parts
178
+
179
+ # Check if it's a binary in current directory without ./
180
+ if "/" not in executable and os.path.isfile(executable):
181
+ # Allow binaries in current directory
182
+ return parts
183
+
184
+ # Check if it's a binary in PATH
185
+ if shutil.which(executable):
186
+ return parts
187
+
188
+ if executable not in ALLOWED_COMMANDS:
189
+ raise ValueError(f"MCP command needs to use one of the following executables: {ALLOWED_COMMANDS}")
190
+
191
+ first_part = parts[0]
192
+ executable = first_part.split("/")[-1]
193
+
194
+ # Allow known commands
195
+ if executable in ALLOWED_COMMANDS:
196
+ return parts
197
+
198
+ # Allow relative paths to custom binaries
199
+ if first_part.startswith(("./", "../")):
200
+ return parts
201
+
202
+ # Allow absolute paths to existing files
203
+ if first_part.startswith("/") and os.path.isfile(first_part):
204
+ return parts
205
+
206
+ # Allow binaries in current directory without ./
207
+ if "/" not in first_part and os.path.isfile(first_part):
208
+ return parts
209
+
210
+ # Allow binaries in PATH
211
+ if shutil.which(first_part):
212
+ return parts
213
+
214
+ raise ValueError(f"MCP command needs to use one of the following executables: {ALLOWED_COMMANDS}")
agno/utils/media.py ADDED
@@ -0,0 +1,352 @@
1
+ import base64
2
+ import time
3
+ from enum import Enum
4
+ from pathlib import Path
5
+ from typing import List, Optional
6
+
7
+ import httpx
8
+
9
+ from agno.media import Audio, File, Image, Video
10
+ from agno.utils.log import log_info, log_warning
11
+
12
+
13
+ class SampleDataFileExtension(str, Enum):
14
+ DOCX = "docx"
15
+ PDF = "pdf"
16
+ TXT = "txt"
17
+ JSON = "json"
18
+ CSV = "csv"
19
+
20
+
21
+ def download_image(url: str, output_path: str) -> bool:
22
+ """
23
+ Downloads an image from the specified URL and saves it to the given local path.
24
+ Parameters:
25
+ - url (str): URL of the image to download.
26
+ - output_path (str): Local filesystem path to save the image
27
+ """
28
+ try:
29
+ # Send HTTP GET request to the image URL
30
+ response = httpx.get(url)
31
+ response.raise_for_status() # Raise an exception for HTTP errors
32
+
33
+ # Check if the response contains image content
34
+ content_type = response.headers.get("Content-Type")
35
+ if not content_type or not content_type.startswith("image"):
36
+ log_warning(f"URL does not point to an image. Content-Type: {content_type}")
37
+ return False
38
+
39
+ path = Path(output_path)
40
+ path.parent.mkdir(parents=True, exist_ok=True)
41
+
42
+ # Write the image to the local file in binary mode
43
+ with open(output_path, "wb") as file:
44
+ for chunk in response.iter_bytes(chunk_size=8192):
45
+ if chunk:
46
+ file.write(chunk)
47
+
48
+ log_info(f"Image successfully downloaded and saved to '{output_path}'.")
49
+ return True
50
+
51
+ except httpx.HTTPError as e:
52
+ log_warning(f"Error downloading the image: {e}")
53
+ return False
54
+ except IOError as e:
55
+ log_warning(f"Error saving the image to '{output_path}': {e}")
56
+ return False
57
+
58
+
59
+ def download_video(url: str, output_path: str) -> str:
60
+ """Download video 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
+
70
+ def download_file(url: str, output_path: str) -> None:
71
+ """
72
+ Download a file from a given URL and save it to the specified path.
73
+
74
+ Args:
75
+ url (str): The URL of the file to download
76
+ output_path (str): The local path where the file should be saved
77
+
78
+ Raises:
79
+ httpx.HTTPError: If the download fails
80
+ """
81
+ try:
82
+ response = httpx.get(url)
83
+ response.raise_for_status()
84
+
85
+ output_file = Path(output_path)
86
+ output_file.parent.mkdir(parents=True, exist_ok=True)
87
+
88
+ with open(output_file, "wb") as f:
89
+ for chunk in response.iter_bytes(chunk_size=8192):
90
+ if chunk:
91
+ f.write(chunk)
92
+
93
+ except httpx.HTTPError as e:
94
+ raise Exception(f"Failed to download file from {url}: {str(e)}")
95
+
96
+
97
+ def save_base64_data(base64_data: str, output_path: str) -> bool:
98
+ """
99
+ Saves base64 string to the specified path as bytes.
100
+ """
101
+ try:
102
+ # Decode the base64 string into bytes
103
+ decoded_data = base64.b64decode(base64_data)
104
+ except Exception as e:
105
+ raise Exception(f"An unexpected error occurred during base64 decoding: {e}")
106
+
107
+ try:
108
+ path = Path(output_path)
109
+ path.parent.mkdir(parents=True, exist_ok=True)
110
+
111
+ # Write the bytes to the local file in binary mode
112
+ with open(path, "wb") as file:
113
+ file.write(decoded_data)
114
+
115
+ log_info(f"Data successfully saved to '{path}'.")
116
+ return True
117
+ except Exception as e:
118
+ raise Exception(f"An unexpected error occurred while saving data to '{output_path}': {e}")
119
+
120
+
121
+ def wait_for_media_ready(url: str, timeout: int = 120, interval: int = 5, verbose: bool = True) -> bool:
122
+ """
123
+ Wait for media to be ready at URL by polling with HEAD requests.
124
+
125
+ Args:
126
+ url (str): The URL to check for media availability
127
+ timeout (int): Maximum time to wait in seconds (default: 120)
128
+ interval (int): Seconds between each check (default: 5)
129
+ verbose (bool): Whether to print progress messages (default: True)
130
+
131
+ Returns:
132
+ bool: True if media is ready, False if timeout reached
133
+ """
134
+ max_attempts = timeout // interval
135
+
136
+ if verbose:
137
+ log_info("Media generated! Waiting for upload to complete...")
138
+
139
+ for attempt in range(max_attempts):
140
+ try:
141
+ response = httpx.head(url, timeout=10)
142
+ response.raise_for_status()
143
+ if verbose:
144
+ log_info(f"Media ready: {url}")
145
+ return True
146
+ except httpx.HTTPError:
147
+ pass
148
+
149
+ if verbose and (attempt + 1) % 3 == 0:
150
+ log_info(f"Still processing... ({(attempt + 1) * interval}s elapsed)")
151
+
152
+ time.sleep(interval)
153
+
154
+ if verbose:
155
+ log_warning(f"Timeout waiting for media. Try this URL later: {url}")
156
+ return False
157
+
158
+
159
+ def download_knowledge_filters_sample_data(
160
+ num_files: int = 5, file_extension: SampleDataFileExtension = SampleDataFileExtension.DOCX
161
+ ) -> List[str]:
162
+ """
163
+ Download sample data files with configurable file extension.
164
+
165
+ Args:
166
+ num_files (int): Number of files to download
167
+ file_extension (SampleDataFileExtension): File extension type (DOCX, PDF, TXT, JSON)
168
+
169
+ Returns:
170
+ List[str]: List of paths to downloaded files
171
+ """
172
+ file_paths = []
173
+ root_path = Path.cwd()
174
+
175
+ for i in range(1, num_files + 1):
176
+ if file_extension == SampleDataFileExtension.CSV:
177
+ filename = f"filters_{i}.csv"
178
+ else:
179
+ filename = f"cv_{i}.{file_extension.value}"
180
+
181
+ download_path = root_path / "cookbook" / "data" / filename
182
+ download_path.parent.mkdir(parents=True, exist_ok=True)
183
+
184
+ download_file(
185
+ f"https://agno-public.s3.us-east-1.amazonaws.com/demo_data/filters/{filename}", str(download_path)
186
+ )
187
+ file_paths.append(str(download_path))
188
+ return file_paths
189
+
190
+
191
+ def reconstruct_image_from_dict(img_data):
192
+ """
193
+ Reconstruct an Image object from dictionary data.
194
+
195
+ Handles both base64-encoded content (from database) and regular image data (url/filepath).
196
+ """
197
+ try:
198
+ if isinstance(img_data, dict):
199
+ # If content is base64 string, decode it back to bytes
200
+ if "content" in img_data and isinstance(img_data["content"], str):
201
+ return Image.from_base64(
202
+ img_data["content"],
203
+ id=img_data.get("id"),
204
+ mime_type=img_data.get("mime_type"),
205
+ format=img_data.get("format"),
206
+ detail=img_data.get("detail"),
207
+ original_prompt=img_data.get("original_prompt"),
208
+ revised_prompt=img_data.get("revised_prompt"),
209
+ alt_text=img_data.get("alt_text"),
210
+ )
211
+ else:
212
+ # Regular image (filepath/url)
213
+ return Image(**img_data)
214
+ return img_data
215
+ except Exception as e:
216
+ log_warning(f"Failed to reconstruct image from dict: {e}")
217
+ return None
218
+
219
+
220
+ def reconstruct_video_from_dict(vid_data):
221
+ """
222
+ Reconstruct a Video object from dictionary data.
223
+
224
+ Handles both base64-encoded content (from database) and regular video data (url/filepath).
225
+ """
226
+ try:
227
+ if isinstance(vid_data, dict):
228
+ # If content is base64 string, decode it back to bytes
229
+ if "content" in vid_data and isinstance(vid_data["content"], str):
230
+ return Video.from_base64(
231
+ vid_data["content"],
232
+ id=vid_data.get("id"),
233
+ mime_type=vid_data.get("mime_type"),
234
+ format=vid_data.get("format"),
235
+ )
236
+ else:
237
+ # Regular video (filepath/url)
238
+ return Video(**vid_data)
239
+ return vid_data
240
+ except Exception as e:
241
+ log_warning(f"Failed to reconstruct video from dict: {e}")
242
+ return None
243
+
244
+
245
+ def reconstruct_audio_from_dict(aud_data):
246
+ """
247
+ Reconstruct an Audio object from dictionary data.
248
+
249
+ Handles both base64-encoded content (from database) and regular audio data (url/filepath).
250
+ """
251
+ try:
252
+ if isinstance(aud_data, dict):
253
+ # If content is base64 string, decode it back to bytes
254
+ if "content" in aud_data and isinstance(aud_data["content"], str):
255
+ return Audio.from_base64(
256
+ aud_data["content"],
257
+ id=aud_data.get("id"),
258
+ mime_type=aud_data.get("mime_type"),
259
+ transcript=aud_data.get("transcript"),
260
+ expires_at=aud_data.get("expires_at"),
261
+ sample_rate=aud_data.get("sample_rate", 24000),
262
+ channels=aud_data.get("channels", 1),
263
+ )
264
+ else:
265
+ # Regular audio (filepath/url)
266
+ return Audio(**aud_data)
267
+ return aud_data
268
+ except Exception as e:
269
+ log_warning(f"Failed to reconstruct audio from dict: {e}")
270
+ return None
271
+
272
+
273
+ def reconstruct_file_from_dict(file_data):
274
+ """
275
+ Reconstruct a File object from dictionary data.
276
+
277
+ Handles both base64-encoded content (from database) and regular file data (url/filepath).
278
+ """
279
+ try:
280
+ if isinstance(file_data, dict):
281
+ # If content is base64 string, decode it back to bytes
282
+ if "content" in file_data and isinstance(file_data["content"], str):
283
+ return File.from_base64(
284
+ file_data["content"],
285
+ id=file_data.get("id"),
286
+ mime_type=file_data.get("mime_type"),
287
+ filename=file_data.get("filename"),
288
+ name=file_data.get("name"),
289
+ format=file_data.get("format"),
290
+ )
291
+ else:
292
+ # Regular file (filepath/url)
293
+ return File(**file_data)
294
+ return file_data
295
+ except Exception as e:
296
+ log_warning(f"Failed to reconstruct file from dict: {e}")
297
+ return None
298
+
299
+
300
+ def reconstruct_images(images: Optional[List[dict]]) -> Optional[List[Image]]:
301
+ """Reconstruct a list of Image objects from list of dictionaries.
302
+
303
+ Failed reconstructions are skipped with a warning logged.
304
+ """
305
+ if not images:
306
+ return None
307
+ reconstructed = [reconstruct_image_from_dict(img_data) for img_data in images]
308
+ valid_images = [img for img in reconstructed if img is not None]
309
+ return valid_images if valid_images else None
310
+
311
+
312
+ def reconstruct_videos(videos: Optional[List[dict]]) -> Optional[List[Video]]:
313
+ """Reconstruct a list of Video objects from list of dictionaries.
314
+
315
+ Failed reconstructions are skipped with a warning logged.
316
+ """
317
+ if not videos:
318
+ return None
319
+ reconstructed = [reconstruct_video_from_dict(vid_data) for vid_data in videos]
320
+ valid_videos = [vid for vid in reconstructed if vid is not None]
321
+ return valid_videos if valid_videos else None
322
+
323
+
324
+ def reconstruct_audio_list(audio: Optional[List[dict]]) -> Optional[List[Audio]]:
325
+ """Reconstruct a list of Audio objects from list of dictionaries.
326
+
327
+ Failed reconstructions are skipped with a warning logged.
328
+ """
329
+ if not audio:
330
+ return None
331
+ reconstructed = [reconstruct_audio_from_dict(aud_data) for aud_data in audio]
332
+ valid_audio = [aud for aud in reconstructed if aud is not None]
333
+ return valid_audio if valid_audio else None
334
+
335
+
336
+ def reconstruct_files(files: Optional[List[dict]]) -> Optional[List[File]]:
337
+ """Reconstruct a list of File objects from list of dictionaries.
338
+
339
+ Failed reconstructions are skipped with a warning logged.
340
+ """
341
+ if not files:
342
+ return None
343
+ reconstructed = [reconstruct_file_from_dict(file_data) for file_data in files]
344
+ valid_files = [f for f in reconstructed if f is not None]
345
+ return valid_files if valid_files else None
346
+
347
+
348
+ def reconstruct_response_audio(audio: Optional[dict]) -> Optional[Audio]:
349
+ """Reconstruct a single Audio object for response audio."""
350
+ if not audio:
351
+ return None
352
+ return reconstruct_audio_from_dict(audio)
@@ -0,0 +1,41 @@
1
+ from typing import Any, Dict, List
2
+
3
+
4
+ def merge_dictionaries(a: Dict[str, Any], b: Dict[str, Any]) -> None:
5
+ """
6
+ Recursively merges two dictionaries.
7
+ If there are conflicting keys, values from 'b' will take precedence.
8
+
9
+ Args:
10
+ a (Dict[str, Any]): The first dictionary to be merged.
11
+ b (Dict[str, Any]): The second dictionary, whose values will take precedence.
12
+
13
+ Returns:
14
+ None: The function modifies the first dictionary in place.
15
+ """
16
+ for key in b:
17
+ if key in a and isinstance(a[key], dict) and isinstance(b[key], dict):
18
+ merge_dictionaries(a[key], b[key])
19
+ else:
20
+ a[key] = b[key]
21
+
22
+
23
+ def merge_parallel_session_states(original_state: Dict[str, Any], modified_states: List[Dict[str, Any]]) -> None:
24
+ """
25
+ Smart merge for parallel session states that only applies actual changes.
26
+ This prevents parallel steps from overwriting each other's changes.
27
+ """
28
+ if not original_state or not modified_states:
29
+ return
30
+
31
+ # Collect all actual changes (keys where value differs from original)
32
+ all_changes = {}
33
+ for modified_state in modified_states:
34
+ if modified_state:
35
+ for key, value in modified_state.items():
36
+ if key not in original_state or original_state[key] != value:
37
+ all_changes[key] = value
38
+
39
+ # Apply all collected changes to the original state
40
+ for key, value in all_changes.items():
41
+ original_state[key] = value