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/hooks/decorator.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from typing import Any, Callable, TypeVar, Union, overload
|
|
3
|
+
|
|
4
|
+
# Type variable for better type hints
|
|
5
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
6
|
+
|
|
7
|
+
# Attribute name used to mark hooks for background execution
|
|
8
|
+
HOOK_RUN_IN_BACKGROUND_ATTR = "_agno_run_in_background"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _is_async_function(func: Callable) -> bool:
|
|
12
|
+
"""
|
|
13
|
+
Check if a function is async, even when wrapped by decorators like @staticmethod.
|
|
14
|
+
Traverses the full wrapper chain to find the original function.
|
|
15
|
+
"""
|
|
16
|
+
from inspect import iscoroutinefunction, unwrap
|
|
17
|
+
|
|
18
|
+
# First, try the standard inspect function on the wrapper
|
|
19
|
+
if iscoroutinefunction(func):
|
|
20
|
+
return True
|
|
21
|
+
|
|
22
|
+
# Use unwrap to traverse the full __wrapped__ chain to the original function
|
|
23
|
+
try:
|
|
24
|
+
original_func = unwrap(func)
|
|
25
|
+
if original_func is not func and iscoroutinefunction(original_func):
|
|
26
|
+
return True
|
|
27
|
+
except ValueError:
|
|
28
|
+
# unwrap raises ValueError if it hits a cycle
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
# Check if the function has CO_COROUTINE flag in its code object
|
|
32
|
+
try:
|
|
33
|
+
if hasattr(func, "__code__") and func.__code__.co_flags & 0x80: # CO_COROUTINE flag
|
|
34
|
+
return True
|
|
35
|
+
except (AttributeError, TypeError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@overload
|
|
42
|
+
def hook() -> Callable[[F], F]: ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
def hook(
|
|
47
|
+
*,
|
|
48
|
+
run_in_background: bool = False,
|
|
49
|
+
) -> Callable[[F], F]: ...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@overload
|
|
53
|
+
def hook(func: F) -> F: ...
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def hook(*args, **kwargs) -> Union[F, Callable[[F], F]]:
|
|
57
|
+
"""Decorator to configure hook behavior.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
run_in_background: If True, this hook will be scheduled as a FastAPI background task
|
|
61
|
+
when background_tasks is available, regardless of the agent/team's
|
|
62
|
+
run_hooks_in_background setting. This allows per-hook control over
|
|
63
|
+
background execution. This is only use-able when running with AgentOS.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Union[F, Callable[[F], F]]: Decorated function or decorator
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
@hook
|
|
70
|
+
def my_hook(run_output, agent):
|
|
71
|
+
# This runs normally (blocking)
|
|
72
|
+
process_output(run_output.content)
|
|
73
|
+
|
|
74
|
+
@hook()
|
|
75
|
+
def another_hook(run_output, agent):
|
|
76
|
+
# Same as above - runs normally
|
|
77
|
+
process_output(run_output.content)
|
|
78
|
+
|
|
79
|
+
@hook(run_in_background=True)
|
|
80
|
+
def my_background_hook(run_output, agent):
|
|
81
|
+
# This will run in the background when background_tasks is available
|
|
82
|
+
send_notification(run_output.content)
|
|
83
|
+
|
|
84
|
+
@hook(run_in_background=True)
|
|
85
|
+
async def my_async_background_hook(run_output, agent):
|
|
86
|
+
# Async hooks also supported
|
|
87
|
+
await send_async_notification(run_output.content)
|
|
88
|
+
|
|
89
|
+
agent = Agent(
|
|
90
|
+
model=OpenAIChat(id="gpt-4o"),
|
|
91
|
+
post_hooks=[my_hook, my_background_hook],
|
|
92
|
+
)
|
|
93
|
+
"""
|
|
94
|
+
# Valid kwargs for the hook decorator
|
|
95
|
+
VALID_KWARGS = frozenset({"run_in_background"})
|
|
96
|
+
|
|
97
|
+
# Validate kwargs
|
|
98
|
+
invalid_kwargs = set(kwargs.keys()) - VALID_KWARGS
|
|
99
|
+
if invalid_kwargs:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
f"Invalid hook configuration arguments: {invalid_kwargs}. Valid arguments are: {sorted(VALID_KWARGS)}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def decorator(func: F) -> F:
|
|
105
|
+
run_in_background = kwargs.get("run_in_background", False)
|
|
106
|
+
|
|
107
|
+
# Preserve existing hook attributes from previously applied decorators
|
|
108
|
+
# Use OR logic: if any decorator sets run_in_background=True, it stays True
|
|
109
|
+
existing_run_in_background = should_run_in_background(func)
|
|
110
|
+
final_run_in_background = run_in_background or existing_run_in_background
|
|
111
|
+
|
|
112
|
+
@wraps(func)
|
|
113
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
114
|
+
return func(*args, **kwargs)
|
|
115
|
+
|
|
116
|
+
@wraps(func)
|
|
117
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
118
|
+
return await func(*args, **kwargs)
|
|
119
|
+
|
|
120
|
+
# Choose appropriate wrapper based on function type
|
|
121
|
+
if _is_async_function(func):
|
|
122
|
+
wrapper = async_wrapper
|
|
123
|
+
else:
|
|
124
|
+
wrapper = sync_wrapper
|
|
125
|
+
|
|
126
|
+
# Set the background execution attribute (combined from all decorators)
|
|
127
|
+
setattr(wrapper, HOOK_RUN_IN_BACKGROUND_ATTR, final_run_in_background)
|
|
128
|
+
|
|
129
|
+
return wrapper # type: ignore
|
|
130
|
+
|
|
131
|
+
# Handle both @hook and @hook() cases
|
|
132
|
+
if len(args) == 1 and callable(args[0]) and not kwargs:
|
|
133
|
+
return decorator(args[0])
|
|
134
|
+
|
|
135
|
+
return decorator
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def should_run_in_background(hook_func: Callable) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Check if a hook function is marked to run in background.
|
|
141
|
+
Traverses the wrapper chain to find the attribute when multiple decorators are stacked.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
hook_func: The hook function to check
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
True if the hook is decorated with @hook(run_in_background=True)
|
|
148
|
+
"""
|
|
149
|
+
# Check the function directly first
|
|
150
|
+
if hasattr(hook_func, HOOK_RUN_IN_BACKGROUND_ATTR):
|
|
151
|
+
return getattr(hook_func, HOOK_RUN_IN_BACKGROUND_ATTR)
|
|
152
|
+
|
|
153
|
+
# Traverse the wrapper chain to find the attribute
|
|
154
|
+
current = hook_func
|
|
155
|
+
seen: set[int] = set()
|
|
156
|
+
while hasattr(current, "__wrapped__"):
|
|
157
|
+
if id(current) in seen:
|
|
158
|
+
break
|
|
159
|
+
seen.add(id(current))
|
|
160
|
+
current = current.__wrapped__
|
|
161
|
+
if hasattr(current, HOOK_RUN_IN_BACKGROUND_ATTR):
|
|
162
|
+
return getattr(current, HOOK_RUN_IN_BACKGROUND_ATTR)
|
|
163
|
+
|
|
164
|
+
return False
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from os import getenv
|
|
2
|
+
from textwrap import dedent
|
|
3
|
+
from typing import Optional, Union
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from agno.agent.agent import Agent, RunOutput
|
|
8
|
+
from agno.media import Audio, File, Image, Video
|
|
9
|
+
from agno.team.team import Team, TeamRunOutput
|
|
10
|
+
from agno.utils.log import log_info, log_warning
|
|
11
|
+
from agno.utils.message import get_text_from_message
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import discord
|
|
15
|
+
|
|
16
|
+
except (ImportError, ModuleNotFoundError):
|
|
17
|
+
raise ImportError("`discord.py` not installed. Please install using `pip install discord.py`")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RequiresConfirmationView(discord.ui.View):
|
|
21
|
+
def __init__(self):
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.value = None
|
|
24
|
+
|
|
25
|
+
@discord.ui.button(label="Confirm", style=discord.ButtonStyle.primary)
|
|
26
|
+
async def confirm(
|
|
27
|
+
self,
|
|
28
|
+
interaction: discord.Interaction,
|
|
29
|
+
button: discord.ui.Button,
|
|
30
|
+
):
|
|
31
|
+
self.value = True
|
|
32
|
+
button.disabled = True
|
|
33
|
+
await interaction.response.edit_message(view=self)
|
|
34
|
+
self.clear_items()
|
|
35
|
+
self.stop()
|
|
36
|
+
|
|
37
|
+
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.secondary)
|
|
38
|
+
async def cancel(
|
|
39
|
+
self,
|
|
40
|
+
interaction: discord.Interaction,
|
|
41
|
+
button: discord.ui.Button,
|
|
42
|
+
):
|
|
43
|
+
self.value = False
|
|
44
|
+
button.disabled = True
|
|
45
|
+
await interaction.response.edit_message(view=self)
|
|
46
|
+
self.clear_items()
|
|
47
|
+
self.stop()
|
|
48
|
+
|
|
49
|
+
async def on_timeout(self):
|
|
50
|
+
log_warning("Agent Timeout Error")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DiscordClient:
|
|
54
|
+
def __init__(
|
|
55
|
+
self, agent: Optional[Agent] = None, team: Optional[Team] = None, client: Optional[discord.Client] = None
|
|
56
|
+
):
|
|
57
|
+
self.agent = agent
|
|
58
|
+
self.team = team
|
|
59
|
+
if client is None:
|
|
60
|
+
self.intents = discord.Intents.all()
|
|
61
|
+
self.client = discord.Client(intents=self.intents)
|
|
62
|
+
else:
|
|
63
|
+
self.client = client
|
|
64
|
+
self._setup_events()
|
|
65
|
+
|
|
66
|
+
def _setup_events(self):
|
|
67
|
+
@self.client.event
|
|
68
|
+
async def on_message(message):
|
|
69
|
+
if message.author == self.client.user:
|
|
70
|
+
log_info(f"sent {message.content}")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
message_image = None
|
|
74
|
+
message_video = None
|
|
75
|
+
message_audio = None
|
|
76
|
+
message_file = None
|
|
77
|
+
media_url = None
|
|
78
|
+
message_text = message.content
|
|
79
|
+
message_url = message.jump_url
|
|
80
|
+
message_user = message.author.name
|
|
81
|
+
message_user_id = message.author.id
|
|
82
|
+
|
|
83
|
+
if message.attachments:
|
|
84
|
+
media = message.attachments[0]
|
|
85
|
+
media_type = media.content_type
|
|
86
|
+
media_url = media.url
|
|
87
|
+
if media_type.startswith("image/"):
|
|
88
|
+
message_image = media_url
|
|
89
|
+
elif media_type.startswith("video/"):
|
|
90
|
+
req = requests.get(media_url)
|
|
91
|
+
video = req.content
|
|
92
|
+
message_video = video
|
|
93
|
+
elif media_type.startswith("application/"):
|
|
94
|
+
req = requests.get(media_url)
|
|
95
|
+
document = req.content
|
|
96
|
+
message_file = document
|
|
97
|
+
elif media_type.startswith("audio/"):
|
|
98
|
+
message_audio = media_url
|
|
99
|
+
|
|
100
|
+
log_info(f"processing message:{message_text} \n with media: {media_url} \n url:{message_url}")
|
|
101
|
+
if isinstance(message.channel, discord.Thread):
|
|
102
|
+
thread = message.channel
|
|
103
|
+
elif isinstance(message.channel, discord.channel.DMChannel):
|
|
104
|
+
thread = message.channel # type: ignore
|
|
105
|
+
elif isinstance(message.channel, discord.TextChannel):
|
|
106
|
+
thread = await message.create_thread(name=f"{message_user}'s thread")
|
|
107
|
+
else:
|
|
108
|
+
log_info(f"received {message.content} but not in a supported channel")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
async with thread.typing():
|
|
112
|
+
# TODO Unhappy with the duplication here but it keeps MyPy from complaining
|
|
113
|
+
additional_context = dedent(f"""
|
|
114
|
+
Discord username: {message_user}
|
|
115
|
+
Discord userid: {message_user_id}
|
|
116
|
+
Discord url: {message_url}
|
|
117
|
+
""")
|
|
118
|
+
if self.agent:
|
|
119
|
+
self.agent.additional_context = additional_context
|
|
120
|
+
agent_response: RunOutput = await self.agent.arun(
|
|
121
|
+
input=message_text,
|
|
122
|
+
user_id=message_user_id,
|
|
123
|
+
session_id=str(thread.id),
|
|
124
|
+
images=[Image(url=message_image)] if message_image else None,
|
|
125
|
+
videos=[Video(content=message_video)] if message_video else None,
|
|
126
|
+
audio=[Audio(url=message_audio)] if message_audio else None,
|
|
127
|
+
files=[File(content=message_file)] if message_file else None,
|
|
128
|
+
)
|
|
129
|
+
await self._handle_response_in_thread(agent_response, thread)
|
|
130
|
+
elif self.team:
|
|
131
|
+
self.team.additional_context = additional_context
|
|
132
|
+
team_response: TeamRunOutput = await self.team.arun(
|
|
133
|
+
input=message_text,
|
|
134
|
+
user_id=message_user_id,
|
|
135
|
+
session_id=str(thread.id),
|
|
136
|
+
images=[Image(url=message_image)] if message_image else None,
|
|
137
|
+
videos=[Video(content=message_video)] if message_video else None,
|
|
138
|
+
audio=[Audio(url=message_audio)] if message_audio else None,
|
|
139
|
+
files=[File(content=message_file)] if message_file else None,
|
|
140
|
+
)
|
|
141
|
+
await self._handle_response_in_thread(team_response, thread)
|
|
142
|
+
|
|
143
|
+
async def handle_hitl(
|
|
144
|
+
self, run_response: RunOutput, thread: Union[discord.Thread, discord.TextChannel]
|
|
145
|
+
) -> RunOutput:
|
|
146
|
+
"""Handles optional Human-In-The-Loop interaction."""
|
|
147
|
+
if run_response.is_paused:
|
|
148
|
+
for tool in run_response.tools_requiring_confirmation:
|
|
149
|
+
view = RequiresConfirmationView()
|
|
150
|
+
await thread.send(f"Tool requiring confirmation: {tool.tool_name}", view=view)
|
|
151
|
+
await view.wait()
|
|
152
|
+
tool.confirmed = view.value if view.value is not None else False
|
|
153
|
+
|
|
154
|
+
if self.agent:
|
|
155
|
+
run_response = await self.agent.acontinue_run(
|
|
156
|
+
run_response=run_response,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return run_response
|
|
160
|
+
|
|
161
|
+
async def _handle_response_in_thread(
|
|
162
|
+
self, response: Union[RunOutput, TeamRunOutput], thread: Union[discord.TextChannel, discord.Thread]
|
|
163
|
+
):
|
|
164
|
+
if isinstance(response, RunOutput):
|
|
165
|
+
response = await self.handle_hitl(response, thread)
|
|
166
|
+
|
|
167
|
+
if response.reasoning_content:
|
|
168
|
+
await self._send_discord_messages(
|
|
169
|
+
thread=thread, message=f"Reasoning: \n{response.reasoning_content}", italics=True
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Handle structured outputs properly
|
|
173
|
+
content_message = get_text_from_message(response.content) if response.content is not None else ""
|
|
174
|
+
|
|
175
|
+
await self._send_discord_messages(thread=thread, message=content_message)
|
|
176
|
+
|
|
177
|
+
async def _send_discord_messages(self, thread: discord.channel, message: str, italics: bool = False): # type: ignore
|
|
178
|
+
if len(message) < 1500:
|
|
179
|
+
if italics:
|
|
180
|
+
formatted_message = "\n".join([f"_{line}_" for line in message.split("\n")])
|
|
181
|
+
await thread.send(formatted_message) # type: ignore
|
|
182
|
+
else:
|
|
183
|
+
await thread.send(message) # type: ignore
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
message_batches = [message[i : i + 1500] for i in range(0, len(message), 1500)]
|
|
187
|
+
|
|
188
|
+
for i, batch in enumerate(message_batches, 1):
|
|
189
|
+
batch_message = f"[{i}/{len(message_batches)}] {batch}"
|
|
190
|
+
if italics:
|
|
191
|
+
formatted_batch = "\n".join([f"_{line}_" for line in batch_message.split("\n")])
|
|
192
|
+
await thread.send(formatted_batch) # type: ignore
|
|
193
|
+
else:
|
|
194
|
+
await thread.send(batch_message) # type: ignore
|
|
195
|
+
|
|
196
|
+
def serve(self):
|
|
197
|
+
try:
|
|
198
|
+
token = getenv("DISCORD_BOT_TOKEN")
|
|
199
|
+
if not token:
|
|
200
|
+
raise ValueError("DISCORD_BOT_TOKEN NOT SET")
|
|
201
|
+
return self.client.run(token)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
raise ValueError(f"Failed to run Discord client: {str(e)}")
|
agno/knowledge/__init__.py
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
1
|
+
from typing import List, Optional, Union
|
|
2
2
|
|
|
3
|
-
from agno.
|
|
4
|
-
from agno.document.
|
|
3
|
+
from agno.knowledge.chunking.strategy import ChunkingStrategy
|
|
4
|
+
from agno.knowledge.document.base import Document
|
|
5
5
|
from agno.models.base import Model
|
|
6
6
|
from agno.models.defaults import DEFAULT_OPENAI_MODEL_ID
|
|
7
7
|
from agno.models.message import Message
|
|
8
|
-
from agno.models.
|
|
8
|
+
from agno.models.utils import get_model
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class AgenticChunking(ChunkingStrategy):
|
|
12
12
|
"""Chunking strategy that uses an LLM to determine natural breakpoints in the text"""
|
|
13
13
|
|
|
14
|
-
def __init__(self, model: Optional[Model] = None, max_chunk_size: int = 5000):
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
def __init__(self, model: Optional[Union[Model, str]] = None, max_chunk_size: int = 5000):
|
|
15
|
+
# Convert model string to Model instance
|
|
16
|
+
model = get_model(model)
|
|
17
|
+
if model is None:
|
|
18
|
+
try:
|
|
19
|
+
from agno.models.openai import OpenAIChat
|
|
20
|
+
except Exception:
|
|
21
|
+
raise ValueError("`openai` isn't installed. Please install it with `pip install openai`")
|
|
22
|
+
model = OpenAIChat(DEFAULT_OPENAI_MODEL_ID)
|
|
23
|
+
self.chunk_size = max_chunk_size
|
|
24
|
+
self.model = model
|
|
17
25
|
|
|
18
26
|
def chunk(self, document: Document) -> List[Document]:
|
|
19
27
|
"""Split text into chunks using LLM to determine natural breakpoints based on context"""
|
|
20
|
-
if len(document.content) <= self.
|
|
28
|
+
if len(document.content) <= self.chunk_size:
|
|
21
29
|
return [document]
|
|
22
30
|
|
|
23
31
|
chunks: List[Document] = []
|
|
@@ -26,22 +34,22 @@ class AgenticChunking(ChunkingStrategy):
|
|
|
26
34
|
chunk_number = 1
|
|
27
35
|
|
|
28
36
|
while remaining_text:
|
|
29
|
-
# Ask model to find a good breakpoint within
|
|
30
|
-
prompt = f"""Analyze this text and determine a natural breakpoint within the first {self.
|
|
37
|
+
# Ask model to find a good breakpoint within chunk_size
|
|
38
|
+
prompt = f"""Analyze this text and determine a natural breakpoint within the first {self.chunk_size} characters.
|
|
31
39
|
Consider semantic completeness, paragraph boundaries, and topic transitions.
|
|
32
40
|
Return only the character position number of where to break the text:
|
|
33
41
|
|
|
34
|
-
{remaining_text[: self.
|
|
42
|
+
{remaining_text[: self.chunk_size]}"""
|
|
35
43
|
|
|
36
44
|
try:
|
|
37
45
|
response = self.model.response([Message(role="user", content=prompt)])
|
|
38
46
|
if response and response.content:
|
|
39
|
-
break_point = min(int(response.content.strip()), self.
|
|
47
|
+
break_point = min(int(response.content.strip()), self.chunk_size)
|
|
40
48
|
else:
|
|
41
|
-
break_point = self.
|
|
49
|
+
break_point = self.chunk_size
|
|
42
50
|
except Exception:
|
|
43
51
|
# Fallback to max size if model fails
|
|
44
|
-
break_point = self.
|
|
52
|
+
break_point = self.chunk_size
|
|
45
53
|
|
|
46
54
|
# Extract chunk and update remaining text
|
|
47
55
|
chunk = remaining_text[:break_point].strip()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
from agno.
|
|
4
|
-
from agno.document.
|
|
3
|
+
from agno.knowledge.chunking.strategy import ChunkingStrategy
|
|
4
|
+
from agno.knowledge.document.base import Document
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class DocumentChunking(ChunkingStrategy):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
from agno.
|
|
4
|
-
from agno.document.
|
|
3
|
+
from agno.knowledge.chunking.strategy import ChunkingStrategy
|
|
4
|
+
from agno.knowledge.document.base import Document
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class FixedSizeChunking(ChunkingStrategy):
|
|
@@ -22,9 +22,8 @@ class FixedSizeChunking(ChunkingStrategy):
|
|
|
22
22
|
chunked_documents: List[Document] = []
|
|
23
23
|
chunk_number = 1
|
|
24
24
|
chunk_meta_data = document.meta_data
|
|
25
|
-
|
|
26
25
|
start = 0
|
|
27
|
-
while start < content_length:
|
|
26
|
+
while start + self.overlap < content_length:
|
|
28
27
|
end = min(start + self.chunk_size, content_length)
|
|
29
28
|
|
|
30
29
|
# Ensure we're not splitting a word in half
|
|
@@ -54,6 +53,8 @@ class FixedSizeChunking(ChunkingStrategy):
|
|
|
54
53
|
)
|
|
55
54
|
)
|
|
56
55
|
chunk_number += 1
|
|
57
|
-
start
|
|
58
|
-
|
|
56
|
+
# Ensure start always advances by at least 1 to prevent infinite loops
|
|
57
|
+
# when overlap is large relative to chunk_size
|
|
58
|
+
new_start = max(start + 1, end - self.overlap)
|
|
59
|
+
start = new_start
|
|
59
60
|
return chunked_documents
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from unstructured.chunking.title import chunk_by_title # type: ignore
|
|
7
|
+
from unstructured.partition.md import partition_md # type: ignore
|
|
8
|
+
except ImportError:
|
|
9
|
+
raise ImportError("`unstructured` not installed. Please install it using `pip install unstructured markdown`")
|
|
10
|
+
|
|
11
|
+
from agno.knowledge.chunking.strategy import ChunkingStrategy
|
|
12
|
+
from agno.knowledge.document.base import Document
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MarkdownChunking(ChunkingStrategy):
|
|
16
|
+
"""A chunking strategy that splits markdown based on structure like headers, paragraphs and sections"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, chunk_size: int = 5000, overlap: int = 0):
|
|
19
|
+
self.chunk_size = chunk_size
|
|
20
|
+
self.overlap = overlap
|
|
21
|
+
|
|
22
|
+
def _partition_markdown_content(self, content: str) -> List[str]:
|
|
23
|
+
"""
|
|
24
|
+
Partition markdown content and return a list of text chunks.
|
|
25
|
+
Falls back to paragraph splitting if the markdown chunking fails.
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
# Create a temporary file with the markdown content.
|
|
29
|
+
# This is the recommended usage of the unstructured library.
|
|
30
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False, encoding="utf-8") as temp_file:
|
|
31
|
+
temp_file.write(content)
|
|
32
|
+
temp_file_path = temp_file.name
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
elements = partition_md(filename=temp_file_path)
|
|
36
|
+
|
|
37
|
+
if not elements:
|
|
38
|
+
return self.clean_text(content).split("\n\n")
|
|
39
|
+
|
|
40
|
+
# Chunk by title with some default values
|
|
41
|
+
chunked_elements = chunk_by_title(
|
|
42
|
+
elements=elements,
|
|
43
|
+
max_characters=self.chunk_size,
|
|
44
|
+
new_after_n_chars=int(self.chunk_size * 0.8),
|
|
45
|
+
combine_text_under_n_chars=self.chunk_size,
|
|
46
|
+
overlap=0,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Generate the final text chunks
|
|
50
|
+
text_chunks = []
|
|
51
|
+
for chunk_group in chunked_elements:
|
|
52
|
+
if isinstance(chunk_group, list):
|
|
53
|
+
chunk_text = "\n\n".join([elem.text for elem in chunk_group if hasattr(elem, "text")])
|
|
54
|
+
else:
|
|
55
|
+
chunk_text = chunk_group.text if hasattr(chunk_group, "text") else str(chunk_group)
|
|
56
|
+
|
|
57
|
+
if chunk_text.strip():
|
|
58
|
+
text_chunks.append(chunk_text.strip())
|
|
59
|
+
|
|
60
|
+
return text_chunks if text_chunks else self.clean_text(content).split("\n\n")
|
|
61
|
+
|
|
62
|
+
# Always clean up the temporary file
|
|
63
|
+
finally:
|
|
64
|
+
os.unlink(temp_file_path)
|
|
65
|
+
|
|
66
|
+
# Fallback to simple paragraph splitting if the markdown chunking fails
|
|
67
|
+
except Exception:
|
|
68
|
+
return self.clean_text(content).split("\n\n")
|
|
69
|
+
|
|
70
|
+
def chunk(self, document: Document) -> List[Document]:
|
|
71
|
+
"""Split markdown document into chunks based on markdown structure"""
|
|
72
|
+
if not document.content or len(document.content) <= self.chunk_size:
|
|
73
|
+
return [document]
|
|
74
|
+
|
|
75
|
+
# Split using markdown chunking logic, or fallback to paragraphs
|
|
76
|
+
sections = self._partition_markdown_content(document.content)
|
|
77
|
+
|
|
78
|
+
chunks: List[Document] = []
|
|
79
|
+
current_chunk = []
|
|
80
|
+
current_size = 0
|
|
81
|
+
chunk_meta_data = document.meta_data
|
|
82
|
+
chunk_number = 1
|
|
83
|
+
|
|
84
|
+
for section in sections:
|
|
85
|
+
section = section.strip()
|
|
86
|
+
section_size = len(section)
|
|
87
|
+
|
|
88
|
+
if current_size + section_size <= self.chunk_size:
|
|
89
|
+
current_chunk.append(section)
|
|
90
|
+
current_size += section_size
|
|
91
|
+
else:
|
|
92
|
+
meta_data = chunk_meta_data.copy()
|
|
93
|
+
meta_data["chunk"] = chunk_number
|
|
94
|
+
chunk_id = None
|
|
95
|
+
if document.id:
|
|
96
|
+
chunk_id = f"{document.id}_{chunk_number}"
|
|
97
|
+
elif document.name:
|
|
98
|
+
chunk_id = f"{document.name}_{chunk_number}"
|
|
99
|
+
meta_data["chunk_size"] = len("\n\n".join(current_chunk))
|
|
100
|
+
|
|
101
|
+
if current_chunk:
|
|
102
|
+
chunks.append(
|
|
103
|
+
Document(
|
|
104
|
+
id=chunk_id, name=document.name, meta_data=meta_data, content="\n\n".join(current_chunk)
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
chunk_number += 1
|
|
108
|
+
|
|
109
|
+
current_chunk = [section]
|
|
110
|
+
current_size = section_size
|
|
111
|
+
|
|
112
|
+
if current_chunk:
|
|
113
|
+
meta_data = chunk_meta_data.copy()
|
|
114
|
+
meta_data["chunk"] = chunk_number
|
|
115
|
+
chunk_id = None
|
|
116
|
+
if document.id:
|
|
117
|
+
chunk_id = f"{document.id}_{chunk_number}"
|
|
118
|
+
elif document.name:
|
|
119
|
+
chunk_id = f"{document.name}_{chunk_number}"
|
|
120
|
+
meta_data["chunk_size"] = len("\n\n".join(current_chunk))
|
|
121
|
+
chunks.append(
|
|
122
|
+
Document(id=chunk_id, name=document.name, meta_data=meta_data, content="\n\n".join(current_chunk))
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Handle overlap if specified
|
|
126
|
+
if self.overlap > 0:
|
|
127
|
+
overlapped_chunks = []
|
|
128
|
+
for i in range(len(chunks)):
|
|
129
|
+
if i > 0:
|
|
130
|
+
# Add overlap from previous chunk
|
|
131
|
+
prev_text = chunks[i - 1].content[-self.overlap :]
|
|
132
|
+
meta_data = chunk_meta_data.copy()
|
|
133
|
+
meta_data["chunk"] = chunks[i].meta_data["chunk"]
|
|
134
|
+
chunk_id = chunks[i].id
|
|
135
|
+
meta_data["chunk_size"] = len(prev_text + chunks[i].content)
|
|
136
|
+
|
|
137
|
+
if prev_text:
|
|
138
|
+
overlapped_chunks.append(
|
|
139
|
+
Document(
|
|
140
|
+
id=chunk_id,
|
|
141
|
+
name=document.name,
|
|
142
|
+
meta_data=meta_data,
|
|
143
|
+
content=prev_text + chunks[i].content,
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
overlapped_chunks.append(chunks[i])
|
|
148
|
+
else:
|
|
149
|
+
overlapped_chunks.append(chunks[i])
|
|
150
|
+
chunks = overlapped_chunks
|
|
151
|
+
return chunks
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import warnings
|
|
1
2
|
from typing import List
|
|
2
3
|
|
|
3
|
-
from agno.
|
|
4
|
-
from agno.document.
|
|
4
|
+
from agno.knowledge.chunking.strategy import ChunkingStrategy
|
|
5
|
+
from agno.knowledge.document.base import Document
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class RecursiveChunking(ChunkingStrategy):
|
|
@@ -12,6 +13,12 @@ class RecursiveChunking(ChunkingStrategy):
|
|
|
12
13
|
if overlap >= chunk_size:
|
|
13
14
|
raise ValueError(f"Invalid parameters: overlap ({overlap}) must be less than chunk size ({chunk_size}).")
|
|
14
15
|
|
|
16
|
+
if overlap > chunk_size * 0.15:
|
|
17
|
+
warnings.warn(
|
|
18
|
+
f"High overlap: {overlap} > 15% of chunk size ({chunk_size}). May cause slow processing.",
|
|
19
|
+
RuntimeWarning,
|
|
20
|
+
)
|
|
21
|
+
|
|
15
22
|
self.chunk_size = chunk_size
|
|
16
23
|
self.overlap = overlap
|
|
17
24
|
|
|
@@ -46,6 +53,11 @@ class RecursiveChunking(ChunkingStrategy):
|
|
|
46
53
|
meta_data["chunk_size"] = len(chunk)
|
|
47
54
|
chunks.append(Document(id=chunk_id, name=document.name, meta_data=meta_data, content=chunk))
|
|
48
55
|
|
|
49
|
-
|
|
56
|
+
new_start = end - self.overlap
|
|
57
|
+
if new_start <= start: # Prevent infinite loop
|
|
58
|
+
new_start = min(
|
|
59
|
+
len(content), start + max(1, self.chunk_size // 10)
|
|
60
|
+
) # Move forward by at least 10% of chunk size
|
|
61
|
+
start = new_start
|
|
50
62
|
|
|
51
63
|
return chunks
|