agno 0.1.2__py3-none-any.whl → 2.3.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 +44 -5
- agno/agent/agent.py +10531 -2975
- agno/api/agent.py +14 -53
- agno/api/api.py +7 -46
- agno/api/evals.py +22 -0
- agno/api/os.py +17 -0
- agno/api/routes.py +6 -25
- agno/api/schemas/__init__.py +9 -0
- agno/api/schemas/agent.py +6 -9
- agno/api/schemas/evals.py +16 -0
- agno/api/schemas/os.py +14 -0
- agno/api/schemas/team.py +10 -10
- agno/api/schemas/utils.py +21 -0
- agno/api/schemas/workflows.py +16 -0
- agno/api/settings.py +53 -0
- agno/api/team.py +22 -26
- 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/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -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 +946 -0
- agno/db/dynamo/__init__.py +3 -0
- agno/db/dynamo/dynamo.py +2781 -0
- agno/db/dynamo/schemas.py +442 -0
- agno/db/dynamo/utils.py +743 -0
- agno/db/firestore/__init__.py +3 -0
- agno/db/firestore/firestore.py +2379 -0
- agno/db/firestore/schemas.py +181 -0
- agno/db/firestore/utils.py +376 -0
- agno/db/gcs_json/__init__.py +3 -0
- agno/db/gcs_json/gcs_json_db.py +1791 -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 +1312 -0
- agno/db/in_memory/utils.py +230 -0
- agno/db/json/__init__.py +3 -0
- agno/db/json/json_db.py +1777 -0
- agno/db/json/utils.py +230 -0
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +635 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +17 -0
- agno/db/mongo/async_mongo.py +2760 -0
- agno/db/mongo/mongo.py +2597 -0
- agno/db/mongo/schemas.py +119 -0
- agno/db/mongo/utils.py +276 -0
- agno/db/mysql/__init__.py +4 -0
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +2923 -0
- agno/db/mysql/schemas.py +186 -0
- agno/db/mysql/utils.py +488 -0
- agno/db/postgres/__init__.py +4 -0
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +2870 -0
- agno/db/postgres/schemas.py +187 -0
- agno/db/postgres/utils.py +442 -0
- agno/db/redis/__init__.py +3 -0
- agno/db/redis/redis.py +2141 -0
- agno/db/redis/schemas.py +159 -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 +34 -0
- agno/db/schemas/knowledge.py +40 -0
- agno/db/schemas/memory.py +61 -0
- agno/db/singlestore/__init__.py +3 -0
- agno/db/singlestore/schemas.py +179 -0
- agno/db/singlestore/singlestore.py +2877 -0
- agno/db/singlestore/utils.py +384 -0
- agno/db/sqlite/__init__.py +4 -0
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +181 -0
- agno/db/sqlite/sqlite.py +2908 -0
- agno/db/sqlite/utils.py +429 -0
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +118 -0
- agno/eval/__init__.py +24 -0
- agno/eval/accuracy.py +666 -276
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +779 -0
- agno/eval/reliability.py +241 -62
- agno/eval/utils.py +120 -0
- agno/exceptions.py +143 -1
- 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/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/__init__.py +3 -0
- agno/integrations/discord/client.py +203 -0
- agno/knowledge/__init__.py +5 -1
- agno/{document → knowledge}/chunking/agentic.py +22 -14
- agno/{document → knowledge}/chunking/document.py +2 -2
- agno/{document → knowledge}/chunking/fixed.py +7 -6
- agno/knowledge/chunking/markdown.py +151 -0
- agno/{document → knowledge}/chunking/recursive.py +15 -3
- agno/knowledge/chunking/row.py +39 -0
- agno/knowledge/chunking/semantic.py +91 -0
- agno/knowledge/chunking/strategy.py +165 -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/knowledge/embedder/aws_bedrock.py +343 -0
- agno/knowledge/embedder/azure_openai.py +210 -0
- agno/{embedder → knowledge/embedder}/base.py +8 -0
- agno/knowledge/embedder/cohere.py +323 -0
- agno/knowledge/embedder/fastembed.py +62 -0
- agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
- 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/{embedder → knowledge/embedder}/together.py +1 -1
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +165 -0
- agno/knowledge/knowledge.py +3006 -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 +164 -0
- agno/knowledge/reader/docx_reader.py +82 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/firecrawl_reader.py +201 -0
- agno/knowledge/reader/json_reader.py +88 -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 +193 -0
- agno/knowledge/reader/text_reader.py +127 -0
- agno/knowledge/reader/web_search_reader.py +325 -0
- agno/knowledge/reader/website_reader.py +455 -0
- agno/knowledge/reader/wikipedia_reader.py +91 -0
- agno/knowledge/reader/youtube_reader.py +78 -0
- agno/knowledge/remote_content/remote_content.py +88 -0
- agno/knowledge/reranker/__init__.py +3 -0
- agno/{reranker → knowledge/reranker}/base.py +1 -1
- agno/{reranker → knowledge/reranker}/cohere.py +2 -2
- 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 +234 -0
- agno/media.py +439 -95
- agno/memory/__init__.py +16 -3
- agno/memory/manager.py +1474 -123
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/__init__.py +5 -0
- agno/models/aimlapi/aimlapi.py +62 -0
- agno/models/anthropic/__init__.py +4 -0
- agno/models/anthropic/claude.py +960 -496
- agno/models/aws/__init__.py +15 -0
- agno/models/aws/bedrock.py +686 -451
- agno/models/aws/claude.py +190 -183
- agno/models/azure/__init__.py +18 -1
- agno/models/azure/ai_foundry.py +489 -0
- agno/models/azure/openai_chat.py +89 -40
- agno/models/base.py +2477 -550
- agno/models/cerebras/__init__.py +12 -0
- agno/models/cerebras/cerebras.py +565 -0
- agno/models/cerebras/cerebras_openai.py +131 -0
- agno/models/cohere/__init__.py +4 -0
- agno/models/cohere/chat.py +306 -492
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +74 -0
- agno/models/dashscope/__init__.py +5 -0
- agno/models/dashscope/dashscope.py +90 -0
- agno/models/deepinfra/__init__.py +5 -0
- agno/models/deepinfra/deepinfra.py +45 -0
- agno/models/deepseek/__init__.py +4 -0
- agno/models/deepseek/deepseek.py +110 -9
- agno/models/fireworks/__init__.py +4 -0
- agno/models/fireworks/fireworks.py +19 -22
- agno/models/google/__init__.py +3 -7
- agno/models/google/gemini.py +1717 -662
- agno/models/google/utils.py +22 -0
- agno/models/groq/__init__.py +4 -0
- agno/models/groq/groq.py +391 -666
- agno/models/huggingface/__init__.py +4 -0
- agno/models/huggingface/huggingface.py +266 -538
- agno/models/ibm/__init__.py +5 -0
- agno/models/ibm/watsonx.py +432 -0
- agno/models/internlm/__init__.py +3 -0
- agno/models/internlm/internlm.py +20 -3
- agno/models/langdb/__init__.py +1 -0
- agno/models/langdb/langdb.py +60 -0
- agno/models/litellm/__init__.py +14 -0
- agno/models/litellm/chat.py +503 -0
- agno/models/litellm/litellm_openai.py +42 -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 +361 -39
- agno/models/meta/__init__.py +12 -0
- agno/models/meta/llama.py +502 -0
- agno/models/meta/llama_openai.py +79 -0
- agno/models/metrics.py +120 -0
- agno/models/mistral/__init__.py +4 -0
- agno/models/mistral/mistral.py +293 -393
- agno/models/nebius/__init__.py +3 -0
- agno/models/nebius/nebius.py +53 -0
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/__init__.py +4 -0
- agno/models/nvidia/nvidia.py +22 -3
- agno/models/ollama/__init__.py +4 -2
- agno/models/ollama/chat.py +257 -492
- agno/models/openai/__init__.py +7 -0
- agno/models/openai/chat.py +725 -770
- agno/models/openai/like.py +16 -2
- agno/models/openai/responses.py +1121 -0
- agno/models/openrouter/__init__.py +4 -0
- agno/models/openrouter/openrouter.py +62 -5
- agno/models/perplexity/__init__.py +5 -0
- agno/models/perplexity/perplexity.py +203 -0
- agno/models/portkey/__init__.py +3 -0
- agno/models/portkey/portkey.py +82 -0
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +69 -0
- agno/models/response.py +177 -7
- agno/models/sambanova/__init__.py +4 -0
- agno/models/sambanova/sambanova.py +23 -4
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +42 -0
- agno/models/together/__init__.py +4 -0
- agno/models/together/together.py +21 -164
- agno/models/utils.py +266 -0
- agno/models/vercel/__init__.py +3 -0
- agno/models/vercel/v0.py +43 -0
- agno/models/vertexai/__init__.py +0 -1
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/__init__.py +3 -0
- agno/models/vllm/vllm.py +83 -0
- agno/models/xai/__init__.py +2 -0
- agno/models/xai/xai.py +111 -7
- agno/os/__init__.py +3 -0
- agno/os/app.py +1027 -0
- agno/os/auth.py +244 -0
- agno/os/config.py +126 -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 +249 -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 +147 -0
- agno/os/interfaces/agui/utils.py +574 -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 +210 -0
- agno/os/interfaces/whatsapp/security.py +55 -0
- agno/os/interfaces/whatsapp/whatsapp.py +36 -0
- agno/os/mcp.py +293 -0
- agno/os/middleware/__init__.py +9 -0
- agno/os/middleware/jwt.py +797 -0
- agno/os/router.py +258 -0
- agno/os/routers/__init__.py +3 -0
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/__init__.py +3 -0
- agno/os/routers/evals/evals.py +450 -0
- agno/os/routers/evals/schemas.py +174 -0
- agno/os/routers/evals/utils.py +231 -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 +1008 -0
- agno/os/routers/knowledge/schemas.py +178 -0
- agno/os/routers/memory/__init__.py +3 -0
- agno/os/routers/memory/memory.py +661 -0
- agno/os/routers/memory/schemas.py +88 -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/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +534 -0
- agno/os/scopes.py +469 -0
- agno/{playground → os}/settings.py +7 -15
- agno/os/utils.py +973 -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 +24 -1
- agno/reasoning/ollama.py +67 -0
- agno/reasoning/openai.py +86 -0
- agno/reasoning/step.py +2 -1
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +822 -0
- agno/run/base.py +247 -0
- agno/run/cancel.py +81 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +767 -0
- agno/run/workflow.py +708 -0
- agno/session/__init__.py +10 -0
- agno/session/agent.py +260 -0
- agno/session/summary.py +265 -0
- agno/session/team.py +342 -0
- agno/session/workflow.py +501 -0
- agno/table.py +10 -0
- agno/team/__init__.py +37 -0
- agno/team/team.py +9536 -0
- agno/tools/__init__.py +7 -0
- agno/tools/agentql.py +120 -0
- agno/tools/airflow.py +22 -12
- agno/tools/api.py +122 -0
- agno/tools/apify.py +276 -83
- agno/tools/{arxiv_toolkit.py → arxiv.py} +20 -12
- agno/tools/aws_lambda.py +28 -7
- agno/tools/aws_ses.py +66 -0
- agno/tools/baidusearch.py +11 -4
- 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 +32 -23
- agno/tools/calculator.py +24 -37
- agno/tools/cartesia.py +187 -0
- agno/tools/{clickup_tool.py → clickup.py} +17 -28
- agno/tools/confluence.py +91 -26
- agno/tools/crawl4ai.py +139 -43
- agno/tools/csv_toolkit.py +28 -22
- agno/tools/dalle.py +36 -22
- agno/tools/daytona.py +475 -0
- agno/tools/decorator.py +169 -14
- agno/tools/desi_vocal.py +23 -11
- agno/tools/discord.py +32 -29
- agno/tools/docker.py +716 -0
- agno/tools/duckdb.py +76 -81
- agno/tools/duckduckgo.py +43 -40
- agno/tools/e2b.py +703 -0
- agno/tools/eleven_labs.py +65 -54
- agno/tools/email.py +13 -5
- agno/tools/evm.py +129 -0
- agno/tools/exa.py +324 -42
- agno/tools/fal.py +39 -35
- agno/tools/file.py +196 -30
- agno/tools/file_generation.py +356 -0
- agno/tools/financial_datasets.py +288 -0
- agno/tools/firecrawl.py +108 -33
- agno/tools/function.py +960 -122
- agno/tools/giphy.py +34 -12
- agno/tools/github.py +1294 -97
- agno/tools/gmail.py +922 -0
- agno/tools/google_bigquery.py +117 -0
- agno/tools/google_drive.py +271 -0
- agno/tools/google_maps.py +253 -0
- agno/tools/googlecalendar.py +607 -107
- agno/tools/googlesheets.py +377 -0
- agno/tools/hackernews.py +20 -12
- agno/tools/jina.py +24 -14
- agno/tools/jira.py +48 -19
- agno/tools/knowledge.py +218 -0
- agno/tools/linear.py +82 -43
- agno/tools/linkup.py +58 -0
- agno/tools/local_file_system.py +15 -7
- agno/tools/lumalab.py +41 -26
- 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/memory.py +419 -0
- agno/tools/mlx_transcribe.py +11 -9
- 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 +163 -82
- agno/tools/moviepy_video.py +18 -13
- agno/tools/nano_banana.py +151 -0
- agno/tools/neo4j.py +134 -0
- agno/tools/newspaper.py +15 -4
- agno/tools/newspaper4k.py +19 -6
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +181 -17
- agno/tools/openbb.py +27 -20
- agno/tools/opencv.py +321 -0
- agno/tools/openweather.py +233 -0
- agno/tools/oxylabs.py +385 -0
- agno/tools/pandas.py +25 -15
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +238 -185
- agno/tools/pubmed.py +125 -13
- agno/tools/python.py +48 -35
- agno/tools/reasoning.py +283 -0
- agno/tools/reddit.py +207 -29
- agno/tools/redshift.py +406 -0
- agno/tools/replicate.py +69 -26
- agno/tools/resend.py +11 -6
- agno/tools/scrapegraph.py +179 -19
- agno/tools/searxng.py +23 -31
- agno/tools/serpapi.py +15 -10
- agno/tools/serper.py +255 -0
- agno/tools/shell.py +23 -12
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +56 -14
- agno/tools/sleep.py +8 -6
- agno/tools/spider.py +35 -11
- agno/tools/spotify.py +919 -0
- agno/tools/sql.py +34 -19
- agno/tools/tavily.py +158 -8
- agno/tools/telegram.py +18 -8
- agno/tools/todoist.py +218 -0
- agno/tools/toolkit.py +134 -9
- agno/tools/trafilatura.py +388 -0
- agno/tools/trello.py +25 -28
- agno/tools/twilio.py +18 -9
- 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 +23 -19
- agno/tools/webtools.py +45 -0
- agno/tools/whatsapp.py +286 -0
- agno/tools/wikipedia.py +28 -19
- agno/tools/workflow.py +285 -0
- agno/tools/{twitter.py → x.py} +142 -46
- agno/tools/yfinance.py +41 -39
- agno/tools/youtube.py +34 -17
- agno/tools/zendesk.py +15 -5
- agno/tools/zep.py +454 -0
- agno/tools/zoom.py +86 -37
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/audio.py +37 -1
- agno/utils/certs.py +27 -0
- agno/utils/code_execution.py +11 -0
- agno/utils/common.py +103 -20
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +700 -0
- agno/utils/functions.py +107 -37
- agno/utils/gemini.py +426 -0
- agno/utils/hooks.py +171 -0
- agno/utils/http.py +185 -0
- agno/utils/json_schema.py +159 -37
- agno/utils/knowledge.py +36 -0
- agno/utils/location.py +19 -0
- agno/utils/log.py +221 -8
- agno/utils/mcp.py +214 -0
- agno/utils/media.py +335 -14
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +77 -2
- agno/utils/models/ai_foundry.py +50 -0
- agno/utils/models/claude.py +373 -0
- agno/utils/models/cohere.py +94 -0
- agno/utils/models/llama.py +85 -0
- agno/utils/models/mistral.py +100 -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 +1 -1
- agno/utils/pprint.py +124 -8
- agno/utils/print_response/agent.py +930 -0
- agno/utils/print_response/team.py +1914 -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/serialize.py +32 -0
- agno/utils/shell.py +4 -4
- agno/utils/streamlit.py +487 -0
- agno/utils/string.py +204 -51
- agno/utils/team.py +139 -0
- agno/utils/timer.py +9 -2
- agno/utils/tokens.py +657 -0
- agno/utils/tools.py +19 -1
- agno/utils/whatsapp.py +305 -0
- agno/utils/yaml_io.py +3 -3
- agno/vectordb/__init__.py +2 -0
- agno/vectordb/base.py +87 -9
- agno/vectordb/cassandra/__init__.py +5 -1
- agno/vectordb/cassandra/cassandra.py +383 -27
- agno/vectordb/chroma/__init__.py +4 -0
- agno/vectordb/chroma/chromadb.py +748 -83
- agno/vectordb/clickhouse/__init__.py +7 -1
- agno/vectordb/clickhouse/clickhousedb.py +554 -53
- agno/vectordb/couchbase/__init__.py +3 -0
- agno/vectordb/couchbase/couchbase.py +1446 -0
- agno/vectordb/lancedb/__init__.py +5 -0
- agno/vectordb/lancedb/lance_db.py +730 -98
- 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 +3 -0
- agno/vectordb/milvus/milvus.py +966 -78
- agno/vectordb/mongodb/__init__.py +9 -1
- agno/vectordb/mongodb/mongodb.py +1175 -172
- agno/vectordb/pgvector/__init__.py +8 -0
- agno/vectordb/pgvector/pgvector.py +599 -115
- agno/vectordb/pineconedb/__init__.py +5 -1
- agno/vectordb/pineconedb/pineconedb.py +406 -43
- agno/vectordb/qdrant/__init__.py +4 -0
- agno/vectordb/qdrant/qdrant.py +914 -61
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/__init__.py +8 -1
- agno/vectordb/singlestore/singlestore.py +771 -0
- agno/vectordb/surrealdb/__init__.py +3 -0
- agno/vectordb/surrealdb/surrealdb.py +663 -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 +1009 -0
- agno/workflow/__init__.py +23 -1
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +759 -0
- agno/workflow/loop.py +756 -0
- agno/workflow/parallel.py +853 -0
- agno/workflow/router.py +723 -0
- agno/workflow/step.py +1564 -0
- agno/workflow/steps.py +613 -0
- agno/workflow/types.py +556 -0
- agno/workflow/workflow.py +4327 -514
- agno-2.3.13.dist-info/METADATA +639 -0
- agno-2.3.13.dist-info/RECORD +613 -0
- {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +1 -1
- agno-2.3.13.dist-info/licenses/LICENSE +201 -0
- agno/api/playground.py +0 -91
- agno/api/schemas/playground.py +0 -22
- agno/api/schemas/user.py +0 -22
- agno/api/schemas/workspace.py +0 -46
- agno/api/user.py +0 -160
- agno/api/workspace.py +0 -151
- agno/cli/auth_server.py +0 -118
- agno/cli/config.py +0 -275
- agno/cli/console.py +0 -88
- agno/cli/credentials.py +0 -23
- agno/cli/entrypoint.py +0 -571
- agno/cli/operator.py +0 -355
- agno/cli/settings.py +0 -85
- agno/cli/ws/ws_cli.py +0 -817
- agno/constants.py +0 -13
- agno/document/__init__.py +0 -1
- agno/document/chunking/semantic.py +0 -47
- agno/document/chunking/strategy.py +0 -31
- agno/document/reader/__init__.py +0 -1
- agno/document/reader/arxiv_reader.py +0 -41
- agno/document/reader/base.py +0 -22
- agno/document/reader/csv_reader.py +0 -84
- agno/document/reader/docx_reader.py +0 -46
- agno/document/reader/firecrawl_reader.py +0 -99
- agno/document/reader/json_reader.py +0 -43
- agno/document/reader/pdf_reader.py +0 -219
- agno/document/reader/s3/pdf_reader.py +0 -46
- agno/document/reader/s3/text_reader.py +0 -51
- agno/document/reader/text_reader.py +0 -41
- agno/document/reader/website_reader.py +0 -175
- agno/document/reader/youtube_reader.py +0 -50
- agno/embedder/__init__.py +0 -1
- agno/embedder/azure_openai.py +0 -86
- agno/embedder/cohere.py +0 -72
- agno/embedder/fastembed.py +0 -37
- agno/embedder/google.py +0 -73
- agno/embedder/huggingface.py +0 -54
- agno/embedder/mistral.py +0 -80
- agno/embedder/ollama.py +0 -57
- agno/embedder/openai.py +0 -74
- agno/embedder/sentence_transformer.py +0 -38
- agno/embedder/voyageai.py +0 -64
- agno/eval/perf.py +0 -201
- agno/file/__init__.py +0 -1
- 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 -230
- agno/knowledge/arxiv.py +0 -22
- agno/knowledge/combined.py +0 -22
- agno/knowledge/csv.py +0 -28
- agno/knowledge/csv_url.py +0 -19
- agno/knowledge/document.py +0 -20
- agno/knowledge/docx.py +0 -30
- agno/knowledge/json.py +0 -28
- agno/knowledge/langchain.py +0 -71
- agno/knowledge/llamaindex.py +0 -66
- agno/knowledge/pdf.py +0 -28
- agno/knowledge/pdf_url.py +0 -26
- agno/knowledge/s3/base.py +0 -60
- agno/knowledge/s3/pdf.py +0 -21
- agno/knowledge/s3/text.py +0 -23
- agno/knowledge/text.py +0 -30
- agno/knowledge/website.py +0 -88
- agno/knowledge/wikipedia.py +0 -31
- agno/knowledge/youtube.py +0 -22
- agno/memory/agent.py +0 -392
- agno/memory/classifier.py +0 -104
- agno/memory/db/__init__.py +0 -1
- 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 -15
- agno/memory/row.py +0 -36
- agno/memory/summarizer.py +0 -192
- agno/memory/summary.py +0 -19
- agno/memory/workflow.py +0 -38
- agno/models/google/gemini_openai.py +0 -26
- agno/models/ollama/hermes.py +0 -221
- agno/models/ollama/tools.py +0 -362
- agno/models/vertexai/gemini.py +0 -595
- agno/playground/__init__.py +0 -3
- agno/playground/async_router.py +0 -421
- agno/playground/deploy.py +0 -249
- agno/playground/operator.py +0 -92
- agno/playground/playground.py +0 -91
- agno/playground/schemas.py +0 -76
- agno/playground/serve.py +0 -55
- agno/playground/sync_router.py +0 -405
- agno/reasoning/agent.py +0 -68
- agno/run/response.py +0 -112
- agno/storage/agent/__init__.py +0 -0
- agno/storage/agent/base.py +0 -38
- agno/storage/agent/dynamodb.py +0 -350
- agno/storage/agent/json.py +0 -92
- agno/storage/agent/mongodb.py +0 -228
- agno/storage/agent/postgres.py +0 -367
- agno/storage/agent/session.py +0 -79
- agno/storage/agent/singlestore.py +0 -303
- agno/storage/agent/sqlite.py +0 -357
- agno/storage/agent/yaml.py +0 -93
- agno/storage/workflow/__init__.py +0 -0
- agno/storage/workflow/base.py +0 -40
- agno/storage/workflow/mongodb.py +0 -233
- agno/storage/workflow/postgres.py +0 -366
- agno/storage/workflow/session.py +0 -60
- agno/storage/workflow/sqlite.py +0 -359
- agno/tools/googlesearch.py +0 -88
- 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/vectordb/singlestore/s2vectordb.py +0 -390
- agno/vectordb/singlestore/s2vectordb2.py +0 -355
- agno/workspace/__init__.py +0 -0
- agno/workspace/config.py +0 -325
- agno/workspace/enums.py +0 -6
- agno/workspace/helpers.py +0 -48
- agno/workspace/operator.py +0 -758
- agno/workspace/settings.py +0 -63
- agno-0.1.2.dist-info/LICENSE +0 -375
- agno-0.1.2.dist-info/METADATA +0 -502
- agno-0.1.2.dist-info/RECORD +0 -352
- agno-0.1.2.dist-info/entry_points.txt +0 -3
- /agno/{cli → db/migrations}/__init__.py +0 -0
- /agno/{cli/ws → db/migrations/versions}/__init__.py +0 -0
- /agno/{document/chunking/__init__.py → db/schemas/metrics.py} +0 -0
- /agno/{document/reader/s3 → integrations}/__init__.py +0 -0
- /agno/{file/local → knowledge/chunking}/__init__.py +0 -0
- /agno/{infra → knowledge/remote_content}/__init__.py +0 -0
- /agno/{knowledge/s3 → tools/models}/__init__.py +0 -0
- /agno/{reranker → utils/models}/__init__.py +0 -0
- /agno/{storage → utils/print_response}/__init__.py +0 -0
- {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/os/app.py
ADDED
|
@@ -0,0 +1,1027 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from functools import partial
|
|
3
|
+
from os import getenv
|
|
4
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, FastAPI, HTTPException
|
|
8
|
+
from fastapi.responses import JSONResponse
|
|
9
|
+
from fastapi.routing import APIRoute
|
|
10
|
+
from rich import box
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from starlette.requests import Request
|
|
13
|
+
|
|
14
|
+
from agno.agent.agent import Agent
|
|
15
|
+
from agno.db.base import AsyncBaseDb, BaseDb
|
|
16
|
+
from agno.knowledge.knowledge import Knowledge
|
|
17
|
+
from agno.os.config import (
|
|
18
|
+
AgentOSConfig,
|
|
19
|
+
AuthorizationConfig,
|
|
20
|
+
DatabaseConfig,
|
|
21
|
+
EvalsConfig,
|
|
22
|
+
EvalsDomainConfig,
|
|
23
|
+
KnowledgeConfig,
|
|
24
|
+
KnowledgeDomainConfig,
|
|
25
|
+
MemoryConfig,
|
|
26
|
+
MemoryDomainConfig,
|
|
27
|
+
MetricsConfig,
|
|
28
|
+
MetricsDomainConfig,
|
|
29
|
+
SessionConfig,
|
|
30
|
+
SessionDomainConfig,
|
|
31
|
+
TracesConfig,
|
|
32
|
+
TracesDomainConfig,
|
|
33
|
+
)
|
|
34
|
+
from agno.os.interfaces.base import BaseInterface
|
|
35
|
+
from agno.os.router import get_base_router
|
|
36
|
+
from agno.os.routers.agents import get_agent_router
|
|
37
|
+
from agno.os.routers.evals import get_eval_router
|
|
38
|
+
from agno.os.routers.health import get_health_router
|
|
39
|
+
from agno.os.routers.home import get_home_router
|
|
40
|
+
from agno.os.routers.knowledge import get_knowledge_router
|
|
41
|
+
from agno.os.routers.memory import get_memory_router
|
|
42
|
+
from agno.os.routers.metrics import get_metrics_router
|
|
43
|
+
from agno.os.routers.session import get_session_router
|
|
44
|
+
from agno.os.routers.teams import get_team_router
|
|
45
|
+
from agno.os.routers.traces import get_traces_router
|
|
46
|
+
from agno.os.routers.workflows import get_websocket_router, get_workflow_router
|
|
47
|
+
from agno.os.settings import AgnoAPISettings
|
|
48
|
+
from agno.os.utils import (
|
|
49
|
+
collect_mcp_tools_from_team,
|
|
50
|
+
collect_mcp_tools_from_workflow,
|
|
51
|
+
find_conflicting_routes,
|
|
52
|
+
load_yaml_config,
|
|
53
|
+
resolve_origins,
|
|
54
|
+
setup_tracing_for_os,
|
|
55
|
+
update_cors_middleware,
|
|
56
|
+
)
|
|
57
|
+
from agno.team.team import Team
|
|
58
|
+
from agno.utils.log import log_debug, log_error, log_info, log_warning
|
|
59
|
+
from agno.utils.string import generate_id, generate_id_from_name
|
|
60
|
+
from agno.workflow.workflow import Workflow
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@asynccontextmanager
|
|
64
|
+
async def mcp_lifespan(_, mcp_tools):
|
|
65
|
+
"""Manage MCP connection lifecycle inside a FastAPI app"""
|
|
66
|
+
# Startup logic: connect to all contextual MCP servers
|
|
67
|
+
for tool in mcp_tools:
|
|
68
|
+
await tool.connect()
|
|
69
|
+
|
|
70
|
+
yield
|
|
71
|
+
|
|
72
|
+
# Shutdown logic: Close all contextual MCP connections
|
|
73
|
+
for tool in mcp_tools:
|
|
74
|
+
await tool.close()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@asynccontextmanager
|
|
78
|
+
async def db_lifespan(app: FastAPI, agent_os: "AgentOS"):
|
|
79
|
+
"""Initializes databases in the event loop"""
|
|
80
|
+
if agent_os.auto_provision_dbs:
|
|
81
|
+
agent_os._initialize_sync_databases()
|
|
82
|
+
await agent_os._initialize_async_databases()
|
|
83
|
+
yield
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _combine_app_lifespans(lifespans: list) -> Any:
|
|
87
|
+
"""Combine multiple FastAPI app lifespan context managers into one."""
|
|
88
|
+
if len(lifespans) == 1:
|
|
89
|
+
return lifespans[0]
|
|
90
|
+
|
|
91
|
+
from contextlib import asynccontextmanager
|
|
92
|
+
|
|
93
|
+
@asynccontextmanager
|
|
94
|
+
async def combined_lifespan(app):
|
|
95
|
+
async def _run_nested(index: int):
|
|
96
|
+
if index >= len(lifespans):
|
|
97
|
+
yield
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
async with lifespans[index](app):
|
|
101
|
+
async for _ in _run_nested(index + 1):
|
|
102
|
+
yield
|
|
103
|
+
|
|
104
|
+
async for _ in _run_nested(0):
|
|
105
|
+
yield
|
|
106
|
+
|
|
107
|
+
return combined_lifespan
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class AgentOS:
|
|
111
|
+
def __init__(
|
|
112
|
+
self,
|
|
113
|
+
id: Optional[str] = None,
|
|
114
|
+
name: Optional[str] = None,
|
|
115
|
+
description: Optional[str] = None,
|
|
116
|
+
version: Optional[str] = None,
|
|
117
|
+
agents: Optional[List[Agent]] = None,
|
|
118
|
+
teams: Optional[List[Team]] = None,
|
|
119
|
+
workflows: Optional[List[Workflow]] = None,
|
|
120
|
+
knowledge: Optional[List[Knowledge]] = None,
|
|
121
|
+
interfaces: Optional[List[BaseInterface]] = None,
|
|
122
|
+
a2a_interface: bool = False,
|
|
123
|
+
authorization: bool = False,
|
|
124
|
+
authorization_config: Optional[AuthorizationConfig] = None,
|
|
125
|
+
cors_allowed_origins: Optional[List[str]] = None,
|
|
126
|
+
config: Optional[Union[str, AgentOSConfig]] = None,
|
|
127
|
+
settings: Optional[AgnoAPISettings] = None,
|
|
128
|
+
lifespan: Optional[Any] = None,
|
|
129
|
+
enable_mcp_server: bool = False,
|
|
130
|
+
base_app: Optional[FastAPI] = None,
|
|
131
|
+
on_route_conflict: Literal["preserve_agentos", "preserve_base_app", "error"] = "preserve_agentos",
|
|
132
|
+
tracing: bool = False,
|
|
133
|
+
tracing_db: Optional[Union[BaseDb, AsyncBaseDb]] = None,
|
|
134
|
+
auto_provision_dbs: bool = True,
|
|
135
|
+
run_hooks_in_background: bool = False,
|
|
136
|
+
telemetry: bool = True,
|
|
137
|
+
):
|
|
138
|
+
"""Initialize AgentOS.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
id: Unique identifier for this AgentOS instance
|
|
142
|
+
name: Name of the AgentOS instance
|
|
143
|
+
description: Description of the AgentOS instance
|
|
144
|
+
version: Version of the AgentOS instance
|
|
145
|
+
agents: List of agents to include in the OS
|
|
146
|
+
teams: List of teams to include in the OS
|
|
147
|
+
workflows: List of workflows to include in the OS
|
|
148
|
+
knowledge: List of knowledge bases to include in the OS
|
|
149
|
+
interfaces: List of interfaces to include in the OS
|
|
150
|
+
a2a_interface: Whether to expose the OS agents and teams in an A2A server
|
|
151
|
+
config: Configuration file path or AgentOSConfig instance
|
|
152
|
+
settings: API settings for the OS
|
|
153
|
+
lifespan: Optional lifespan context manager for the FastAPI app
|
|
154
|
+
enable_mcp_server: Whether to enable MCP (Model Context Protocol)
|
|
155
|
+
base_app: Optional base FastAPI app to use for the AgentOS. All routes and middleware will be added to this app.
|
|
156
|
+
on_route_conflict: What to do when a route conflict is detected in case a custom base_app is provided.
|
|
157
|
+
auto_provision_dbs: Whether to automatically provision databases
|
|
158
|
+
authorization: Whether to enable authorization
|
|
159
|
+
authorization_config: Configuration for the authorization middleware
|
|
160
|
+
cors_allowed_origins: List of allowed CORS origins (will be merged with default Agno domains)
|
|
161
|
+
tracing: If True, enables OpenTelemetry tracing for all agents and teams in the OS
|
|
162
|
+
tracing_db: Dedicated database for storing and reading traces. Recommended for multi-db setups.
|
|
163
|
+
If not provided and tracing=True, the first available db from agents/teams/workflows is used.
|
|
164
|
+
run_hooks_in_background: If True, run agent/team pre/post hooks as FastAPI background tasks (non-blocking)
|
|
165
|
+
telemetry: Whether to enable telemetry
|
|
166
|
+
|
|
167
|
+
"""
|
|
168
|
+
if not agents and not workflows and not teams and not knowledge:
|
|
169
|
+
raise ValueError("Either agents, teams, workflows or knowledge bases must be provided.")
|
|
170
|
+
|
|
171
|
+
self.config = load_yaml_config(config) if isinstance(config, str) else config
|
|
172
|
+
|
|
173
|
+
self.agents: Optional[List[Agent]] = agents
|
|
174
|
+
self.workflows: Optional[List[Workflow]] = workflows
|
|
175
|
+
self.teams: Optional[List[Team]] = teams
|
|
176
|
+
self.interfaces = interfaces or []
|
|
177
|
+
self.a2a_interface = a2a_interface
|
|
178
|
+
self.knowledge = knowledge
|
|
179
|
+
self.settings: AgnoAPISettings = settings or AgnoAPISettings()
|
|
180
|
+
self.auto_provision_dbs = auto_provision_dbs
|
|
181
|
+
self._app_set = False
|
|
182
|
+
|
|
183
|
+
if base_app:
|
|
184
|
+
self.base_app: Optional[FastAPI] = base_app
|
|
185
|
+
self._app_set = True
|
|
186
|
+
self.on_route_conflict = on_route_conflict
|
|
187
|
+
else:
|
|
188
|
+
self.base_app = None
|
|
189
|
+
self._app_set = False
|
|
190
|
+
self.on_route_conflict = on_route_conflict
|
|
191
|
+
|
|
192
|
+
self.interfaces = interfaces or []
|
|
193
|
+
|
|
194
|
+
self.name = name
|
|
195
|
+
|
|
196
|
+
self.id = id
|
|
197
|
+
if not self.id:
|
|
198
|
+
self.id = generate_id(self.name) if self.name else str(uuid4())
|
|
199
|
+
|
|
200
|
+
self.version = version
|
|
201
|
+
self.description = description
|
|
202
|
+
|
|
203
|
+
self.telemetry = telemetry
|
|
204
|
+
self.tracing = tracing
|
|
205
|
+
self.tracing_db = tracing_db
|
|
206
|
+
|
|
207
|
+
self.enable_mcp_server = enable_mcp_server
|
|
208
|
+
self.lifespan = lifespan
|
|
209
|
+
|
|
210
|
+
# RBAC
|
|
211
|
+
self.authorization = authorization
|
|
212
|
+
self.authorization_config = authorization_config
|
|
213
|
+
|
|
214
|
+
# CORS configuration - merge user-provided origins with defaults from settings
|
|
215
|
+
self.cors_allowed_origins = resolve_origins(cors_allowed_origins, self.settings.cors_origin_list)
|
|
216
|
+
|
|
217
|
+
# If True, run agent/team hooks as FastAPI background tasks
|
|
218
|
+
self.run_hooks_in_background = run_hooks_in_background
|
|
219
|
+
|
|
220
|
+
# List of all MCP tools used inside the AgentOS
|
|
221
|
+
self.mcp_tools: List[Any] = []
|
|
222
|
+
self._mcp_app: Optional[Any] = None
|
|
223
|
+
|
|
224
|
+
self._initialize_agents()
|
|
225
|
+
self._initialize_teams()
|
|
226
|
+
self._initialize_workflows()
|
|
227
|
+
|
|
228
|
+
if self.tracing:
|
|
229
|
+
self._setup_tracing()
|
|
230
|
+
|
|
231
|
+
if self.telemetry:
|
|
232
|
+
from agno.api.os import OSLaunch, log_os_telemetry
|
|
233
|
+
|
|
234
|
+
log_os_telemetry(launch=OSLaunch(os_id=self.id, data=self._get_telemetry_data()))
|
|
235
|
+
|
|
236
|
+
def _add_agent_os_to_lifespan_function(self, lifespan):
|
|
237
|
+
"""
|
|
238
|
+
Inspect a lifespan function and wrap it to pass agent_os if it accepts it.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
A wrapped lifespan that passes agent_os if the lifespan function expects it.
|
|
242
|
+
"""
|
|
243
|
+
# Getting the actual function inside the lifespan
|
|
244
|
+
lifespan_function = lifespan
|
|
245
|
+
if hasattr(lifespan, "__wrapped__"):
|
|
246
|
+
lifespan_function = lifespan.__wrapped__
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
from inspect import signature
|
|
250
|
+
|
|
251
|
+
# Inspecting the lifespan function signature to find its parameters
|
|
252
|
+
sig = signature(lifespan_function)
|
|
253
|
+
params = list(sig.parameters.keys())
|
|
254
|
+
|
|
255
|
+
# If the lifespan function expects the 'agent_os' parameter, add it
|
|
256
|
+
if "agent_os" in params:
|
|
257
|
+
return partial(lifespan, agent_os=self)
|
|
258
|
+
else:
|
|
259
|
+
return lifespan
|
|
260
|
+
|
|
261
|
+
except (ValueError, TypeError):
|
|
262
|
+
return lifespan
|
|
263
|
+
|
|
264
|
+
def resync(self, app: FastAPI) -> None:
|
|
265
|
+
"""Resync the AgentOS to discover, initialize and configure: agents, teams, workflows, databases and knowledge bases."""
|
|
266
|
+
self._initialize_agents()
|
|
267
|
+
self._initialize_teams()
|
|
268
|
+
self._initialize_workflows()
|
|
269
|
+
self._auto_discover_databases()
|
|
270
|
+
self._auto_discover_knowledge_instances()
|
|
271
|
+
|
|
272
|
+
if self.enable_mcp_server:
|
|
273
|
+
from agno.os.mcp import get_mcp_server
|
|
274
|
+
|
|
275
|
+
self._mcp_app = get_mcp_server(self)
|
|
276
|
+
|
|
277
|
+
self._reprovision_routers(app=app)
|
|
278
|
+
|
|
279
|
+
def _reprovision_routers(self, app: FastAPI) -> None:
|
|
280
|
+
"""Re-provision all routes for the AgentOS."""
|
|
281
|
+
updated_routers = [
|
|
282
|
+
get_session_router(dbs=self.dbs),
|
|
283
|
+
get_metrics_router(dbs=self.dbs),
|
|
284
|
+
get_knowledge_router(knowledge_instances=self.knowledge_instances),
|
|
285
|
+
get_traces_router(dbs=self.dbs),
|
|
286
|
+
get_memory_router(dbs=self.dbs),
|
|
287
|
+
get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
# Clear all previously existing routes
|
|
291
|
+
app.router.routes = [
|
|
292
|
+
route
|
|
293
|
+
for route in app.router.routes
|
|
294
|
+
if hasattr(route, "path")
|
|
295
|
+
and route.path in ["/docs", "/redoc", "/openapi.json", "/docs/oauth2-redirect"]
|
|
296
|
+
or route.path.startswith("/mcp") # type: ignore
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
# Add the built-in routes
|
|
300
|
+
self._add_built_in_routes(app=app)
|
|
301
|
+
|
|
302
|
+
# Add the updated routes
|
|
303
|
+
for router in updated_routers:
|
|
304
|
+
self._add_router(app, router)
|
|
305
|
+
|
|
306
|
+
# Mount MCP if needed
|
|
307
|
+
if self.enable_mcp_server and self._mcp_app:
|
|
308
|
+
app.mount("/", self._mcp_app)
|
|
309
|
+
|
|
310
|
+
def _add_built_in_routes(self, app: FastAPI) -> None:
|
|
311
|
+
"""Add all AgentOSbuilt-in routes to the given app."""
|
|
312
|
+
# Add the home router if MCP server is not enabled
|
|
313
|
+
if not self.enable_mcp_server:
|
|
314
|
+
self._add_router(app, get_home_router(self))
|
|
315
|
+
|
|
316
|
+
self._add_router(app, get_health_router(health_endpoint="/health"))
|
|
317
|
+
self._add_router(app, get_base_router(self, settings=self.settings))
|
|
318
|
+
self._add_router(app, get_agent_router(self, settings=self.settings))
|
|
319
|
+
self._add_router(app, get_team_router(self, settings=self.settings))
|
|
320
|
+
self._add_router(app, get_workflow_router(self, settings=self.settings))
|
|
321
|
+
self._add_router(app, get_websocket_router(self, settings=self.settings))
|
|
322
|
+
|
|
323
|
+
# Add A2A interface if relevant
|
|
324
|
+
has_a2a_interface = False
|
|
325
|
+
for interface in self.interfaces:
|
|
326
|
+
if not has_a2a_interface and interface.__class__.__name__ == "A2A":
|
|
327
|
+
has_a2a_interface = True
|
|
328
|
+
interface_router = interface.get_router()
|
|
329
|
+
self._add_router(app, interface_router)
|
|
330
|
+
if self.a2a_interface and not has_a2a_interface:
|
|
331
|
+
from agno.os.interfaces.a2a import A2A
|
|
332
|
+
|
|
333
|
+
a2a_interface = A2A(agents=self.agents, teams=self.teams, workflows=self.workflows)
|
|
334
|
+
self.interfaces.append(a2a_interface)
|
|
335
|
+
self._add_router(app, a2a_interface.get_router())
|
|
336
|
+
|
|
337
|
+
def _make_app(self, lifespan: Optional[Any] = None) -> FastAPI:
|
|
338
|
+
# Adjust the FastAPI app lifespan to handle MCP connections if relevant
|
|
339
|
+
app_lifespan = lifespan
|
|
340
|
+
if self.mcp_tools is not None:
|
|
341
|
+
mcp_tools_lifespan = partial(mcp_lifespan, mcp_tools=self.mcp_tools)
|
|
342
|
+
# If there is already a lifespan, combine it with the MCP lifespan
|
|
343
|
+
if lifespan is not None:
|
|
344
|
+
# Combine both lifespans
|
|
345
|
+
@asynccontextmanager
|
|
346
|
+
async def combined_lifespan(app: FastAPI):
|
|
347
|
+
# Run both lifespans
|
|
348
|
+
async with lifespan(app): # type: ignore
|
|
349
|
+
async with mcp_tools_lifespan(app): # type: ignore
|
|
350
|
+
yield
|
|
351
|
+
|
|
352
|
+
app_lifespan = combined_lifespan
|
|
353
|
+
else:
|
|
354
|
+
app_lifespan = mcp_tools_lifespan
|
|
355
|
+
|
|
356
|
+
return FastAPI(
|
|
357
|
+
title=self.name or "Agno AgentOS",
|
|
358
|
+
version=self.version or "1.0.0",
|
|
359
|
+
description=self.description or "An agent operating system.",
|
|
360
|
+
docs_url="/docs" if self.settings.docs_enabled else None,
|
|
361
|
+
redoc_url="/redoc" if self.settings.docs_enabled else None,
|
|
362
|
+
openapi_url="/openapi.json" if self.settings.docs_enabled else None,
|
|
363
|
+
lifespan=app_lifespan,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
def _initialize_agents(self) -> None:
|
|
367
|
+
"""Initialize and configure all agents for AgentOS usage."""
|
|
368
|
+
if not self.agents:
|
|
369
|
+
return
|
|
370
|
+
for agent in self.agents:
|
|
371
|
+
# Track all MCP tools to later handle their connection
|
|
372
|
+
if agent.tools:
|
|
373
|
+
for tool in agent.tools:
|
|
374
|
+
# Checking if the tool is an instance of MCPTools, MultiMCPTools, or a subclass of those
|
|
375
|
+
if hasattr(type(tool), "__mro__"):
|
|
376
|
+
mro_names = {cls.__name__ for cls in type(tool).__mro__}
|
|
377
|
+
if mro_names & {"MCPTools", "MultiMCPTools"}:
|
|
378
|
+
if tool not in self.mcp_tools:
|
|
379
|
+
self.mcp_tools.append(tool)
|
|
380
|
+
|
|
381
|
+
agent.initialize_agent()
|
|
382
|
+
|
|
383
|
+
# Required for the built-in routes to work
|
|
384
|
+
agent.store_events = True
|
|
385
|
+
|
|
386
|
+
# Propagate run_hooks_in_background setting from AgentOS to agents
|
|
387
|
+
agent._run_hooks_in_background = self.run_hooks_in_background
|
|
388
|
+
|
|
389
|
+
def _initialize_teams(self) -> None:
|
|
390
|
+
"""Initialize and configure all teams for AgentOS usage."""
|
|
391
|
+
if not self.teams:
|
|
392
|
+
return
|
|
393
|
+
|
|
394
|
+
for team in self.teams:
|
|
395
|
+
# Track all MCP tools recursively
|
|
396
|
+
collect_mcp_tools_from_team(team, self.mcp_tools)
|
|
397
|
+
|
|
398
|
+
team.initialize_team()
|
|
399
|
+
|
|
400
|
+
for member in team.members:
|
|
401
|
+
if isinstance(member, Agent):
|
|
402
|
+
member.team_id = None
|
|
403
|
+
member.initialize_agent()
|
|
404
|
+
elif isinstance(member, Team):
|
|
405
|
+
member.initialize_team()
|
|
406
|
+
|
|
407
|
+
# Required for the built-in routes to work
|
|
408
|
+
team.store_events = True
|
|
409
|
+
|
|
410
|
+
# Propagate run_hooks_in_background setting to team and all nested members
|
|
411
|
+
team.propagate_run_hooks_in_background(self.run_hooks_in_background)
|
|
412
|
+
|
|
413
|
+
def _initialize_workflows(self) -> None:
|
|
414
|
+
"""Initialize and configure all workflows for AgentOS usage."""
|
|
415
|
+
if not self.workflows:
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
if self.workflows:
|
|
419
|
+
for workflow in self.workflows:
|
|
420
|
+
# Track MCP tools recursively in workflow members
|
|
421
|
+
collect_mcp_tools_from_workflow(workflow, self.mcp_tools)
|
|
422
|
+
|
|
423
|
+
if not workflow.id:
|
|
424
|
+
workflow.id = generate_id_from_name(workflow.name)
|
|
425
|
+
|
|
426
|
+
# Required for the built-in routes to work
|
|
427
|
+
workflow.store_events = True
|
|
428
|
+
|
|
429
|
+
# Propagate run_hooks_in_background setting to workflow and all its step agents/teams
|
|
430
|
+
workflow.propagate_run_hooks_in_background(self.run_hooks_in_background)
|
|
431
|
+
|
|
432
|
+
def _setup_tracing(self) -> None:
|
|
433
|
+
"""Set up OpenTelemetry tracing for this AgentOS.
|
|
434
|
+
|
|
435
|
+
Uses tracing_db if provided, otherwise falls back to the first available
|
|
436
|
+
database from agents/teams/workflows.
|
|
437
|
+
"""
|
|
438
|
+
# Use tracing_db if explicitly provided
|
|
439
|
+
if self.tracing_db is not None:
|
|
440
|
+
setup_tracing_for_os(db=self.tracing_db)
|
|
441
|
+
return
|
|
442
|
+
|
|
443
|
+
# Fall back to finding the first available database
|
|
444
|
+
db: Optional[Union[BaseDb, AsyncBaseDb]] = None
|
|
445
|
+
|
|
446
|
+
for agent in self.agents or []:
|
|
447
|
+
if agent.db:
|
|
448
|
+
db = agent.db
|
|
449
|
+
break
|
|
450
|
+
|
|
451
|
+
if db is None:
|
|
452
|
+
for team in self.teams or []:
|
|
453
|
+
if team.db:
|
|
454
|
+
db = team.db
|
|
455
|
+
break
|
|
456
|
+
|
|
457
|
+
if db is None:
|
|
458
|
+
for workflow in self.workflows or []:
|
|
459
|
+
if workflow.db:
|
|
460
|
+
db = workflow.db
|
|
461
|
+
break
|
|
462
|
+
|
|
463
|
+
if db is None:
|
|
464
|
+
log_warning(
|
|
465
|
+
"tracing=True but no database found. "
|
|
466
|
+
"Provide 'tracing_db' parameter or 'db' parameter to at least one agent/team/workflow."
|
|
467
|
+
)
|
|
468
|
+
return
|
|
469
|
+
|
|
470
|
+
setup_tracing_for_os(db=db)
|
|
471
|
+
|
|
472
|
+
def get_app(self) -> FastAPI:
|
|
473
|
+
if self.base_app:
|
|
474
|
+
fastapi_app = self.base_app
|
|
475
|
+
|
|
476
|
+
# Initialize MCP server if enabled
|
|
477
|
+
if self.enable_mcp_server:
|
|
478
|
+
from agno.os.mcp import get_mcp_server
|
|
479
|
+
|
|
480
|
+
self._mcp_app = get_mcp_server(self)
|
|
481
|
+
|
|
482
|
+
# Collect all lifespans that need to be combined
|
|
483
|
+
lifespans = []
|
|
484
|
+
|
|
485
|
+
# The user provided lifespan
|
|
486
|
+
if self.lifespan:
|
|
487
|
+
# Wrap the user lifespan with agent_os parameter
|
|
488
|
+
wrapped_lifespan = self._add_agent_os_to_lifespan_function(self.lifespan)
|
|
489
|
+
lifespans.append(wrapped_lifespan)
|
|
490
|
+
|
|
491
|
+
# The provided app's existing lifespan
|
|
492
|
+
if fastapi_app.router.lifespan_context:
|
|
493
|
+
lifespans.append(fastapi_app.router.lifespan_context)
|
|
494
|
+
|
|
495
|
+
# The MCP tools lifespan
|
|
496
|
+
if self.mcp_tools:
|
|
497
|
+
lifespans.append(partial(mcp_lifespan, mcp_tools=self.mcp_tools))
|
|
498
|
+
|
|
499
|
+
# The /mcp server lifespan
|
|
500
|
+
if self.enable_mcp_server and self._mcp_app:
|
|
501
|
+
lifespans.append(self._mcp_app.lifespan)
|
|
502
|
+
|
|
503
|
+
# The async database lifespan
|
|
504
|
+
lifespans.append(partial(db_lifespan, agent_os=self))
|
|
505
|
+
|
|
506
|
+
# Combine lifespans and set them in the app
|
|
507
|
+
if lifespans:
|
|
508
|
+
fastapi_app.router.lifespan_context = _combine_app_lifespans(lifespans)
|
|
509
|
+
|
|
510
|
+
else:
|
|
511
|
+
lifespans = []
|
|
512
|
+
|
|
513
|
+
# User provided lifespan
|
|
514
|
+
if self.lifespan:
|
|
515
|
+
lifespans.append(self._add_agent_os_to_lifespan_function(self.lifespan))
|
|
516
|
+
|
|
517
|
+
# MCP tools lifespan
|
|
518
|
+
if self.mcp_tools:
|
|
519
|
+
lifespans.append(partial(mcp_lifespan, mcp_tools=self.mcp_tools))
|
|
520
|
+
|
|
521
|
+
# MCP server lifespan
|
|
522
|
+
if self.enable_mcp_server:
|
|
523
|
+
from agno.os.mcp import get_mcp_server
|
|
524
|
+
|
|
525
|
+
self._mcp_app = get_mcp_server(self)
|
|
526
|
+
lifespans.append(self._mcp_app.lifespan)
|
|
527
|
+
|
|
528
|
+
# Async database initialization lifespan
|
|
529
|
+
lifespans.append(partial(db_lifespan, agent_os=self)) # type: ignore
|
|
530
|
+
|
|
531
|
+
final_lifespan = _combine_app_lifespans(lifespans) if lifespans else None
|
|
532
|
+
fastapi_app = self._make_app(lifespan=final_lifespan)
|
|
533
|
+
|
|
534
|
+
self._add_built_in_routes(app=fastapi_app)
|
|
535
|
+
|
|
536
|
+
self._auto_discover_databases()
|
|
537
|
+
self._auto_discover_knowledge_instances()
|
|
538
|
+
|
|
539
|
+
routers = [
|
|
540
|
+
get_session_router(dbs=self.dbs),
|
|
541
|
+
get_memory_router(dbs=self.dbs),
|
|
542
|
+
get_eval_router(dbs=self.dbs, agents=self.agents, teams=self.teams),
|
|
543
|
+
get_metrics_router(dbs=self.dbs),
|
|
544
|
+
get_knowledge_router(knowledge_instances=self.knowledge_instances),
|
|
545
|
+
get_traces_router(dbs=self.dbs),
|
|
546
|
+
]
|
|
547
|
+
|
|
548
|
+
for router in routers:
|
|
549
|
+
self._add_router(fastapi_app, router)
|
|
550
|
+
|
|
551
|
+
# Mount MCP if needed
|
|
552
|
+
if self.enable_mcp_server and self._mcp_app:
|
|
553
|
+
fastapi_app.mount("/", self._mcp_app)
|
|
554
|
+
|
|
555
|
+
if not self._app_set:
|
|
556
|
+
|
|
557
|
+
@fastapi_app.exception_handler(HTTPException)
|
|
558
|
+
async def http_exception_handler(_, exc: HTTPException) -> JSONResponse:
|
|
559
|
+
log_error(f"HTTP exception: {exc.status_code} {exc.detail}")
|
|
560
|
+
return JSONResponse(
|
|
561
|
+
status_code=exc.status_code,
|
|
562
|
+
content={"detail": str(exc.detail)},
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
@fastapi_app.exception_handler(Exception)
|
|
566
|
+
async def general_exception_handler(_: Request, exc: Exception) -> JSONResponse:
|
|
567
|
+
import traceback
|
|
568
|
+
|
|
569
|
+
log_error(f"Unhandled exception:\n{traceback.format_exc(limit=5)}")
|
|
570
|
+
|
|
571
|
+
return JSONResponse(
|
|
572
|
+
status_code=getattr(exc, "status_code", 500),
|
|
573
|
+
content={"detail": str(exc)},
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
# Update CORS middleware
|
|
577
|
+
update_cors_middleware(fastapi_app, self.cors_allowed_origins) # type: ignore
|
|
578
|
+
|
|
579
|
+
# Set agent_os_id and cors_allowed_origins on app state
|
|
580
|
+
# This allows middleware (like JWT) to access these values
|
|
581
|
+
fastapi_app.state.agent_os_id = self.id
|
|
582
|
+
fastapi_app.state.cors_allowed_origins = self.cors_allowed_origins
|
|
583
|
+
|
|
584
|
+
# Add JWT middleware if authorization is enabled
|
|
585
|
+
if self.authorization:
|
|
586
|
+
self._add_jwt_middleware(fastapi_app)
|
|
587
|
+
|
|
588
|
+
return fastapi_app
|
|
589
|
+
|
|
590
|
+
def _add_jwt_middleware(self, fastapi_app: FastAPI) -> None:
|
|
591
|
+
from agno.os.middleware.jwt import JWTMiddleware, JWTValidator
|
|
592
|
+
|
|
593
|
+
verify_audience = False
|
|
594
|
+
jwks_file = None
|
|
595
|
+
verification_keys = None
|
|
596
|
+
algorithm = "RS256"
|
|
597
|
+
|
|
598
|
+
if self.authorization_config:
|
|
599
|
+
algorithm = self.authorization_config.algorithm or "RS256"
|
|
600
|
+
verification_keys = self.authorization_config.verification_keys
|
|
601
|
+
jwks_file = self.authorization_config.jwks_file
|
|
602
|
+
verify_audience = self.authorization_config.verify_audience or False
|
|
603
|
+
|
|
604
|
+
log_info(f"Adding JWT middleware for authorization (algorithm: {algorithm})")
|
|
605
|
+
|
|
606
|
+
# Create validator and store on app.state for WebSocket access
|
|
607
|
+
jwt_validator = JWTValidator(
|
|
608
|
+
verification_keys=verification_keys,
|
|
609
|
+
jwks_file=jwks_file,
|
|
610
|
+
algorithm=algorithm,
|
|
611
|
+
)
|
|
612
|
+
fastapi_app.state.jwt_validator = jwt_validator
|
|
613
|
+
|
|
614
|
+
# Add middleware to stack
|
|
615
|
+
fastapi_app.add_middleware(
|
|
616
|
+
JWTMiddleware,
|
|
617
|
+
verification_keys=verification_keys,
|
|
618
|
+
jwks_file=jwks_file,
|
|
619
|
+
algorithm=algorithm,
|
|
620
|
+
authorization=self.authorization,
|
|
621
|
+
verify_audience=verify_audience,
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
def get_routes(self) -> List[Any]:
|
|
625
|
+
"""Retrieve all routes from the FastAPI app.
|
|
626
|
+
|
|
627
|
+
Returns:
|
|
628
|
+
List[Any]: List of routes included in the FastAPI app.
|
|
629
|
+
"""
|
|
630
|
+
app = self.get_app()
|
|
631
|
+
|
|
632
|
+
return app.routes
|
|
633
|
+
|
|
634
|
+
def _add_router(self, fastapi_app: FastAPI, router: APIRouter) -> None:
|
|
635
|
+
"""Add a router to the FastAPI app, avoiding route conflicts.
|
|
636
|
+
|
|
637
|
+
Args:
|
|
638
|
+
router: The APIRouter to add
|
|
639
|
+
"""
|
|
640
|
+
|
|
641
|
+
conflicts = find_conflicting_routes(fastapi_app, router)
|
|
642
|
+
conflicting_routes = [conflict["route"] for conflict in conflicts]
|
|
643
|
+
|
|
644
|
+
if conflicts and self._app_set:
|
|
645
|
+
if self.on_route_conflict == "preserve_base_app":
|
|
646
|
+
# Skip conflicting AgentOS routes, prefer user's existing routes
|
|
647
|
+
for conflict in conflicts:
|
|
648
|
+
methods_str = ", ".join(conflict["methods"]) # type: ignore
|
|
649
|
+
log_debug(
|
|
650
|
+
f"Skipping conflicting AgentOS route: {methods_str} {conflict['path']} - "
|
|
651
|
+
f"Using existing custom route instead"
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
# Create a new router without the conflicting routes
|
|
655
|
+
filtered_router = APIRouter()
|
|
656
|
+
for route in router.routes:
|
|
657
|
+
if route not in conflicting_routes:
|
|
658
|
+
filtered_router.routes.append(route)
|
|
659
|
+
|
|
660
|
+
# Use the filtered router if it has any routes left
|
|
661
|
+
if filtered_router.routes:
|
|
662
|
+
fastapi_app.include_router(filtered_router)
|
|
663
|
+
|
|
664
|
+
elif self.on_route_conflict == "preserve_agentos":
|
|
665
|
+
# Log warnings but still add all routes (AgentOS routes will override)
|
|
666
|
+
for conflict in conflicts:
|
|
667
|
+
methods_str = ", ".join(conflict["methods"]) # type: ignore
|
|
668
|
+
log_warning(
|
|
669
|
+
f"Route conflict detected: {methods_str} {conflict['path']} - "
|
|
670
|
+
f"AgentOS route will override existing custom route"
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
# Remove conflicting routes
|
|
674
|
+
for route in fastapi_app.routes:
|
|
675
|
+
for conflict in conflicts:
|
|
676
|
+
if isinstance(route, APIRoute):
|
|
677
|
+
if route.path == conflict["path"] and list(route.methods) == list(conflict["methods"]): # type: ignore
|
|
678
|
+
fastapi_app.routes.pop(fastapi_app.routes.index(route))
|
|
679
|
+
|
|
680
|
+
fastapi_app.include_router(router)
|
|
681
|
+
|
|
682
|
+
elif self.on_route_conflict == "error":
|
|
683
|
+
conflicting_paths = [conflict["path"] for conflict in conflicts]
|
|
684
|
+
raise ValueError(f"Route conflict detected: {conflicting_paths}")
|
|
685
|
+
|
|
686
|
+
else:
|
|
687
|
+
# No conflicts, add router normally
|
|
688
|
+
fastapi_app.include_router(router)
|
|
689
|
+
|
|
690
|
+
def _get_telemetry_data(self) -> Dict[str, Any]:
|
|
691
|
+
"""Get the telemetry data for the OS"""
|
|
692
|
+
return {
|
|
693
|
+
"agents": [agent.id for agent in self.agents] if self.agents else None,
|
|
694
|
+
"teams": [team.id for team in self.teams] if self.teams else None,
|
|
695
|
+
"workflows": [workflow.id for workflow in self.workflows] if self.workflows else None,
|
|
696
|
+
"interfaces": [interface.type for interface in self.interfaces] if self.interfaces else None,
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
def _auto_discover_databases(self) -> None:
|
|
700
|
+
"""Auto-discover and initialize the databases used by all contextual agents, teams and workflows."""
|
|
701
|
+
|
|
702
|
+
dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]] = {}
|
|
703
|
+
knowledge_dbs: Dict[
|
|
704
|
+
str, List[Union[BaseDb, AsyncBaseDb]]
|
|
705
|
+
] = {} # Track databases specifically used for knowledge
|
|
706
|
+
|
|
707
|
+
for agent in self.agents or []:
|
|
708
|
+
if agent.db:
|
|
709
|
+
self._register_db_with_validation(dbs, agent.db)
|
|
710
|
+
if agent.knowledge and agent.knowledge.contents_db:
|
|
711
|
+
self._register_db_with_validation(knowledge_dbs, agent.knowledge.contents_db)
|
|
712
|
+
|
|
713
|
+
for team in self.teams or []:
|
|
714
|
+
if team.db:
|
|
715
|
+
self._register_db_with_validation(dbs, team.db)
|
|
716
|
+
if team.knowledge and team.knowledge.contents_db:
|
|
717
|
+
self._register_db_with_validation(knowledge_dbs, team.knowledge.contents_db)
|
|
718
|
+
|
|
719
|
+
for workflow in self.workflows or []:
|
|
720
|
+
if workflow.db:
|
|
721
|
+
self._register_db_with_validation(dbs, workflow.db)
|
|
722
|
+
|
|
723
|
+
for knowledge_base in self.knowledge or []:
|
|
724
|
+
if knowledge_base.contents_db:
|
|
725
|
+
self._register_db_with_validation(knowledge_dbs, knowledge_base.contents_db)
|
|
726
|
+
|
|
727
|
+
for interface in self.interfaces or []:
|
|
728
|
+
if interface.agent and interface.agent.db:
|
|
729
|
+
self._register_db_with_validation(dbs, interface.agent.db)
|
|
730
|
+
elif interface.team and interface.team.db:
|
|
731
|
+
self._register_db_with_validation(dbs, interface.team.db)
|
|
732
|
+
|
|
733
|
+
# Register tracing_db if provided (for traces reading)
|
|
734
|
+
if self.tracing_db is not None:
|
|
735
|
+
self._register_db_with_validation(dbs, self.tracing_db)
|
|
736
|
+
|
|
737
|
+
self.dbs = dbs
|
|
738
|
+
self.knowledge_dbs = knowledge_dbs
|
|
739
|
+
|
|
740
|
+
# Initialize all discovered databases
|
|
741
|
+
if self.auto_provision_dbs:
|
|
742
|
+
self._pending_async_db_init = True
|
|
743
|
+
|
|
744
|
+
def _initialize_sync_databases(self) -> None:
|
|
745
|
+
"""Initialize sync databases."""
|
|
746
|
+
from itertools import chain
|
|
747
|
+
|
|
748
|
+
unique_dbs = list(
|
|
749
|
+
{
|
|
750
|
+
id(db): db
|
|
751
|
+
for db in chain(
|
|
752
|
+
chain.from_iterable(self.dbs.values()), chain.from_iterable(self.knowledge_dbs.values())
|
|
753
|
+
)
|
|
754
|
+
}.values()
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
for db in unique_dbs:
|
|
758
|
+
if isinstance(db, AsyncBaseDb):
|
|
759
|
+
continue # Skip async dbs
|
|
760
|
+
|
|
761
|
+
try:
|
|
762
|
+
if hasattr(db, "_create_all_tables") and callable(db._create_all_tables):
|
|
763
|
+
db._create_all_tables()
|
|
764
|
+
except Exception as e:
|
|
765
|
+
log_warning(f"Failed to initialize {db.__class__.__name__} (id: {db.id}): {e}")
|
|
766
|
+
|
|
767
|
+
async def _initialize_async_databases(self) -> None:
|
|
768
|
+
"""Initialize async databases."""
|
|
769
|
+
|
|
770
|
+
from itertools import chain
|
|
771
|
+
|
|
772
|
+
unique_dbs = list(
|
|
773
|
+
{
|
|
774
|
+
id(db): db
|
|
775
|
+
for db in chain(
|
|
776
|
+
chain.from_iterable(self.dbs.values()), chain.from_iterable(self.knowledge_dbs.values())
|
|
777
|
+
)
|
|
778
|
+
}.values()
|
|
779
|
+
)
|
|
780
|
+
|
|
781
|
+
for db in unique_dbs:
|
|
782
|
+
if not isinstance(db, AsyncBaseDb):
|
|
783
|
+
continue # Skip sync dbs
|
|
784
|
+
|
|
785
|
+
try:
|
|
786
|
+
if hasattr(db, "_create_all_tables") and callable(db._create_all_tables):
|
|
787
|
+
await db._create_all_tables()
|
|
788
|
+
except Exception as e:
|
|
789
|
+
log_warning(f"Failed to initialize async {db.__class__.__name__} (id: {db.id}): {e}")
|
|
790
|
+
|
|
791
|
+
def _get_db_table_names(self, db: BaseDb) -> Dict[str, str]:
|
|
792
|
+
"""Get the table names for a database"""
|
|
793
|
+
table_names = {
|
|
794
|
+
"session_table_name": db.session_table_name,
|
|
795
|
+
"culture_table_name": db.culture_table_name,
|
|
796
|
+
"memory_table_name": db.memory_table_name,
|
|
797
|
+
"metrics_table_name": db.metrics_table_name,
|
|
798
|
+
"evals_table_name": db.eval_table_name,
|
|
799
|
+
"knowledge_table_name": db.knowledge_table_name,
|
|
800
|
+
}
|
|
801
|
+
return {k: v for k, v in table_names.items() if v is not None}
|
|
802
|
+
|
|
803
|
+
def _register_db_with_validation(
|
|
804
|
+
self, registered_dbs: Dict[str, List[Union[BaseDb, AsyncBaseDb]]], db: Union[BaseDb, AsyncBaseDb]
|
|
805
|
+
) -> None:
|
|
806
|
+
"""Register a database in the contextual OS after validating it is not conflicting with registered databases"""
|
|
807
|
+
if db.id in registered_dbs:
|
|
808
|
+
registered_dbs[db.id].append(db)
|
|
809
|
+
else:
|
|
810
|
+
registered_dbs[db.id] = [db]
|
|
811
|
+
|
|
812
|
+
def _auto_discover_knowledge_instances(self) -> None:
|
|
813
|
+
"""Auto-discover the knowledge instances used by all contextual agents, teams and workflows."""
|
|
814
|
+
seen_ids = set()
|
|
815
|
+
knowledge_instances: List[Knowledge] = []
|
|
816
|
+
|
|
817
|
+
def _add_knowledge_if_not_duplicate(knowledge: "Knowledge") -> None:
|
|
818
|
+
"""Add knowledge instance if it's not already in the list (by object identity or db_id)."""
|
|
819
|
+
# Use database ID if available, otherwise use object ID as fallback
|
|
820
|
+
if not knowledge.contents_db:
|
|
821
|
+
return
|
|
822
|
+
if knowledge.contents_db.id in seen_ids:
|
|
823
|
+
return
|
|
824
|
+
seen_ids.add(knowledge.contents_db.id)
|
|
825
|
+
knowledge_instances.append(knowledge)
|
|
826
|
+
|
|
827
|
+
for agent in self.agents or []:
|
|
828
|
+
if agent.knowledge:
|
|
829
|
+
_add_knowledge_if_not_duplicate(agent.knowledge)
|
|
830
|
+
|
|
831
|
+
for team in self.teams or []:
|
|
832
|
+
if team.knowledge:
|
|
833
|
+
_add_knowledge_if_not_duplicate(team.knowledge)
|
|
834
|
+
|
|
835
|
+
for knowledge_base in self.knowledge or []:
|
|
836
|
+
_add_knowledge_if_not_duplicate(knowledge_base)
|
|
837
|
+
|
|
838
|
+
self.knowledge_instances = knowledge_instances
|
|
839
|
+
|
|
840
|
+
def _get_session_config(self) -> SessionConfig:
|
|
841
|
+
session_config = self.config.session if self.config and self.config.session else SessionConfig()
|
|
842
|
+
|
|
843
|
+
if session_config.dbs is None:
|
|
844
|
+
session_config.dbs = []
|
|
845
|
+
|
|
846
|
+
dbs_with_specific_config = [db.db_id for db in session_config.dbs]
|
|
847
|
+
for db_id, dbs in self.dbs.items():
|
|
848
|
+
if db_id not in dbs_with_specific_config:
|
|
849
|
+
# Collect unique table names from all databases with the same id
|
|
850
|
+
unique_tables = list(set(db.session_table_name for db in dbs))
|
|
851
|
+
session_config.dbs.append(
|
|
852
|
+
DatabaseConfig(
|
|
853
|
+
db_id=db_id,
|
|
854
|
+
domain_config=SessionDomainConfig(display_name=db_id),
|
|
855
|
+
tables=unique_tables,
|
|
856
|
+
)
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
return session_config
|
|
860
|
+
|
|
861
|
+
def _get_memory_config(self) -> MemoryConfig:
|
|
862
|
+
memory_config = self.config.memory if self.config and self.config.memory else MemoryConfig()
|
|
863
|
+
|
|
864
|
+
if memory_config.dbs is None:
|
|
865
|
+
memory_config.dbs = []
|
|
866
|
+
|
|
867
|
+
dbs_with_specific_config = [db.db_id for db in memory_config.dbs]
|
|
868
|
+
|
|
869
|
+
for db_id, dbs in self.dbs.items():
|
|
870
|
+
if db_id not in dbs_with_specific_config:
|
|
871
|
+
# Collect unique table names from all databases with the same id
|
|
872
|
+
unique_tables = list(set(db.memory_table_name for db in dbs))
|
|
873
|
+
memory_config.dbs.append(
|
|
874
|
+
DatabaseConfig(
|
|
875
|
+
db_id=db_id,
|
|
876
|
+
domain_config=MemoryDomainConfig(display_name=db_id),
|
|
877
|
+
tables=unique_tables,
|
|
878
|
+
)
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
return memory_config
|
|
882
|
+
|
|
883
|
+
def _get_knowledge_config(self) -> KnowledgeConfig:
|
|
884
|
+
knowledge_config = self.config.knowledge if self.config and self.config.knowledge else KnowledgeConfig()
|
|
885
|
+
|
|
886
|
+
if knowledge_config.dbs is None:
|
|
887
|
+
knowledge_config.dbs = []
|
|
888
|
+
|
|
889
|
+
dbs_with_specific_config = [db.db_id for db in knowledge_config.dbs]
|
|
890
|
+
|
|
891
|
+
# Only add databases that are actually used for knowledge contents
|
|
892
|
+
for db_id in self.knowledge_dbs.keys():
|
|
893
|
+
if db_id not in dbs_with_specific_config:
|
|
894
|
+
knowledge_config.dbs.append(
|
|
895
|
+
DatabaseConfig(
|
|
896
|
+
db_id=db_id,
|
|
897
|
+
domain_config=KnowledgeDomainConfig(display_name=db_id),
|
|
898
|
+
)
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
return knowledge_config
|
|
902
|
+
|
|
903
|
+
def _get_metrics_config(self) -> MetricsConfig:
|
|
904
|
+
metrics_config = self.config.metrics if self.config and self.config.metrics else MetricsConfig()
|
|
905
|
+
|
|
906
|
+
if metrics_config.dbs is None:
|
|
907
|
+
metrics_config.dbs = []
|
|
908
|
+
|
|
909
|
+
dbs_with_specific_config = [db.db_id for db in metrics_config.dbs]
|
|
910
|
+
|
|
911
|
+
for db_id, dbs in self.dbs.items():
|
|
912
|
+
if db_id not in dbs_with_specific_config:
|
|
913
|
+
# Collect unique table names from all databases with the same id
|
|
914
|
+
unique_tables = list(set(db.metrics_table_name for db in dbs))
|
|
915
|
+
metrics_config.dbs.append(
|
|
916
|
+
DatabaseConfig(
|
|
917
|
+
db_id=db_id,
|
|
918
|
+
domain_config=MetricsDomainConfig(display_name=db_id),
|
|
919
|
+
tables=unique_tables,
|
|
920
|
+
)
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
return metrics_config
|
|
924
|
+
|
|
925
|
+
def _get_evals_config(self) -> EvalsConfig:
|
|
926
|
+
evals_config = self.config.evals if self.config and self.config.evals else EvalsConfig()
|
|
927
|
+
|
|
928
|
+
if evals_config.dbs is None:
|
|
929
|
+
evals_config.dbs = []
|
|
930
|
+
|
|
931
|
+
dbs_with_specific_config = [db.db_id for db in evals_config.dbs]
|
|
932
|
+
|
|
933
|
+
for db_id, dbs in self.dbs.items():
|
|
934
|
+
if db_id not in dbs_with_specific_config:
|
|
935
|
+
# Collect unique table names from all databases with the same id
|
|
936
|
+
unique_tables = list(set(db.eval_table_name for db in dbs))
|
|
937
|
+
evals_config.dbs.append(
|
|
938
|
+
DatabaseConfig(
|
|
939
|
+
db_id=db_id,
|
|
940
|
+
domain_config=EvalsDomainConfig(display_name=db_id),
|
|
941
|
+
tables=unique_tables,
|
|
942
|
+
)
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
return evals_config
|
|
946
|
+
|
|
947
|
+
def _get_traces_config(self) -> TracesConfig:
|
|
948
|
+
traces_config = self.config.traces if self.config and self.config.traces else TracesConfig()
|
|
949
|
+
|
|
950
|
+
if traces_config.dbs is None:
|
|
951
|
+
traces_config.dbs = []
|
|
952
|
+
|
|
953
|
+
dbs_with_specific_config = [db.db_id for db in traces_config.dbs]
|
|
954
|
+
|
|
955
|
+
# If tracing_db is explicitly set, only use that database for traces
|
|
956
|
+
if self.tracing_db is not None:
|
|
957
|
+
if self.tracing_db.id not in dbs_with_specific_config:
|
|
958
|
+
traces_config.dbs.append(
|
|
959
|
+
DatabaseConfig(
|
|
960
|
+
db_id=self.tracing_db.id,
|
|
961
|
+
domain_config=TracesDomainConfig(display_name=self.tracing_db.id),
|
|
962
|
+
)
|
|
963
|
+
)
|
|
964
|
+
else:
|
|
965
|
+
# Fall back to all discovered databases
|
|
966
|
+
for db_id in self.dbs.keys():
|
|
967
|
+
if db_id not in dbs_with_specific_config:
|
|
968
|
+
traces_config.dbs.append(
|
|
969
|
+
DatabaseConfig(
|
|
970
|
+
db_id=db_id,
|
|
971
|
+
domain_config=TracesDomainConfig(display_name=db_id),
|
|
972
|
+
)
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
return traces_config
|
|
976
|
+
|
|
977
|
+
def serve(
|
|
978
|
+
self,
|
|
979
|
+
app: Union[str, FastAPI],
|
|
980
|
+
*,
|
|
981
|
+
host: str = "localhost",
|
|
982
|
+
port: int = 7777,
|
|
983
|
+
reload: bool = False,
|
|
984
|
+
workers: Optional[int] = None,
|
|
985
|
+
access_log: bool = False,
|
|
986
|
+
**kwargs,
|
|
987
|
+
):
|
|
988
|
+
import uvicorn
|
|
989
|
+
|
|
990
|
+
if getenv("AGNO_API_RUNTIME", "").lower() == "stg":
|
|
991
|
+
public_endpoint = "https://os-stg.agno.com/"
|
|
992
|
+
else:
|
|
993
|
+
public_endpoint = "https://os.agno.com/"
|
|
994
|
+
|
|
995
|
+
# Create a terminal panel to announce OS initialization and provide useful info
|
|
996
|
+
from rich.align import Align
|
|
997
|
+
from rich.console import Console, Group
|
|
998
|
+
|
|
999
|
+
panel_group = [
|
|
1000
|
+
Align.center(f"[bold cyan]{public_endpoint}[/bold cyan]"),
|
|
1001
|
+
Align.center(f"\n\n[bold dark_orange]OS running on:[/bold dark_orange] http://{host}:{port}"),
|
|
1002
|
+
]
|
|
1003
|
+
if bool(self.settings.os_security_key):
|
|
1004
|
+
panel_group.append(Align.center("\n\n[bold chartreuse3]:lock: Security Enabled[/bold chartreuse3]"))
|
|
1005
|
+
|
|
1006
|
+
console = Console()
|
|
1007
|
+
console.print(
|
|
1008
|
+
Panel(
|
|
1009
|
+
Group(*panel_group),
|
|
1010
|
+
title="AgentOS",
|
|
1011
|
+
expand=False,
|
|
1012
|
+
border_style="dark_orange",
|
|
1013
|
+
box=box.DOUBLE_EDGE,
|
|
1014
|
+
padding=(2, 2),
|
|
1015
|
+
)
|
|
1016
|
+
)
|
|
1017
|
+
|
|
1018
|
+
uvicorn.run(
|
|
1019
|
+
app=app,
|
|
1020
|
+
host=host,
|
|
1021
|
+
port=port,
|
|
1022
|
+
reload=reload,
|
|
1023
|
+
workers=workers,
|
|
1024
|
+
access_log=access_log,
|
|
1025
|
+
lifespan="on",
|
|
1026
|
+
**kwargs,
|
|
1027
|
+
)
|