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.
- agno/__init__.py +8 -0
- agno/agent/__init__.py +51 -0
- agno/agent/agent.py +10405 -0
- agno/api/__init__.py +0 -0
- agno/api/agent.py +28 -0
- agno/api/api.py +40 -0
- agno/api/evals.py +22 -0
- agno/api/os.py +17 -0
- agno/api/routes.py +13 -0
- agno/api/schemas/__init__.py +9 -0
- agno/api/schemas/agent.py +16 -0
- agno/api/schemas/evals.py +16 -0
- agno/api/schemas/os.py +14 -0
- agno/api/schemas/response.py +6 -0
- agno/api/schemas/team.py +16 -0
- agno/api/schemas/utils.py +21 -0
- agno/api/schemas/workflows.py +16 -0
- agno/api/settings.py +53 -0
- agno/api/team.py +30 -0
- agno/api/workflow.py +28 -0
- agno/cloud/aws/base.py +214 -0
- agno/cloud/aws/s3/__init__.py +2 -0
- agno/cloud/aws/s3/api_client.py +43 -0
- agno/cloud/aws/s3/bucket.py +195 -0
- agno/cloud/aws/s3/object.py +57 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/__init__.py +24 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +598 -0
- agno/db/dynamo/__init__.py +3 -0
- agno/db/dynamo/dynamo.py +2042 -0
- agno/db/dynamo/schemas.py +314 -0
- agno/db/dynamo/utils.py +743 -0
- agno/db/firestore/__init__.py +3 -0
- agno/db/firestore/firestore.py +1795 -0
- agno/db/firestore/schemas.py +140 -0
- agno/db/firestore/utils.py +376 -0
- agno/db/gcs_json/__init__.py +3 -0
- agno/db/gcs_json/gcs_json_db.py +1335 -0
- agno/db/gcs_json/utils.py +228 -0
- agno/db/in_memory/__init__.py +3 -0
- agno/db/in_memory/in_memory_db.py +1160 -0
- agno/db/in_memory/utils.py +230 -0
- agno/db/json/__init__.py +3 -0
- agno/db/json/json_db.py +1328 -0
- agno/db/json/utils.py +230 -0
- agno/db/migrations/__init__.py +0 -0
- agno/db/migrations/v1_to_v2.py +635 -0
- agno/db/mongo/__init__.py +17 -0
- agno/db/mongo/async_mongo.py +2026 -0
- agno/db/mongo/mongo.py +1982 -0
- agno/db/mongo/schemas.py +87 -0
- agno/db/mongo/utils.py +259 -0
- agno/db/mysql/__init__.py +3 -0
- agno/db/mysql/mysql.py +2308 -0
- agno/db/mysql/schemas.py +138 -0
- agno/db/mysql/utils.py +355 -0
- agno/db/postgres/__init__.py +4 -0
- agno/db/postgres/async_postgres.py +1927 -0
- agno/db/postgres/postgres.py +2260 -0
- agno/db/postgres/schemas.py +139 -0
- agno/db/postgres/utils.py +442 -0
- agno/db/redis/__init__.py +3 -0
- agno/db/redis/redis.py +1660 -0
- agno/db/redis/schemas.py +123 -0
- agno/db/redis/utils.py +346 -0
- agno/db/schemas/__init__.py +4 -0
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +33 -0
- agno/db/schemas/knowledge.py +40 -0
- agno/db/schemas/memory.py +46 -0
- agno/db/schemas/metrics.py +0 -0
- agno/db/singlestore/__init__.py +3 -0
- agno/db/singlestore/schemas.py +130 -0
- agno/db/singlestore/singlestore.py +2272 -0
- agno/db/singlestore/utils.py +384 -0
- agno/db/sqlite/__init__.py +4 -0
- agno/db/sqlite/async_sqlite.py +2293 -0
- agno/db/sqlite/schemas.py +133 -0
- agno/db/sqlite/sqlite.py +2288 -0
- agno/db/sqlite/utils.py +431 -0
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1353 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +116 -0
- agno/debug.py +18 -0
- agno/eval/__init__.py +14 -0
- agno/eval/accuracy.py +834 -0
- agno/eval/performance.py +773 -0
- agno/eval/reliability.py +306 -0
- agno/eval/utils.py +119 -0
- agno/exceptions.py +161 -0
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/__init__.py +0 -0
- agno/integrations/discord/__init__.py +3 -0
- agno/integrations/discord/client.py +203 -0
- agno/knowledge/__init__.py +5 -0
- agno/knowledge/chunking/__init__.py +0 -0
- agno/knowledge/chunking/agentic.py +79 -0
- agno/knowledge/chunking/document.py +91 -0
- agno/knowledge/chunking/fixed.py +57 -0
- agno/knowledge/chunking/markdown.py +151 -0
- agno/knowledge/chunking/recursive.py +63 -0
- agno/knowledge/chunking/row.py +39 -0
- agno/knowledge/chunking/semantic.py +86 -0
- agno/knowledge/chunking/strategy.py +165 -0
- agno/knowledge/content.py +74 -0
- agno/knowledge/document/__init__.py +5 -0
- agno/knowledge/document/base.py +58 -0
- agno/knowledge/embedder/__init__.py +5 -0
- agno/knowledge/embedder/aws_bedrock.py +343 -0
- agno/knowledge/embedder/azure_openai.py +210 -0
- agno/knowledge/embedder/base.py +23 -0
- agno/knowledge/embedder/cohere.py +323 -0
- agno/knowledge/embedder/fastembed.py +62 -0
- agno/knowledge/embedder/fireworks.py +13 -0
- agno/knowledge/embedder/google.py +258 -0
- agno/knowledge/embedder/huggingface.py +94 -0
- agno/knowledge/embedder/jina.py +182 -0
- agno/knowledge/embedder/langdb.py +22 -0
- agno/knowledge/embedder/mistral.py +206 -0
- agno/knowledge/embedder/nebius.py +13 -0
- agno/knowledge/embedder/ollama.py +154 -0
- agno/knowledge/embedder/openai.py +195 -0
- agno/knowledge/embedder/sentence_transformer.py +63 -0
- agno/knowledge/embedder/together.py +13 -0
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +165 -0
- agno/knowledge/knowledge.py +1988 -0
- agno/knowledge/reader/__init__.py +7 -0
- agno/knowledge/reader/arxiv_reader.py +81 -0
- agno/knowledge/reader/base.py +95 -0
- agno/knowledge/reader/csv_reader.py +166 -0
- agno/knowledge/reader/docx_reader.py +82 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +292 -0
- agno/knowledge/reader/firecrawl_reader.py +201 -0
- agno/knowledge/reader/json_reader.py +87 -0
- agno/knowledge/reader/markdown_reader.py +137 -0
- agno/knowledge/reader/pdf_reader.py +431 -0
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +313 -0
- agno/knowledge/reader/s3_reader.py +89 -0
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +115 -0
- agno/knowledge/reader/web_search_reader.py +372 -0
- agno/knowledge/reader/website_reader.py +455 -0
- agno/knowledge/reader/wikipedia_reader.py +59 -0
- agno/knowledge/reader/youtube_reader.py +78 -0
- agno/knowledge/remote_content/__init__.py +0 -0
- agno/knowledge/remote_content/remote_content.py +88 -0
- agno/knowledge/reranker/__init__.py +3 -0
- agno/knowledge/reranker/base.py +14 -0
- agno/knowledge/reranker/cohere.py +64 -0
- agno/knowledge/reranker/infinity.py +195 -0
- agno/knowledge/reranker/sentence_transformer.py +54 -0
- agno/knowledge/types.py +39 -0
- agno/knowledge/utils.py +189 -0
- agno/media.py +462 -0
- agno/memory/__init__.py +3 -0
- agno/memory/manager.py +1327 -0
- agno/models/__init__.py +0 -0
- agno/models/aimlapi/__init__.py +5 -0
- agno/models/aimlapi/aimlapi.py +45 -0
- agno/models/anthropic/__init__.py +5 -0
- agno/models/anthropic/claude.py +757 -0
- agno/models/aws/__init__.py +15 -0
- agno/models/aws/bedrock.py +701 -0
- agno/models/aws/claude.py +378 -0
- agno/models/azure/__init__.py +18 -0
- agno/models/azure/ai_foundry.py +485 -0
- agno/models/azure/openai_chat.py +131 -0
- agno/models/base.py +2175 -0
- agno/models/cerebras/__init__.py +12 -0
- agno/models/cerebras/cerebras.py +501 -0
- agno/models/cerebras/cerebras_openai.py +112 -0
- agno/models/cohere/__init__.py +5 -0
- agno/models/cohere/chat.py +389 -0
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/__init__.py +5 -0
- agno/models/dashscope/dashscope.py +91 -0
- agno/models/deepinfra/__init__.py +5 -0
- agno/models/deepinfra/deepinfra.py +28 -0
- agno/models/deepseek/__init__.py +5 -0
- agno/models/deepseek/deepseek.py +61 -0
- agno/models/defaults.py +1 -0
- agno/models/fireworks/__init__.py +5 -0
- agno/models/fireworks/fireworks.py +26 -0
- agno/models/google/__init__.py +5 -0
- agno/models/google/gemini.py +1085 -0
- agno/models/groq/__init__.py +5 -0
- agno/models/groq/groq.py +556 -0
- agno/models/huggingface/__init__.py +5 -0
- agno/models/huggingface/huggingface.py +491 -0
- agno/models/ibm/__init__.py +5 -0
- agno/models/ibm/watsonx.py +422 -0
- agno/models/internlm/__init__.py +3 -0
- agno/models/internlm/internlm.py +26 -0
- agno/models/langdb/__init__.py +1 -0
- agno/models/langdb/langdb.py +48 -0
- agno/models/litellm/__init__.py +14 -0
- agno/models/litellm/chat.py +468 -0
- agno/models/litellm/litellm_openai.py +25 -0
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/lmstudio/__init__.py +5 -0
- agno/models/lmstudio/lmstudio.py +25 -0
- agno/models/message.py +434 -0
- agno/models/meta/__init__.py +12 -0
- agno/models/meta/llama.py +475 -0
- agno/models/meta/llama_openai.py +78 -0
- agno/models/metrics.py +120 -0
- agno/models/mistral/__init__.py +5 -0
- agno/models/mistral/mistral.py +432 -0
- agno/models/nebius/__init__.py +3 -0
- agno/models/nebius/nebius.py +54 -0
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/__init__.py +5 -0
- agno/models/nvidia/nvidia.py +28 -0
- agno/models/ollama/__init__.py +5 -0
- agno/models/ollama/chat.py +441 -0
- agno/models/openai/__init__.py +9 -0
- agno/models/openai/chat.py +883 -0
- agno/models/openai/like.py +27 -0
- agno/models/openai/responses.py +1050 -0
- agno/models/openrouter/__init__.py +5 -0
- agno/models/openrouter/openrouter.py +66 -0
- agno/models/perplexity/__init__.py +5 -0
- agno/models/perplexity/perplexity.py +187 -0
- agno/models/portkey/__init__.py +3 -0
- agno/models/portkey/portkey.py +81 -0
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +199 -0
- agno/models/sambanova/__init__.py +5 -0
- agno/models/sambanova/sambanova.py +28 -0
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/__init__.py +5 -0
- agno/models/together/together.py +25 -0
- agno/models/utils.py +266 -0
- agno/models/vercel/__init__.py +3 -0
- agno/models/vercel/v0.py +26 -0
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +70 -0
- agno/models/vllm/__init__.py +3 -0
- agno/models/vllm/vllm.py +78 -0
- agno/models/xai/__init__.py +3 -0
- agno/models/xai/xai.py +113 -0
- agno/os/__init__.py +3 -0
- agno/os/app.py +876 -0
- agno/os/auth.py +57 -0
- agno/os/config.py +104 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/__init__.py +3 -0
- agno/os/interfaces/agui/agui.py +47 -0
- agno/os/interfaces/agui/router.py +144 -0
- agno/os/interfaces/agui/utils.py +534 -0
- agno/os/interfaces/base.py +25 -0
- agno/os/interfaces/slack/__init__.py +3 -0
- agno/os/interfaces/slack/router.py +148 -0
- agno/os/interfaces/slack/security.py +30 -0
- agno/os/interfaces/slack/slack.py +47 -0
- agno/os/interfaces/whatsapp/__init__.py +3 -0
- agno/os/interfaces/whatsapp/router.py +211 -0
- agno/os/interfaces/whatsapp/security.py +53 -0
- agno/os/interfaces/whatsapp/whatsapp.py +36 -0
- agno/os/mcp.py +292 -0
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +1763 -0
- agno/os/routers/__init__.py +3 -0
- agno/os/routers/evals/__init__.py +3 -0
- agno/os/routers/evals/evals.py +430 -0
- agno/os/routers/evals/schemas.py +142 -0
- agno/os/routers/evals/utils.py +162 -0
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/__init__.py +3 -0
- agno/os/routers/knowledge/knowledge.py +997 -0
- agno/os/routers/knowledge/schemas.py +178 -0
- agno/os/routers/memory/__init__.py +3 -0
- agno/os/routers/memory/memory.py +515 -0
- agno/os/routers/memory/schemas.py +62 -0
- agno/os/routers/metrics/__init__.py +3 -0
- agno/os/routers/metrics/metrics.py +190 -0
- agno/os/routers/metrics/schemas.py +47 -0
- agno/os/routers/session/__init__.py +3 -0
- agno/os/routers/session/session.py +997 -0
- agno/os/schema.py +1055 -0
- agno/os/settings.py +43 -0
- agno/os/utils.py +630 -0
- agno/py.typed +0 -0
- agno/reasoning/__init__.py +0 -0
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +67 -0
- agno/reasoning/deepseek.py +63 -0
- agno/reasoning/default.py +97 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +71 -0
- agno/reasoning/helpers.py +63 -0
- agno/reasoning/ollama.py +67 -0
- agno/reasoning/openai.py +86 -0
- agno/reasoning/step.py +31 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +787 -0
- agno/run/base.py +229 -0
- agno/run/cancel.py +81 -0
- agno/run/messages.py +32 -0
- agno/run/team.py +753 -0
- agno/run/workflow.py +708 -0
- agno/session/__init__.py +10 -0
- agno/session/agent.py +295 -0
- agno/session/summary.py +265 -0
- agno/session/team.py +392 -0
- agno/session/workflow.py +205 -0
- agno/team/__init__.py +37 -0
- agno/team/team.py +8793 -0
- agno/tools/__init__.py +10 -0
- agno/tools/agentql.py +120 -0
- agno/tools/airflow.py +69 -0
- agno/tools/api.py +122 -0
- agno/tools/apify.py +314 -0
- agno/tools/arxiv.py +127 -0
- agno/tools/aws_lambda.py +53 -0
- agno/tools/aws_ses.py +66 -0
- agno/tools/baidusearch.py +89 -0
- agno/tools/bitbucket.py +292 -0
- agno/tools/brandfetch.py +213 -0
- agno/tools/bravesearch.py +106 -0
- agno/tools/brightdata.py +367 -0
- agno/tools/browserbase.py +209 -0
- agno/tools/calcom.py +255 -0
- agno/tools/calculator.py +151 -0
- agno/tools/cartesia.py +187 -0
- agno/tools/clickup.py +244 -0
- agno/tools/confluence.py +240 -0
- agno/tools/crawl4ai.py +158 -0
- agno/tools/csv_toolkit.py +185 -0
- agno/tools/dalle.py +110 -0
- agno/tools/daytona.py +475 -0
- agno/tools/decorator.py +262 -0
- agno/tools/desi_vocal.py +108 -0
- agno/tools/discord.py +161 -0
- agno/tools/docker.py +716 -0
- agno/tools/duckdb.py +379 -0
- agno/tools/duckduckgo.py +91 -0
- agno/tools/e2b.py +703 -0
- agno/tools/eleven_labs.py +196 -0
- agno/tools/email.py +67 -0
- agno/tools/evm.py +129 -0
- agno/tools/exa.py +396 -0
- agno/tools/fal.py +127 -0
- agno/tools/file.py +240 -0
- agno/tools/file_generation.py +350 -0
- agno/tools/financial_datasets.py +288 -0
- agno/tools/firecrawl.py +143 -0
- agno/tools/function.py +1187 -0
- agno/tools/giphy.py +93 -0
- agno/tools/github.py +1760 -0
- agno/tools/gmail.py +922 -0
- agno/tools/google_bigquery.py +117 -0
- agno/tools/google_drive.py +270 -0
- agno/tools/google_maps.py +253 -0
- agno/tools/googlecalendar.py +674 -0
- agno/tools/googlesearch.py +98 -0
- agno/tools/googlesheets.py +377 -0
- agno/tools/hackernews.py +77 -0
- agno/tools/jina.py +101 -0
- agno/tools/jira.py +170 -0
- agno/tools/knowledge.py +218 -0
- agno/tools/linear.py +426 -0
- agno/tools/linkup.py +58 -0
- agno/tools/local_file_system.py +90 -0
- agno/tools/lumalab.py +183 -0
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +193 -0
- agno/tools/memori.py +339 -0
- agno/tools/memory.py +419 -0
- agno/tools/mlx_transcribe.py +139 -0
- agno/tools/models/__init__.py +0 -0
- agno/tools/models/azure_openai.py +190 -0
- agno/tools/models/gemini.py +203 -0
- agno/tools/models/groq.py +158 -0
- agno/tools/models/morph.py +186 -0
- agno/tools/models/nebius.py +124 -0
- agno/tools/models_labs.py +195 -0
- agno/tools/moviepy_video.py +349 -0
- agno/tools/neo4j.py +134 -0
- agno/tools/newspaper.py +46 -0
- agno/tools/newspaper4k.py +93 -0
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +202 -0
- agno/tools/openbb.py +160 -0
- agno/tools/opencv.py +321 -0
- agno/tools/openweather.py +233 -0
- agno/tools/oxylabs.py +385 -0
- agno/tools/pandas.py +102 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +257 -0
- agno/tools/pubmed.py +188 -0
- agno/tools/python.py +205 -0
- agno/tools/reasoning.py +283 -0
- agno/tools/reddit.py +467 -0
- agno/tools/replicate.py +117 -0
- agno/tools/resend.py +62 -0
- agno/tools/scrapegraph.py +222 -0
- agno/tools/searxng.py +152 -0
- agno/tools/serpapi.py +116 -0
- agno/tools/serper.py +255 -0
- agno/tools/shell.py +53 -0
- agno/tools/slack.py +136 -0
- agno/tools/sleep.py +20 -0
- agno/tools/spider.py +116 -0
- agno/tools/sql.py +154 -0
- agno/tools/streamlit/__init__.py +0 -0
- agno/tools/streamlit/components.py +113 -0
- agno/tools/tavily.py +254 -0
- agno/tools/telegram.py +48 -0
- agno/tools/todoist.py +218 -0
- agno/tools/tool_registry.py +1 -0
- agno/tools/toolkit.py +146 -0
- agno/tools/trafilatura.py +388 -0
- agno/tools/trello.py +274 -0
- agno/tools/twilio.py +186 -0
- agno/tools/user_control_flow.py +78 -0
- agno/tools/valyu.py +228 -0
- agno/tools/visualization.py +467 -0
- agno/tools/webbrowser.py +28 -0
- agno/tools/webex.py +76 -0
- agno/tools/website.py +54 -0
- agno/tools/webtools.py +45 -0
- agno/tools/whatsapp.py +286 -0
- agno/tools/wikipedia.py +63 -0
- agno/tools/workflow.py +278 -0
- agno/tools/x.py +335 -0
- agno/tools/yfinance.py +257 -0
- agno/tools/youtube.py +184 -0
- agno/tools/zendesk.py +82 -0
- agno/tools/zep.py +454 -0
- agno/tools/zoom.py +382 -0
- agno/utils/__init__.py +0 -0
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +49 -0
- agno/utils/certs.py +27 -0
- agno/utils/code_execution.py +11 -0
- agno/utils/common.py +132 -0
- agno/utils/dttm.py +13 -0
- agno/utils/enum.py +22 -0
- agno/utils/env.py +11 -0
- agno/utils/events.py +696 -0
- agno/utils/format_str.py +16 -0
- agno/utils/functions.py +166 -0
- agno/utils/gemini.py +426 -0
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +74 -0
- agno/utils/json_schema.py +234 -0
- agno/utils/knowledge.py +36 -0
- agno/utils/location.py +19 -0
- agno/utils/log.py +255 -0
- agno/utils/mcp.py +214 -0
- agno/utils/media.py +352 -0
- agno/utils/merge_dict.py +41 -0
- agno/utils/message.py +118 -0
- agno/utils/models/__init__.py +0 -0
- agno/utils/models/ai_foundry.py +43 -0
- agno/utils/models/claude.py +358 -0
- agno/utils/models/cohere.py +87 -0
- agno/utils/models/llama.py +78 -0
- agno/utils/models/mistral.py +98 -0
- agno/utils/models/openai_responses.py +140 -0
- agno/utils/models/schema_utils.py +153 -0
- agno/utils/models/watsonx.py +41 -0
- agno/utils/openai.py +257 -0
- agno/utils/pickle.py +32 -0
- agno/utils/pprint.py +178 -0
- agno/utils/print_response/__init__.py +0 -0
- agno/utils/print_response/agent.py +842 -0
- agno/utils/print_response/team.py +1724 -0
- agno/utils/print_response/workflow.py +1668 -0
- agno/utils/prompts.py +111 -0
- agno/utils/reasoning.py +108 -0
- agno/utils/response.py +163 -0
- agno/utils/response_iterator.py +17 -0
- agno/utils/safe_formatter.py +24 -0
- agno/utils/serialize.py +32 -0
- agno/utils/shell.py +22 -0
- agno/utils/streamlit.py +487 -0
- agno/utils/string.py +231 -0
- agno/utils/team.py +139 -0
- agno/utils/timer.py +41 -0
- agno/utils/tools.py +102 -0
- agno/utils/web.py +23 -0
- agno/utils/whatsapp.py +305 -0
- agno/utils/yaml_io.py +25 -0
- agno/vectordb/__init__.py +3 -0
- agno/vectordb/base.py +127 -0
- agno/vectordb/cassandra/__init__.py +5 -0
- agno/vectordb/cassandra/cassandra.py +501 -0
- agno/vectordb/cassandra/extra_param_mixin.py +11 -0
- agno/vectordb/cassandra/index.py +13 -0
- agno/vectordb/chroma/__init__.py +5 -0
- agno/vectordb/chroma/chromadb.py +929 -0
- agno/vectordb/clickhouse/__init__.py +9 -0
- agno/vectordb/clickhouse/clickhousedb.py +835 -0
- agno/vectordb/clickhouse/index.py +9 -0
- agno/vectordb/couchbase/__init__.py +3 -0
- agno/vectordb/couchbase/couchbase.py +1442 -0
- agno/vectordb/distance.py +7 -0
- agno/vectordb/lancedb/__init__.py +6 -0
- agno/vectordb/lancedb/lance_db.py +995 -0
- agno/vectordb/langchaindb/__init__.py +5 -0
- agno/vectordb/langchaindb/langchaindb.py +163 -0
- agno/vectordb/lightrag/__init__.py +5 -0
- agno/vectordb/lightrag/lightrag.py +388 -0
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +166 -0
- agno/vectordb/milvus/__init__.py +4 -0
- agno/vectordb/milvus/milvus.py +1182 -0
- agno/vectordb/mongodb/__init__.py +9 -0
- agno/vectordb/mongodb/mongodb.py +1417 -0
- agno/vectordb/pgvector/__init__.py +12 -0
- agno/vectordb/pgvector/index.py +23 -0
- agno/vectordb/pgvector/pgvector.py +1462 -0
- agno/vectordb/pineconedb/__init__.py +5 -0
- agno/vectordb/pineconedb/pineconedb.py +747 -0
- agno/vectordb/qdrant/__init__.py +5 -0
- agno/vectordb/qdrant/qdrant.py +1134 -0
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/search.py +7 -0
- agno/vectordb/singlestore/__init__.py +10 -0
- agno/vectordb/singlestore/index.py +41 -0
- agno/vectordb/singlestore/singlestore.py +763 -0
- agno/vectordb/surrealdb/__init__.py +3 -0
- agno/vectordb/surrealdb/surrealdb.py +699 -0
- agno/vectordb/upstashdb/__init__.py +5 -0
- agno/vectordb/upstashdb/upstashdb.py +718 -0
- agno/vectordb/weaviate/__init__.py +8 -0
- agno/vectordb/weaviate/index.py +15 -0
- agno/vectordb/weaviate/weaviate.py +1005 -0
- agno/workflow/__init__.py +23 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +738 -0
- agno/workflow/loop.py +735 -0
- agno/workflow/parallel.py +824 -0
- agno/workflow/router.py +702 -0
- agno/workflow/step.py +1432 -0
- agno/workflow/steps.py +592 -0
- agno/workflow/types.py +520 -0
- agno/workflow/workflow.py +4321 -0
- agno-2.2.13.dist-info/METADATA +614 -0
- agno-2.2.13.dist-info/RECORD +575 -0
- agno-2.2.13.dist-info/WHEEL +5 -0
- agno-2.2.13.dist-info/licenses/LICENSE +201 -0
- 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
|