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,1668 @@
1
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
2
+
3
+ from pydantic import BaseModel
4
+ from rich.console import Group
5
+ from rich.live import Live
6
+ from rich.markdown import Markdown
7
+ from rich.status import Status
8
+ from rich.text import Text
9
+
10
+ from agno.media import Audio, File, Image, Video
11
+ from agno.models.message import Message
12
+ from agno.run.workflow import (
13
+ ConditionExecutionCompletedEvent,
14
+ ConditionExecutionStartedEvent,
15
+ LoopExecutionCompletedEvent,
16
+ LoopExecutionStartedEvent,
17
+ LoopIterationCompletedEvent,
18
+ LoopIterationStartedEvent,
19
+ ParallelExecutionCompletedEvent,
20
+ ParallelExecutionStartedEvent,
21
+ RouterExecutionCompletedEvent,
22
+ RouterExecutionStartedEvent,
23
+ StepCompletedEvent,
24
+ StepOutputEvent,
25
+ StepsExecutionCompletedEvent,
26
+ StepsExecutionStartedEvent,
27
+ StepStartedEvent,
28
+ WorkflowAgentCompletedEvent,
29
+ WorkflowAgentStartedEvent,
30
+ WorkflowCompletedEvent,
31
+ WorkflowErrorEvent,
32
+ WorkflowRunOutput,
33
+ WorkflowRunOutputEvent,
34
+ WorkflowStartedEvent,
35
+ )
36
+ from agno.utils.response import create_panel
37
+ from agno.utils.timer import Timer
38
+ from agno.workflow.types import StepOutput
39
+
40
+ if TYPE_CHECKING:
41
+ from agno.workflow.workflow import Workflow
42
+
43
+
44
+ def print_response(
45
+ workflow: "Workflow",
46
+ input: Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]],
47
+ user_id: Optional[str] = None,
48
+ session_id: Optional[str] = None,
49
+ additional_data: Optional[Dict[str, Any]] = None,
50
+ audio: Optional[List[Audio]] = None,
51
+ images: Optional[List[Image]] = None,
52
+ videos: Optional[List[Video]] = None,
53
+ files: Optional[List[File]] = None,
54
+ markdown: bool = True,
55
+ show_time: bool = True,
56
+ show_step_details: bool = True,
57
+ console: Optional[Any] = None,
58
+ **kwargs: Any,
59
+ ) -> None:
60
+ """Print workflow execution with rich formatting (non-streaming)"""
61
+ from rich.live import Live
62
+ from rich.markdown import Markdown
63
+ from rich.status import Status
64
+ from rich.text import Text
65
+
66
+ from agno.utils.response import create_panel
67
+ from agno.utils.timer import Timer
68
+
69
+ if console is None:
70
+ from rich.console import Console
71
+
72
+ console = Console()
73
+
74
+ # Show workflow info
75
+ media_info = []
76
+ if audio:
77
+ media_info.append(f"Audio files: {len(audio)}")
78
+ if images:
79
+ media_info.append(f"Images: {len(images)}")
80
+ if videos:
81
+ media_info.append(f"Videos: {len(videos)}")
82
+ if files:
83
+ media_info.append(f"Files: {len(files)}")
84
+
85
+ workflow_info = f"""**Workflow:** {workflow.name}"""
86
+ if workflow.description:
87
+ workflow_info += f"""\n\n**Description:** {workflow.description}"""
88
+ workflow_info += f"""\n\n**Steps:** {workflow._get_step_count()} steps"""
89
+ if input:
90
+ if isinstance(input, str):
91
+ workflow_info += f"""\n\n**Message:** {input}"""
92
+ else:
93
+ # Handle structured input message
94
+ if isinstance(input, BaseModel):
95
+ data_display = input.model_dump_json(indent=2, exclude_none=True)
96
+ elif isinstance(input, (dict, list)):
97
+ import json
98
+
99
+ data_display = json.dumps(input, indent=2, default=str)
100
+ else:
101
+ data_display = str(input)
102
+ workflow_info += f"""\n\n**Structured Input:**\n```json\n{data_display}\n```"""
103
+ if user_id:
104
+ workflow_info += f"""\n\n**User ID:** {user_id}"""
105
+ if session_id:
106
+ workflow_info += f"""\n\n**Session ID:** {session_id}"""
107
+ workflow_info = workflow_info.strip()
108
+
109
+ workflow_panel = create_panel(
110
+ content=Markdown(workflow_info) if markdown else workflow_info,
111
+ title="Workflow Information",
112
+ border_style="cyan",
113
+ )
114
+ console.print(workflow_panel) # type: ignore
115
+
116
+ # Start timer
117
+ response_timer = Timer()
118
+ response_timer.start()
119
+
120
+ with Live(console=console) as live_log:
121
+ status = Status("Starting workflow...", spinner="dots")
122
+ live_log.update(status)
123
+
124
+ try:
125
+ # Execute workflow and get the response directly
126
+ workflow_response: WorkflowRunOutput = workflow.run(
127
+ input=input,
128
+ user_id=user_id,
129
+ session_id=session_id,
130
+ additional_data=additional_data,
131
+ audio=audio,
132
+ images=images,
133
+ videos=videos,
134
+ files=files,
135
+ **kwargs,
136
+ ) # type: ignore
137
+
138
+ response_timer.stop()
139
+
140
+ # Check if this is a workflow agent direct response
141
+ if workflow_response.workflow_agent_run is not None and not workflow_response.workflow_agent_run.tools:
142
+ # Agent answered directly from history without executing workflow
143
+ agent_response_panel = create_panel(
144
+ content=Markdown(str(workflow_response.content)) if markdown else str(workflow_response.content),
145
+ title="Workflow Agent Response",
146
+ border_style="green",
147
+ )
148
+ console.print(agent_response_panel) # type: ignore
149
+ elif show_step_details and workflow_response.step_results:
150
+ for i, step_output in enumerate(workflow_response.step_results):
151
+ print_step_output_recursive(step_output, i + 1, markdown, console) # type: ignore
152
+
153
+ # For callable functions, show the content directly since there are no step_results
154
+ elif show_step_details and callable(workflow.steps) and workflow_response.content:
155
+ step_panel = create_panel(
156
+ content=Markdown(workflow_response.content) if markdown else workflow_response.content, # type: ignore
157
+ title="Custom Function (Completed)",
158
+ border_style="orange3",
159
+ )
160
+ console.print(step_panel) # type: ignore
161
+
162
+ # Show final summary
163
+ if workflow_response.metadata:
164
+ status = workflow_response.status.value # type: ignore
165
+ summary_content = ""
166
+ summary_content += f"""\n\n**Status:** {status}"""
167
+ summary_content += f"""\n\n**Steps Completed:** {len(workflow_response.step_results) if workflow_response.step_results else 0}"""
168
+ summary_content = summary_content.strip()
169
+
170
+ summary_panel = create_panel(
171
+ content=Markdown(summary_content) if markdown else summary_content,
172
+ title="Execution Summary",
173
+ border_style="blue",
174
+ )
175
+ console.print(summary_panel) # type: ignore
176
+
177
+ live_log.update("")
178
+
179
+ # Final completion message
180
+ if show_time:
181
+ completion_text = Text(f"Completed in {response_timer.elapsed:.1f}s", style="bold green")
182
+ console.print(completion_text) # type: ignore
183
+
184
+ except Exception as e:
185
+ import traceback
186
+
187
+ traceback.print_exc()
188
+ response_timer.stop()
189
+ error_panel = create_panel(
190
+ content=f"Workflow execution failed: {str(e)}", title="Execution Error", border_style="red"
191
+ )
192
+ console.print(error_panel) # type: ignore
193
+
194
+
195
+ def print_response_stream(
196
+ workflow: "Workflow",
197
+ input: Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]],
198
+ user_id: Optional[str] = None,
199
+ session_id: Optional[str] = None,
200
+ additional_data: Optional[Dict[str, Any]] = None,
201
+ audio: Optional[List[Audio]] = None,
202
+ images: Optional[List[Image]] = None,
203
+ videos: Optional[List[Video]] = None,
204
+ files: Optional[List[File]] = None,
205
+ stream_events: bool = False,
206
+ stream_intermediate_steps: bool = False,
207
+ markdown: bool = True,
208
+ show_time: bool = True,
209
+ show_step_details: bool = True,
210
+ console: Optional[Any] = None,
211
+ **kwargs: Any,
212
+ ) -> None:
213
+ """Print workflow execution with clean streaming"""
214
+ if console is None:
215
+ from rich.console import Console
216
+
217
+ console = Console()
218
+
219
+ stream_events = True # With streaming print response, we need to stream intermediate steps
220
+
221
+ # Show workflow info (same as before)
222
+ media_info = []
223
+ if audio:
224
+ media_info.append(f"Audio files: {len(audio)}")
225
+ if images:
226
+ media_info.append(f"Images: {len(images)}")
227
+ if videos:
228
+ media_info.append(f"Videos: {len(videos)}")
229
+ if files:
230
+ media_info.append(f"Files: {len(files)}")
231
+
232
+ workflow_info = f"""**Workflow:** {workflow.name}"""
233
+ if workflow.description:
234
+ workflow_info += f"""\n\n**Description:** {workflow.description}"""
235
+ workflow_info += f"""\n\n**Steps:** {workflow._get_step_count()} steps"""
236
+ if input:
237
+ if isinstance(input, str):
238
+ workflow_info += f"""\n\n**Message:** {input}"""
239
+ else:
240
+ # Handle structured input message
241
+ if isinstance(input, BaseModel):
242
+ data_display = input.model_dump_json(indent=2, exclude_none=True)
243
+ elif isinstance(input, (dict, list)):
244
+ import json
245
+
246
+ data_display = json.dumps(input, indent=2, default=str)
247
+ else:
248
+ data_display = str(input)
249
+ workflow_info += f"""\n\n**Structured Input:**\n```json\n{data_display}\n```"""
250
+ if user_id:
251
+ workflow_info += f"""\n\n**User ID:** {user_id}"""
252
+ if session_id:
253
+ workflow_info += f"""\n\n**Session ID:** {session_id}"""
254
+ workflow_info = workflow_info.strip()
255
+
256
+ workflow_panel = create_panel(
257
+ content=Markdown(workflow_info) if markdown else workflow_info,
258
+ title="Workflow Information",
259
+ border_style="cyan",
260
+ )
261
+ console.print(workflow_panel) # type: ignore
262
+
263
+ # Start timer
264
+ response_timer = Timer()
265
+ response_timer.start()
266
+
267
+ # Streaming execution variables with smart step tracking
268
+ current_step_content = ""
269
+ current_step_name = ""
270
+ current_step_index = 0
271
+ step_results = []
272
+ step_started_printed = False
273
+ is_callable_function = callable(workflow.steps)
274
+ workflow_started = False # Track if workflow has actually started
275
+ is_workflow_agent_response = False # Track if this is a workflow agent direct response
276
+
277
+ # Smart step hierarchy tracking
278
+ current_primitive_context = None # Current primitive being executed (parallel, loop, etc.)
279
+ step_display_cache = {} # type: ignore
280
+
281
+ # Parallel-aware tracking for simultaneous steps
282
+ parallel_step_states: Dict[
283
+ Any, Dict[str, Any]
284
+ ] = {} # track state of each parallel step: {step_index: {"name": str, "content": str, "started": bool, "completed": bool}}
285
+
286
+ def get_step_display_number(step_index: Union[int, tuple], step_name: str = "") -> str:
287
+ """Generate clean two-level step numbering: x.y format only"""
288
+
289
+ # Handle tuple format for child steps
290
+ if isinstance(step_index, tuple):
291
+ if len(step_index) >= 2:
292
+ parent_idx, sub_idx = step_index[0], step_index[1]
293
+
294
+ # Extract base parent index if it's nested
295
+ if isinstance(parent_idx, tuple):
296
+ base_idx = parent_idx[0] if len(parent_idx) > 0 else 0
297
+ while isinstance(base_idx, tuple) and len(base_idx) > 0:
298
+ base_idx = base_idx[0]
299
+ else:
300
+ base_idx = parent_idx
301
+
302
+ # Check context for parallel special case
303
+ if current_primitive_context and current_primitive_context["type"] == "parallel":
304
+ # For parallel child steps, all get the same number based on their actual step_index
305
+ return f"Step {base_idx + 1}.{sub_idx + 1}"
306
+ elif current_primitive_context and current_primitive_context["type"] == "loop":
307
+ iteration = current_primitive_context.get("current_iteration", 1)
308
+ return f"Step {base_idx + 1}.{sub_idx + 1} (Iteration {iteration})"
309
+ else:
310
+ # Regular child step numbering
311
+ return f"Step {base_idx + 1}.{sub_idx + 1}" # type: ignore
312
+ else:
313
+ # Single element tuple - treat as main step
314
+ return f"Step {step_index[0] + 1}"
315
+
316
+ # Handle integer step_index - main step
317
+ if not current_primitive_context:
318
+ # Regular main step
319
+ return f"Step {step_index + 1}"
320
+ else:
321
+ # This shouldn't happen with the new logic, but fallback
322
+ return f"Step {step_index + 1}"
323
+
324
+ with Live(console=console, refresh_per_second=10) as live_log:
325
+ status = Status("Starting workflow...", spinner="dots")
326
+ live_log.update(status)
327
+
328
+ try:
329
+ for response in workflow.run(
330
+ input=input,
331
+ user_id=user_id,
332
+ session_id=session_id,
333
+ additional_data=additional_data,
334
+ audio=audio,
335
+ images=images,
336
+ videos=videos,
337
+ files=files,
338
+ stream=True,
339
+ stream_events=stream_events,
340
+ **kwargs,
341
+ ): # type: ignore
342
+ # Handle the new event types
343
+ if isinstance(response, WorkflowStartedEvent):
344
+ workflow_started = True
345
+ status.update("Workflow started...")
346
+ if is_callable_function:
347
+ current_step_name = "Custom Function"
348
+ current_step_index = 0
349
+ live_log.update(status)
350
+
351
+ elif isinstance(response, WorkflowAgentStartedEvent):
352
+ # Workflow agent is starting to process
353
+ status.update("Workflow agent processing...")
354
+ live_log.update(status)
355
+ continue
356
+
357
+ elif isinstance(response, WorkflowAgentCompletedEvent):
358
+ # Workflow agent has completed
359
+ status.update("Workflow agent completed")
360
+ live_log.update(status)
361
+ continue
362
+
363
+ elif isinstance(response, StepStartedEvent):
364
+ step_name = response.step_name or "Unknown"
365
+ step_index = response.step_index or 0 # type: ignore
366
+
367
+ current_step_name = step_name
368
+ current_step_index = step_index # type: ignore
369
+ current_step_content = ""
370
+ step_started_printed = False
371
+
372
+ # Generate smart step number
373
+ step_display = get_step_display_number(current_step_index, current_step_name)
374
+ status.update(f"Starting {step_display}: {current_step_name}...")
375
+ live_log.update(status)
376
+
377
+ elif isinstance(response, StepCompletedEvent):
378
+ step_name = response.step_name or "Unknown"
379
+ step_index = response.step_index or 0
380
+
381
+ # Skip parallel sub-step completed events - they're handled in ParallelExecutionCompletedEvent (avoid duplication)
382
+ if (
383
+ current_primitive_context
384
+ and current_primitive_context["type"] == "parallel"
385
+ and isinstance(step_index, tuple)
386
+ ):
387
+ continue
388
+
389
+ # Generate smart step number for completion (will use cached value)
390
+ step_display = get_step_display_number(step_index, step_name)
391
+ status.update(f"Completed {step_display}: {step_name}")
392
+
393
+ if response.content:
394
+ step_results.append(
395
+ {
396
+ "step_name": step_name,
397
+ "step_index": step_index,
398
+ "content": response.content,
399
+ "event": response.event,
400
+ }
401
+ )
402
+
403
+ # Print the final step result in orange (only once)
404
+ if show_step_details and current_step_content and not step_started_printed:
405
+ live_log.update(status, refresh=True)
406
+
407
+ final_step_panel = create_panel(
408
+ content=Markdown(current_step_content) if markdown else current_step_content,
409
+ title=f"{step_display}: {step_name} (Completed)",
410
+ border_style="orange3",
411
+ )
412
+ console.print(final_step_panel) # type: ignore
413
+ step_started_printed = True
414
+
415
+ elif isinstance(response, LoopExecutionStartedEvent):
416
+ current_step_name = response.step_name or "Loop"
417
+ current_step_index = response.step_index or 0 # type: ignore
418
+ current_step_content = ""
419
+ step_started_printed = False
420
+
421
+ # Set up loop context
422
+ current_primitive_context = {
423
+ "type": "loop",
424
+ "step_index": current_step_index,
425
+ "sub_step_counter": 0,
426
+ "current_iteration": 1,
427
+ "max_iterations": response.max_iterations,
428
+ }
429
+
430
+ # Initialize parallel step tracking - clear previous states
431
+ parallel_step_states.clear()
432
+ step_display_cache.clear()
433
+
434
+ status.update(f"Starting loop: {current_step_name} (max {response.max_iterations} iterations)...")
435
+ live_log.update(status)
436
+
437
+ elif isinstance(response, LoopIterationStartedEvent):
438
+ if current_primitive_context and current_primitive_context["type"] == "loop":
439
+ current_primitive_context["current_iteration"] = response.iteration
440
+ current_primitive_context["sub_step_counter"] = 0 # Reset for new iteration
441
+ # Clear cache for new iteration
442
+ step_display_cache.clear()
443
+
444
+ status.update(
445
+ f"Loop iteration {response.iteration}/{response.max_iterations}: {response.step_name}..."
446
+ )
447
+ live_log.update(status)
448
+
449
+ elif isinstance(response, LoopIterationCompletedEvent):
450
+ status.update(
451
+ f"Completed iteration {response.iteration}/{response.max_iterations}: {response.step_name}"
452
+ )
453
+
454
+ elif isinstance(response, LoopExecutionCompletedEvent):
455
+ step_name = response.step_name or "Loop"
456
+ step_index = response.step_index or 0
457
+
458
+ status.update(f"Completed loop: {step_name} ({response.total_iterations} iterations)")
459
+ live_log.update(status, refresh=True)
460
+
461
+ # Print loop summary
462
+ if show_step_details:
463
+ summary_content = "**Loop Summary:**\n\n"
464
+ summary_content += (
465
+ f"- Total iterations: {response.total_iterations}/{response.max_iterations}\n"
466
+ )
467
+ summary_content += (
468
+ f"- Total steps executed: {sum(len(iteration) for iteration in response.all_results)}\n"
469
+ )
470
+
471
+ loop_summary_panel = create_panel(
472
+ content=Markdown(summary_content) if markdown else summary_content,
473
+ title=f"Loop {step_name} (Completed)",
474
+ border_style="yellow",
475
+ )
476
+ console.print(loop_summary_panel) # type: ignore
477
+
478
+ # Reset context
479
+ current_primitive_context = None
480
+ step_display_cache.clear()
481
+ step_started_printed = True
482
+
483
+ elif isinstance(response, ParallelExecutionStartedEvent):
484
+ current_step_name = response.step_name or "Parallel Steps"
485
+ current_step_index = response.step_index or 0 # type: ignore
486
+ current_step_content = ""
487
+ step_started_printed = False
488
+
489
+ # Set up parallel context
490
+ current_primitive_context = {
491
+ "type": "parallel",
492
+ "step_index": current_step_index,
493
+ "sub_step_counter": 0,
494
+ "total_steps": response.parallel_step_count,
495
+ }
496
+
497
+ # Initialize parallel step tracking - clear previous states
498
+ parallel_step_states.clear()
499
+ step_display_cache.clear()
500
+
501
+ # Print parallel execution summary panel
502
+ live_log.update(status, refresh=True)
503
+ parallel_summary = f"**Parallel Steps:** {response.parallel_step_count}"
504
+ # Use get_step_display_number for consistent numbering
505
+ step_display = get_step_display_number(current_step_index, current_step_name)
506
+ parallel_panel = create_panel(
507
+ content=Markdown(parallel_summary) if markdown else parallel_summary,
508
+ title=f"{step_display}: {current_step_name}",
509
+ border_style="cyan",
510
+ )
511
+ console.print(parallel_panel) # type: ignore
512
+
513
+ status.update(
514
+ f"Starting parallel execution: {current_step_name} ({response.parallel_step_count} steps)..."
515
+ )
516
+ live_log.update(status)
517
+
518
+ elif isinstance(response, ParallelExecutionCompletedEvent):
519
+ step_name = response.step_name or "Parallel Steps"
520
+ step_index = response.step_index or 0
521
+
522
+ status.update(f"Completed parallel execution: {step_name}")
523
+
524
+ # Display individual parallel step results immediately
525
+ if show_step_details and response.step_results:
526
+ live_log.update(status, refresh=True)
527
+
528
+ # Get the parallel container's display number for consistent numbering
529
+ parallel_step_display = get_step_display_number(step_index, step_name)
530
+
531
+ # Show each parallel step with the same number (1.1, 1.1)
532
+ for step_result in response.step_results:
533
+ if step_result.content:
534
+ step_result_name = step_result.step_name or "Parallel Step"
535
+ formatted_content = format_step_content_for_display(step_result.content) # type: ignore
536
+
537
+ # All parallel sub-steps get the same number
538
+ parallel_step_panel = create_panel(
539
+ content=Markdown(formatted_content) if markdown else formatted_content,
540
+ title=f"{parallel_step_display}: {step_result_name} (Completed)",
541
+ border_style="orange3",
542
+ )
543
+ console.print(parallel_step_panel) # type: ignore
544
+
545
+ # Reset context
546
+ current_primitive_context = None
547
+ parallel_step_states.clear()
548
+ step_display_cache.clear()
549
+
550
+ elif isinstance(response, ConditionExecutionStartedEvent):
551
+ current_step_name = response.step_name or "Condition"
552
+ current_step_index = response.step_index or 0 # type: ignore
553
+ current_step_content = ""
554
+ step_started_printed = False
555
+
556
+ # Set up condition context
557
+ current_primitive_context = {
558
+ "type": "condition",
559
+ "step_index": current_step_index,
560
+ "sub_step_counter": 0,
561
+ "condition_result": response.condition_result,
562
+ }
563
+
564
+ # Initialize parallel step tracking - clear previous states
565
+ parallel_step_states.clear()
566
+ step_display_cache.clear()
567
+
568
+ condition_text = "met" if response.condition_result else "not met"
569
+ status.update(f"Starting condition: {current_step_name} (condition {condition_text})...")
570
+ live_log.update(status)
571
+
572
+ elif isinstance(response, ConditionExecutionCompletedEvent):
573
+ step_name = response.step_name or "Condition"
574
+ step_index = response.step_index or 0
575
+
576
+ status.update(f"Completed condition: {step_name}")
577
+
578
+ # Reset context
579
+ current_primitive_context = None
580
+ step_display_cache.clear()
581
+
582
+ elif isinstance(response, RouterExecutionStartedEvent):
583
+ current_step_name = response.step_name or "Router"
584
+ current_step_index = response.step_index or 0 # type: ignore
585
+ current_step_content = ""
586
+ step_started_printed = False
587
+
588
+ # Set up router context
589
+ current_primitive_context = {
590
+ "type": "router",
591
+ "step_index": current_step_index,
592
+ "sub_step_counter": 0,
593
+ "selected_steps": response.selected_steps,
594
+ }
595
+
596
+ # Initialize parallel step tracking - clear previous states
597
+ parallel_step_states.clear()
598
+ step_display_cache.clear()
599
+
600
+ selected_steps_text = ", ".join(response.selected_steps) if response.selected_steps else "none"
601
+ status.update(f"Starting router: {current_step_name} (selected: {selected_steps_text})...")
602
+ live_log.update(status)
603
+
604
+ elif isinstance(response, RouterExecutionCompletedEvent):
605
+ step_name = response.step_name or "Router"
606
+ step_index = response.step_index or 0
607
+
608
+ status.update(f"Completed router: {step_name}")
609
+
610
+ # Print router summary
611
+ if show_step_details:
612
+ selected_steps_text = ", ".join(response.selected_steps) if response.selected_steps else "none"
613
+ summary_content = "**Router Summary:**\n\n"
614
+ summary_content += f"- Selected steps: {selected_steps_text}\n"
615
+ summary_content += f"- Executed steps: {response.executed_steps or 0}\n"
616
+
617
+ router_summary_panel = create_panel(
618
+ content=Markdown(summary_content) if markdown else summary_content,
619
+ title=f"Router {step_name} (Completed)",
620
+ border_style="purple",
621
+ )
622
+ console.print(router_summary_panel) # type: ignore
623
+
624
+ # Reset context
625
+ current_primitive_context = None
626
+ step_display_cache.clear()
627
+ step_started_printed = True
628
+
629
+ elif isinstance(response, StepsExecutionStartedEvent):
630
+ current_step_name = response.step_name or "Steps"
631
+ current_step_index = response.step_index or 0 # type: ignore
632
+ current_step_content = ""
633
+ step_started_printed = False
634
+ status.update(f"Starting steps: {current_step_name} ({response.steps_count} steps)...")
635
+ live_log.update(status)
636
+
637
+ elif isinstance(response, StepsExecutionCompletedEvent):
638
+ step_name = response.step_name or "Steps"
639
+ step_index = response.step_index or 0
640
+
641
+ status.update(f"Completed steps: {step_name}")
642
+
643
+ # Add results from executed steps to step_results
644
+ if response.step_results:
645
+ for i, step_result in enumerate(response.step_results):
646
+ # Use the same numbering system as other primitives
647
+ step_display_number = get_step_display_number(step_index, step_result.step_name or "")
648
+ step_results.append(
649
+ {
650
+ "step_name": f"{step_display_number}: {step_result.step_name}",
651
+ "step_index": step_index,
652
+ "content": step_result.content,
653
+ "event": "StepsStepResult",
654
+ }
655
+ )
656
+
657
+ # Print steps summary
658
+ if show_step_details:
659
+ summary_content = "**Steps Summary:**\n\n"
660
+ summary_content += f"- Total steps: {response.steps_count or 0}\n"
661
+ summary_content += f"- Executed steps: {response.executed_steps or 0}\n"
662
+
663
+ steps_summary_panel = create_panel(
664
+ content=Markdown(summary_content) if markdown else summary_content,
665
+ title=f"Steps {step_name} (Completed)",
666
+ border_style="yellow",
667
+ )
668
+ console.print(steps_summary_panel) # type: ignore
669
+
670
+ step_started_printed = True
671
+
672
+ elif isinstance(response, WorkflowCompletedEvent):
673
+ status.update("Workflow completed!")
674
+
675
+ # Check if this is an agent direct response
676
+ if response.metadata and response.metadata.get("agent_direct_response"):
677
+ is_workflow_agent_response = True
678
+ # Print the agent's direct response from history
679
+ if show_step_details:
680
+ live_log.update(status, refresh=True)
681
+ agent_response_panel = create_panel(
682
+ content=Markdown(str(response.content)) if markdown else str(response.content),
683
+ title="Workflow Agent Response",
684
+ border_style="green",
685
+ )
686
+ console.print(agent_response_panel) # type: ignore
687
+ step_started_printed = True
688
+ # For callable functions, print the final content block here since there are no step events
689
+ elif (
690
+ is_callable_function and show_step_details and current_step_content and not step_started_printed
691
+ ):
692
+ final_step_panel = create_panel(
693
+ content=Markdown(current_step_content) if markdown else current_step_content,
694
+ title="Custom Function (Completed)",
695
+ border_style="orange3",
696
+ )
697
+ console.print(final_step_panel) # type: ignore
698
+ step_started_printed = True
699
+
700
+ live_log.update(status, refresh=True)
701
+
702
+ # Show final summary (skip for agent responses)
703
+ if response.metadata and not is_workflow_agent_response:
704
+ status = response.status
705
+ summary_content = ""
706
+ summary_content += f"""\n\n**Status:** {status}"""
707
+ summary_content += (
708
+ f"""\n\n**Steps Completed:** {len(response.step_results) if response.step_results else 0}"""
709
+ )
710
+ summary_content = summary_content.strip()
711
+
712
+ summary_panel = create_panel(
713
+ content=Markdown(summary_content) if markdown else summary_content,
714
+ title="Execution Summary",
715
+ border_style="blue",
716
+ )
717
+ console.print(summary_panel) # type: ignore
718
+
719
+ else:
720
+ # Handle streaming content
721
+ if isinstance(response, str):
722
+ response_str = response
723
+ elif isinstance(response, StepOutputEvent):
724
+ response_str = response.content or "" # type: ignore
725
+ else:
726
+ from agno.run.agent import RunContentEvent
727
+ from agno.run.team import RunContentEvent as TeamRunContentEvent
728
+
729
+ current_step_executor_type = None
730
+ # Handle both integer and tuple step indices for parallel execution
731
+ actual_step_index = current_step_index
732
+ if isinstance(current_step_index, tuple):
733
+ # For tuple indices, use the first element (parent step index)
734
+ actual_step_index = current_step_index[0]
735
+ # If it's nested tuple, keep extracting until we get an integer
736
+ while isinstance(actual_step_index, tuple) and len(actual_step_index) > 0:
737
+ actual_step_index = actual_step_index[0]
738
+
739
+ if not is_callable_function and workflow.steps and actual_step_index < len(workflow.steps): # type: ignore
740
+ step = workflow.steps[actual_step_index] # type: ignore
741
+ if hasattr(step, "executor_type"):
742
+ current_step_executor_type = step.executor_type
743
+
744
+ # Check if this is a streaming content event from agent or team
745
+ if isinstance(response, (TeamRunContentEvent, WorkflowRunOutputEvent)): # type: ignore
746
+ # Check if this is a team's final structured output
747
+ is_structured_output = (
748
+ isinstance(response, TeamRunContentEvent)
749
+ and hasattr(response, "content_type")
750
+ and response.content_type != "str"
751
+ and response.content_type != ""
752
+ )
753
+ response_str = response.content # type: ignore
754
+
755
+ if isinstance(response, RunContentEvent) and not workflow_started:
756
+ is_workflow_agent_response = True
757
+ continue
758
+
759
+ elif isinstance(response, RunContentEvent) and current_step_executor_type != "team":
760
+ response_str = response.content # type: ignore
761
+ # If we get RunContentEvent BEFORE workflow starts, it's an agent direct response
762
+ if not workflow_started and not is_workflow_agent_response:
763
+ is_workflow_agent_response = True
764
+ else:
765
+ continue
766
+
767
+ # Use the unified formatting function for consistency
768
+ response_str = format_step_content_for_display(response_str) # type: ignore
769
+
770
+ # Skip streaming content from parallel sub-steps - they're handled in ParallelExecutionCompletedEvent
771
+ if (
772
+ current_primitive_context
773
+ and current_primitive_context["type"] == "parallel"
774
+ and isinstance(current_step_index, tuple)
775
+ ):
776
+ continue
777
+
778
+ # Filter out empty responses and add to current step content
779
+ if response_str and response_str.strip():
780
+ # If it's a structured output from a team, replace the content instead of appending
781
+ if "is_structured_output" in locals() and is_structured_output:
782
+ current_step_content = response_str
783
+ else:
784
+ current_step_content += response_str
785
+
786
+ # Live update the step panel with streaming content (skip for workflow agent responses)
787
+ if show_step_details and not step_started_printed and not is_workflow_agent_response:
788
+ # Generate smart step number for streaming title (will use cached value)
789
+ step_display = get_step_display_number(current_step_index, current_step_name)
790
+ title = f"{step_display}: {current_step_name} (Streaming...)"
791
+ if is_callable_function:
792
+ title = "Custom Function (Streaming...)"
793
+
794
+ # Show the streaming content live in orange panel
795
+ live_step_panel = create_panel(
796
+ content=Markdown(current_step_content) if markdown else current_step_content,
797
+ title=title,
798
+ border_style="orange3",
799
+ )
800
+
801
+ # Create group with status and current step content
802
+ group = Group(status, live_step_panel)
803
+ live_log.update(group)
804
+
805
+ response_timer.stop()
806
+
807
+ live_log.update("")
808
+
809
+ # Final completion message (skip for agent responses)
810
+ if show_time and not is_workflow_agent_response:
811
+ completion_text = Text(f"Completed in {response_timer.elapsed:.1f}s", style="bold green")
812
+ console.print(completion_text) # type: ignore
813
+
814
+ except Exception as e:
815
+ import traceback
816
+
817
+ traceback.print_exc()
818
+ response_timer.stop()
819
+ error_panel = create_panel(
820
+ content=f"Workflow execution failed: {str(e)}", title="Execution Error", border_style="red"
821
+ )
822
+ console.print(error_panel) # type: ignore
823
+
824
+
825
+ def print_step_output_recursive(
826
+ step_output: StepOutput, step_number: int, markdown: bool, console, depth: int = 0
827
+ ) -> None:
828
+ """Recursively print step output and its nested steps"""
829
+ from rich.markdown import Markdown
830
+
831
+ from agno.utils.response import create_panel
832
+
833
+ # Print the current step
834
+ if step_output.content:
835
+ formatted_content = format_step_content_for_display(step_output)
836
+
837
+ # Create title with proper nesting indication
838
+ if depth == 0:
839
+ title = f"Step {step_number}: {step_output.step_name} (Completed)"
840
+ else:
841
+ title = f"{' ' * depth}└─ {step_output.step_name} (Completed)"
842
+
843
+ step_panel = create_panel(
844
+ content=Markdown(formatted_content) if markdown else formatted_content,
845
+ title=title,
846
+ border_style="orange3",
847
+ )
848
+ console.print(step_panel)
849
+
850
+ # Print nested steps if they exist
851
+ if step_output.steps:
852
+ for j, nested_step in enumerate(step_output.steps):
853
+ print_step_output_recursive(nested_step, j + 1, markdown, console, depth + 1)
854
+
855
+
856
+ def format_step_content_for_display(step_output: StepOutput) -> str:
857
+ """Format content for display, handling structured outputs. Works for both raw content and StepOutput objects."""
858
+ # If it's a StepOutput, extract the content
859
+ if hasattr(step_output, "content"):
860
+ actual_content = step_output.content
861
+ else:
862
+ actual_content = step_output
863
+
864
+ if not actual_content:
865
+ return ""
866
+
867
+ # If it's already a string, return as-is
868
+ if isinstance(actual_content, str):
869
+ return actual_content
870
+
871
+ # If it's a structured output (BaseModel or dict), format it nicely
872
+ if isinstance(actual_content, BaseModel):
873
+ return f"**Structured Output:**\n\n```json\n{actual_content.model_dump_json(indent=2, exclude_none=True)}\n```"
874
+ elif isinstance(actual_content, (dict, list)):
875
+ import json
876
+
877
+ return f"**Structured Output:**\n\n```json\n{json.dumps(actual_content, indent=2, default=str)}\n```"
878
+ else:
879
+ # Fallback to string conversion
880
+ return str(actual_content)
881
+
882
+
883
+ async def aprint_response(
884
+ workflow: "Workflow",
885
+ input: Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]],
886
+ additional_data: Optional[Dict[str, Any]] = None,
887
+ user_id: Optional[str] = None,
888
+ session_id: Optional[str] = None,
889
+ audio: Optional[List[Audio]] = None,
890
+ images: Optional[List[Image]] = None,
891
+ videos: Optional[List[Video]] = None,
892
+ files: Optional[List[File]] = None,
893
+ markdown: bool = True,
894
+ show_time: bool = True,
895
+ show_step_details: bool = True,
896
+ console: Optional[Any] = None,
897
+ **kwargs: Any,
898
+ ) -> None:
899
+ """Print workflow execution with rich formatting (non-streaming)"""
900
+ from rich.live import Live
901
+ from rich.markdown import Markdown
902
+ from rich.status import Status
903
+ from rich.text import Text
904
+
905
+ from agno.utils.response import create_panel
906
+ from agno.utils.timer import Timer
907
+
908
+ if console is None:
909
+ from rich.console import Console
910
+
911
+ console = Console()
912
+
913
+ # Show workflow info
914
+ media_info = []
915
+ if audio:
916
+ media_info.append(f"Audio files: {len(audio)}")
917
+ if images:
918
+ media_info.append(f"Images: {len(images)}")
919
+ if videos:
920
+ media_info.append(f"Videos: {len(videos)}")
921
+ if files:
922
+ media_info.append(f"Files: {len(files)}")
923
+
924
+ workflow_info = f"""**Workflow:** {workflow.name}"""
925
+ if workflow.description:
926
+ workflow_info += f"""\n\n**Description:** {workflow.description}"""
927
+ workflow_info += f"""\n\n**Steps:** {workflow._get_step_count()} steps"""
928
+ if input:
929
+ if isinstance(input, str):
930
+ workflow_info += f"""\n\n**Message:** {input}"""
931
+ else:
932
+ # Handle structured input message
933
+ if isinstance(input, BaseModel):
934
+ data_display = input.model_dump_json(indent=2, exclude_none=True)
935
+ elif isinstance(input, (dict, list)):
936
+ import json
937
+
938
+ data_display = json.dumps(input, indent=2, default=str)
939
+ else:
940
+ data_display = str(input)
941
+ workflow_info += f"""\n\n**Structured Input:**\n```json\n{data_display}\n```"""
942
+ if user_id:
943
+ workflow_info += f"""\n\n**User ID:** {user_id}"""
944
+ if session_id:
945
+ workflow_info += f"""\n\n**Session ID:** {session_id}"""
946
+ workflow_info = workflow_info.strip()
947
+
948
+ workflow_panel = create_panel(
949
+ content=Markdown(workflow_info) if markdown else workflow_info,
950
+ title="Workflow Information",
951
+ border_style="cyan",
952
+ )
953
+ console.print(workflow_panel) # type: ignore
954
+
955
+ # Start timer
956
+ response_timer = Timer()
957
+ response_timer.start()
958
+
959
+ with Live(console=console) as live_log:
960
+ status = Status("Starting async workflow...\n", spinner="dots")
961
+ live_log.update(status)
962
+
963
+ try:
964
+ # Execute workflow and get the response directly
965
+ workflow_response: WorkflowRunOutput = await workflow.arun(
966
+ input=input,
967
+ user_id=user_id,
968
+ session_id=session_id,
969
+ additional_data=additional_data,
970
+ audio=audio,
971
+ images=images,
972
+ videos=videos,
973
+ files=files,
974
+ **kwargs,
975
+ ) # type: ignore
976
+
977
+ response_timer.stop()
978
+
979
+ # Check if this is a workflow agent direct response
980
+ if workflow_response.workflow_agent_run is not None and not workflow_response.workflow_agent_run.tools:
981
+ # Agent answered directly from history without executing workflow
982
+ agent_response_panel = create_panel(
983
+ content=Markdown(str(workflow_response.content)) if markdown else str(workflow_response.content),
984
+ title="Workflow Agent Response",
985
+ border_style="green",
986
+ )
987
+ console.print(agent_response_panel) # type: ignore
988
+ elif show_step_details and workflow_response.step_results:
989
+ for i, step_output in enumerate(workflow_response.step_results):
990
+ print_step_output_recursive(step_output, i + 1, markdown, console) # type: ignore
991
+
992
+ # For callable functions, show the content directly since there are no step_results
993
+ elif show_step_details and callable(workflow.steps) and workflow_response.content:
994
+ step_panel = create_panel(
995
+ content=Markdown(workflow_response.content) if markdown else workflow_response.content, # type: ignore
996
+ title="Custom Function (Completed)",
997
+ border_style="orange3",
998
+ )
999
+ console.print(step_panel) # type: ignore
1000
+
1001
+ # Show final summary
1002
+ if workflow_response.metadata:
1003
+ status = workflow_response.status.value # type: ignore
1004
+ summary_content = ""
1005
+ summary_content += f"""\n\n**Status:** {status}"""
1006
+ summary_content += f"""\n\n**Steps Completed:** {len(workflow_response.step_results) if workflow_response.step_results else 0}"""
1007
+ summary_content = summary_content.strip()
1008
+
1009
+ summary_panel = create_panel(
1010
+ content=Markdown(summary_content) if markdown else summary_content,
1011
+ title="Execution Summary",
1012
+ border_style="blue",
1013
+ )
1014
+ console.print(summary_panel) # type: ignore
1015
+
1016
+ live_log.update("")
1017
+
1018
+ # Final completion message
1019
+ if show_time:
1020
+ completion_text = Text(f"Completed in {response_timer.elapsed:.1f}s", style="bold green")
1021
+ console.print(completion_text) # type: ignore
1022
+
1023
+ except Exception as e:
1024
+ import traceback
1025
+
1026
+ traceback.print_exc()
1027
+ response_timer.stop()
1028
+ error_panel = create_panel(
1029
+ content=f"Workflow execution failed: {str(e)}", title="Execution Error", border_style="red"
1030
+ )
1031
+ console.print(error_panel) # type: ignore
1032
+
1033
+
1034
+ async def aprint_response_stream(
1035
+ workflow: "Workflow",
1036
+ input: Union[str, Dict[str, Any], List[Any], BaseModel, List[Message]],
1037
+ additional_data: Optional[Dict[str, Any]] = None,
1038
+ user_id: Optional[str] = None,
1039
+ session_id: Optional[str] = None,
1040
+ audio: Optional[List[Audio]] = None,
1041
+ images: Optional[List[Image]] = None,
1042
+ videos: Optional[List[Video]] = None,
1043
+ files: Optional[List[File]] = None,
1044
+ stream_events: bool = False,
1045
+ stream_intermediate_steps: bool = False,
1046
+ markdown: bool = True,
1047
+ show_time: bool = True,
1048
+ show_step_details: bool = True,
1049
+ console: Optional[Any] = None,
1050
+ **kwargs: Any,
1051
+ ) -> None:
1052
+ """Print workflow execution with clean streaming - orange step blocks displayed once"""
1053
+ if console is None:
1054
+ from rich.console import Console
1055
+
1056
+ console = Console()
1057
+
1058
+ stream_events = True # With streaming print response, we need to stream intermediate steps
1059
+
1060
+ # Show workflow info (same as before)
1061
+ media_info = []
1062
+ if audio:
1063
+ media_info.append(f"Audio files: {len(audio)}")
1064
+ if images:
1065
+ media_info.append(f"Images: {len(images)}")
1066
+ if videos:
1067
+ media_info.append(f"Videos: {len(videos)}")
1068
+ if files:
1069
+ media_info.append(f"Files: {len(files)}")
1070
+
1071
+ workflow_info = f"""**Workflow:** {workflow.name}"""
1072
+ if workflow.description:
1073
+ workflow_info += f"""\n\n**Description:** {workflow.description}"""
1074
+ workflow_info += f"""\n\n**Steps:** {workflow._get_step_count()} steps"""
1075
+ if input:
1076
+ if isinstance(input, str):
1077
+ workflow_info += f"""\n\n**Message:** {input}"""
1078
+ else:
1079
+ # Handle structured input message
1080
+ if isinstance(input, BaseModel):
1081
+ data_display = input.model_dump_json(indent=2, exclude_none=True)
1082
+ elif isinstance(input, (dict, list)):
1083
+ import json
1084
+
1085
+ data_display = json.dumps(input, indent=2, default=str)
1086
+ else:
1087
+ data_display = str(input)
1088
+ workflow_info += f"""\n\n**Structured Input:**\n```json\n{data_display}\n```"""
1089
+ if user_id:
1090
+ workflow_info += f"""\n\n**User ID:** {user_id}"""
1091
+ if session_id:
1092
+ workflow_info += f"""\n\n**Session ID:** {session_id}"""
1093
+ workflow_info = workflow_info.strip()
1094
+
1095
+ workflow_panel = create_panel(
1096
+ content=Markdown(workflow_info) if markdown else workflow_info,
1097
+ title="Workflow Information",
1098
+ border_style="cyan",
1099
+ )
1100
+ console.print(workflow_panel) # type: ignore
1101
+
1102
+ # Start timer
1103
+ response_timer = Timer()
1104
+ response_timer.start()
1105
+
1106
+ # Streaming execution variables
1107
+ current_step_content = ""
1108
+ current_step_name = ""
1109
+ current_step_index = 0
1110
+ step_results = []
1111
+ step_started_printed = False
1112
+ is_callable_function = callable(workflow.steps)
1113
+ workflow_started = False # Track if workflow has actually started
1114
+ is_workflow_agent_response = False # Track if this is a workflow agent direct response
1115
+
1116
+ # Smart step hierarchy tracking
1117
+ current_primitive_context = None # Current primitive being executed (parallel, loop, etc.)
1118
+ step_display_cache = {} # type: ignore
1119
+
1120
+ # Parallel-aware tracking for simultaneous steps
1121
+ parallel_step_states: Dict[
1122
+ Any, Dict[str, Any]
1123
+ ] = {} # track state of each parallel step: {step_index: {"name": str, "content": str, "started": bool, "completed": bool}}
1124
+
1125
+ def get_step_display_number(step_index: Union[int, tuple], step_name: str = "") -> str:
1126
+ """Generate clean two-level step numbering: x.y format only"""
1127
+
1128
+ # Handle tuple format for child steps
1129
+ if isinstance(step_index, tuple):
1130
+ if len(step_index) >= 2:
1131
+ parent_idx, sub_idx = step_index[0], step_index[1]
1132
+
1133
+ # Extract base parent index if it's nested
1134
+ if isinstance(parent_idx, tuple):
1135
+ base_idx = parent_idx[0] if len(parent_idx) > 0 else 0
1136
+ while isinstance(base_idx, tuple) and len(base_idx) > 0:
1137
+ base_idx = base_idx[0]
1138
+ else:
1139
+ base_idx = parent_idx
1140
+
1141
+ # Check context for parallel special case
1142
+ if current_primitive_context and current_primitive_context["type"] == "parallel":
1143
+ # For parallel child steps, all get the same number based on their actual step_index
1144
+ return f"Step {base_idx + 1}.{sub_idx + 1}"
1145
+ elif current_primitive_context and current_primitive_context["type"] == "loop":
1146
+ iteration = current_primitive_context.get("current_iteration", 1)
1147
+ return f"Step {base_idx + 1}.{sub_idx + 1} (Iteration {iteration})"
1148
+ else:
1149
+ # Regular child step numbering
1150
+ return f"Step {base_idx + 1}.{sub_idx + 1}" # type: ignore
1151
+ else:
1152
+ # Single element tuple - treat as main step
1153
+ return f"Step {step_index[0] + 1}"
1154
+
1155
+ # Handle integer step_index - main step
1156
+ if not current_primitive_context:
1157
+ # Regular main step
1158
+ return f"Step {step_index + 1}"
1159
+ else:
1160
+ # This shouldn't happen with the new logic, but fallback
1161
+ return f"Step {step_index + 1}"
1162
+
1163
+ with Live(console=console, refresh_per_second=10) as live_log:
1164
+ status = Status("Starting async workflow...", spinner="dots")
1165
+ live_log.update(status)
1166
+
1167
+ try:
1168
+ async for response in workflow.arun(
1169
+ input=input,
1170
+ additional_data=additional_data,
1171
+ user_id=user_id,
1172
+ session_id=session_id,
1173
+ audio=audio,
1174
+ images=images,
1175
+ videos=videos,
1176
+ files=files,
1177
+ stream=True,
1178
+ stream_events=stream_events,
1179
+ **kwargs,
1180
+ ): # type: ignore
1181
+ # Handle the new event types
1182
+ if isinstance(response, WorkflowStartedEvent):
1183
+ workflow_started = True
1184
+ status.update("Workflow started...")
1185
+ if is_callable_function:
1186
+ current_step_name = "Custom Function"
1187
+ current_step_index = 0
1188
+ live_log.update(status)
1189
+
1190
+ elif isinstance(response, WorkflowAgentStartedEvent):
1191
+ # Workflow agent is starting to process
1192
+ status.update("Workflow agent processing...")
1193
+ live_log.update(status)
1194
+ continue
1195
+
1196
+ elif isinstance(response, WorkflowAgentCompletedEvent):
1197
+ # Workflow agent has completed
1198
+ status.update("Workflow agent completed")
1199
+ live_log.update(status)
1200
+ continue
1201
+
1202
+ elif isinstance(response, StepStartedEvent):
1203
+ # Skip step events if workflow hasn't started (agent direct response)
1204
+ if not workflow_started:
1205
+ continue
1206
+
1207
+ step_name = response.step_name or "Unknown"
1208
+ step_index = response.step_index or 0 # type: ignore
1209
+
1210
+ current_step_name = step_name
1211
+ current_step_index = step_index # type: ignore
1212
+ current_step_content = ""
1213
+ step_started_printed = False
1214
+
1215
+ # Generate smart step number
1216
+ step_display = get_step_display_number(current_step_index, current_step_name)
1217
+ status.update(f"Starting {step_display}: {current_step_name}...")
1218
+ live_log.update(status)
1219
+
1220
+ elif isinstance(response, StepCompletedEvent):
1221
+ step_name = response.step_name or "Unknown"
1222
+ step_index = response.step_index or 0
1223
+
1224
+ # Skip parallel sub-step completed events - they're handled in ParallelExecutionCompletedEvent (avoid duplication)
1225
+ if (
1226
+ current_primitive_context
1227
+ and current_primitive_context["type"] == "parallel"
1228
+ and isinstance(step_index, tuple)
1229
+ ):
1230
+ continue
1231
+
1232
+ # Generate smart step number for completion (will use cached value)
1233
+ step_display = get_step_display_number(step_index, step_name)
1234
+ status.update(f"Completed {step_display}: {step_name}")
1235
+
1236
+ if response.content:
1237
+ step_results.append(
1238
+ {
1239
+ "step_name": step_name,
1240
+ "step_index": step_index,
1241
+ "content": response.content,
1242
+ "event": response.event,
1243
+ }
1244
+ )
1245
+
1246
+ # Print the final step result in orange (only once)
1247
+ if show_step_details and current_step_content and not step_started_printed:
1248
+ live_log.update(status, refresh=True)
1249
+
1250
+ final_step_panel = create_panel(
1251
+ content=Markdown(current_step_content) if markdown else current_step_content,
1252
+ title=f"{step_display}: {step_name} (Completed)",
1253
+ border_style="orange3",
1254
+ )
1255
+ console.print(final_step_panel) # type: ignore
1256
+ step_started_printed = True
1257
+
1258
+ elif isinstance(response, LoopExecutionStartedEvent):
1259
+ current_step_name = response.step_name or "Loop"
1260
+ current_step_index = response.step_index or 0 # type: ignore
1261
+ current_step_content = ""
1262
+ step_started_printed = False
1263
+
1264
+ # Set up loop context
1265
+ current_primitive_context = {
1266
+ "type": "loop",
1267
+ "step_index": current_step_index,
1268
+ "sub_step_counter": 0,
1269
+ "current_iteration": 1,
1270
+ "max_iterations": response.max_iterations,
1271
+ }
1272
+
1273
+ # Initialize parallel step tracking - clear previous states
1274
+ parallel_step_states.clear()
1275
+ step_display_cache.clear()
1276
+
1277
+ status.update(f"Starting loop: {current_step_name} (max {response.max_iterations} iterations)...")
1278
+ live_log.update(status)
1279
+
1280
+ elif isinstance(response, LoopIterationStartedEvent):
1281
+ if current_primitive_context and current_primitive_context["type"] == "loop":
1282
+ current_primitive_context["current_iteration"] = response.iteration
1283
+ current_primitive_context["sub_step_counter"] = 0 # Reset for new iteration
1284
+ # Clear cache for new iteration
1285
+ step_display_cache.clear()
1286
+
1287
+ status.update(
1288
+ f"Loop iteration {response.iteration}/{response.max_iterations}: {response.step_name}..."
1289
+ )
1290
+ live_log.update(status)
1291
+
1292
+ elif isinstance(response, LoopIterationCompletedEvent):
1293
+ status.update(
1294
+ f"Completed iteration {response.iteration}/{response.max_iterations}: {response.step_name}"
1295
+ )
1296
+
1297
+ elif isinstance(response, LoopExecutionCompletedEvent):
1298
+ step_name = response.step_name or "Loop"
1299
+ step_index = response.step_index or 0
1300
+
1301
+ status.update(f"Completed loop: {step_name} ({response.total_iterations} iterations)")
1302
+ live_log.update(status, refresh=True)
1303
+
1304
+ # Print loop summary
1305
+ if show_step_details:
1306
+ summary_content = "**Loop Summary:**\n\n"
1307
+ summary_content += (
1308
+ f"- Total iterations: {response.total_iterations}/{response.max_iterations}\n"
1309
+ )
1310
+ summary_content += (
1311
+ f"- Total steps executed: {sum(len(iteration) for iteration in response.all_results)}\n"
1312
+ )
1313
+
1314
+ loop_summary_panel = create_panel(
1315
+ content=Markdown(summary_content) if markdown else summary_content,
1316
+ title=f"Loop {step_name} (Completed)",
1317
+ border_style="yellow",
1318
+ )
1319
+ console.print(loop_summary_panel) # type: ignore
1320
+
1321
+ # Reset context
1322
+ current_primitive_context = None
1323
+ step_display_cache.clear()
1324
+ step_started_printed = True
1325
+
1326
+ elif isinstance(response, ParallelExecutionStartedEvent):
1327
+ current_step_name = response.step_name or "Parallel Steps"
1328
+ current_step_index = response.step_index or 0 # type: ignore
1329
+ current_step_content = ""
1330
+ step_started_printed = False
1331
+
1332
+ # Set up parallel context
1333
+ current_primitive_context = {
1334
+ "type": "parallel",
1335
+ "step_index": current_step_index,
1336
+ "sub_step_counter": 0,
1337
+ "total_steps": response.parallel_step_count,
1338
+ }
1339
+
1340
+ # Initialize parallel step tracking - clear previous states
1341
+ parallel_step_states.clear()
1342
+ step_display_cache.clear()
1343
+
1344
+ # Print parallel execution summary panel
1345
+ live_log.update(status, refresh=True)
1346
+ parallel_summary = f"**Parallel Steps:** {response.parallel_step_count}"
1347
+ # Use get_step_display_number for consistent numbering
1348
+ step_display = get_step_display_number(current_step_index, current_step_name)
1349
+ parallel_panel = create_panel(
1350
+ content=Markdown(parallel_summary) if markdown else parallel_summary,
1351
+ title=f"{step_display}: {current_step_name}",
1352
+ border_style="cyan",
1353
+ )
1354
+ console.print(parallel_panel) # type: ignore
1355
+
1356
+ status.update(
1357
+ f"Starting parallel execution: {current_step_name} ({response.parallel_step_count} steps)..."
1358
+ )
1359
+ live_log.update(status)
1360
+
1361
+ elif isinstance(response, ParallelExecutionCompletedEvent):
1362
+ step_name = response.step_name or "Parallel Steps"
1363
+ step_index = response.step_index or 0
1364
+
1365
+ status.update(f"Completed parallel execution: {step_name}")
1366
+
1367
+ # Display individual parallel step results immediately
1368
+ if show_step_details and response.step_results:
1369
+ live_log.update(status, refresh=True)
1370
+
1371
+ # Get the parallel container's display number for consistent numbering
1372
+ parallel_step_display = get_step_display_number(step_index, step_name)
1373
+
1374
+ # Show each parallel step with the same number (1.1, 1.1)
1375
+ for step_result in response.step_results:
1376
+ if step_result.content:
1377
+ step_result_name = step_result.step_name or "Parallel Step"
1378
+ formatted_content = format_step_content_for_display(step_result.content) # type: ignore
1379
+
1380
+ # All parallel sub-steps get the same number
1381
+ parallel_step_panel = create_panel(
1382
+ content=Markdown(formatted_content) if markdown else formatted_content,
1383
+ title=f"{parallel_step_display}: {step_result_name} (Completed)",
1384
+ border_style="orange3",
1385
+ )
1386
+ console.print(parallel_step_panel) # type: ignore
1387
+
1388
+ # Reset context
1389
+ current_primitive_context = None
1390
+ parallel_step_states.clear()
1391
+ step_display_cache.clear()
1392
+
1393
+ elif isinstance(response, ConditionExecutionStartedEvent):
1394
+ current_step_name = response.step_name or "Condition"
1395
+ current_step_index = response.step_index or 0 # type: ignore
1396
+ current_step_content = ""
1397
+ step_started_printed = False
1398
+
1399
+ # Set up condition context
1400
+ current_primitive_context = {
1401
+ "type": "condition",
1402
+ "step_index": current_step_index,
1403
+ "sub_step_counter": 0,
1404
+ "condition_result": response.condition_result,
1405
+ }
1406
+
1407
+ # Initialize parallel step tracking - clear previous states
1408
+ parallel_step_states.clear()
1409
+ step_display_cache.clear()
1410
+
1411
+ condition_text = "met" if response.condition_result else "not met"
1412
+ status.update(f"Starting condition: {current_step_name} (condition {condition_text})...")
1413
+ live_log.update(status)
1414
+
1415
+ elif isinstance(response, ConditionExecutionCompletedEvent):
1416
+ step_name = response.step_name or "Condition"
1417
+ step_index = response.step_index or 0
1418
+
1419
+ status.update(f"Completed condition: {step_name}")
1420
+
1421
+ # Reset context
1422
+ current_primitive_context = None
1423
+ step_display_cache.clear()
1424
+
1425
+ elif isinstance(response, RouterExecutionStartedEvent):
1426
+ current_step_name = response.step_name or "Router"
1427
+ current_step_index = response.step_index or 0 # type: ignore
1428
+ current_step_content = ""
1429
+ step_started_printed = False
1430
+
1431
+ # Set up router context
1432
+ current_primitive_context = {
1433
+ "type": "router",
1434
+ "step_index": current_step_index,
1435
+ "sub_step_counter": 0,
1436
+ "selected_steps": response.selected_steps,
1437
+ }
1438
+
1439
+ # Initialize parallel step tracking - clear previous states
1440
+ parallel_step_states.clear()
1441
+ step_display_cache.clear()
1442
+
1443
+ selected_steps_text = ", ".join(response.selected_steps) if response.selected_steps else "none"
1444
+ status.update(f"Starting router: {current_step_name} (selected: {selected_steps_text})...")
1445
+ live_log.update(status)
1446
+
1447
+ elif isinstance(response, RouterExecutionCompletedEvent):
1448
+ step_name = response.step_name or "Router"
1449
+ step_index = response.step_index or 0
1450
+
1451
+ status.update(f"Completed router: {step_name}")
1452
+
1453
+ # Print router summary
1454
+ if show_step_details:
1455
+ selected_steps_text = ", ".join(response.selected_steps) if response.selected_steps else "none"
1456
+ summary_content = "**Router Summary:**\n\n"
1457
+ summary_content += f"- Selected steps: {selected_steps_text}\n"
1458
+ summary_content += f"- Executed steps: {response.executed_steps or 0}\n"
1459
+
1460
+ router_summary_panel = create_panel(
1461
+ content=Markdown(summary_content) if markdown else summary_content,
1462
+ title=f"Router {step_name} (Completed)",
1463
+ border_style="purple",
1464
+ )
1465
+ console.print(router_summary_panel) # type: ignore
1466
+
1467
+ # Reset context
1468
+ current_primitive_context = None
1469
+ step_display_cache.clear()
1470
+ step_started_printed = True
1471
+
1472
+ elif isinstance(response, StepsExecutionStartedEvent):
1473
+ current_step_name = response.step_name or "Steps"
1474
+ current_step_index = response.step_index or 0 # type: ignore
1475
+ current_step_content = ""
1476
+ step_started_printed = False
1477
+ status.update(f"Starting steps: {current_step_name} ({response.steps_count} steps)...")
1478
+ live_log.update(status)
1479
+
1480
+ elif isinstance(response, StepsExecutionCompletedEvent):
1481
+ step_name = response.step_name or "Steps"
1482
+ step_index = response.step_index or 0
1483
+
1484
+ status.update(f"Completed steps: {step_name}")
1485
+
1486
+ # Add results from executed steps to step_results
1487
+ if response.step_results:
1488
+ for i, step_result in enumerate(response.step_results):
1489
+ # Use the same numbering system as other primitives
1490
+ step_display_number = get_step_display_number(step_index, step_result.step_name or "")
1491
+ step_results.append(
1492
+ {
1493
+ "step_name": f"{step_display_number}: {step_result.step_name}",
1494
+ "step_index": step_index,
1495
+ "content": step_result.content,
1496
+ "event": "StepsStepResult",
1497
+ }
1498
+ )
1499
+
1500
+ # Print steps summary
1501
+ if show_step_details:
1502
+ summary_content = "**Steps Summary:**\n\n"
1503
+ summary_content += f"- Total steps: {response.steps_count or 0}\n"
1504
+ summary_content += f"- Executed steps: {response.executed_steps or 0}\n"
1505
+
1506
+ steps_summary_panel = create_panel(
1507
+ content=Markdown(summary_content) if markdown else summary_content,
1508
+ title=f"Steps {step_name} (Completed)",
1509
+ border_style="yellow",
1510
+ )
1511
+ console.print(steps_summary_panel) # type: ignore
1512
+
1513
+ step_started_printed = True
1514
+
1515
+ elif isinstance(response, WorkflowCompletedEvent):
1516
+ status.update("Workflow completed!")
1517
+
1518
+ # Check if this is an agent direct response
1519
+ if response.metadata and response.metadata.get("agent_direct_response"):
1520
+ is_workflow_agent_response = True
1521
+ # Print the agent's direct response from history
1522
+ if show_step_details:
1523
+ live_log.update(status, refresh=True)
1524
+ agent_response_panel = create_panel(
1525
+ content=Markdown(str(response.content)) if markdown else str(response.content),
1526
+ title="Workflow Agent Response",
1527
+ border_style="green",
1528
+ )
1529
+ console.print(agent_response_panel) # type: ignore
1530
+ step_started_printed = True
1531
+ # For callable functions, print the final content block here since there are no step events
1532
+ elif (
1533
+ is_callable_function and show_step_details and current_step_content and not step_started_printed
1534
+ ):
1535
+ final_step_panel = create_panel(
1536
+ content=Markdown(current_step_content) if markdown else current_step_content,
1537
+ title="Custom Function (Completed)",
1538
+ border_style="orange3",
1539
+ )
1540
+ console.print(final_step_panel) # type: ignore
1541
+ step_started_printed = True
1542
+
1543
+ live_log.update(status, refresh=True)
1544
+
1545
+ # Show final summary (skip for agent responses)
1546
+ if response.metadata and not is_workflow_agent_response:
1547
+ status = response.status
1548
+ summary_content = ""
1549
+ summary_content += f"""\n\n**Status:** {status}"""
1550
+ summary_content += (
1551
+ f"""\n\n**Steps Completed:** {len(response.step_results) if response.step_results else 0}"""
1552
+ )
1553
+ summary_content = summary_content.strip()
1554
+
1555
+ summary_panel = create_panel(
1556
+ content=Markdown(summary_content) if markdown else summary_content,
1557
+ title="Execution Summary",
1558
+ border_style="blue",
1559
+ )
1560
+ console.print(summary_panel) # type: ignore
1561
+
1562
+ else:
1563
+ if isinstance(response, str):
1564
+ response_str = response
1565
+ elif isinstance(response, StepOutputEvent):
1566
+ # Handle StepOutputEvent objects yielded from workflow
1567
+ response_str = response.content or "" # type: ignore
1568
+ else:
1569
+ from agno.run.agent import RunContentEvent
1570
+ from agno.run.team import RunContentEvent as TeamRunContentEvent
1571
+
1572
+ current_step_executor_type = None
1573
+ # Handle both integer and tuple step indices for parallel execution
1574
+ actual_step_index = current_step_index
1575
+ if isinstance(current_step_index, tuple):
1576
+ # For tuple indices, use the first element (parent step index)
1577
+ actual_step_index = current_step_index[0]
1578
+ # If it's nested tuple, keep extracting until we get an integer
1579
+ while isinstance(actual_step_index, tuple) and len(actual_step_index) > 0:
1580
+ actual_step_index = actual_step_index[0]
1581
+
1582
+ # Check if this is a streaming content event from agent or team
1583
+ if isinstance(
1584
+ response,
1585
+ (RunContentEvent, TeamRunContentEvent, WorkflowRunOutputEvent), # type: ignore
1586
+ ): # type: ignore
1587
+ # Handle WorkflowErrorEvent specifically
1588
+ if isinstance(response, WorkflowErrorEvent): # type: ignore
1589
+ response_str = response.error or "Workflow execution error" # type: ignore
1590
+ else:
1591
+ # Extract the content from the streaming event
1592
+ response_str = response.content # type: ignore
1593
+
1594
+ # If we get RunContentEvent BEFORE workflow starts, it's an agent direct response
1595
+ if isinstance(response, RunContentEvent) and not workflow_started:
1596
+ is_workflow_agent_response = True
1597
+ continue # Skip ALL agent direct response content
1598
+
1599
+ # Check if this is a team's final structured output
1600
+ is_structured_output = (
1601
+ isinstance(response, TeamRunContentEvent)
1602
+ and hasattr(response, "content_type")
1603
+ and response.content_type != "str"
1604
+ and response.content_type != ""
1605
+ )
1606
+ elif isinstance(response, RunContentEvent) and current_step_executor_type != "team":
1607
+ response_str = response.content # type: ignore
1608
+ # If we get RunContentEvent BEFORE workflow starts, it's an agent direct response
1609
+ if not workflow_started and not is_workflow_agent_response:
1610
+ is_workflow_agent_response = True
1611
+ else:
1612
+ continue
1613
+
1614
+ # Use the unified formatting function for consistency
1615
+ response_str = format_step_content_for_display(response_str) # type: ignore
1616
+
1617
+ # Skip streaming content from parallel sub-steps - they're handled in ParallelExecutionCompletedEvent
1618
+ if (
1619
+ current_primitive_context
1620
+ and current_primitive_context["type"] == "parallel"
1621
+ and isinstance(current_step_index, tuple)
1622
+ ):
1623
+ continue
1624
+
1625
+ # Filter out empty responses and add to current step content
1626
+ if response_str and response_str.strip():
1627
+ # If it's a structured output from a team, replace the content instead of appending
1628
+ if "is_structured_output" in locals() and is_structured_output:
1629
+ current_step_content = response_str
1630
+ else:
1631
+ current_step_content += response_str
1632
+
1633
+ # Live update the step panel with streaming content (skip for workflow agent responses)
1634
+ if show_step_details and not step_started_printed and not is_workflow_agent_response:
1635
+ # Generate smart step number for streaming title (will use cached value)
1636
+ step_display = get_step_display_number(current_step_index, current_step_name)
1637
+ title = f"{step_display}: {current_step_name} (Streaming...)"
1638
+ if is_callable_function:
1639
+ title = "Custom Function (Streaming...)"
1640
+
1641
+ # Show the streaming content live in orange panel
1642
+ live_step_panel = create_panel(
1643
+ content=Markdown(current_step_content) if markdown else current_step_content,
1644
+ title=title,
1645
+ border_style="orange3",
1646
+ )
1647
+
1648
+ # Create group with status and current step content
1649
+ group = Group(status, live_step_panel)
1650
+ live_log.update(group)
1651
+
1652
+ response_timer.stop()
1653
+
1654
+ live_log.update("")
1655
+
1656
+ if show_time and not is_workflow_agent_response:
1657
+ completion_text = Text(f"Completed in {response_timer.elapsed:.1f}s", style="bold green")
1658
+ console.print(completion_text) # type: ignore
1659
+
1660
+ except Exception as e:
1661
+ import traceback
1662
+
1663
+ traceback.print_exc()
1664
+ response_timer.stop()
1665
+ error_panel = create_panel(
1666
+ content=f"Workflow execution failed: {str(e)}", title="Execution Error", border_style="red"
1667
+ )
1668
+ console.print(error_panel) # type: ignore