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
@@ -0,0 +1,824 @@
1
+ import asyncio
2
+ from concurrent.futures import ThreadPoolExecutor, as_completed
3
+ from copy import deepcopy
4
+ from dataclasses import dataclass
5
+ from typing import Any, AsyncIterator, Awaitable, Callable, Dict, Iterator, List, Optional, Union
6
+ from uuid import uuid4
7
+
8
+ from agno.models.metrics import Metrics
9
+ from agno.run.agent import RunOutputEvent
10
+ from agno.run.base import RunContext
11
+ from agno.run.team import TeamRunOutputEvent
12
+ from agno.run.workflow import (
13
+ ParallelExecutionCompletedEvent,
14
+ ParallelExecutionStartedEvent,
15
+ WorkflowRunOutput,
16
+ WorkflowRunOutputEvent,
17
+ )
18
+ from agno.session.workflow import WorkflowSession
19
+ from agno.utils.log import log_debug, logger
20
+ from agno.utils.merge_dict import merge_parallel_session_states
21
+ from agno.workflow.condition import Condition
22
+ from agno.workflow.step import Step
23
+ from agno.workflow.types import StepInput, StepOutput, StepType
24
+
25
+ WorkflowSteps = List[
26
+ Union[
27
+ Callable[
28
+ [StepInput], Union[StepOutput, Awaitable[StepOutput], Iterator[StepOutput], AsyncIterator[StepOutput]]
29
+ ],
30
+ Step,
31
+ "Steps", # type: ignore # noqa: F821
32
+ "Loop", # type: ignore # noqa: F821
33
+ "Parallel", # type: ignore # noqa: F821
34
+ "Condition", # type: ignore # noqa: F821
35
+ "Router", # type: ignore # noqa: F821
36
+ ]
37
+ ]
38
+
39
+
40
+ @dataclass
41
+ class Parallel:
42
+ """A list of steps that execute in parallel"""
43
+
44
+ steps: WorkflowSteps
45
+
46
+ name: Optional[str] = None
47
+ description: Optional[str] = None
48
+
49
+ def __init__(
50
+ self,
51
+ *steps: WorkflowSteps,
52
+ name: Optional[str] = None,
53
+ description: Optional[str] = None,
54
+ ):
55
+ self.steps = list(steps)
56
+ self.name = name
57
+ self.description = description
58
+
59
+ def _prepare_steps(self):
60
+ """Prepare the steps for execution - mirrors workflow logic"""
61
+ from agno.agent.agent import Agent
62
+ from agno.team.team import Team
63
+ from agno.workflow.loop import Loop
64
+ from agno.workflow.router import Router
65
+ from agno.workflow.step import Step
66
+ from agno.workflow.steps import Steps
67
+
68
+ prepared_steps: WorkflowSteps = []
69
+ for step in self.steps:
70
+ if callable(step) and hasattr(step, "__name__"):
71
+ prepared_steps.append(Step(name=step.__name__, description="User-defined callable step", executor=step))
72
+ elif isinstance(step, Agent):
73
+ prepared_steps.append(Step(name=step.name, description=step.description, agent=step))
74
+ elif isinstance(step, Team):
75
+ prepared_steps.append(Step(name=step.name, description=step.description, team=step))
76
+ elif isinstance(step, (Step, Steps, Loop, Parallel, Condition, Router)):
77
+ prepared_steps.append(step)
78
+ else:
79
+ raise ValueError(f"Invalid step type: {type(step).__name__}")
80
+
81
+ self.steps = prepared_steps
82
+
83
+ def _aggregate_results(self, step_outputs: List[StepOutput]) -> StepOutput:
84
+ """Aggregate multiple step outputs into a single StepOutput"""
85
+ if not step_outputs:
86
+ return StepOutput(
87
+ step_name=self.name or "Parallel",
88
+ step_id=str(uuid4()),
89
+ step_type="Parallel",
90
+ content="No parallel steps executed",
91
+ steps=[],
92
+ )
93
+
94
+ if len(step_outputs) == 1:
95
+ # Single result, but still create a Parallel container
96
+ single_result = step_outputs[0]
97
+ aggregated_metrics = self._extract_metrics_from_response(step_outputs)
98
+
99
+ return StepOutput(
100
+ step_name=self.name or "Parallel",
101
+ step_id=str(uuid4()),
102
+ step_type=StepType.PARALLEL,
103
+ content=f"Parallel {self.name or 'execution'} completed with 1 result",
104
+ executor_name=self.name or "Parallel",
105
+ images=single_result.images,
106
+ videos=single_result.videos,
107
+ audio=single_result.audio,
108
+ metrics=aggregated_metrics,
109
+ success=single_result.success,
110
+ error=single_result.error,
111
+ stop=single_result.stop,
112
+ steps=step_outputs, # This is the key addition
113
+ )
114
+
115
+ early_termination_requested = any(output.stop for output in step_outputs if hasattr(output, "stop"))
116
+
117
+ # Multiple results - aggregate them
118
+ aggregated_content = f"Parallel {self.name or 'execution'} completed with {len(step_outputs)} results"
119
+
120
+ # Combine all media from parallel steps
121
+ all_images = []
122
+ all_videos = []
123
+ all_audio = []
124
+ has_any_failure = False
125
+
126
+ for result in step_outputs:
127
+ all_images.extend(result.images or [])
128
+ all_videos.extend(result.videos or [])
129
+ all_audio.extend(result.audio or [])
130
+ if result.success is False:
131
+ has_any_failure = True
132
+
133
+ # Extract metrics using the dedicated method
134
+ aggregated_metrics = self._extract_metrics_from_response(step_outputs)
135
+
136
+ return StepOutput(
137
+ step_name=self.name or "Parallel",
138
+ step_id=str(uuid4()),
139
+ step_type=StepType.PARALLEL,
140
+ executor_type="parallel",
141
+ executor_name=self.name or "Parallel",
142
+ content=aggregated_content,
143
+ images=all_images if all_images else None,
144
+ videos=all_videos if all_videos else None,
145
+ audio=all_audio if all_audio else None,
146
+ success=not has_any_failure,
147
+ stop=early_termination_requested,
148
+ metrics=aggregated_metrics,
149
+ steps=step_outputs,
150
+ )
151
+
152
+ def _extract_metrics_from_response(self, step_outputs: List[StepOutput]) -> Optional[Metrics]:
153
+ """Extract and aggregate metrics from parallel step outputs"""
154
+ if not step_outputs:
155
+ return None
156
+
157
+ # Aggregate metrics from all parallel step outputs
158
+ total_metrics = Metrics()
159
+
160
+ for result in step_outputs:
161
+ if result.metrics:
162
+ total_metrics = total_metrics + result.metrics
163
+
164
+ # If no metrics were found, return None
165
+ if (
166
+ total_metrics.input_tokens == 0
167
+ and total_metrics.output_tokens == 0
168
+ and total_metrics.total_tokens == 0
169
+ and total_metrics.duration is None
170
+ ):
171
+ return None
172
+
173
+ return total_metrics
174
+
175
+ def _build_aggregated_content(self, step_outputs: List[StepOutput]) -> str:
176
+ """Build aggregated content from multiple step outputs"""
177
+ aggregated = "## Parallel Execution Results\n\n"
178
+
179
+ for i, output in enumerate(step_outputs):
180
+ step_name = output.step_name or f"Step {i + 1}"
181
+ content = output.content or ""
182
+
183
+ # Add status indicator
184
+ if output.success is False:
185
+ status_icon = "❌ FAILURE:"
186
+ else:
187
+ status_icon = "✅ SUCCESS:"
188
+
189
+ aggregated += f"### {status_icon} {step_name}\n"
190
+ if content and str(content).strip():
191
+ aggregated += f"{content}\n\n"
192
+ else:
193
+ aggregated += "*(No content)*\n\n"
194
+
195
+ return aggregated.strip()
196
+
197
+ def execute(
198
+ self,
199
+ step_input: StepInput,
200
+ session_id: Optional[str] = None,
201
+ user_id: Optional[str] = None,
202
+ workflow_run_response: Optional[WorkflowRunOutput] = None,
203
+ store_executor_outputs: bool = True,
204
+ run_context: Optional[RunContext] = None,
205
+ session_state: Optional[Dict[str, Any]] = None,
206
+ workflow_session: Optional[WorkflowSession] = None,
207
+ add_workflow_history_to_steps: Optional[bool] = False,
208
+ num_history_runs: int = 3,
209
+ ) -> StepOutput:
210
+ """Execute all steps in parallel and return aggregated result"""
211
+ # Use workflow logger for parallel orchestration
212
+ log_debug(f"Parallel Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="=")
213
+
214
+ self._prepare_steps()
215
+
216
+ # Create individual session_state copies for each step to prevent race conditions
217
+ session_state_copies = []
218
+ for _ in range(len(self.steps)):
219
+ # If using run context, no need to deepcopy the state. We want the direct reference.
220
+ if run_context is not None and run_context.session_state is not None:
221
+ session_state_copies.append(run_context.session_state)
222
+ else:
223
+ if session_state is not None:
224
+ session_state_copies.append(deepcopy(session_state))
225
+ else:
226
+ session_state_copies.append({})
227
+
228
+ def execute_step_with_index(step_with_index):
229
+ """Execute a single step and preserve its original index"""
230
+ idx, step = step_with_index
231
+ # Use the individual session_state copy for this step
232
+ step_session_state = session_state_copies[idx]
233
+
234
+ try:
235
+ step_result = step.execute(
236
+ step_input,
237
+ session_id=session_id,
238
+ user_id=user_id,
239
+ workflow_run_response=workflow_run_response,
240
+ store_executor_outputs=store_executor_outputs,
241
+ workflow_session=workflow_session,
242
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
243
+ num_history_runs=num_history_runs,
244
+ run_context=run_context,
245
+ session_state=step_session_state,
246
+ ) # type: ignore[union-attr]
247
+ return idx, step_result, step_session_state
248
+ except Exception as exc:
249
+ parallel_step_name = getattr(step, "name", f"step_{idx}")
250
+ logger.error(f"Parallel step {parallel_step_name} failed: {exc}")
251
+ return (
252
+ idx,
253
+ StepOutput(
254
+ step_name=parallel_step_name,
255
+ content=f"Step {parallel_step_name} failed: {str(exc)}",
256
+ success=False,
257
+ error=str(exc),
258
+ ),
259
+ step_session_state,
260
+ )
261
+
262
+ # Use index to preserve order
263
+ indexed_steps = list(enumerate(self.steps))
264
+
265
+ with ThreadPoolExecutor(max_workers=len(self.steps)) as executor:
266
+ # Submit all tasks with their original indices
267
+ future_to_index = {
268
+ executor.submit(execute_step_with_index, indexed_step): indexed_step[0]
269
+ for indexed_step in indexed_steps
270
+ }
271
+
272
+ # Collect results and modified session_state copies
273
+ results_with_indices = []
274
+ modified_session_states = []
275
+ for future in as_completed(future_to_index):
276
+ try:
277
+ index, result, modified_session_state = future.result()
278
+ results_with_indices.append((index, result))
279
+ modified_session_states.append(modified_session_state)
280
+ step_name = getattr(self.steps[index], "name", f"step_{index}")
281
+ log_debug(f"Parallel step {step_name} completed")
282
+ except Exception as e:
283
+ index = future_to_index[future]
284
+ step_name = getattr(self.steps[index], "name", f"step_{index}")
285
+ logger.error(f"Parallel step {step_name} failed: {e}")
286
+ results_with_indices.append(
287
+ (
288
+ index,
289
+ StepOutput(
290
+ step_name=step_name,
291
+ content=f"Step {step_name} failed: {str(e)}",
292
+ success=False,
293
+ error=str(e),
294
+ ),
295
+ )
296
+ )
297
+
298
+ if run_context is None and session_state is not None:
299
+ merge_parallel_session_states(session_state, modified_session_states)
300
+
301
+ # Sort by original index to preserve order
302
+ results_with_indices.sort(key=lambda x: x[0])
303
+ results = [result for _, result in results_with_indices]
304
+
305
+ # Flatten results - handle steps that return List[StepOutput] (like Condition/Loop)
306
+ flattened_results: List[StepOutput] = []
307
+ for result in results:
308
+ if isinstance(result, list):
309
+ flattened_results.extend(result)
310
+ else:
311
+ flattened_results.append(result)
312
+
313
+ # Aggregate all results into a single StepOutput
314
+ aggregated_result = self._aggregate_results(flattened_results)
315
+
316
+ # Use workflow logger for parallel completion
317
+ log_debug(f"Parallel End: {self.name} ({len(self.steps)} steps)", center=True, symbol="=")
318
+
319
+ return aggregated_result
320
+
321
+ def execute_stream(
322
+ self,
323
+ step_input: StepInput,
324
+ session_id: Optional[str] = None,
325
+ user_id: Optional[str] = None,
326
+ stream_events: bool = False,
327
+ stream_intermediate_steps: bool = False,
328
+ stream_executor_events: bool = True,
329
+ workflow_run_response: Optional[WorkflowRunOutput] = None,
330
+ step_index: Optional[Union[int, tuple]] = None,
331
+ store_executor_outputs: bool = True,
332
+ run_context: Optional[RunContext] = None,
333
+ session_state: Optional[Dict[str, Any]] = None,
334
+ parent_step_id: Optional[str] = None,
335
+ workflow_session: Optional[WorkflowSession] = None,
336
+ add_workflow_history_to_steps: Optional[bool] = False,
337
+ num_history_runs: int = 3,
338
+ ) -> Iterator[Union[WorkflowRunOutputEvent, StepOutput]]:
339
+ """Execute all steps in parallel with streaming support"""
340
+ log_debug(f"Parallel Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="=")
341
+
342
+ parallel_step_id = str(uuid4())
343
+
344
+ self._prepare_steps()
345
+
346
+ # Create individual session_state copies for each step to prevent race conditions
347
+ session_state_copies = []
348
+ for _ in range(len(self.steps)):
349
+ # If using run context, no need to deepcopy the state. We want the direct reference.
350
+ if run_context is not None and run_context.session_state is not None:
351
+ session_state_copies.append(run_context.session_state)
352
+ else:
353
+ if session_state is not None:
354
+ session_state_copies.append(deepcopy(session_state))
355
+ else:
356
+ session_state_copies.append({})
357
+
358
+ # Considering both stream_events and stream_intermediate_steps (deprecated)
359
+ stream_events = stream_events or stream_intermediate_steps
360
+
361
+ if stream_events and workflow_run_response:
362
+ # Yield parallel step started event
363
+ yield ParallelExecutionStartedEvent(
364
+ run_id=workflow_run_response.run_id or "",
365
+ workflow_name=workflow_run_response.workflow_name or "",
366
+ workflow_id=workflow_run_response.workflow_id or "",
367
+ session_id=workflow_run_response.session_id or "",
368
+ step_name=self.name,
369
+ step_index=step_index,
370
+ parallel_step_count=len(self.steps),
371
+ step_id=parallel_step_id,
372
+ parent_step_id=parent_step_id,
373
+ )
374
+
375
+ import queue
376
+
377
+ event_queue = queue.Queue() # type: ignore
378
+ step_results = []
379
+ modified_session_states = []
380
+
381
+ def execute_step_stream_with_index(step_with_index):
382
+ """Execute a single step with streaming and put events in queue immediately"""
383
+ idx, step = step_with_index
384
+ # Use the individual session_state copy for this step
385
+ step_session_state = session_state_copies[idx]
386
+
387
+ try:
388
+ step_outputs = []
389
+
390
+ # If step_index is None or integer (main step): create (step_index, sub_index)
391
+ # If step_index is tuple (child step): all parallel sub-steps get same index
392
+ if step_index is None or isinstance(step_index, int):
393
+ # Parallel is a main step - sub-steps get sequential numbers: 1.1, 1.2, 1.3
394
+ sub_step_index = (step_index if step_index is not None else 0, idx)
395
+ else:
396
+ # Parallel is a child step - all sub-steps get the same parent number: 1.1, 1.1, 1.1
397
+ sub_step_index = step_index
398
+
399
+ # All workflow step types have execute_stream() method
400
+ for event in step.execute_stream( # type: ignore[union-attr]
401
+ step_input,
402
+ session_id=session_id,
403
+ user_id=user_id,
404
+ stream_events=stream_events,
405
+ stream_executor_events=stream_executor_events,
406
+ workflow_run_response=workflow_run_response,
407
+ step_index=sub_step_index,
408
+ store_executor_outputs=store_executor_outputs,
409
+ session_state=step_session_state,
410
+ parent_step_id=parallel_step_id,
411
+ workflow_session=workflow_session,
412
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
413
+ num_history_runs=num_history_runs,
414
+ ):
415
+ # Put event immediately in queue
416
+ event_queue.put(("event", idx, event))
417
+ if isinstance(event, StepOutput):
418
+ step_outputs.append(event)
419
+
420
+ # Signal completion for this step
421
+ event_queue.put(("complete", idx, step_outputs, step_session_state))
422
+ return idx, step_outputs, step_session_state
423
+ except Exception as exc:
424
+ parallel_step_name = getattr(step, "name", f"step_{idx}")
425
+ logger.error(f"Parallel step {parallel_step_name} streaming failed: {exc}")
426
+ error_event = StepOutput(
427
+ step_name=parallel_step_name,
428
+ content=f"Step {parallel_step_name} failed: {str(exc)}",
429
+ success=False,
430
+ error=str(exc),
431
+ )
432
+ event_queue.put(("event", idx, error_event))
433
+ event_queue.put(("complete", idx, [error_event], step_session_state))
434
+ return idx, [error_event], step_session_state
435
+
436
+ # Submit all parallel tasks
437
+ indexed_steps = list(enumerate(self.steps))
438
+
439
+ with ThreadPoolExecutor(max_workers=len(self.steps)) as executor:
440
+ # Submit all tasks
441
+ futures = [executor.submit(execute_step_stream_with_index, indexed_step) for indexed_step in indexed_steps]
442
+
443
+ # Process events from queue as they arrive
444
+ completed_steps = 0
445
+ total_steps = len(self.steps)
446
+
447
+ while completed_steps < total_steps:
448
+ try:
449
+ message_type, step_idx, *data = event_queue.get(timeout=1.0)
450
+
451
+ if message_type == "event":
452
+ event = data[0]
453
+ # Yield events immediately as they arrive (except StepOutputs)
454
+ if not isinstance(event, StepOutput):
455
+ yield event
456
+
457
+ elif message_type == "complete":
458
+ step_outputs, step_session_state = data
459
+ step_results.extend(step_outputs)
460
+ modified_session_states.append(step_session_state)
461
+ completed_steps += 1
462
+
463
+ step_name = getattr(self.steps[step_idx], "name", f"step_{step_idx}")
464
+ log_debug(f"Parallel step {step_name} streaming completed")
465
+
466
+ except queue.Empty:
467
+ for i, future in enumerate(futures):
468
+ if future.done() and future.exception():
469
+ logger.error(f"Parallel step {i} failed: {future.exception()}")
470
+ if completed_steps < total_steps:
471
+ completed_steps += 1
472
+ except Exception as e:
473
+ logger.error(f"Error processing parallel step events: {e}")
474
+ completed_steps += 1
475
+
476
+ for future in futures:
477
+ try:
478
+ future.result()
479
+ except Exception as e:
480
+ logger.error(f"Future completion error: {e}")
481
+
482
+ # Merge all session_state changes back into the original session_state
483
+ if run_context is None and session_state is not None:
484
+ merge_parallel_session_states(session_state, modified_session_states)
485
+
486
+ # Flatten step_results - handle steps that return List[StepOutput] (like Condition/Loop)
487
+ flattened_step_results: List[StepOutput] = []
488
+ for result in step_results:
489
+ if isinstance(result, list):
490
+ flattened_step_results.extend(result)
491
+ else:
492
+ flattened_step_results.append(result)
493
+
494
+ # Create aggregated result from all step outputs
495
+ aggregated_result = self._aggregate_results(flattened_step_results)
496
+
497
+ # Yield the final aggregated StepOutput
498
+ yield aggregated_result
499
+
500
+ log_debug(f"Parallel End: {self.name} ({len(self.steps)} steps)", center=True, symbol="=")
501
+
502
+ if stream_events and workflow_run_response:
503
+ # Yield parallel step completed event
504
+ yield ParallelExecutionCompletedEvent(
505
+ run_id=workflow_run_response.run_id or "",
506
+ workflow_name=workflow_run_response.workflow_name or "",
507
+ workflow_id=workflow_run_response.workflow_id or "",
508
+ session_id=workflow_run_response.session_id or "",
509
+ step_name=self.name,
510
+ step_index=step_index,
511
+ parallel_step_count=len(self.steps),
512
+ step_results=flattened_step_results,
513
+ step_id=parallel_step_id,
514
+ parent_step_id=parent_step_id,
515
+ )
516
+
517
+ async def aexecute(
518
+ self,
519
+ step_input: StepInput,
520
+ session_id: Optional[str] = None,
521
+ user_id: Optional[str] = None,
522
+ workflow_run_response: Optional[WorkflowRunOutput] = None,
523
+ store_executor_outputs: bool = True,
524
+ run_context: Optional[RunContext] = None,
525
+ session_state: Optional[Dict[str, Any]] = None,
526
+ workflow_session: Optional[WorkflowSession] = None,
527
+ add_workflow_history_to_steps: Optional[bool] = False,
528
+ num_history_runs: int = 3,
529
+ ) -> StepOutput:
530
+ """Execute all steps in parallel using asyncio and return aggregated result"""
531
+ # Use workflow logger for async parallel orchestration
532
+ log_debug(f"Parallel Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="=")
533
+
534
+ self._prepare_steps()
535
+
536
+ # Create individual session_state copies for each step to prevent race conditions
537
+ session_state_copies = []
538
+ for _ in range(len(self.steps)):
539
+ # If using run context, no need to deepcopy the state. We want the direct reference.
540
+ if run_context is not None and run_context.session_state is not None:
541
+ session_state_copies.append(run_context.session_state)
542
+ else:
543
+ if session_state is not None:
544
+ session_state_copies.append(deepcopy(session_state))
545
+ else:
546
+ session_state_copies.append({})
547
+
548
+ async def execute_step_async_with_index(step_with_index):
549
+ """Execute a single step asynchronously and preserve its original index"""
550
+ idx, step = step_with_index
551
+ # Use the individual session_state copy for this step
552
+ step_session_state = session_state_copies[idx]
553
+
554
+ try:
555
+ inner_step_result = await step.aexecute(
556
+ step_input,
557
+ session_id=session_id,
558
+ user_id=user_id,
559
+ workflow_run_response=workflow_run_response,
560
+ store_executor_outputs=store_executor_outputs,
561
+ workflow_session=workflow_session,
562
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
563
+ num_history_runs=num_history_runs,
564
+ session_state=step_session_state,
565
+ ) # type: ignore[union-attr]
566
+ return idx, inner_step_result, step_session_state
567
+ except Exception as exc:
568
+ parallel_step_name = getattr(step, "name", f"step_{idx}")
569
+ logger.error(f"Parallel step {parallel_step_name} failed: {exc}")
570
+ return (
571
+ idx,
572
+ StepOutput(
573
+ step_name=parallel_step_name,
574
+ content=f"Step {parallel_step_name} failed: {str(exc)}",
575
+ success=False,
576
+ error=str(exc),
577
+ ),
578
+ step_session_state,
579
+ )
580
+
581
+ # Use index to preserve order
582
+ indexed_steps = list(enumerate(self.steps))
583
+
584
+ # Create tasks for all steps with their indices
585
+ tasks = [execute_step_async_with_index(indexed_step) for indexed_step in indexed_steps]
586
+
587
+ # Execute all tasks concurrently
588
+ results_with_indices = await asyncio.gather(*tasks, return_exceptions=True)
589
+
590
+ # Process results and handle exceptions, preserving order
591
+ processed_results_with_indices = []
592
+ modified_session_states = []
593
+ for i, result in enumerate(results_with_indices):
594
+ if isinstance(result, Exception):
595
+ step_name = getattr(self.steps[i], "name", f"step_{i}")
596
+ logger.error(f"Parallel step {step_name} failed: {result}")
597
+ processed_results_with_indices.append(
598
+ (
599
+ i,
600
+ StepOutput(
601
+ step_name=step_name,
602
+ content=f"Step {step_name} failed: {str(result)}",
603
+ success=False,
604
+ error=str(result),
605
+ ),
606
+ )
607
+ )
608
+ # Still collect the session state copy for failed steps
609
+ modified_session_states.append(session_state_copies[i])
610
+ else:
611
+ index, step_result, modified_session_state = result # type: ignore[misc]
612
+ processed_results_with_indices.append((index, step_result))
613
+ modified_session_states.append(modified_session_state)
614
+ step_name = getattr(self.steps[index], "name", f"step_{index}")
615
+ log_debug(f"Parallel step {step_name} completed")
616
+
617
+ # Smart merge all session_state changes back into the original session_state
618
+ if run_context is None and session_state is not None:
619
+ merge_parallel_session_states(session_state, modified_session_states)
620
+
621
+ # Sort by original index to preserve order
622
+ processed_results_with_indices.sort(key=lambda x: x[0])
623
+ results = [result for _, result in processed_results_with_indices]
624
+
625
+ # Flatten results - handle steps that return List[StepOutput] (like Condition/Loop)
626
+ flattened_results: List[StepOutput] = []
627
+ for result in results:
628
+ if isinstance(result, list):
629
+ flattened_results.extend(result)
630
+ else:
631
+ flattened_results.append(result)
632
+
633
+ # Aggregate all results into a single StepOutput
634
+ aggregated_result = self._aggregate_results(flattened_results)
635
+
636
+ # Use workflow logger for async parallel completion
637
+ log_debug(f"Parallel End: {self.name} ({len(self.steps)} steps)", center=True, symbol="=")
638
+
639
+ return aggregated_result
640
+
641
+ async def aexecute_stream(
642
+ self,
643
+ step_input: StepInput,
644
+ session_id: Optional[str] = None,
645
+ user_id: Optional[str] = None,
646
+ stream_events: bool = False,
647
+ stream_intermediate_steps: bool = False,
648
+ stream_executor_events: bool = True,
649
+ workflow_run_response: Optional[WorkflowRunOutput] = None,
650
+ step_index: Optional[Union[int, tuple]] = None,
651
+ store_executor_outputs: bool = True,
652
+ run_context: Optional[RunContext] = None,
653
+ session_state: Optional[Dict[str, Any]] = None,
654
+ parent_step_id: Optional[str] = None,
655
+ workflow_session: Optional[WorkflowSession] = None,
656
+ add_workflow_history_to_steps: Optional[bool] = False,
657
+ num_history_runs: int = 3,
658
+ ) -> AsyncIterator[Union[WorkflowRunOutputEvent, TeamRunOutputEvent, RunOutputEvent, StepOutput]]:
659
+ """Execute all steps in parallel with async streaming support"""
660
+ log_debug(f"Parallel Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="=")
661
+
662
+ parallel_step_id = str(uuid4())
663
+
664
+ self._prepare_steps()
665
+
666
+ # Create individual session_state copies for each step to prevent race conditions
667
+ session_state_copies = []
668
+ for _ in range(len(self.steps)):
669
+ # If using run context, no need to deepcopy the state. We want the direct reference.
670
+ if run_context is not None and run_context.session_state is not None:
671
+ session_state_copies.append(run_context.session_state)
672
+ else:
673
+ if session_state is not None:
674
+ session_state_copies.append(deepcopy(session_state))
675
+ else:
676
+ session_state_copies.append({})
677
+
678
+ # Considering both stream_events and stream_intermediate_steps (deprecated)
679
+ stream_events = stream_events or stream_intermediate_steps
680
+
681
+ if stream_events and workflow_run_response:
682
+ # Yield parallel step started event
683
+ yield ParallelExecutionStartedEvent(
684
+ run_id=workflow_run_response.run_id or "",
685
+ workflow_name=workflow_run_response.workflow_name or "",
686
+ workflow_id=workflow_run_response.workflow_id or "",
687
+ session_id=workflow_run_response.session_id or "",
688
+ step_name=self.name,
689
+ step_index=step_index,
690
+ parallel_step_count=len(self.steps),
691
+ step_id=parallel_step_id,
692
+ parent_step_id=parent_step_id,
693
+ )
694
+
695
+ import asyncio
696
+
697
+ event_queue = asyncio.Queue() # type: ignore
698
+ step_results = []
699
+ modified_session_states = []
700
+
701
+ async def execute_step_stream_async_with_index(step_with_index):
702
+ """Execute a single step with async streaming and yield events immediately"""
703
+ idx, step = step_with_index
704
+ # Use the individual session_state copy for this step
705
+ step_session_state = session_state_copies[idx]
706
+
707
+ try:
708
+ step_outputs = []
709
+
710
+ # If step_index is None or integer (main step): create (step_index, sub_index)
711
+ # If step_index is tuple (child step): all parallel sub-steps get same index
712
+ if step_index is None or isinstance(step_index, int):
713
+ # Parallel is a main step - sub-steps get sequential numbers: 1.1, 1.2, 1.3
714
+ sub_step_index = (step_index if step_index is not None else 0, idx)
715
+ else:
716
+ # Parallel is a child step - all sub-steps get the same parent number: 1.1, 1.1, 1.1
717
+ sub_step_index = step_index
718
+
719
+ # All workflow step types have aexecute_stream() method
720
+ async for event in step.aexecute_stream(
721
+ step_input,
722
+ session_id=session_id,
723
+ user_id=user_id,
724
+ stream_events=stream_events,
725
+ stream_executor_events=stream_executor_events,
726
+ workflow_run_response=workflow_run_response,
727
+ step_index=sub_step_index,
728
+ store_executor_outputs=store_executor_outputs,
729
+ session_state=step_session_state,
730
+ run_context=run_context,
731
+ parent_step_id=parallel_step_id,
732
+ workflow_session=workflow_session,
733
+ add_workflow_history_to_steps=add_workflow_history_to_steps,
734
+ num_history_runs=num_history_runs,
735
+ ): # type: ignore[union-attr]
736
+ # Yield events immediately to the queue
737
+ await event_queue.put(("event", idx, event))
738
+ if isinstance(event, StepOutput):
739
+ step_outputs.append(event)
740
+
741
+ # Signal completion for this step
742
+ await event_queue.put(("complete", idx, step_outputs, step_session_state))
743
+ return idx, step_outputs, step_session_state
744
+ except Exception as e:
745
+ parallel_step_name = getattr(step, "name", f"step_{idx}")
746
+ logger.error(f"Parallel step {parallel_step_name} async streaming failed: {e}")
747
+ error_event = StepOutput(
748
+ step_name=parallel_step_name,
749
+ content=f"Step {parallel_step_name} failed: {str(e)}",
750
+ success=False,
751
+ error=str(e),
752
+ )
753
+ await event_queue.put(("event", idx, error_event))
754
+ await event_queue.put(("complete", idx, [error_event], step_session_state))
755
+ return idx, [error_event], step_session_state
756
+
757
+ # Start all parallel tasks
758
+ indexed_steps = list(enumerate(self.steps))
759
+ tasks = [
760
+ asyncio.create_task(execute_step_stream_async_with_index(indexed_step)) for indexed_step in indexed_steps
761
+ ]
762
+
763
+ # Process events as they arrive and track completion
764
+ completed_steps = 0
765
+ total_steps = len(self.steps)
766
+
767
+ while completed_steps < total_steps:
768
+ try:
769
+ message_type, step_idx, *data = await event_queue.get()
770
+
771
+ if message_type == "event":
772
+ event = data[0]
773
+ if not isinstance(event, StepOutput):
774
+ yield event
775
+
776
+ elif message_type == "complete":
777
+ step_outputs, step_session_state = data
778
+ step_results.extend(step_outputs)
779
+ modified_session_states.append(step_session_state)
780
+ completed_steps += 1
781
+
782
+ step_name = getattr(self.steps[step_idx], "name", f"step_{step_idx}")
783
+ log_debug(f"Parallel step {step_name} async streaming completed")
784
+
785
+ except Exception as e:
786
+ logger.error(f"Error processing parallel step events: {e}")
787
+ completed_steps += 1
788
+
789
+ await asyncio.gather(*tasks, return_exceptions=True)
790
+
791
+ # Merge all session_state changes back into the original session_state
792
+ if run_context is None and session_state is not None:
793
+ merge_parallel_session_states(session_state, modified_session_states)
794
+
795
+ # Flatten step_results - handle steps that return List[StepOutput] (like Condition/Loop)
796
+ flattened_step_results: List[StepOutput] = []
797
+ for result in step_results:
798
+ if isinstance(result, list):
799
+ flattened_step_results.extend(result)
800
+ else:
801
+ flattened_step_results.append(result)
802
+
803
+ # Create aggregated result from all step outputs
804
+ aggregated_result = self._aggregate_results(flattened_step_results)
805
+
806
+ # Yield the final aggregated StepOutput
807
+ yield aggregated_result
808
+
809
+ log_debug(f"Parallel End: {self.name} ({len(self.steps)} steps)", center=True, symbol="=")
810
+
811
+ if stream_events and workflow_run_response:
812
+ # Yield parallel step completed event
813
+ yield ParallelExecutionCompletedEvent(
814
+ run_id=workflow_run_response.run_id or "",
815
+ workflow_name=workflow_run_response.workflow_name or "",
816
+ workflow_id=workflow_run_response.workflow_id or "",
817
+ session_id=workflow_run_response.session_id or "",
818
+ step_name=self.name,
819
+ step_index=step_index,
820
+ parallel_step_count=len(self.steps),
821
+ step_results=flattened_step_results,
822
+ step_id=parallel_step_id,
823
+ parent_step_id=parent_step_id,
824
+ )