agno 1.8.1__py3-none-any.whl → 2.0.0a1__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 +19 -27
- agno/agent/agent.py +2778 -4123
- agno/api/agent.py +9 -65
- agno/api/api.py +5 -46
- agno/api/evals.py +6 -17
- agno/api/os.py +17 -0
- agno/api/routes.py +6 -41
- agno/api/schemas/__init__.py +9 -0
- agno/api/schemas/agent.py +5 -21
- agno/api/schemas/evals.py +7 -16
- agno/api/schemas/os.py +14 -0
- agno/api/schemas/team.py +5 -21
- agno/api/schemas/utils.py +21 -0
- agno/api/schemas/workflows.py +11 -7
- agno/api/settings.py +53 -0
- agno/api/team.py +9 -64
- 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/db/__init__.py +24 -0
- agno/db/base.py +245 -0
- agno/db/dynamo/__init__.py +3 -0
- agno/db/dynamo/dynamo.py +1749 -0
- agno/db/dynamo/schemas.py +278 -0
- agno/db/dynamo/utils.py +684 -0
- agno/db/firestore/__init__.py +3 -0
- agno/db/firestore/firestore.py +1438 -0
- agno/db/firestore/schemas.py +130 -0
- agno/db/firestore/utils.py +278 -0
- agno/db/gcs_json/__init__.py +3 -0
- agno/db/gcs_json/gcs_json_db.py +1001 -0
- agno/db/gcs_json/utils.py +194 -0
- agno/db/in_memory/__init__.py +3 -0
- agno/db/in_memory/in_memory_db.py +888 -0
- agno/db/in_memory/utils.py +172 -0
- agno/db/json/__init__.py +3 -0
- agno/db/json/json_db.py +1051 -0
- agno/db/json/utils.py +196 -0
- agno/db/migrations/v1_to_v2.py +162 -0
- agno/db/mongo/__init__.py +3 -0
- agno/db/mongo/mongo.py +1417 -0
- agno/db/mongo/schemas.py +77 -0
- agno/db/mongo/utils.py +204 -0
- agno/db/mysql/__init__.py +3 -0
- agno/db/mysql/mysql.py +1719 -0
- agno/db/mysql/schemas.py +124 -0
- agno/db/mysql/utils.py +298 -0
- agno/db/postgres/__init__.py +3 -0
- agno/db/postgres/postgres.py +1720 -0
- agno/db/postgres/schemas.py +124 -0
- agno/db/postgres/utils.py +281 -0
- agno/db/redis/__init__.py +3 -0
- agno/db/redis/redis.py +1371 -0
- agno/db/redis/schemas.py +109 -0
- agno/db/redis/utils.py +288 -0
- agno/db/schemas/__init__.py +3 -0
- agno/db/schemas/evals.py +33 -0
- agno/db/schemas/knowledge.py +40 -0
- agno/db/schemas/memory.py +46 -0
- agno/db/singlestore/__init__.py +3 -0
- agno/db/singlestore/schemas.py +116 -0
- agno/db/singlestore/singlestore.py +1722 -0
- agno/db/singlestore/utils.py +327 -0
- agno/db/sqlite/__init__.py +3 -0
- agno/db/sqlite/schemas.py +119 -0
- agno/db/sqlite/sqlite.py +1680 -0
- agno/db/sqlite/utils.py +269 -0
- agno/db/utils.py +88 -0
- agno/eval/__init__.py +14 -0
- agno/eval/accuracy.py +142 -43
- agno/eval/performance.py +88 -23
- agno/eval/reliability.py +73 -20
- agno/eval/utils.py +23 -13
- agno/integrations/discord/__init__.py +3 -0
- agno/{app → integrations}/discord/client.py +10 -10
- agno/knowledge/__init__.py +2 -2
- agno/{document → knowledge}/chunking/agentic.py +2 -2
- agno/{document → knowledge}/chunking/document.py +2 -2
- agno/{document → knowledge}/chunking/fixed.py +3 -3
- agno/{document → knowledge}/chunking/markdown.py +2 -2
- agno/{document → knowledge}/chunking/recursive.py +2 -2
- agno/{document → knowledge}/chunking/row.py +2 -2
- agno/knowledge/chunking/semantic.py +59 -0
- agno/knowledge/chunking/strategy.py +121 -0
- agno/knowledge/content.py +74 -0
- agno/knowledge/document/__init__.py +5 -0
- agno/{document → knowledge/document}/base.py +12 -2
- agno/knowledge/embedder/__init__.py +5 -0
- agno/{embedder → knowledge/embedder}/aws_bedrock.py +127 -1
- agno/{embedder → knowledge/embedder}/azure_openai.py +65 -1
- agno/{embedder → knowledge/embedder}/base.py +6 -0
- agno/{embedder → knowledge/embedder}/cohere.py +72 -1
- agno/{embedder → knowledge/embedder}/fastembed.py +17 -1
- agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
- agno/{embedder → knowledge/embedder}/google.py +74 -1
- agno/{embedder → knowledge/embedder}/huggingface.py +36 -2
- agno/{embedder → knowledge/embedder}/jina.py +48 -2
- agno/knowledge/embedder/langdb.py +22 -0
- agno/knowledge/embedder/mistral.py +139 -0
- agno/{embedder → knowledge/embedder}/nebius.py +1 -1
- agno/{embedder → knowledge/embedder}/ollama.py +54 -3
- agno/knowledge/embedder/openai.py +223 -0
- agno/{embedder → knowledge/embedder}/sentence_transformer.py +16 -1
- agno/{embedder → knowledge/embedder}/together.py +1 -1
- agno/{embedder → knowledge/embedder}/voyageai.py +49 -1
- agno/knowledge/knowledge.py +1515 -0
- agno/knowledge/reader/__init__.py +7 -0
- agno/{document → knowledge}/reader/arxiv_reader.py +32 -4
- agno/knowledge/reader/base.py +88 -0
- agno/{document → knowledge}/reader/csv_reader.py +68 -15
- agno/knowledge/reader/docx_reader.py +83 -0
- agno/{document → knowledge}/reader/firecrawl_reader.py +42 -21
- agno/knowledge/reader/gcs_reader.py +67 -0
- agno/{document → knowledge}/reader/json_reader.py +30 -9
- agno/{document → knowledge}/reader/markdown_reader.py +36 -9
- agno/{document → knowledge}/reader/pdf_reader.py +79 -21
- agno/knowledge/reader/reader_factory.py +275 -0
- agno/knowledge/reader/s3_reader.py +171 -0
- agno/{document → knowledge}/reader/text_reader.py +31 -10
- agno/knowledge/reader/url_reader.py +84 -0
- agno/knowledge/reader/web_search_reader.py +389 -0
- agno/{document → knowledge}/reader/website_reader.py +37 -10
- agno/knowledge/reader/wikipedia_reader.py +59 -0
- agno/knowledge/reader/youtube_reader.py +78 -0
- agno/knowledge/remote_content/remote_content.py +88 -0
- agno/{reranker → knowledge/reranker}/base.py +1 -1
- agno/{reranker → knowledge/reranker}/cohere.py +2 -2
- agno/{reranker → knowledge/reranker}/infinity.py +2 -2
- agno/{reranker → knowledge/reranker}/sentence_transformer.py +2 -2
- agno/knowledge/types.py +30 -0
- agno/knowledge/utils.py +169 -0
- agno/memory/__init__.py +2 -10
- agno/memory/manager.py +1003 -148
- agno/models/aimlapi/__init__.py +2 -2
- agno/models/aimlapi/aimlapi.py +6 -6
- agno/models/anthropic/claude.py +129 -82
- agno/models/aws/bedrock.py +107 -175
- agno/models/aws/claude.py +64 -18
- agno/models/azure/ai_foundry.py +73 -23
- agno/models/base.py +347 -287
- agno/models/cerebras/cerebras.py +84 -27
- agno/models/cohere/chat.py +106 -98
- agno/models/google/gemini.py +100 -42
- agno/models/groq/groq.py +97 -35
- agno/models/huggingface/huggingface.py +92 -27
- agno/models/ibm/watsonx.py +72 -13
- agno/models/litellm/chat.py +85 -13
- agno/models/message.py +38 -144
- agno/models/meta/llama.py +85 -49
- agno/models/metrics.py +120 -0
- agno/models/mistral/mistral.py +90 -21
- agno/models/ollama/__init__.py +0 -2
- agno/models/ollama/chat.py +84 -46
- agno/models/openai/chat.py +121 -23
- agno/models/openai/responses.py +178 -105
- agno/models/perplexity/perplexity.py +26 -2
- agno/models/portkey/portkey.py +0 -7
- agno/models/response.py +14 -8
- agno/models/utils.py +20 -0
- agno/models/vercel/__init__.py +2 -2
- agno/models/vercel/v0.py +1 -1
- agno/models/vllm/__init__.py +2 -2
- agno/models/vllm/vllm.py +3 -3
- agno/models/xai/xai.py +10 -10
- agno/os/__init__.py +3 -0
- agno/os/app.py +393 -0
- agno/os/auth.py +47 -0
- agno/os/config.py +103 -0
- agno/os/interfaces/agui/__init__.py +3 -0
- agno/os/interfaces/agui/agui.py +31 -0
- agno/{app/agui/async_router.py → os/interfaces/agui/router.py} +16 -16
- agno/{app → os/interfaces}/agui/utils.py +65 -28
- agno/os/interfaces/base.py +21 -0
- agno/os/interfaces/slack/__init__.py +3 -0
- agno/{app/slack/async_router.py → os/interfaces/slack/router.py} +3 -5
- agno/os/interfaces/slack/slack.py +33 -0
- agno/os/interfaces/whatsapp/__init__.py +3 -0
- agno/{app/whatsapp/async_router.py → os/interfaces/whatsapp/router.py} +4 -7
- agno/os/interfaces/whatsapp/whatsapp.py +30 -0
- agno/os/router.py +843 -0
- agno/os/routers/__init__.py +3 -0
- agno/os/routers/evals/__init__.py +3 -0
- agno/os/routers/evals/evals.py +204 -0
- agno/os/routers/evals/schemas.py +142 -0
- agno/os/routers/evals/utils.py +161 -0
- agno/os/routers/knowledge/__init__.py +3 -0
- agno/os/routers/knowledge/knowledge.py +413 -0
- agno/os/routers/knowledge/schemas.py +118 -0
- agno/os/routers/memory/__init__.py +3 -0
- agno/os/routers/memory/memory.py +179 -0
- agno/os/routers/memory/schemas.py +58 -0
- agno/os/routers/metrics/__init__.py +3 -0
- agno/os/routers/metrics/metrics.py +58 -0
- agno/os/routers/metrics/schemas.py +47 -0
- agno/os/routers/session/__init__.py +3 -0
- agno/os/routers/session/session.py +163 -0
- agno/os/schema.py +892 -0
- agno/{app/playground → os}/settings.py +8 -15
- agno/os/utils.py +270 -0
- agno/reasoning/azure_ai_foundry.py +4 -4
- agno/reasoning/deepseek.py +4 -4
- agno/reasoning/default.py +6 -11
- agno/reasoning/groq.py +4 -4
- agno/reasoning/helpers.py +4 -6
- agno/reasoning/ollama.py +4 -4
- agno/reasoning/openai.py +4 -4
- agno/run/{response.py → agent.py} +144 -72
- agno/run/base.py +44 -58
- agno/run/cancel.py +83 -0
- agno/run/team.py +133 -77
- agno/run/workflow.py +537 -12
- agno/session/__init__.py +10 -0
- agno/session/agent.py +244 -0
- agno/session/summary.py +225 -0
- agno/session/team.py +262 -0
- agno/{storage/session/v2 → session}/workflow.py +47 -24
- agno/team/__init__.py +15 -16
- agno/team/team.py +2961 -4253
- agno/tools/agentql.py +14 -5
- agno/tools/airflow.py +9 -4
- agno/tools/api.py +7 -3
- agno/tools/apify.py +2 -46
- agno/tools/arxiv.py +8 -3
- agno/tools/aws_lambda.py +7 -5
- agno/tools/aws_ses.py +7 -1
- agno/tools/baidusearch.py +4 -1
- agno/tools/bitbucket.py +4 -4
- agno/tools/brandfetch.py +14 -11
- agno/tools/bravesearch.py +4 -1
- agno/tools/brightdata.py +42 -22
- agno/tools/browserbase.py +13 -4
- agno/tools/calcom.py +12 -10
- agno/tools/calculator.py +10 -27
- agno/tools/cartesia.py +18 -13
- agno/tools/{clickup_tool.py → clickup.py} +12 -25
- agno/tools/confluence.py +8 -8
- agno/tools/crawl4ai.py +7 -1
- agno/tools/csv_toolkit.py +9 -8
- agno/tools/dalle.py +18 -11
- agno/tools/daytona.py +13 -16
- agno/tools/decorator.py +6 -3
- agno/tools/desi_vocal.py +16 -7
- agno/tools/discord.py +11 -8
- agno/tools/docker.py +30 -42
- agno/tools/duckdb.py +34 -53
- agno/tools/duckduckgo.py +8 -7
- agno/tools/e2b.py +61 -61
- agno/tools/eleven_labs.py +35 -28
- agno/tools/email.py +4 -1
- agno/tools/evm.py +7 -1
- agno/tools/exa.py +19 -14
- agno/tools/fal.py +29 -29
- agno/tools/file.py +9 -8
- agno/tools/financial_datasets.py +25 -44
- agno/tools/firecrawl.py +22 -22
- agno/tools/function.py +68 -17
- agno/tools/giphy.py +22 -10
- agno/tools/github.py +48 -126
- agno/tools/gmail.py +45 -61
- agno/tools/google_bigquery.py +7 -6
- agno/tools/google_maps.py +11 -26
- agno/tools/googlesearch.py +7 -2
- agno/tools/googlesheets.py +21 -17
- agno/tools/hackernews.py +9 -5
- agno/tools/jina.py +5 -4
- agno/tools/jira.py +18 -9
- agno/tools/knowledge.py +31 -32
- agno/tools/linear.py +18 -33
- agno/tools/linkup.py +5 -1
- agno/tools/local_file_system.py +8 -5
- agno/tools/lumalab.py +31 -19
- agno/tools/mem0.py +18 -12
- agno/tools/memori.py +14 -10
- agno/tools/mlx_transcribe.py +3 -2
- agno/tools/models/azure_openai.py +32 -14
- agno/tools/models/gemini.py +58 -31
- agno/tools/models/groq.py +29 -20
- agno/tools/models/nebius.py +27 -11
- agno/tools/models_labs.py +39 -15
- agno/tools/moviepy_video.py +7 -6
- agno/tools/neo4j.py +10 -8
- agno/tools/newspaper.py +7 -2
- agno/tools/newspaper4k.py +8 -3
- agno/tools/openai.py +57 -26
- agno/tools/openbb.py +12 -11
- agno/tools/opencv.py +62 -46
- agno/tools/openweather.py +14 -12
- agno/tools/pandas.py +11 -3
- agno/tools/postgres.py +4 -12
- agno/tools/pubmed.py +4 -1
- agno/tools/python.py +9 -22
- agno/tools/reasoning.py +35 -27
- agno/tools/reddit.py +11 -26
- agno/tools/replicate.py +54 -41
- agno/tools/resend.py +4 -1
- agno/tools/scrapegraph.py +15 -14
- agno/tools/searxng.py +10 -23
- agno/tools/serpapi.py +6 -3
- agno/tools/serper.py +13 -4
- agno/tools/shell.py +9 -2
- agno/tools/slack.py +12 -11
- agno/tools/sleep.py +3 -2
- agno/tools/spider.py +24 -4
- agno/tools/sql.py +7 -6
- agno/tools/tavily.py +6 -4
- agno/tools/telegram.py +12 -4
- agno/tools/todoist.py +11 -31
- agno/tools/toolkit.py +1 -1
- agno/tools/trafilatura.py +22 -6
- agno/tools/trello.py +9 -22
- agno/tools/twilio.py +10 -3
- agno/tools/user_control_flow.py +6 -1
- agno/tools/valyu.py +34 -5
- agno/tools/visualization.py +19 -28
- agno/tools/webbrowser.py +4 -3
- agno/tools/webex.py +11 -7
- agno/tools/website.py +15 -46
- agno/tools/webtools.py +12 -4
- agno/tools/whatsapp.py +5 -9
- agno/tools/wikipedia.py +20 -13
- agno/tools/x.py +14 -13
- agno/tools/yfinance.py +13 -40
- agno/tools/youtube.py +26 -20
- agno/tools/zendesk.py +7 -2
- agno/tools/zep.py +10 -7
- agno/tools/zoom.py +10 -9
- agno/utils/common.py +1 -19
- agno/utils/events.py +95 -118
- agno/utils/knowledge.py +29 -0
- agno/utils/log.py +2 -2
- agno/utils/mcp.py +11 -5
- agno/utils/media.py +39 -0
- agno/utils/message.py +12 -1
- agno/utils/models/claude.py +6 -4
- agno/utils/models/mistral.py +8 -7
- agno/utils/models/schema_utils.py +3 -3
- agno/utils/pprint.py +33 -32
- agno/utils/print_response/agent.py +779 -0
- agno/utils/print_response/team.py +1565 -0
- agno/utils/print_response/workflow.py +1451 -0
- agno/utils/prompts.py +14 -14
- agno/utils/reasoning.py +87 -0
- agno/utils/response.py +42 -42
- agno/utils/string.py +8 -22
- agno/utils/team.py +50 -0
- agno/utils/timer.py +2 -2
- agno/vectordb/base.py +33 -21
- agno/vectordb/cassandra/cassandra.py +287 -23
- agno/vectordb/chroma/chromadb.py +482 -59
- agno/vectordb/clickhouse/clickhousedb.py +270 -63
- agno/vectordb/couchbase/couchbase.py +309 -29
- agno/vectordb/lancedb/lance_db.py +360 -21
- agno/vectordb/langchaindb/__init__.py +5 -0
- agno/vectordb/langchaindb/langchaindb.py +145 -0
- agno/vectordb/lightrag/__init__.py +5 -0
- agno/vectordb/lightrag/lightrag.py +374 -0
- agno/vectordb/llamaindex/llamaindexdb.py +127 -0
- agno/vectordb/milvus/milvus.py +242 -32
- agno/vectordb/mongodb/mongodb.py +200 -24
- agno/vectordb/pgvector/pgvector.py +319 -37
- agno/vectordb/pineconedb/pineconedb.py +221 -27
- agno/vectordb/qdrant/qdrant.py +334 -14
- agno/vectordb/singlestore/singlestore.py +286 -29
- agno/vectordb/surrealdb/surrealdb.py +187 -7
- agno/vectordb/upstashdb/upstashdb.py +342 -26
- agno/vectordb/weaviate/weaviate.py +227 -165
- agno/workflow/__init__.py +17 -13
- agno/workflow/{v2/condition.py → condition.py} +135 -32
- agno/workflow/{v2/loop.py → loop.py} +115 -28
- agno/workflow/{v2/parallel.py → parallel.py} +138 -108
- agno/workflow/{v2/router.py → router.py} +133 -32
- agno/workflow/{v2/step.py → step.py} +200 -42
- agno/workflow/{v2/steps.py → steps.py} +147 -66
- agno/workflow/types.py +482 -0
- agno/workflow/workflow.py +2394 -696
- agno-2.0.0a1.dist-info/METADATA +355 -0
- agno-2.0.0a1.dist-info/RECORD +514 -0
- agno/agent/metrics.py +0 -107
- agno/api/app.py +0 -35
- agno/api/playground.py +0 -92
- agno/api/schemas/app.py +0 -12
- agno/api/schemas/playground.py +0 -22
- agno/api/schemas/user.py +0 -35
- agno/api/schemas/workspace.py +0 -46
- agno/api/user.py +0 -160
- agno/api/workflows.py +0 -33
- agno/api/workspace.py +0 -175
- agno/app/agui/__init__.py +0 -3
- agno/app/agui/app.py +0 -17
- agno/app/agui/sync_router.py +0 -120
- agno/app/base.py +0 -186
- agno/app/discord/__init__.py +0 -3
- agno/app/fastapi/__init__.py +0 -3
- agno/app/fastapi/app.py +0 -107
- agno/app/fastapi/async_router.py +0 -457
- agno/app/fastapi/sync_router.py +0 -448
- agno/app/playground/app.py +0 -228
- agno/app/playground/async_router.py +0 -1050
- agno/app/playground/deploy.py +0 -249
- agno/app/playground/operator.py +0 -183
- agno/app/playground/schemas.py +0 -220
- agno/app/playground/serve.py +0 -55
- agno/app/playground/sync_router.py +0 -1042
- agno/app/playground/utils.py +0 -46
- agno/app/settings.py +0 -15
- agno/app/slack/__init__.py +0 -3
- agno/app/slack/app.py +0 -19
- agno/app/slack/sync_router.py +0 -92
- agno/app/utils.py +0 -54
- agno/app/whatsapp/__init__.py +0 -3
- agno/app/whatsapp/app.py +0 -15
- agno/app/whatsapp/sync_router.py +0 -197
- agno/cli/auth_server.py +0 -249
- agno/cli/config.py +0 -274
- agno/cli/console.py +0 -88
- agno/cli/credentials.py +0 -23
- agno/cli/entrypoint.py +0 -571
- agno/cli/operator.py +0 -357
- agno/cli/settings.py +0 -96
- agno/cli/ws/ws_cli.py +0 -817
- agno/constants.py +0 -13
- agno/document/__init__.py +0 -5
- agno/document/chunking/semantic.py +0 -45
- agno/document/chunking/strategy.py +0 -31
- agno/document/reader/__init__.py +0 -5
- agno/document/reader/base.py +0 -47
- agno/document/reader/docx_reader.py +0 -60
- agno/document/reader/gcs/pdf_reader.py +0 -44
- agno/document/reader/s3/pdf_reader.py +0 -59
- agno/document/reader/s3/text_reader.py +0 -63
- agno/document/reader/url_reader.py +0 -59
- agno/document/reader/youtube_reader.py +0 -58
- agno/embedder/__init__.py +0 -5
- agno/embedder/langdb.py +0 -80
- agno/embedder/mistral.py +0 -82
- agno/embedder/openai.py +0 -78
- agno/file/__init__.py +0 -5
- agno/file/file.py +0 -16
- agno/file/local/csv.py +0 -32
- agno/file/local/txt.py +0 -19
- agno/infra/app.py +0 -240
- agno/infra/base.py +0 -144
- agno/infra/context.py +0 -20
- agno/infra/db_app.py +0 -52
- agno/infra/resource.py +0 -205
- agno/infra/resources.py +0 -55
- agno/knowledge/agent.py +0 -702
- agno/knowledge/arxiv.py +0 -33
- agno/knowledge/combined.py +0 -36
- agno/knowledge/csv.py +0 -144
- agno/knowledge/csv_url.py +0 -124
- agno/knowledge/document.py +0 -223
- agno/knowledge/docx.py +0 -137
- agno/knowledge/firecrawl.py +0 -34
- agno/knowledge/gcs/__init__.py +0 -0
- agno/knowledge/gcs/base.py +0 -39
- agno/knowledge/gcs/pdf.py +0 -125
- agno/knowledge/json.py +0 -137
- agno/knowledge/langchain.py +0 -71
- agno/knowledge/light_rag.py +0 -273
- agno/knowledge/llamaindex.py +0 -66
- agno/knowledge/markdown.py +0 -154
- agno/knowledge/pdf.py +0 -164
- agno/knowledge/pdf_bytes.py +0 -42
- agno/knowledge/pdf_url.py +0 -148
- agno/knowledge/s3/__init__.py +0 -0
- agno/knowledge/s3/base.py +0 -64
- agno/knowledge/s3/pdf.py +0 -33
- agno/knowledge/s3/text.py +0 -34
- agno/knowledge/text.py +0 -141
- agno/knowledge/url.py +0 -46
- agno/knowledge/website.py +0 -179
- agno/knowledge/wikipedia.py +0 -32
- agno/knowledge/youtube.py +0 -35
- agno/memory/agent.py +0 -423
- agno/memory/classifier.py +0 -104
- agno/memory/db/__init__.py +0 -5
- agno/memory/db/base.py +0 -42
- agno/memory/db/mongodb.py +0 -189
- agno/memory/db/postgres.py +0 -203
- agno/memory/db/sqlite.py +0 -193
- agno/memory/memory.py +0 -22
- agno/memory/row.py +0 -36
- agno/memory/summarizer.py +0 -201
- agno/memory/summary.py +0 -19
- agno/memory/team.py +0 -415
- agno/memory/v2/__init__.py +0 -2
- agno/memory/v2/db/__init__.py +0 -1
- agno/memory/v2/db/base.py +0 -42
- agno/memory/v2/db/firestore.py +0 -339
- agno/memory/v2/db/mongodb.py +0 -196
- agno/memory/v2/db/postgres.py +0 -214
- agno/memory/v2/db/redis.py +0 -187
- agno/memory/v2/db/schema.py +0 -54
- agno/memory/v2/db/sqlite.py +0 -209
- agno/memory/v2/manager.py +0 -437
- agno/memory/v2/memory.py +0 -1097
- agno/memory/v2/schema.py +0 -55
- agno/memory/v2/summarizer.py +0 -215
- agno/memory/workflow.py +0 -38
- agno/models/ollama/tools.py +0 -430
- agno/models/qwen/__init__.py +0 -5
- agno/playground/__init__.py +0 -10
- agno/playground/deploy.py +0 -3
- agno/playground/playground.py +0 -3
- agno/playground/serve.py +0 -3
- agno/playground/settings.py +0 -3
- agno/reranker/__init__.py +0 -0
- agno/run/v2/__init__.py +0 -0
- agno/run/v2/workflow.py +0 -567
- agno/storage/__init__.py +0 -0
- agno/storage/agent/__init__.py +0 -0
- agno/storage/agent/dynamodb.py +0 -1
- agno/storage/agent/json.py +0 -1
- agno/storage/agent/mongodb.py +0 -1
- agno/storage/agent/postgres.py +0 -1
- agno/storage/agent/singlestore.py +0 -1
- agno/storage/agent/sqlite.py +0 -1
- agno/storage/agent/yaml.py +0 -1
- agno/storage/base.py +0 -60
- agno/storage/dynamodb.py +0 -673
- agno/storage/firestore.py +0 -297
- agno/storage/gcs_json.py +0 -261
- agno/storage/in_memory.py +0 -234
- agno/storage/json.py +0 -237
- agno/storage/mongodb.py +0 -328
- agno/storage/mysql.py +0 -685
- agno/storage/postgres.py +0 -682
- agno/storage/redis.py +0 -336
- agno/storage/session/__init__.py +0 -16
- agno/storage/session/agent.py +0 -64
- agno/storage/session/team.py +0 -63
- agno/storage/session/v2/__init__.py +0 -5
- agno/storage/session/workflow.py +0 -61
- agno/storage/singlestore.py +0 -606
- agno/storage/sqlite.py +0 -646
- agno/storage/workflow/__init__.py +0 -0
- agno/storage/workflow/mongodb.py +0 -1
- agno/storage/workflow/postgres.py +0 -1
- agno/storage/workflow/sqlite.py +0 -1
- agno/storage/yaml.py +0 -241
- agno/tools/thinking.py +0 -73
- agno/utils/defaults.py +0 -57
- agno/utils/filesystem.py +0 -39
- agno/utils/git.py +0 -52
- agno/utils/json_io.py +0 -30
- agno/utils/load_env.py +0 -19
- agno/utils/py_io.py +0 -19
- agno/utils/pyproject.py +0 -18
- agno/utils/resource_filter.py +0 -31
- agno/workflow/v2/__init__.py +0 -21
- agno/workflow/v2/types.py +0 -357
- agno/workflow/v2/workflow.py +0 -3312
- agno/workspace/__init__.py +0 -0
- agno/workspace/config.py +0 -325
- agno/workspace/enums.py +0 -6
- agno/workspace/helpers.py +0 -52
- agno/workspace/operator.py +0 -757
- agno/workspace/settings.py +0 -158
- agno-1.8.1.dist-info/METADATA +0 -982
- agno-1.8.1.dist-info/RECORD +0 -566
- agno-1.8.1.dist-info/entry_points.txt +0 -3
- /agno/{app → db/migrations}/__init__.py +0 -0
- /agno/{app/playground/__init__.py → db/schemas/metrics.py} +0 -0
- /agno/{cli → integrations}/__init__.py +0 -0
- /agno/{cli/ws → knowledge/chunking}/__init__.py +0 -0
- /agno/{document/chunking → knowledge/remote_content}/__init__.py +0 -0
- /agno/{document/reader/gcs → knowledge/reranker}/__init__.py +0 -0
- /agno/{document/reader/s3 → os/interfaces}/__init__.py +0 -0
- /agno/{app → os/interfaces}/slack/security.py +0 -0
- /agno/{app → os/interfaces}/whatsapp/security.py +0 -0
- /agno/{file/local → utils/print_response}/__init__.py +0 -0
- /agno/{infra → vectordb/llamaindex}/__init__.py +0 -0
- {agno-1.8.1.dist-info → agno-2.0.0a1.dist-info}/WHEEL +0 -0
- {agno-1.8.1.dist-info → agno-2.0.0a1.dist-info}/licenses/LICENSE +0 -0
- {agno-1.8.1.dist-info → agno-2.0.0a1.dist-info}/top_level.txt +0 -0
agno/os/router.py
ADDED
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, List, Optional, Union, cast
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from fastapi import (
|
|
6
|
+
APIRouter,
|
|
7
|
+
Depends,
|
|
8
|
+
File,
|
|
9
|
+
Form,
|
|
10
|
+
HTTPException,
|
|
11
|
+
UploadFile,
|
|
12
|
+
WebSocket,
|
|
13
|
+
)
|
|
14
|
+
from fastapi.responses import JSONResponse, StreamingResponse
|
|
15
|
+
from pydantic import BaseModel
|
|
16
|
+
|
|
17
|
+
from agno.agent.agent import Agent
|
|
18
|
+
from agno.media import Audio, Image, Video
|
|
19
|
+
from agno.media import File as FileMedia
|
|
20
|
+
from agno.os.auth import get_authentication_dependency
|
|
21
|
+
from agno.os.schema import (
|
|
22
|
+
AgentResponse,
|
|
23
|
+
AgentSummaryResponse,
|
|
24
|
+
ConfigResponse,
|
|
25
|
+
InterfaceResponse,
|
|
26
|
+
Model,
|
|
27
|
+
TeamResponse,
|
|
28
|
+
TeamSummaryResponse,
|
|
29
|
+
WorkflowResponse,
|
|
30
|
+
WorkflowSummaryResponse,
|
|
31
|
+
)
|
|
32
|
+
from agno.os.settings import AgnoAPISettings
|
|
33
|
+
from agno.os.utils import (
|
|
34
|
+
get_agent_by_id,
|
|
35
|
+
get_team_by_id,
|
|
36
|
+
get_workflow_by_id,
|
|
37
|
+
process_audio,
|
|
38
|
+
process_document,
|
|
39
|
+
process_image,
|
|
40
|
+
process_video,
|
|
41
|
+
)
|
|
42
|
+
from agno.run.agent import RunErrorEvent, RunOutput
|
|
43
|
+
from agno.run.team import RunErrorEvent as TeamRunErrorEvent
|
|
44
|
+
from agno.run.workflow import WorkflowErrorEvent
|
|
45
|
+
from agno.team.team import Team
|
|
46
|
+
from agno.utils.log import log_debug, log_error, log_warning, logger
|
|
47
|
+
from agno.workflow.workflow import Workflow
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from agno.os.app import AgentOS
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def format_sse_event(json_data: str) -> str:
|
|
54
|
+
"""Parse JSON data into SSE-compliant format.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
json_data: JSON string containing the event data
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
SSE-formatted response:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
event: EventName
|
|
64
|
+
data: { ... }
|
|
65
|
+
|
|
66
|
+
event: AnotherEventName
|
|
67
|
+
data: { ... }
|
|
68
|
+
```
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
# Parse the JSON to extract the event type
|
|
72
|
+
data = json.loads(json_data)
|
|
73
|
+
event_type = data.get("event", "message")
|
|
74
|
+
|
|
75
|
+
# Format as SSE: event: <event_type>\ndata: <json_data>\n\n
|
|
76
|
+
return f"event: {event_type}\ndata: {json_data}\n\n"
|
|
77
|
+
except (json.JSONDecodeError, KeyError):
|
|
78
|
+
# Fallback to generic message event if parsing fails
|
|
79
|
+
return f"event: message\ndata: {json_data}\n\n"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class WebSocketManager:
|
|
83
|
+
"""Manages WebSocket connections for workflow runs"""
|
|
84
|
+
|
|
85
|
+
active_connections: Dict[str, WebSocket] # {run_id: websocket}
|
|
86
|
+
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
active_connections: Optional[Dict[str, WebSocket]] = None,
|
|
90
|
+
):
|
|
91
|
+
# Store active connections: {run_id: websocket}
|
|
92
|
+
self.active_connections = active_connections or {}
|
|
93
|
+
|
|
94
|
+
async def connect(self, websocket: WebSocket):
|
|
95
|
+
"""Accept WebSocket connection"""
|
|
96
|
+
await websocket.accept()
|
|
97
|
+
logger.debug("WebSocket connected")
|
|
98
|
+
|
|
99
|
+
# Send connection confirmation
|
|
100
|
+
await websocket.send_text(
|
|
101
|
+
json.dumps(
|
|
102
|
+
{
|
|
103
|
+
"event": "connected",
|
|
104
|
+
"message": "Connected to workflow events",
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
async def register_workflow_websocket(self, run_id: str, websocket: WebSocket):
|
|
110
|
+
"""Register a workflow run with its WebSocket connection"""
|
|
111
|
+
self.active_connections[run_id] = websocket
|
|
112
|
+
logger.debug(f"Registered WebSocket for run_id: {run_id}")
|
|
113
|
+
|
|
114
|
+
async def disconnect_by_run_id(self, run_id: str):
|
|
115
|
+
"""Remove WebSocket connection by run_id"""
|
|
116
|
+
if run_id in self.active_connections:
|
|
117
|
+
del self.active_connections[run_id]
|
|
118
|
+
logger.debug(f"WebSocket disconnected for run_id: {run_id}")
|
|
119
|
+
|
|
120
|
+
async def get_websocket_for_run(self, run_id: str) -> Optional[WebSocket]:
|
|
121
|
+
"""Get WebSocket connection for a workflow run"""
|
|
122
|
+
return self.active_connections.get(run_id)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# Global manager instance
|
|
126
|
+
websocket_manager = WebSocketManager(
|
|
127
|
+
active_connections={},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def agent_response_streamer(
|
|
132
|
+
agent: Agent,
|
|
133
|
+
message: str,
|
|
134
|
+
session_id: Optional[str] = None,
|
|
135
|
+
user_id: Optional[str] = None,
|
|
136
|
+
images: Optional[List[Image]] = None,
|
|
137
|
+
audio: Optional[List[Audio]] = None,
|
|
138
|
+
videos: Optional[List[Video]] = None,
|
|
139
|
+
files: Optional[List[FileMedia]] = None,
|
|
140
|
+
) -> AsyncGenerator:
|
|
141
|
+
try:
|
|
142
|
+
run_response = agent.arun(
|
|
143
|
+
input=message,
|
|
144
|
+
session_id=session_id,
|
|
145
|
+
user_id=user_id,
|
|
146
|
+
images=images,
|
|
147
|
+
audio=audio,
|
|
148
|
+
videos=videos,
|
|
149
|
+
files=files,
|
|
150
|
+
stream=True,
|
|
151
|
+
stream_intermediate_steps=True,
|
|
152
|
+
)
|
|
153
|
+
async for run_response_chunk in run_response:
|
|
154
|
+
yield format_sse_event(run_response_chunk.to_json())
|
|
155
|
+
|
|
156
|
+
except Exception as e:
|
|
157
|
+
import traceback
|
|
158
|
+
|
|
159
|
+
traceback.print_exc(limit=3)
|
|
160
|
+
error_response = RunErrorEvent(
|
|
161
|
+
content=str(e),
|
|
162
|
+
)
|
|
163
|
+
yield format_sse_event(error_response.to_json())
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def agent_continue_response_streamer(
|
|
167
|
+
agent: Agent,
|
|
168
|
+
run_id: Optional[str] = None,
|
|
169
|
+
updated_tools: Optional[List] = None,
|
|
170
|
+
session_id: Optional[str] = None,
|
|
171
|
+
user_id: Optional[str] = None,
|
|
172
|
+
) -> AsyncGenerator:
|
|
173
|
+
try:
|
|
174
|
+
continue_response = agent.acontinue_run(
|
|
175
|
+
run_id=run_id,
|
|
176
|
+
updated_tools=updated_tools,
|
|
177
|
+
session_id=session_id,
|
|
178
|
+
user_id=user_id,
|
|
179
|
+
stream=True,
|
|
180
|
+
stream_intermediate_steps=True,
|
|
181
|
+
)
|
|
182
|
+
async for run_response_chunk in continue_response:
|
|
183
|
+
yield format_sse_event(run_response_chunk.to_json())
|
|
184
|
+
|
|
185
|
+
except Exception as e:
|
|
186
|
+
import traceback
|
|
187
|
+
|
|
188
|
+
traceback.print_exc(limit=3)
|
|
189
|
+
error_response = RunErrorEvent(
|
|
190
|
+
content=str(e),
|
|
191
|
+
)
|
|
192
|
+
yield format_sse_event(error_response.to_json())
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
async def team_response_streamer(
|
|
197
|
+
team: Team,
|
|
198
|
+
message: str,
|
|
199
|
+
session_id: Optional[str] = None,
|
|
200
|
+
user_id: Optional[str] = None,
|
|
201
|
+
images: Optional[List[Image]] = None,
|
|
202
|
+
audio: Optional[List[Audio]] = None,
|
|
203
|
+
videos: Optional[List[Video]] = None,
|
|
204
|
+
files: Optional[List[FileMedia]] = None,
|
|
205
|
+
) -> AsyncGenerator:
|
|
206
|
+
"""Run the given team asynchronously and yield its response"""
|
|
207
|
+
try:
|
|
208
|
+
run_response = team.arun(
|
|
209
|
+
input=message,
|
|
210
|
+
session_id=session_id,
|
|
211
|
+
user_id=user_id,
|
|
212
|
+
images=images,
|
|
213
|
+
audio=audio,
|
|
214
|
+
videos=videos,
|
|
215
|
+
files=files,
|
|
216
|
+
stream=True,
|
|
217
|
+
stream_intermediate_steps=True,
|
|
218
|
+
)
|
|
219
|
+
async for run_response_chunk in run_response:
|
|
220
|
+
yield format_sse_event(run_response_chunk.to_json())
|
|
221
|
+
|
|
222
|
+
except Exception as e:
|
|
223
|
+
import traceback
|
|
224
|
+
|
|
225
|
+
traceback.print_exc()
|
|
226
|
+
error_response = TeamRunErrorEvent(
|
|
227
|
+
content=str(e),
|
|
228
|
+
)
|
|
229
|
+
yield format_sse_event(error_response.to_json())
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
async def handle_workflow_via_websocket(websocket: WebSocket, message: dict, os: "AgentOS"):
|
|
234
|
+
"""Handle workflow execution directly via WebSocket"""
|
|
235
|
+
try:
|
|
236
|
+
workflow_id = message.get("workflow_id")
|
|
237
|
+
session_id = message.get("session_id")
|
|
238
|
+
user_message = message.get("message", "")
|
|
239
|
+
user_id = message.get("user_id")
|
|
240
|
+
|
|
241
|
+
if not workflow_id:
|
|
242
|
+
await websocket.send_text(json.dumps({"event": "error", "error": "workflow_id is required"}))
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
# Get workflow from OS
|
|
246
|
+
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
247
|
+
if not workflow:
|
|
248
|
+
await websocket.send_text(json.dumps({"event": "error", "error": f"Workflow {workflow_id} not found"}))
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
# Generate session_id if not provided
|
|
252
|
+
# Use workflow's default session_id if not provided in message
|
|
253
|
+
if not session_id:
|
|
254
|
+
if workflow.session_id:
|
|
255
|
+
session_id = workflow.session_id
|
|
256
|
+
else:
|
|
257
|
+
session_id = str(uuid4())
|
|
258
|
+
|
|
259
|
+
# Execute workflow in background with streaming
|
|
260
|
+
await workflow.arun(
|
|
261
|
+
input=user_message,
|
|
262
|
+
session_id=session_id,
|
|
263
|
+
user_id=user_id,
|
|
264
|
+
stream=True,
|
|
265
|
+
stream_intermediate_steps=True,
|
|
266
|
+
background=True,
|
|
267
|
+
websocket=websocket,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logger.error(f"Error executing workflow via WebSocket: {e}")
|
|
272
|
+
await websocket.send_text(json.dumps({"event": "error", "error": str(e)}))
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
async def workflow_response_streamer(
|
|
276
|
+
workflow: Workflow,
|
|
277
|
+
input: Optional[Union[str, Dict[str, Any], List[Any], BaseModel]] = None,
|
|
278
|
+
session_id: Optional[str] = None,
|
|
279
|
+
user_id: Optional[str] = None,
|
|
280
|
+
**kwargs: Any,
|
|
281
|
+
) -> AsyncGenerator:
|
|
282
|
+
try:
|
|
283
|
+
run_response = await workflow.arun(
|
|
284
|
+
input=input,
|
|
285
|
+
session_id=session_id,
|
|
286
|
+
user_id=user_id,
|
|
287
|
+
stream=True,
|
|
288
|
+
stream_intermediate_steps=True,
|
|
289
|
+
**kwargs,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
async for run_response_chunk in run_response:
|
|
293
|
+
yield format_sse_event(run_response_chunk.to_json())
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
import traceback
|
|
297
|
+
|
|
298
|
+
traceback.print_exc()
|
|
299
|
+
error_response = WorkflowErrorEvent(
|
|
300
|
+
error=str(e),
|
|
301
|
+
)
|
|
302
|
+
yield format_sse_event(error_response.to_json())
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def get_base_router(
|
|
307
|
+
os: "AgentOS",
|
|
308
|
+
settings: AgnoAPISettings = AgnoAPISettings(),
|
|
309
|
+
) -> APIRouter:
|
|
310
|
+
router = APIRouter(dependencies=[Depends(get_authentication_dependency(settings))])
|
|
311
|
+
|
|
312
|
+
# -- Main Routes ---
|
|
313
|
+
|
|
314
|
+
@router.get("/health", tags=["Core"])
|
|
315
|
+
async def health_check():
|
|
316
|
+
return JSONResponse(content={"status": "ok"})
|
|
317
|
+
|
|
318
|
+
@router.get(
|
|
319
|
+
"/config",
|
|
320
|
+
response_model=ConfigResponse,
|
|
321
|
+
response_model_exclude_none=True,
|
|
322
|
+
tags=["Core"],
|
|
323
|
+
)
|
|
324
|
+
async def config() -> ConfigResponse:
|
|
325
|
+
return ConfigResponse(
|
|
326
|
+
os_id=os.os_id or "Unnamed OS",
|
|
327
|
+
description=os.description,
|
|
328
|
+
available_models=os.config.available_models if os.config else [],
|
|
329
|
+
databases=[db.id for db in os.dbs.values()],
|
|
330
|
+
chat=os.config.chat if os.config else None,
|
|
331
|
+
session=os._get_session_config(),
|
|
332
|
+
memory=os._get_memory_config(),
|
|
333
|
+
knowledge=os._get_knowledge_config(),
|
|
334
|
+
evals=os._get_evals_config(),
|
|
335
|
+
metrics=os._get_metrics_config(),
|
|
336
|
+
agents=[AgentSummaryResponse.from_agent(agent) for agent in os.agents] if os.agents else [],
|
|
337
|
+
teams=[TeamSummaryResponse.from_team(team) for team in os.teams] if os.teams else [],
|
|
338
|
+
workflows=[WorkflowSummaryResponse.from_workflow(w) for w in os.workflows] if os.workflows else [],
|
|
339
|
+
interfaces=[
|
|
340
|
+
InterfaceResponse(type=interface.type, version=interface.version, route=interface.router_prefix)
|
|
341
|
+
for interface in os.interfaces
|
|
342
|
+
],
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
@router.get(
|
|
346
|
+
"/models",
|
|
347
|
+
response_model=List[Model],
|
|
348
|
+
response_model_exclude_none=True,
|
|
349
|
+
tags=["Core"],
|
|
350
|
+
)
|
|
351
|
+
async def get_models():
|
|
352
|
+
"""Return the list of all models used by agents and teams in the contextual OS"""
|
|
353
|
+
all_components: List[Union[Agent, Team]] = []
|
|
354
|
+
if os.agents:
|
|
355
|
+
all_components.extend(os.agents)
|
|
356
|
+
if os.teams:
|
|
357
|
+
all_components.extend(os.teams)
|
|
358
|
+
|
|
359
|
+
unique_models = {}
|
|
360
|
+
for item in all_components:
|
|
361
|
+
model = cast(Model, item.model)
|
|
362
|
+
if model.id is not None and model.provider is not None:
|
|
363
|
+
key = (model.id, model.provider)
|
|
364
|
+
if key not in unique_models:
|
|
365
|
+
unique_models[key] = Model(id=model.id, provider=model.provider)
|
|
366
|
+
|
|
367
|
+
return list(unique_models.values())
|
|
368
|
+
|
|
369
|
+
# -- Agent routes ---
|
|
370
|
+
|
|
371
|
+
@router.post("/agents/{agent_id}/runs", tags=["Agents"])
|
|
372
|
+
async def create_agent_run(
|
|
373
|
+
agent_id: str,
|
|
374
|
+
message: str = Form(...),
|
|
375
|
+
stream: bool = Form(False),
|
|
376
|
+
session_id: Optional[str] = Form(None),
|
|
377
|
+
user_id: Optional[str] = Form(None),
|
|
378
|
+
files: Optional[List[UploadFile]] = File(None),
|
|
379
|
+
):
|
|
380
|
+
agent = get_agent_by_id(agent_id, os.agents)
|
|
381
|
+
if agent is None:
|
|
382
|
+
raise HTTPException(status_code=404, detail="Agent not found")
|
|
383
|
+
|
|
384
|
+
if session_id is None or session_id == "":
|
|
385
|
+
log_debug("Creating new session")
|
|
386
|
+
session_id = str(uuid4())
|
|
387
|
+
|
|
388
|
+
base64_images: List[Image] = []
|
|
389
|
+
base64_audios: List[Audio] = []
|
|
390
|
+
base64_videos: List[Video] = []
|
|
391
|
+
input_files: List[FileMedia] = []
|
|
392
|
+
|
|
393
|
+
if files:
|
|
394
|
+
for file in files:
|
|
395
|
+
if file.content_type in ["image/png", "image/jpeg", "image/jpg", "image/webp"]:
|
|
396
|
+
try:
|
|
397
|
+
base64_image = process_image(file)
|
|
398
|
+
base64_images.append(base64_image)
|
|
399
|
+
except Exception as e:
|
|
400
|
+
log_error(f"Error processing image {file.filename}: {e}")
|
|
401
|
+
continue
|
|
402
|
+
elif file.content_type in ["audio/wav", "audio/mp3", "audio/mpeg"]:
|
|
403
|
+
try:
|
|
404
|
+
base64_audio = process_audio(file)
|
|
405
|
+
base64_audios.append(base64_audio)
|
|
406
|
+
except Exception as e:
|
|
407
|
+
log_error(f"Error processing audio {file.filename}: {e}")
|
|
408
|
+
continue
|
|
409
|
+
elif file.content_type in [
|
|
410
|
+
"video/x-flv",
|
|
411
|
+
"video/quicktime",
|
|
412
|
+
"video/mpeg",
|
|
413
|
+
"video/mpegs",
|
|
414
|
+
"video/mpgs",
|
|
415
|
+
"video/mpg",
|
|
416
|
+
"video/mpg",
|
|
417
|
+
"video/mp4",
|
|
418
|
+
"video/webm",
|
|
419
|
+
"video/wmv",
|
|
420
|
+
"video/3gpp",
|
|
421
|
+
]:
|
|
422
|
+
try:
|
|
423
|
+
base64_video = process_video(file)
|
|
424
|
+
base64_videos.append(base64_video)
|
|
425
|
+
except Exception as e:
|
|
426
|
+
log_error(f"Error processing video {file.filename}: {e}")
|
|
427
|
+
continue
|
|
428
|
+
elif file.content_type in [
|
|
429
|
+
"application/pdf",
|
|
430
|
+
"text/csv",
|
|
431
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
432
|
+
"text/plain",
|
|
433
|
+
"application/json",
|
|
434
|
+
]:
|
|
435
|
+
# Process document files
|
|
436
|
+
try:
|
|
437
|
+
file_content = await file.read()
|
|
438
|
+
input_files.append(FileMedia(content=file_content))
|
|
439
|
+
except Exception as e:
|
|
440
|
+
log_error(f"Error processing file {file.filename}: {e}")
|
|
441
|
+
continue
|
|
442
|
+
else:
|
|
443
|
+
raise HTTPException(status_code=400, detail="Unsupported file type")
|
|
444
|
+
|
|
445
|
+
if stream:
|
|
446
|
+
return StreamingResponse(
|
|
447
|
+
agent_response_streamer(
|
|
448
|
+
agent,
|
|
449
|
+
message,
|
|
450
|
+
session_id=session_id,
|
|
451
|
+
user_id=user_id,
|
|
452
|
+
images=base64_images if base64_images else None,
|
|
453
|
+
audio=base64_audios if base64_audios else None,
|
|
454
|
+
videos=base64_videos if base64_videos else None,
|
|
455
|
+
files=input_files if input_files else None,
|
|
456
|
+
),
|
|
457
|
+
media_type="text/event-stream",
|
|
458
|
+
)
|
|
459
|
+
else:
|
|
460
|
+
run_response = cast(
|
|
461
|
+
RunOutput,
|
|
462
|
+
await agent.arun(
|
|
463
|
+
input=message,
|
|
464
|
+
session_id=session_id,
|
|
465
|
+
user_id=user_id,
|
|
466
|
+
images=base64_images if base64_images else None,
|
|
467
|
+
audio=base64_audios if base64_audios else None,
|
|
468
|
+
videos=base64_videos if base64_videos else None,
|
|
469
|
+
files=input_files if input_files else None,
|
|
470
|
+
stream=False,
|
|
471
|
+
),
|
|
472
|
+
)
|
|
473
|
+
return run_response.to_dict()
|
|
474
|
+
|
|
475
|
+
@router.post(
|
|
476
|
+
"/agents/{agent_id}/runs/{run_id}/cancel",
|
|
477
|
+
tags=["Agents"],
|
|
478
|
+
)
|
|
479
|
+
async def cancel_agent_run(
|
|
480
|
+
agent_id: str,
|
|
481
|
+
run_id: str,
|
|
482
|
+
):
|
|
483
|
+
agent = get_agent_by_id(agent_id, os.agents)
|
|
484
|
+
if agent is None:
|
|
485
|
+
raise HTTPException(status_code=404, detail="Agent not found")
|
|
486
|
+
|
|
487
|
+
if not agent.cancel_run(run_id=run_id):
|
|
488
|
+
raise HTTPException(status_code=500, detail="Failed to cancel run")
|
|
489
|
+
|
|
490
|
+
return JSONResponse(content={}, status_code=200)
|
|
491
|
+
|
|
492
|
+
@router.post(
|
|
493
|
+
"/agents/{agent_id}/runs/{run_id}/continue",
|
|
494
|
+
tags=["Agents"],
|
|
495
|
+
)
|
|
496
|
+
async def continue_agent_run(
|
|
497
|
+
agent_id: str,
|
|
498
|
+
run_id: str,
|
|
499
|
+
tools: str = Form(...), # JSON string of tools
|
|
500
|
+
session_id: Optional[str] = Form(None),
|
|
501
|
+
user_id: Optional[str] = Form(None),
|
|
502
|
+
stream: bool = Form(True),
|
|
503
|
+
):
|
|
504
|
+
# Parse the JSON string manually
|
|
505
|
+
try:
|
|
506
|
+
tools_data = json.loads(tools) if tools else None
|
|
507
|
+
except json.JSONDecodeError:
|
|
508
|
+
raise HTTPException(status_code=400, detail="Invalid JSON in tools field")
|
|
509
|
+
|
|
510
|
+
agent = get_agent_by_id(agent_id, os.agents)
|
|
511
|
+
if agent is None:
|
|
512
|
+
raise HTTPException(status_code=404, detail="Agent not found")
|
|
513
|
+
|
|
514
|
+
if session_id is None or session_id == "":
|
|
515
|
+
log_warning(
|
|
516
|
+
"Continuing run without session_id. This might lead to unexpected behavior if session context is important."
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
# Convert tools dict to ToolExecution objects if provided
|
|
520
|
+
updated_tools = None
|
|
521
|
+
if tools_data:
|
|
522
|
+
try:
|
|
523
|
+
from agno.models.response import ToolExecution
|
|
524
|
+
|
|
525
|
+
updated_tools = [ToolExecution.from_dict(tool) for tool in tools_data]
|
|
526
|
+
except Exception as e:
|
|
527
|
+
raise HTTPException(status_code=400, detail=f"Invalid structure or content for tools: {str(e)}")
|
|
528
|
+
|
|
529
|
+
if stream:
|
|
530
|
+
return StreamingResponse(
|
|
531
|
+
agent_continue_response_streamer(
|
|
532
|
+
agent,
|
|
533
|
+
run_id=run_id, # run_id from path
|
|
534
|
+
updated_tools=updated_tools,
|
|
535
|
+
session_id=session_id,
|
|
536
|
+
user_id=user_id,
|
|
537
|
+
),
|
|
538
|
+
media_type="text/event-stream",
|
|
539
|
+
)
|
|
540
|
+
else:
|
|
541
|
+
run_response_obj = cast(
|
|
542
|
+
RunOutput,
|
|
543
|
+
await agent.acontinue_run(
|
|
544
|
+
run_id=run_id, # run_id from path
|
|
545
|
+
updated_tools=updated_tools,
|
|
546
|
+
session_id=session_id,
|
|
547
|
+
user_id=user_id,
|
|
548
|
+
stream=False,
|
|
549
|
+
),
|
|
550
|
+
)
|
|
551
|
+
return run_response_obj.to_dict()
|
|
552
|
+
|
|
553
|
+
@router.get(
|
|
554
|
+
"/agents",
|
|
555
|
+
response_model=List[AgentResponse],
|
|
556
|
+
response_model_exclude_none=True,
|
|
557
|
+
tags=["Agents"],
|
|
558
|
+
)
|
|
559
|
+
async def get_agents():
|
|
560
|
+
"""Return the list of all Agents present in the contextual OS"""
|
|
561
|
+
if os.agents is None:
|
|
562
|
+
return []
|
|
563
|
+
|
|
564
|
+
agents = []
|
|
565
|
+
for agent in os.agents:
|
|
566
|
+
agents.append(AgentResponse.from_agent(agent=agent))
|
|
567
|
+
|
|
568
|
+
return agents
|
|
569
|
+
|
|
570
|
+
@router.get(
|
|
571
|
+
"/agents/{agent_id}",
|
|
572
|
+
response_model=AgentResponse,
|
|
573
|
+
response_model_exclude_none=True,
|
|
574
|
+
tags=["Agents"],
|
|
575
|
+
)
|
|
576
|
+
async def get_agent(agent_id: str):
|
|
577
|
+
agent = get_agent_by_id(agent_id, os.agents)
|
|
578
|
+
if agent is None:
|
|
579
|
+
raise HTTPException(status_code=404, detail="Agent not found")
|
|
580
|
+
|
|
581
|
+
return AgentResponse.from_agent(agent)
|
|
582
|
+
|
|
583
|
+
# -- Team routes ---
|
|
584
|
+
|
|
585
|
+
@router.post("/teams/{team_id}/runs", tags=["Teams"])
|
|
586
|
+
async def create_team_run(
|
|
587
|
+
team_id: str,
|
|
588
|
+
message: str = Form(...),
|
|
589
|
+
stream: bool = Form(True),
|
|
590
|
+
monitor: bool = Form(True),
|
|
591
|
+
session_id: Optional[str] = Form(None),
|
|
592
|
+
user_id: Optional[str] = Form(None),
|
|
593
|
+
files: Optional[List[UploadFile]] = File(None),
|
|
594
|
+
):
|
|
595
|
+
logger.debug(f"Creating team run: {message} {session_id} {monitor} {user_id} {team_id} {files}")
|
|
596
|
+
team = get_team_by_id(team_id, os.teams)
|
|
597
|
+
if team is None:
|
|
598
|
+
raise HTTPException(status_code=404, detail="Team not found")
|
|
599
|
+
|
|
600
|
+
if session_id is not None and session_id != "":
|
|
601
|
+
logger.debug(f"Continuing session: {session_id}")
|
|
602
|
+
else:
|
|
603
|
+
logger.debug("Creating new session")
|
|
604
|
+
session_id = str(uuid4())
|
|
605
|
+
|
|
606
|
+
base64_images: List[Image] = []
|
|
607
|
+
base64_audios: List[Audio] = []
|
|
608
|
+
base64_videos: List[Video] = []
|
|
609
|
+
document_files: List[FileMedia] = []
|
|
610
|
+
|
|
611
|
+
if files:
|
|
612
|
+
for file in files:
|
|
613
|
+
if file.content_type in ["image/png", "image/jpeg", "image/jpg", "image/webp"]:
|
|
614
|
+
try:
|
|
615
|
+
base64_image = process_image(file)
|
|
616
|
+
base64_images.append(base64_image)
|
|
617
|
+
except Exception as e:
|
|
618
|
+
logger.error(f"Error processing image {file.filename}: {e}")
|
|
619
|
+
continue
|
|
620
|
+
elif file.content_type in ["audio/wav", "audio/mp3", "audio/mpeg"]:
|
|
621
|
+
try:
|
|
622
|
+
base64_audio = process_audio(file)
|
|
623
|
+
base64_audios.append(base64_audio)
|
|
624
|
+
except Exception as e:
|
|
625
|
+
logger.error(f"Error processing audio {file.filename}: {e}")
|
|
626
|
+
continue
|
|
627
|
+
elif file.content_type in [
|
|
628
|
+
"video/x-flv",
|
|
629
|
+
"video/quicktime",
|
|
630
|
+
"video/mpeg",
|
|
631
|
+
"video/mpegs",
|
|
632
|
+
"video/mpgs",
|
|
633
|
+
"video/mpg",
|
|
634
|
+
"video/mpg",
|
|
635
|
+
"video/mp4",
|
|
636
|
+
"video/webm",
|
|
637
|
+
"video/wmv",
|
|
638
|
+
"video/3gpp",
|
|
639
|
+
]:
|
|
640
|
+
try:
|
|
641
|
+
base64_video = process_video(file)
|
|
642
|
+
base64_videos.append(base64_video)
|
|
643
|
+
except Exception as e:
|
|
644
|
+
logger.error(f"Error processing video {file.filename}: {e}")
|
|
645
|
+
continue
|
|
646
|
+
elif file.content_type in [
|
|
647
|
+
"application/pdf",
|
|
648
|
+
"text/csv",
|
|
649
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
650
|
+
"text/plain",
|
|
651
|
+
"application/json",
|
|
652
|
+
]:
|
|
653
|
+
document_file = process_document(file)
|
|
654
|
+
if document_file is not None:
|
|
655
|
+
document_files.append(document_file)
|
|
656
|
+
else:
|
|
657
|
+
raise HTTPException(status_code=400, detail="Unsupported file type")
|
|
658
|
+
|
|
659
|
+
if stream:
|
|
660
|
+
return StreamingResponse(
|
|
661
|
+
team_response_streamer(
|
|
662
|
+
team,
|
|
663
|
+
message,
|
|
664
|
+
session_id=session_id,
|
|
665
|
+
user_id=user_id,
|
|
666
|
+
images=base64_images if base64_images else None,
|
|
667
|
+
audio=base64_audios if base64_audios else None,
|
|
668
|
+
videos=base64_videos if base64_videos else None,
|
|
669
|
+
files=document_files if document_files else None,
|
|
670
|
+
),
|
|
671
|
+
media_type="text/event-stream",
|
|
672
|
+
)
|
|
673
|
+
else:
|
|
674
|
+
run_response = await team.arun(
|
|
675
|
+
input=message,
|
|
676
|
+
session_id=session_id,
|
|
677
|
+
user_id=user_id,
|
|
678
|
+
images=base64_images if base64_images else None,
|
|
679
|
+
audio=base64_audios if base64_audios else None,
|
|
680
|
+
videos=base64_videos if base64_videos else None,
|
|
681
|
+
files=document_files if document_files else None,
|
|
682
|
+
stream=False,
|
|
683
|
+
)
|
|
684
|
+
return run_response.to_dict()
|
|
685
|
+
|
|
686
|
+
@router.post(
|
|
687
|
+
"/teams/{team_id}/runs/{run_id}/cancel",
|
|
688
|
+
tags=["Teams"],
|
|
689
|
+
)
|
|
690
|
+
async def cancel_team_run(
|
|
691
|
+
team_id: str,
|
|
692
|
+
run_id: str,
|
|
693
|
+
):
|
|
694
|
+
team = get_team_by_id(team_id, os.teams)
|
|
695
|
+
if team is None:
|
|
696
|
+
raise HTTPException(status_code=404, detail="Team not found")
|
|
697
|
+
|
|
698
|
+
if not team.cancel_run(run_id=run_id):
|
|
699
|
+
raise HTTPException(status_code=500, detail="Failed to cancel run")
|
|
700
|
+
|
|
701
|
+
return JSONResponse(content={}, status_code=200)
|
|
702
|
+
|
|
703
|
+
@router.get(
|
|
704
|
+
"/teams",
|
|
705
|
+
response_model=List[TeamResponse],
|
|
706
|
+
response_model_exclude_none=True,
|
|
707
|
+
tags=["Teams"],
|
|
708
|
+
)
|
|
709
|
+
async def get_teams():
|
|
710
|
+
"""Return the list of all Teams present in the contextual OS"""
|
|
711
|
+
if os.teams is None:
|
|
712
|
+
return []
|
|
713
|
+
|
|
714
|
+
teams = []
|
|
715
|
+
for team in os.teams:
|
|
716
|
+
teams.append(TeamResponse.from_team(team=team))
|
|
717
|
+
|
|
718
|
+
return teams
|
|
719
|
+
|
|
720
|
+
@router.get(
|
|
721
|
+
"/teams/{team_id}",
|
|
722
|
+
response_model=TeamResponse,
|
|
723
|
+
response_model_exclude_none=True,
|
|
724
|
+
tags=["Teams"],
|
|
725
|
+
)
|
|
726
|
+
async def get_team(team_id: str):
|
|
727
|
+
team = get_team_by_id(team_id, os.teams)
|
|
728
|
+
if team is None:
|
|
729
|
+
raise HTTPException(status_code=404, detail="Team not found")
|
|
730
|
+
|
|
731
|
+
return TeamResponse.from_team(team)
|
|
732
|
+
|
|
733
|
+
# -- Workflow routes ---
|
|
734
|
+
|
|
735
|
+
@router.websocket("/workflows/ws")
|
|
736
|
+
async def workflow_websocket_endpoint(websocket: WebSocket):
|
|
737
|
+
"""WebSocket endpoint for receiving real-time workflow events"""
|
|
738
|
+
await websocket_manager.connect(websocket)
|
|
739
|
+
|
|
740
|
+
try:
|
|
741
|
+
while True:
|
|
742
|
+
data = await websocket.receive_text()
|
|
743
|
+
message = json.loads(data)
|
|
744
|
+
action = message.get("action")
|
|
745
|
+
|
|
746
|
+
if action == "ping":
|
|
747
|
+
await websocket.send_text(json.dumps({"event": "pong"}))
|
|
748
|
+
|
|
749
|
+
elif action == "start-workflow":
|
|
750
|
+
# Handle workflow execution directly via WebSocket
|
|
751
|
+
await handle_workflow_via_websocket(websocket, message, os)
|
|
752
|
+
|
|
753
|
+
except Exception as e:
|
|
754
|
+
logger.error(f"WebSocket error: {e}")
|
|
755
|
+
# Clean up any run_ids associated with this websocket
|
|
756
|
+
runs_to_remove = [run_id for run_id, ws in websocket_manager.active_connections.items() if ws == websocket]
|
|
757
|
+
for run_id in runs_to_remove:
|
|
758
|
+
await websocket_manager.disconnect_by_run_id(run_id)
|
|
759
|
+
|
|
760
|
+
@router.get(
|
|
761
|
+
"/workflows",
|
|
762
|
+
response_model=List[WorkflowResponse],
|
|
763
|
+
response_model_exclude_none=True,
|
|
764
|
+
tags=["Workflows"],
|
|
765
|
+
)
|
|
766
|
+
async def get_workflows():
|
|
767
|
+
if os.workflows is None:
|
|
768
|
+
return []
|
|
769
|
+
|
|
770
|
+
return [WorkflowSummaryResponse.from_workflow(workflow) for workflow in os.workflows]
|
|
771
|
+
|
|
772
|
+
@router.get(
|
|
773
|
+
"/workflows/{workflow_id}",
|
|
774
|
+
response_model=WorkflowResponse,
|
|
775
|
+
response_model_exclude_none=True,
|
|
776
|
+
tags=["Workflows"],
|
|
777
|
+
)
|
|
778
|
+
async def get_workflow(workflow_id: str):
|
|
779
|
+
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
780
|
+
if workflow is None:
|
|
781
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
782
|
+
|
|
783
|
+
return WorkflowResponse.from_workflow(workflow)
|
|
784
|
+
|
|
785
|
+
@router.post("/workflows/{workflow_id}/runs", tags=["Workflows"])
|
|
786
|
+
async def create_workflow_run(
|
|
787
|
+
workflow_id: str,
|
|
788
|
+
message: str = Form(...),
|
|
789
|
+
stream: bool = Form(True),
|
|
790
|
+
session_id: Optional[str] = Form(None),
|
|
791
|
+
user_id: Optional[str] = Form(None),
|
|
792
|
+
**kwargs: Any,
|
|
793
|
+
):
|
|
794
|
+
# Retrieve the workflow by ID
|
|
795
|
+
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
796
|
+
if workflow is None:
|
|
797
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
798
|
+
|
|
799
|
+
if session_id:
|
|
800
|
+
logger.debug(f"Continuing session: {session_id}")
|
|
801
|
+
else:
|
|
802
|
+
logger.debug("Creating new session")
|
|
803
|
+
session_id = str(uuid4())
|
|
804
|
+
|
|
805
|
+
# Return based on stream parameter
|
|
806
|
+
try:
|
|
807
|
+
if stream:
|
|
808
|
+
return StreamingResponse(
|
|
809
|
+
workflow_response_streamer(
|
|
810
|
+
workflow,
|
|
811
|
+
input=message,
|
|
812
|
+
session_id=session_id,
|
|
813
|
+
user_id=user_id,
|
|
814
|
+
**kwargs,
|
|
815
|
+
),
|
|
816
|
+
media_type="text/event-stream",
|
|
817
|
+
)
|
|
818
|
+
else:
|
|
819
|
+
run_response = await workflow.arun(
|
|
820
|
+
input=message,
|
|
821
|
+
session_id=session_id,
|
|
822
|
+
user_id=user_id,
|
|
823
|
+
stream=False,
|
|
824
|
+
**kwargs,
|
|
825
|
+
)
|
|
826
|
+
return run_response.to_dict()
|
|
827
|
+
except Exception as e:
|
|
828
|
+
# Handle unexpected runtime errors
|
|
829
|
+
raise HTTPException(status_code=500, detail=f"Error running workflow: {str(e)}")
|
|
830
|
+
|
|
831
|
+
@router.post("/workflows/{workflow_id}/runs/{run_id}/cancel", tags=["Workflows"])
|
|
832
|
+
async def cancel_workflow_run(workflow_id: str, run_id: str):
|
|
833
|
+
workflow = get_workflow_by_id(workflow_id, os.workflows)
|
|
834
|
+
|
|
835
|
+
if workflow is None:
|
|
836
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
837
|
+
|
|
838
|
+
if not workflow.cancel_run(run_id=run_id):
|
|
839
|
+
raise HTTPException(status_code=500, detail="Failed to cancel run")
|
|
840
|
+
|
|
841
|
+
return JSONResponse(content={}, status_code=200)
|
|
842
|
+
|
|
843
|
+
return router
|