agno 2.2.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/__init__.py +8 -0
- agno/agent/__init__.py +51 -0
- agno/agent/agent.py +10405 -0
- agno/api/__init__.py +0 -0
- agno/api/agent.py +28 -0
- agno/api/api.py +40 -0
- agno/api/evals.py +22 -0
- agno/api/os.py +17 -0
- agno/api/routes.py +13 -0
- agno/api/schemas/__init__.py +9 -0
- agno/api/schemas/agent.py +16 -0
- agno/api/schemas/evals.py +16 -0
- agno/api/schemas/os.py +14 -0
- agno/api/schemas/response.py +6 -0
- agno/api/schemas/team.py +16 -0
- agno/api/schemas/utils.py +21 -0
- agno/api/schemas/workflows.py +16 -0
- agno/api/settings.py +53 -0
- agno/api/team.py +30 -0
- agno/api/workflow.py +28 -0
- agno/cloud/aws/base.py +214 -0
- agno/cloud/aws/s3/__init__.py +2 -0
- agno/cloud/aws/s3/api_client.py +43 -0
- agno/cloud/aws/s3/bucket.py +195 -0
- agno/cloud/aws/s3/object.py +57 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/__init__.py +24 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +598 -0
- agno/db/dynamo/__init__.py +3 -0
- agno/db/dynamo/dynamo.py +2042 -0
- agno/db/dynamo/schemas.py +314 -0
- agno/db/dynamo/utils.py +743 -0
- agno/db/firestore/__init__.py +3 -0
- agno/db/firestore/firestore.py +1795 -0
- agno/db/firestore/schemas.py +140 -0
- agno/db/firestore/utils.py +376 -0
- agno/db/gcs_json/__init__.py +3 -0
- agno/db/gcs_json/gcs_json_db.py +1335 -0
- agno/db/gcs_json/utils.py +228 -0
- agno/db/in_memory/__init__.py +3 -0
- agno/db/in_memory/in_memory_db.py +1160 -0
- agno/db/in_memory/utils.py +230 -0
- agno/db/json/__init__.py +3 -0
- agno/db/json/json_db.py +1328 -0
- agno/db/json/utils.py +230 -0
- agno/db/migrations/__init__.py +0 -0
- agno/db/migrations/v1_to_v2.py +635 -0
- agno/db/mongo/__init__.py +17 -0
- agno/db/mongo/async_mongo.py +2026 -0
- agno/db/mongo/mongo.py +1982 -0
- agno/db/mongo/schemas.py +87 -0
- agno/db/mongo/utils.py +259 -0
- agno/db/mysql/__init__.py +3 -0
- agno/db/mysql/mysql.py +2308 -0
- agno/db/mysql/schemas.py +138 -0
- agno/db/mysql/utils.py +355 -0
- agno/db/postgres/__init__.py +4 -0
- agno/db/postgres/async_postgres.py +1927 -0
- agno/db/postgres/postgres.py +2260 -0
- agno/db/postgres/schemas.py +139 -0
- agno/db/postgres/utils.py +442 -0
- agno/db/redis/__init__.py +3 -0
- agno/db/redis/redis.py +1660 -0
- agno/db/redis/schemas.py +123 -0
- agno/db/redis/utils.py +346 -0
- agno/db/schemas/__init__.py +4 -0
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +33 -0
- agno/db/schemas/knowledge.py +40 -0
- agno/db/schemas/memory.py +46 -0
- agno/db/schemas/metrics.py +0 -0
- agno/db/singlestore/__init__.py +3 -0
- agno/db/singlestore/schemas.py +130 -0
- agno/db/singlestore/singlestore.py +2272 -0
- agno/db/singlestore/utils.py +384 -0
- agno/db/sqlite/__init__.py +4 -0
- agno/db/sqlite/async_sqlite.py +2293 -0
- agno/db/sqlite/schemas.py +133 -0
- agno/db/sqlite/sqlite.py +2288 -0
- agno/db/sqlite/utils.py +431 -0
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1353 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +116 -0
- agno/debug.py +18 -0
- agno/eval/__init__.py +14 -0
- agno/eval/accuracy.py +834 -0
- agno/eval/performance.py +773 -0
- agno/eval/reliability.py +306 -0
- agno/eval/utils.py +119 -0
- agno/exceptions.py +161 -0
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/__init__.py +0 -0
- agno/integrations/discord/__init__.py +3 -0
- agno/integrations/discord/client.py +203 -0
- agno/knowledge/__init__.py +5 -0
- agno/knowledge/chunking/__init__.py +0 -0
- agno/knowledge/chunking/agentic.py +79 -0
- agno/knowledge/chunking/document.py +91 -0
- agno/knowledge/chunking/fixed.py +57 -0
- agno/knowledge/chunking/markdown.py +151 -0
- agno/knowledge/chunking/recursive.py +63 -0
- agno/knowledge/chunking/row.py +39 -0
- agno/knowledge/chunking/semantic.py +86 -0
- agno/knowledge/chunking/strategy.py +165 -0
- agno/knowledge/content.py +74 -0
- agno/knowledge/document/__init__.py +5 -0
- agno/knowledge/document/base.py +58 -0
- agno/knowledge/embedder/__init__.py +5 -0
- agno/knowledge/embedder/aws_bedrock.py +343 -0
- agno/knowledge/embedder/azure_openai.py +210 -0
- agno/knowledge/embedder/base.py +23 -0
- agno/knowledge/embedder/cohere.py +323 -0
- agno/knowledge/embedder/fastembed.py +62 -0
- agno/knowledge/embedder/fireworks.py +13 -0
- agno/knowledge/embedder/google.py +258 -0
- agno/knowledge/embedder/huggingface.py +94 -0
- agno/knowledge/embedder/jina.py +182 -0
- agno/knowledge/embedder/langdb.py +22 -0
- agno/knowledge/embedder/mistral.py +206 -0
- agno/knowledge/embedder/nebius.py +13 -0
- agno/knowledge/embedder/ollama.py +154 -0
- agno/knowledge/embedder/openai.py +195 -0
- agno/knowledge/embedder/sentence_transformer.py +63 -0
- agno/knowledge/embedder/together.py +13 -0
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +165 -0
- agno/knowledge/knowledge.py +1988 -0
- agno/knowledge/reader/__init__.py +7 -0
- agno/knowledge/reader/arxiv_reader.py +81 -0
- agno/knowledge/reader/base.py +95 -0
- agno/knowledge/reader/csv_reader.py +166 -0
- agno/knowledge/reader/docx_reader.py +82 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +292 -0
- agno/knowledge/reader/firecrawl_reader.py +201 -0
- agno/knowledge/reader/json_reader.py +87 -0
- agno/knowledge/reader/markdown_reader.py +137 -0
- agno/knowledge/reader/pdf_reader.py +431 -0
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +313 -0
- agno/knowledge/reader/s3_reader.py +89 -0
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +115 -0
- agno/knowledge/reader/web_search_reader.py +372 -0
- agno/knowledge/reader/website_reader.py +455 -0
- agno/knowledge/reader/wikipedia_reader.py +59 -0
- agno/knowledge/reader/youtube_reader.py +78 -0
- agno/knowledge/remote_content/__init__.py +0 -0
- agno/knowledge/remote_content/remote_content.py +88 -0
- agno/knowledge/reranker/__init__.py +3 -0
- agno/knowledge/reranker/base.py +14 -0
- agno/knowledge/reranker/cohere.py +64 -0
- agno/knowledge/reranker/infinity.py +195 -0
- agno/knowledge/reranker/sentence_transformer.py +54 -0
- agno/knowledge/types.py +39 -0
- agno/knowledge/utils.py +189 -0
- agno/media.py +462 -0
- agno/memory/__init__.py +3 -0
- agno/memory/manager.py +1327 -0
- agno/models/__init__.py +0 -0
- agno/models/aimlapi/__init__.py +5 -0
- agno/models/aimlapi/aimlapi.py +45 -0
- agno/models/anthropic/__init__.py +5 -0
- agno/models/anthropic/claude.py +757 -0
- agno/models/aws/__init__.py +15 -0
- agno/models/aws/bedrock.py +701 -0
- agno/models/aws/claude.py +378 -0
- agno/models/azure/__init__.py +18 -0
- agno/models/azure/ai_foundry.py +485 -0
- agno/models/azure/openai_chat.py +131 -0
- agno/models/base.py +2175 -0
- agno/models/cerebras/__init__.py +12 -0
- agno/models/cerebras/cerebras.py +501 -0
- agno/models/cerebras/cerebras_openai.py +112 -0
- agno/models/cohere/__init__.py +5 -0
- agno/models/cohere/chat.py +389 -0
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/__init__.py +5 -0
- agno/models/dashscope/dashscope.py +91 -0
- agno/models/deepinfra/__init__.py +5 -0
- agno/models/deepinfra/deepinfra.py +28 -0
- agno/models/deepseek/__init__.py +5 -0
- agno/models/deepseek/deepseek.py +61 -0
- agno/models/defaults.py +1 -0
- agno/models/fireworks/__init__.py +5 -0
- agno/models/fireworks/fireworks.py +26 -0
- agno/models/google/__init__.py +5 -0
- agno/models/google/gemini.py +1085 -0
- agno/models/groq/__init__.py +5 -0
- agno/models/groq/groq.py +556 -0
- agno/models/huggingface/__init__.py +5 -0
- agno/models/huggingface/huggingface.py +491 -0
- agno/models/ibm/__init__.py +5 -0
- agno/models/ibm/watsonx.py +422 -0
- agno/models/internlm/__init__.py +3 -0
- agno/models/internlm/internlm.py +26 -0
- agno/models/langdb/__init__.py +1 -0
- agno/models/langdb/langdb.py +48 -0
- agno/models/litellm/__init__.py +14 -0
- agno/models/litellm/chat.py +468 -0
- agno/models/litellm/litellm_openai.py +25 -0
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/lmstudio/__init__.py +5 -0
- agno/models/lmstudio/lmstudio.py +25 -0
- agno/models/message.py +434 -0
- agno/models/meta/__init__.py +12 -0
- agno/models/meta/llama.py +475 -0
- agno/models/meta/llama_openai.py +78 -0
- agno/models/metrics.py +120 -0
- agno/models/mistral/__init__.py +5 -0
- agno/models/mistral/mistral.py +432 -0
- agno/models/nebius/__init__.py +3 -0
- agno/models/nebius/nebius.py +54 -0
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/__init__.py +5 -0
- agno/models/nvidia/nvidia.py +28 -0
- agno/models/ollama/__init__.py +5 -0
- agno/models/ollama/chat.py +441 -0
- agno/models/openai/__init__.py +9 -0
- agno/models/openai/chat.py +883 -0
- agno/models/openai/like.py +27 -0
- agno/models/openai/responses.py +1050 -0
- agno/models/openrouter/__init__.py +5 -0
- agno/models/openrouter/openrouter.py +66 -0
- agno/models/perplexity/__init__.py +5 -0
- agno/models/perplexity/perplexity.py +187 -0
- agno/models/portkey/__init__.py +3 -0
- agno/models/portkey/portkey.py +81 -0
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +199 -0
- agno/models/sambanova/__init__.py +5 -0
- agno/models/sambanova/sambanova.py +28 -0
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/__init__.py +5 -0
- agno/models/together/together.py +25 -0
- agno/models/utils.py +266 -0
- agno/models/vercel/__init__.py +3 -0
- agno/models/vercel/v0.py +26 -0
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +70 -0
- agno/models/vllm/__init__.py +3 -0
- agno/models/vllm/vllm.py +78 -0
- agno/models/xai/__init__.py +3 -0
- agno/models/xai/xai.py +113 -0
- agno/os/__init__.py +3 -0
- agno/os/app.py +876 -0
- agno/os/auth.py +57 -0
- agno/os/config.py +104 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/__init__.py +3 -0
- agno/os/interfaces/agui/agui.py +47 -0
- agno/os/interfaces/agui/router.py +144 -0
- agno/os/interfaces/agui/utils.py +534 -0
- agno/os/interfaces/base.py +25 -0
- agno/os/interfaces/slack/__init__.py +3 -0
- agno/os/interfaces/slack/router.py +148 -0
- agno/os/interfaces/slack/security.py +30 -0
- agno/os/interfaces/slack/slack.py +47 -0
- agno/os/interfaces/whatsapp/__init__.py +3 -0
- agno/os/interfaces/whatsapp/router.py +211 -0
- agno/os/interfaces/whatsapp/security.py +53 -0
- agno/os/interfaces/whatsapp/whatsapp.py +36 -0
- agno/os/mcp.py +292 -0
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +1763 -0
- agno/os/routers/__init__.py +3 -0
- agno/os/routers/evals/__init__.py +3 -0
- agno/os/routers/evals/evals.py +430 -0
- agno/os/routers/evals/schemas.py +142 -0
- agno/os/routers/evals/utils.py +162 -0
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/__init__.py +3 -0
- agno/os/routers/knowledge/knowledge.py +997 -0
- agno/os/routers/knowledge/schemas.py +178 -0
- agno/os/routers/memory/__init__.py +3 -0
- agno/os/routers/memory/memory.py +515 -0
- agno/os/routers/memory/schemas.py +62 -0
- agno/os/routers/metrics/__init__.py +3 -0
- agno/os/routers/metrics/metrics.py +190 -0
- agno/os/routers/metrics/schemas.py +47 -0
- agno/os/routers/session/__init__.py +3 -0
- agno/os/routers/session/session.py +997 -0
- agno/os/schema.py +1055 -0
- agno/os/settings.py +43 -0
- agno/os/utils.py +630 -0
- agno/py.typed +0 -0
- agno/reasoning/__init__.py +0 -0
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +67 -0
- agno/reasoning/deepseek.py +63 -0
- agno/reasoning/default.py +97 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +71 -0
- agno/reasoning/helpers.py +63 -0
- agno/reasoning/ollama.py +67 -0
- agno/reasoning/openai.py +86 -0
- agno/reasoning/step.py +31 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +787 -0
- agno/run/base.py +229 -0
- agno/run/cancel.py +81 -0
- agno/run/messages.py +32 -0
- agno/run/team.py +753 -0
- agno/run/workflow.py +708 -0
- agno/session/__init__.py +10 -0
- agno/session/agent.py +295 -0
- agno/session/summary.py +265 -0
- agno/session/team.py +392 -0
- agno/session/workflow.py +205 -0
- agno/team/__init__.py +37 -0
- agno/team/team.py +8793 -0
- agno/tools/__init__.py +10 -0
- agno/tools/agentql.py +120 -0
- agno/tools/airflow.py +69 -0
- agno/tools/api.py +122 -0
- agno/tools/apify.py +314 -0
- agno/tools/arxiv.py +127 -0
- agno/tools/aws_lambda.py +53 -0
- agno/tools/aws_ses.py +66 -0
- agno/tools/baidusearch.py +89 -0
- agno/tools/bitbucket.py +292 -0
- agno/tools/brandfetch.py +213 -0
- agno/tools/bravesearch.py +106 -0
- agno/tools/brightdata.py +367 -0
- agno/tools/browserbase.py +209 -0
- agno/tools/calcom.py +255 -0
- agno/tools/calculator.py +151 -0
- agno/tools/cartesia.py +187 -0
- agno/tools/clickup.py +244 -0
- agno/tools/confluence.py +240 -0
- agno/tools/crawl4ai.py +158 -0
- agno/tools/csv_toolkit.py +185 -0
- agno/tools/dalle.py +110 -0
- agno/tools/daytona.py +475 -0
- agno/tools/decorator.py +262 -0
- agno/tools/desi_vocal.py +108 -0
- agno/tools/discord.py +161 -0
- agno/tools/docker.py +716 -0
- agno/tools/duckdb.py +379 -0
- agno/tools/duckduckgo.py +91 -0
- agno/tools/e2b.py +703 -0
- agno/tools/eleven_labs.py +196 -0
- agno/tools/email.py +67 -0
- agno/tools/evm.py +129 -0
- agno/tools/exa.py +396 -0
- agno/tools/fal.py +127 -0
- agno/tools/file.py +240 -0
- agno/tools/file_generation.py +350 -0
- agno/tools/financial_datasets.py +288 -0
- agno/tools/firecrawl.py +143 -0
- agno/tools/function.py +1187 -0
- agno/tools/giphy.py +93 -0
- agno/tools/github.py +1760 -0
- agno/tools/gmail.py +922 -0
- agno/tools/google_bigquery.py +117 -0
- agno/tools/google_drive.py +270 -0
- agno/tools/google_maps.py +253 -0
- agno/tools/googlecalendar.py +674 -0
- agno/tools/googlesearch.py +98 -0
- agno/tools/googlesheets.py +377 -0
- agno/tools/hackernews.py +77 -0
- agno/tools/jina.py +101 -0
- agno/tools/jira.py +170 -0
- agno/tools/knowledge.py +218 -0
- agno/tools/linear.py +426 -0
- agno/tools/linkup.py +58 -0
- agno/tools/local_file_system.py +90 -0
- agno/tools/lumalab.py +183 -0
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +193 -0
- agno/tools/memori.py +339 -0
- agno/tools/memory.py +419 -0
- agno/tools/mlx_transcribe.py +139 -0
- agno/tools/models/__init__.py +0 -0
- agno/tools/models/azure_openai.py +190 -0
- agno/tools/models/gemini.py +203 -0
- agno/tools/models/groq.py +158 -0
- agno/tools/models/morph.py +186 -0
- agno/tools/models/nebius.py +124 -0
- agno/tools/models_labs.py +195 -0
- agno/tools/moviepy_video.py +349 -0
- agno/tools/neo4j.py +134 -0
- agno/tools/newspaper.py +46 -0
- agno/tools/newspaper4k.py +93 -0
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +202 -0
- agno/tools/openbb.py +160 -0
- agno/tools/opencv.py +321 -0
- agno/tools/openweather.py +233 -0
- agno/tools/oxylabs.py +385 -0
- agno/tools/pandas.py +102 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +257 -0
- agno/tools/pubmed.py +188 -0
- agno/tools/python.py +205 -0
- agno/tools/reasoning.py +283 -0
- agno/tools/reddit.py +467 -0
- agno/tools/replicate.py +117 -0
- agno/tools/resend.py +62 -0
- agno/tools/scrapegraph.py +222 -0
- agno/tools/searxng.py +152 -0
- agno/tools/serpapi.py +116 -0
- agno/tools/serper.py +255 -0
- agno/tools/shell.py +53 -0
- agno/tools/slack.py +136 -0
- agno/tools/sleep.py +20 -0
- agno/tools/spider.py +116 -0
- agno/tools/sql.py +154 -0
- agno/tools/streamlit/__init__.py +0 -0
- agno/tools/streamlit/components.py +113 -0
- agno/tools/tavily.py +254 -0
- agno/tools/telegram.py +48 -0
- agno/tools/todoist.py +218 -0
- agno/tools/tool_registry.py +1 -0
- agno/tools/toolkit.py +146 -0
- agno/tools/trafilatura.py +388 -0
- agno/tools/trello.py +274 -0
- agno/tools/twilio.py +186 -0
- agno/tools/user_control_flow.py +78 -0
- agno/tools/valyu.py +228 -0
- agno/tools/visualization.py +467 -0
- agno/tools/webbrowser.py +28 -0
- agno/tools/webex.py +76 -0
- agno/tools/website.py +54 -0
- agno/tools/webtools.py +45 -0
- agno/tools/whatsapp.py +286 -0
- agno/tools/wikipedia.py +63 -0
- agno/tools/workflow.py +278 -0
- agno/tools/x.py +335 -0
- agno/tools/yfinance.py +257 -0
- agno/tools/youtube.py +184 -0
- agno/tools/zendesk.py +82 -0
- agno/tools/zep.py +454 -0
- agno/tools/zoom.py +382 -0
- agno/utils/__init__.py +0 -0
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +49 -0
- agno/utils/certs.py +27 -0
- agno/utils/code_execution.py +11 -0
- agno/utils/common.py +132 -0
- agno/utils/dttm.py +13 -0
- agno/utils/enum.py +22 -0
- agno/utils/env.py +11 -0
- agno/utils/events.py +696 -0
- agno/utils/format_str.py +16 -0
- agno/utils/functions.py +166 -0
- agno/utils/gemini.py +426 -0
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +74 -0
- agno/utils/json_schema.py +234 -0
- agno/utils/knowledge.py +36 -0
- agno/utils/location.py +19 -0
- agno/utils/log.py +255 -0
- agno/utils/mcp.py +214 -0
- agno/utils/media.py +352 -0
- agno/utils/merge_dict.py +41 -0
- agno/utils/message.py +118 -0
- agno/utils/models/__init__.py +0 -0
- agno/utils/models/ai_foundry.py +43 -0
- agno/utils/models/claude.py +358 -0
- agno/utils/models/cohere.py +87 -0
- agno/utils/models/llama.py +78 -0
- agno/utils/models/mistral.py +98 -0
- agno/utils/models/openai_responses.py +140 -0
- agno/utils/models/schema_utils.py +153 -0
- agno/utils/models/watsonx.py +41 -0
- agno/utils/openai.py +257 -0
- agno/utils/pickle.py +32 -0
- agno/utils/pprint.py +178 -0
- agno/utils/print_response/__init__.py +0 -0
- agno/utils/print_response/agent.py +842 -0
- agno/utils/print_response/team.py +1724 -0
- agno/utils/print_response/workflow.py +1668 -0
- agno/utils/prompts.py +111 -0
- agno/utils/reasoning.py +108 -0
- agno/utils/response.py +163 -0
- agno/utils/response_iterator.py +17 -0
- agno/utils/safe_formatter.py +24 -0
- agno/utils/serialize.py +32 -0
- agno/utils/shell.py +22 -0
- agno/utils/streamlit.py +487 -0
- agno/utils/string.py +231 -0
- agno/utils/team.py +139 -0
- agno/utils/timer.py +41 -0
- agno/utils/tools.py +102 -0
- agno/utils/web.py +23 -0
- agno/utils/whatsapp.py +305 -0
- agno/utils/yaml_io.py +25 -0
- agno/vectordb/__init__.py +3 -0
- agno/vectordb/base.py +127 -0
- agno/vectordb/cassandra/__init__.py +5 -0
- agno/vectordb/cassandra/cassandra.py +501 -0
- agno/vectordb/cassandra/extra_param_mixin.py +11 -0
- agno/vectordb/cassandra/index.py +13 -0
- agno/vectordb/chroma/__init__.py +5 -0
- agno/vectordb/chroma/chromadb.py +929 -0
- agno/vectordb/clickhouse/__init__.py +9 -0
- agno/vectordb/clickhouse/clickhousedb.py +835 -0
- agno/vectordb/clickhouse/index.py +9 -0
- agno/vectordb/couchbase/__init__.py +3 -0
- agno/vectordb/couchbase/couchbase.py +1442 -0
- agno/vectordb/distance.py +7 -0
- agno/vectordb/lancedb/__init__.py +6 -0
- agno/vectordb/lancedb/lance_db.py +995 -0
- agno/vectordb/langchaindb/__init__.py +5 -0
- agno/vectordb/langchaindb/langchaindb.py +163 -0
- agno/vectordb/lightrag/__init__.py +5 -0
- agno/vectordb/lightrag/lightrag.py +388 -0
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +166 -0
- agno/vectordb/milvus/__init__.py +4 -0
- agno/vectordb/milvus/milvus.py +1182 -0
- agno/vectordb/mongodb/__init__.py +9 -0
- agno/vectordb/mongodb/mongodb.py +1417 -0
- agno/vectordb/pgvector/__init__.py +12 -0
- agno/vectordb/pgvector/index.py +23 -0
- agno/vectordb/pgvector/pgvector.py +1462 -0
- agno/vectordb/pineconedb/__init__.py +5 -0
- agno/vectordb/pineconedb/pineconedb.py +747 -0
- agno/vectordb/qdrant/__init__.py +5 -0
- agno/vectordb/qdrant/qdrant.py +1134 -0
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/search.py +7 -0
- agno/vectordb/singlestore/__init__.py +10 -0
- agno/vectordb/singlestore/index.py +41 -0
- agno/vectordb/singlestore/singlestore.py +763 -0
- agno/vectordb/surrealdb/__init__.py +3 -0
- agno/vectordb/surrealdb/surrealdb.py +699 -0
- agno/vectordb/upstashdb/__init__.py +5 -0
- agno/vectordb/upstashdb/upstashdb.py +718 -0
- agno/vectordb/weaviate/__init__.py +8 -0
- agno/vectordb/weaviate/index.py +15 -0
- agno/vectordb/weaviate/weaviate.py +1005 -0
- agno/workflow/__init__.py +23 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +738 -0
- agno/workflow/loop.py +735 -0
- agno/workflow/parallel.py +824 -0
- agno/workflow/router.py +702 -0
- agno/workflow/step.py +1432 -0
- agno/workflow/steps.py +592 -0
- agno/workflow/types.py +520 -0
- agno/workflow/workflow.py +4321 -0
- agno-2.2.13.dist-info/METADATA +614 -0
- agno-2.2.13.dist-info/RECORD +575 -0
- agno-2.2.13.dist-info/WHEEL +5 -0
- agno-2.2.13.dist-info/licenses/LICENSE +201 -0
- agno-2.2.13.dist-info/top_level.txt +1 -0
agno/tools/reddit.py
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from os import getenv
|
|
3
|
+
from typing import Callable, Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from agno.tools import Toolkit
|
|
6
|
+
from agno.utils.log import log_debug, log_info, logger
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import praw # type: ignore
|
|
10
|
+
except ImportError:
|
|
11
|
+
raise ImportError("praw` not installed. Please install using `pip install praw`")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RedditTools(Toolkit):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
reddit_instance: Optional[praw.Reddit] = None,
|
|
18
|
+
client_id: Optional[str] = None,
|
|
19
|
+
client_secret: Optional[str] = None,
|
|
20
|
+
user_agent: Optional[str] = None,
|
|
21
|
+
username: Optional[str] = None,
|
|
22
|
+
password: Optional[str] = None,
|
|
23
|
+
**kwargs,
|
|
24
|
+
):
|
|
25
|
+
if reddit_instance is not None:
|
|
26
|
+
log_info("Using provided Reddit instance")
|
|
27
|
+
self.reddit = reddit_instance
|
|
28
|
+
else:
|
|
29
|
+
# Get credentials from environment variables if not provided
|
|
30
|
+
self.client_id = client_id or getenv("REDDIT_CLIENT_ID")
|
|
31
|
+
self.client_secret = client_secret or getenv("REDDIT_CLIENT_SECRET")
|
|
32
|
+
self.user_agent = user_agent or getenv("REDDIT_USER_AGENT", "RedditTools v1.0")
|
|
33
|
+
self.username = username or getenv("REDDIT_USERNAME")
|
|
34
|
+
self.password = password or getenv("REDDIT_PASSWORD")
|
|
35
|
+
|
|
36
|
+
self.reddit = None
|
|
37
|
+
# Check if we have all required credentials
|
|
38
|
+
if all([self.client_id, self.client_secret]):
|
|
39
|
+
# Initialize with read-only access if no user credentials
|
|
40
|
+
if not all([self.username, self.password]):
|
|
41
|
+
log_info("Initializing Reddit client with read-only access")
|
|
42
|
+
self.reddit = praw.Reddit(
|
|
43
|
+
client_id=self.client_id,
|
|
44
|
+
client_secret=self.client_secret,
|
|
45
|
+
user_agent=self.user_agent,
|
|
46
|
+
)
|
|
47
|
+
# Initialize with user authentication if credentials provided
|
|
48
|
+
else:
|
|
49
|
+
log_info(f"Initializing Reddit client with user authentication for u/{self.username}")
|
|
50
|
+
self.reddit = praw.Reddit(
|
|
51
|
+
client_id=self.client_id,
|
|
52
|
+
client_secret=self.client_secret,
|
|
53
|
+
user_agent=self.user_agent,
|
|
54
|
+
username=self.username,
|
|
55
|
+
password=self.password,
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
logger.warning("Missing Reddit API credentials")
|
|
59
|
+
|
|
60
|
+
tools: List[Callable] = [
|
|
61
|
+
self.get_user_info,
|
|
62
|
+
self.get_top_posts,
|
|
63
|
+
self.get_subreddit_info,
|
|
64
|
+
self.get_trending_subreddits,
|
|
65
|
+
self.get_subreddit_stats,
|
|
66
|
+
self.create_post,
|
|
67
|
+
self.reply_to_post,
|
|
68
|
+
self.reply_to_comment,
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
super().__init__(name="reddit", tools=tools, **kwargs)
|
|
72
|
+
|
|
73
|
+
def _check_user_auth(self) -> bool:
|
|
74
|
+
"""
|
|
75
|
+
Check if user authentication is available for actions that require it.
|
|
76
|
+
Returns:
|
|
77
|
+
bool: True if user is authenticated, False otherwise
|
|
78
|
+
"""
|
|
79
|
+
if not self.reddit:
|
|
80
|
+
logger.error("Reddit client not initialized")
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
if not all([self.username, self.password]):
|
|
84
|
+
logger.error("User authentication required. Please provide username and password.")
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
# Verify authentication by checking if we can get the authenticated user
|
|
89
|
+
self.reddit.user.me()
|
|
90
|
+
return True
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.error(f"Authentication error: {e}")
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
def get_user_info(self, username: str) -> str:
|
|
96
|
+
"""Get information about a Reddit user."""
|
|
97
|
+
if not self.reddit:
|
|
98
|
+
return "Please provide Reddit API credentials"
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
log_info(f"Getting info for u/{username}")
|
|
102
|
+
|
|
103
|
+
user = self.reddit.redditor(username)
|
|
104
|
+
info: Dict[str, Union[str, int, bool, float]] = {
|
|
105
|
+
"name": user.name,
|
|
106
|
+
"comment_karma": user.comment_karma,
|
|
107
|
+
"link_karma": user.link_karma,
|
|
108
|
+
"is_mod": user.is_mod,
|
|
109
|
+
"is_gold": user.is_gold,
|
|
110
|
+
"is_employee": user.is_employee,
|
|
111
|
+
"created_utc": user.created_utc,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return json.dumps(info)
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
return f"Error getting user info: {e}"
|
|
118
|
+
|
|
119
|
+
def get_top_posts(self, subreddit: str, time_filter: str = "week", limit: int = 10) -> str:
|
|
120
|
+
"""
|
|
121
|
+
Get top posts from a subreddit for a specific time period.
|
|
122
|
+
Args:
|
|
123
|
+
subreddit (str): Name of the subreddit.
|
|
124
|
+
time_filter (str): Time period to filter posts.
|
|
125
|
+
limit (int): Number of posts to fetch.
|
|
126
|
+
Returns:
|
|
127
|
+
str: JSON string containing top posts.
|
|
128
|
+
"""
|
|
129
|
+
if not self.reddit:
|
|
130
|
+
return "Please provide Reddit API credentials"
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
log_debug(f"Getting top posts from r/{subreddit}")
|
|
134
|
+
posts = self.reddit.subreddit(subreddit).top(time_filter=time_filter, limit=limit)
|
|
135
|
+
top_posts: List[Dict[str, Union[str, int, float]]] = [
|
|
136
|
+
{
|
|
137
|
+
"id": post.id,
|
|
138
|
+
"title": post.title,
|
|
139
|
+
"score": post.score,
|
|
140
|
+
"url": post.url,
|
|
141
|
+
"selftext": post.selftext,
|
|
142
|
+
"author": str(post.author),
|
|
143
|
+
"permalink": post.permalink,
|
|
144
|
+
"created_utc": post.created_utc,
|
|
145
|
+
"subreddit": str(post.subreddit),
|
|
146
|
+
"subreddit_name_prefixed": post.subreddit_name_prefixed,
|
|
147
|
+
}
|
|
148
|
+
for post in posts
|
|
149
|
+
]
|
|
150
|
+
return json.dumps({"top_posts": top_posts})
|
|
151
|
+
except Exception as e:
|
|
152
|
+
return f"Error getting top posts: {e}"
|
|
153
|
+
|
|
154
|
+
def get_subreddit_info(self, subreddit_name: str) -> str:
|
|
155
|
+
"""
|
|
156
|
+
Get information about a specific subreddit.
|
|
157
|
+
Args:
|
|
158
|
+
subreddit_name (str): Name of the subreddit.
|
|
159
|
+
Returns:
|
|
160
|
+
str: JSON string containing subreddit information.
|
|
161
|
+
"""
|
|
162
|
+
if not self.reddit:
|
|
163
|
+
return "Please provide Reddit API credentials"
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
log_info(f"Getting info for r/{subreddit_name}")
|
|
167
|
+
|
|
168
|
+
subreddit = self.reddit.subreddit(subreddit_name)
|
|
169
|
+
flairs = [flair["text"] for flair in subreddit.flair.link_templates]
|
|
170
|
+
info: Dict[str, Union[str, int, bool, float, List[str]]] = {
|
|
171
|
+
"display_name": subreddit.display_name,
|
|
172
|
+
"title": subreddit.title,
|
|
173
|
+
"description": subreddit.description,
|
|
174
|
+
"subscribers": subreddit.subscribers,
|
|
175
|
+
"created_utc": subreddit.created_utc,
|
|
176
|
+
"over18": subreddit.over18,
|
|
177
|
+
"available_flairs": flairs,
|
|
178
|
+
"public_description": subreddit.public_description,
|
|
179
|
+
"url": subreddit.url,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return json.dumps(info)
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
return f"Error getting subreddit info: {e}"
|
|
186
|
+
|
|
187
|
+
def get_trending_subreddits(self) -> str:
|
|
188
|
+
"""Get currently trending subreddits."""
|
|
189
|
+
if not self.reddit:
|
|
190
|
+
return "Please provide Reddit API credentials"
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
log_debug("Getting trending subreddits")
|
|
194
|
+
popular_subreddits = self.reddit.subreddits.popular(limit=5)
|
|
195
|
+
trending: List[str] = [subreddit.display_name for subreddit in popular_subreddits]
|
|
196
|
+
return json.dumps({"trending_subreddits": trending})
|
|
197
|
+
except Exception as e:
|
|
198
|
+
return f"Error getting trending subreddits: {e}"
|
|
199
|
+
|
|
200
|
+
def get_subreddit_stats(self, subreddit: str) -> str:
|
|
201
|
+
"""
|
|
202
|
+
Get statistics about a subreddit.
|
|
203
|
+
Args:
|
|
204
|
+
subreddit (str): Name of the subreddit.
|
|
205
|
+
Returns:
|
|
206
|
+
str: JSON string containing subreddit statistics
|
|
207
|
+
"""
|
|
208
|
+
if not self.reddit:
|
|
209
|
+
return "Please provide Reddit API credentials"
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
log_debug(f"Getting stats for r/{subreddit}")
|
|
213
|
+
sub = self.reddit.subreddit(subreddit)
|
|
214
|
+
stats: Dict[str, Union[str, int, bool, float]] = {
|
|
215
|
+
"display_name": sub.display_name,
|
|
216
|
+
"subscribers": sub.subscribers,
|
|
217
|
+
"active_users": sub.active_user_count,
|
|
218
|
+
"description": sub.description,
|
|
219
|
+
"created_utc": sub.created_utc,
|
|
220
|
+
"over18": sub.over18,
|
|
221
|
+
"public_description": sub.public_description,
|
|
222
|
+
}
|
|
223
|
+
return json.dumps({"subreddit_stats": stats})
|
|
224
|
+
except Exception as e:
|
|
225
|
+
return f"Error getting subreddit stats: {e}"
|
|
226
|
+
|
|
227
|
+
def create_post(
|
|
228
|
+
self,
|
|
229
|
+
subreddit: str,
|
|
230
|
+
title: str,
|
|
231
|
+
content: str,
|
|
232
|
+
flair: Optional[str] = None,
|
|
233
|
+
is_self: bool = True,
|
|
234
|
+
) -> str:
|
|
235
|
+
"""
|
|
236
|
+
Create a new post in a subreddit.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
subreddit (str): Name of the subreddit to post in.
|
|
240
|
+
title (str): Title of the post.
|
|
241
|
+
content (str): Content of the post (text for self posts, URL for link posts).
|
|
242
|
+
flair (Optional[str]): Flair to add to the post. Must be an available flair in the subreddit.
|
|
243
|
+
is_self (bool): Whether this is a self (text) post (True) or link post (False).
|
|
244
|
+
Returns:
|
|
245
|
+
str: JSON string containing the created post information.
|
|
246
|
+
"""
|
|
247
|
+
if not self.reddit:
|
|
248
|
+
return "Please provide Reddit API credentials"
|
|
249
|
+
|
|
250
|
+
if not self._check_user_auth():
|
|
251
|
+
return "User authentication required for posting. Please provide username and password."
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
log_info(f"Creating post in r/{subreddit}")
|
|
255
|
+
|
|
256
|
+
subreddit_obj = self.reddit.subreddit(subreddit)
|
|
257
|
+
|
|
258
|
+
if flair:
|
|
259
|
+
available_flairs = [f["text"] for f in subreddit_obj.flair.link_templates]
|
|
260
|
+
if flair not in available_flairs:
|
|
261
|
+
return f"Invalid flair. Available flairs: {', '.join(available_flairs)}"
|
|
262
|
+
|
|
263
|
+
if is_self:
|
|
264
|
+
submission = subreddit_obj.submit(
|
|
265
|
+
title=title,
|
|
266
|
+
selftext=content,
|
|
267
|
+
flair_id=flair,
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
submission = subreddit_obj.submit(
|
|
271
|
+
title=title,
|
|
272
|
+
url=content,
|
|
273
|
+
flair_id=flair,
|
|
274
|
+
)
|
|
275
|
+
log_info(f"Post created: {submission.permalink}")
|
|
276
|
+
|
|
277
|
+
post_info: Dict[str, Union[str, int, float]] = {
|
|
278
|
+
"id": submission.id,
|
|
279
|
+
"title": submission.title,
|
|
280
|
+
"url": submission.url,
|
|
281
|
+
"permalink": submission.permalink,
|
|
282
|
+
"created_utc": submission.created_utc,
|
|
283
|
+
"author": str(submission.author),
|
|
284
|
+
"flair": submission.link_flair_text,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return json.dumps({"post": post_info})
|
|
288
|
+
|
|
289
|
+
except Exception as e:
|
|
290
|
+
return f"Error creating post: {e}"
|
|
291
|
+
|
|
292
|
+
def reply_to_post(self, post_id: str, content: str, subreddit: Optional[str] = None) -> str:
|
|
293
|
+
"""
|
|
294
|
+
Post a reply to an existing Reddit post or comment.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
post_id (str): The ID of the post or comment to reply to.
|
|
298
|
+
Can be a full URL, permalink, or just the ID.
|
|
299
|
+
content (str): The content of the reply.
|
|
300
|
+
subreddit (Optional[str]): The subreddit name if known.
|
|
301
|
+
This helps with error handling and validation.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
str: JSON string containing information about the created reply.
|
|
305
|
+
"""
|
|
306
|
+
if not self.reddit:
|
|
307
|
+
logger.error("Reddit instance not initialized")
|
|
308
|
+
return "Please provide Reddit API credentials"
|
|
309
|
+
|
|
310
|
+
if not self._check_user_auth():
|
|
311
|
+
logger.error("User authentication failed")
|
|
312
|
+
return "User authentication required for posting replies. Please provide username and password."
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
log_debug(f"Creating reply to post {post_id}")
|
|
316
|
+
|
|
317
|
+
# Clean up the post_id if it's a full URL or permalink
|
|
318
|
+
if "/" in post_id:
|
|
319
|
+
# Extract the actual ID from the URL/permalink
|
|
320
|
+
original_id = post_id
|
|
321
|
+
post_id = post_id.split("/")[-1]
|
|
322
|
+
log_debug(f"Extracted post ID {post_id} from {original_id}")
|
|
323
|
+
|
|
324
|
+
# Verify post exists
|
|
325
|
+
if not self._check_post_exists(post_id):
|
|
326
|
+
error_msg = f"Post with ID {post_id} does not exist or is not accessible"
|
|
327
|
+
logger.error(error_msg)
|
|
328
|
+
return error_msg
|
|
329
|
+
|
|
330
|
+
# Get the submission object
|
|
331
|
+
submission = self.reddit.submission(id=post_id)
|
|
332
|
+
|
|
333
|
+
log_debug(
|
|
334
|
+
f"Post details: Title: {submission.title}, Author: {submission.author}, Subreddit: {submission.subreddit.display_name}"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# If subreddit was provided, verify we're in the right place
|
|
338
|
+
if subreddit and submission.subreddit.display_name.lower() != subreddit.lower():
|
|
339
|
+
error_msg = f"Error: Post ID belongs to r/{submission.subreddit.display_name}, not r/{subreddit}"
|
|
340
|
+
logger.error(error_msg)
|
|
341
|
+
return error_msg
|
|
342
|
+
|
|
343
|
+
# Create the reply
|
|
344
|
+
log_debug(f"Attempting to post reply with content length: {len(content)}")
|
|
345
|
+
reply = submission.reply(body=content)
|
|
346
|
+
|
|
347
|
+
# Prepare the response information
|
|
348
|
+
reply_info: Dict[str, Union[str, int, float]] = {
|
|
349
|
+
"id": reply.id,
|
|
350
|
+
"body": reply.body,
|
|
351
|
+
"score": reply.score,
|
|
352
|
+
"permalink": reply.permalink,
|
|
353
|
+
"created_utc": reply.created_utc,
|
|
354
|
+
"author": str(reply.author),
|
|
355
|
+
"parent_id": reply.parent_id,
|
|
356
|
+
"submission_id": submission.id,
|
|
357
|
+
"subreddit": str(reply.subreddit),
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
log_debug(f"Reply created successfully: {reply.permalink}")
|
|
361
|
+
return json.dumps({"reply": reply_info})
|
|
362
|
+
|
|
363
|
+
except praw.exceptions.RedditAPIException as api_error:
|
|
364
|
+
# Handle specific Reddit API errors
|
|
365
|
+
error_messages = [f"{error.error_type}: {error.message}" for error in api_error.items]
|
|
366
|
+
error_msg = f"Reddit API Error: {'; '.join(error_messages)}"
|
|
367
|
+
logger.error(error_msg)
|
|
368
|
+
return error_msg
|
|
369
|
+
|
|
370
|
+
except Exception as e:
|
|
371
|
+
error_msg = f"Error creating reply: {str(e)}"
|
|
372
|
+
logger.error(error_msg)
|
|
373
|
+
return error_msg
|
|
374
|
+
|
|
375
|
+
def reply_to_comment(self, comment_id: str, content: str, subreddit: Optional[str] = None) -> str:
|
|
376
|
+
"""
|
|
377
|
+
Post a reply to an existing Reddit comment.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
comment_id (str): The ID of the comment to reply to.
|
|
381
|
+
Can be a full URL, permalink, or just the ID.
|
|
382
|
+
content (str): The content of the reply.
|
|
383
|
+
subreddit (Optional[str]): The subreddit name if known.
|
|
384
|
+
This helps with error handling and validation.
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
str: JSON string containing information about the created reply.
|
|
388
|
+
"""
|
|
389
|
+
if not self.reddit:
|
|
390
|
+
logger.error("Reddit instance not initialized")
|
|
391
|
+
return "Please provide Reddit API credentials"
|
|
392
|
+
|
|
393
|
+
if not self._check_user_auth():
|
|
394
|
+
logger.error("User authentication failed")
|
|
395
|
+
return "User authentication required for posting replies. Please provide username and password."
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
log_debug(f"Creating reply to comment {comment_id}")
|
|
399
|
+
|
|
400
|
+
# Clean up the comment_id if it's a full URL or permalink
|
|
401
|
+
if "/" in comment_id:
|
|
402
|
+
original_id = comment_id
|
|
403
|
+
comment_id = comment_id.split("/")[-1]
|
|
404
|
+
log_info(f"Extracted comment ID {comment_id} from {original_id}")
|
|
405
|
+
|
|
406
|
+
# Get the comment object
|
|
407
|
+
comment = self.reddit.comment(id=comment_id)
|
|
408
|
+
|
|
409
|
+
log_debug(f"Comment details: Author: {comment.author}, Subreddit: {comment.subreddit.display_name}")
|
|
410
|
+
|
|
411
|
+
# If subreddit was provided, verify we're in the right place
|
|
412
|
+
if subreddit and comment.subreddit.display_name.lower() != subreddit.lower():
|
|
413
|
+
error_msg = f"Error: Comment ID belongs to r/{comment.subreddit.display_name}, not r/{subreddit}"
|
|
414
|
+
logger.error(error_msg)
|
|
415
|
+
return error_msg
|
|
416
|
+
|
|
417
|
+
# Create the reply
|
|
418
|
+
log_debug(f"Attempting to post reply with content length: {len(content)}")
|
|
419
|
+
reply = comment.reply(body=content)
|
|
420
|
+
|
|
421
|
+
# Prepare the response information
|
|
422
|
+
reply_info: Dict[str, Union[str, int, float]] = {
|
|
423
|
+
"id": reply.id,
|
|
424
|
+
"body": reply.body,
|
|
425
|
+
"score": reply.score,
|
|
426
|
+
"permalink": reply.permalink,
|
|
427
|
+
"created_utc": reply.created_utc,
|
|
428
|
+
"author": str(reply.author),
|
|
429
|
+
"parent_id": reply.parent_id,
|
|
430
|
+
"submission_id": comment.submission.id,
|
|
431
|
+
"subreddit": str(reply.subreddit),
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
log_debug(f"Reply created successfully: {reply.permalink}")
|
|
435
|
+
return json.dumps({"reply": reply_info})
|
|
436
|
+
|
|
437
|
+
except praw.exceptions.RedditAPIException as api_error:
|
|
438
|
+
# Handle specific Reddit API errors
|
|
439
|
+
error_messages = [f"{error.error_type}: {error.message}" for error in api_error.items]
|
|
440
|
+
error_msg = f"Reddit API Error: {'; '.join(error_messages)}"
|
|
441
|
+
logger.error(error_msg)
|
|
442
|
+
return error_msg
|
|
443
|
+
|
|
444
|
+
except Exception as e:
|
|
445
|
+
error_msg = f"Error creating reply: {str(e)}"
|
|
446
|
+
logger.error(error_msg)
|
|
447
|
+
return error_msg
|
|
448
|
+
|
|
449
|
+
def _check_post_exists(self, post_id: str) -> bool:
|
|
450
|
+
"""
|
|
451
|
+
Verify that a post exists and is accessible.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
post_id (str): The ID of the post to check
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
bool: True if post exists and is accessible, False otherwise
|
|
458
|
+
"""
|
|
459
|
+
try:
|
|
460
|
+
submission = self.reddit.submission(id=post_id)
|
|
461
|
+
# Try to access some attributes to verify the post exists
|
|
462
|
+
_ = submission.title
|
|
463
|
+
_ = submission.author
|
|
464
|
+
return True
|
|
465
|
+
except Exception as e:
|
|
466
|
+
logger.error(f"Error checking post existence: {str(e)}")
|
|
467
|
+
return False
|
agno/tools/replicate.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from os import getenv
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Iterable, Iterator, List, Optional, Tuple, Union
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from agno.agent import Agent
|
|
8
|
+
from agno.media import Image, Video
|
|
9
|
+
from agno.team.team import Team
|
|
10
|
+
from agno.tools import Toolkit
|
|
11
|
+
from agno.tools.function import ToolResult
|
|
12
|
+
from agno.utils.log import logger
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import replicate
|
|
16
|
+
from replicate.helpers import FileOutput
|
|
17
|
+
except ImportError:
|
|
18
|
+
raise ImportError("`replicate` not installed. Please install using `pip install replicate`.")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ReplicateTools(Toolkit):
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
api_key: Optional[str] = None,
|
|
25
|
+
model: str = "minimax/video-01",
|
|
26
|
+
enable_generate_media: bool = True,
|
|
27
|
+
all: bool = False,
|
|
28
|
+
**kwargs,
|
|
29
|
+
):
|
|
30
|
+
self.api_key = api_key or getenv("REPLICATE_API_KEY")
|
|
31
|
+
if not self.api_key:
|
|
32
|
+
logger.error("REPLICATE_API_KEY not set. Please set the REPLICATE_API_KEY environment variable.")
|
|
33
|
+
self.model = model
|
|
34
|
+
|
|
35
|
+
tools: List[Any] = []
|
|
36
|
+
if all or enable_generate_media:
|
|
37
|
+
tools.append(self.generate_media)
|
|
38
|
+
|
|
39
|
+
super().__init__(name="replicate_toolkit", tools=tools, **kwargs)
|
|
40
|
+
|
|
41
|
+
def generate_media(self, agent: Union[Agent, Team], prompt: str) -> ToolResult:
|
|
42
|
+
"""
|
|
43
|
+
Use this function to generate an image or a video using a replicate model.
|
|
44
|
+
Args:
|
|
45
|
+
prompt (str): A text description of the content.
|
|
46
|
+
Returns:
|
|
47
|
+
ToolResult: A ToolResult containing the generated media or error message.
|
|
48
|
+
"""
|
|
49
|
+
if not self.api_key:
|
|
50
|
+
logger.error("API key is not set. Please provide a valid API key.")
|
|
51
|
+
return ToolResult(content="API key is not set.")
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
outputs = replicate.run(ref=self.model, input={"prompt": prompt})
|
|
55
|
+
if isinstance(outputs, FileOutput):
|
|
56
|
+
outputs = [outputs]
|
|
57
|
+
elif isinstance(outputs, (Iterable, Iterator)) and not isinstance(outputs, str):
|
|
58
|
+
outputs = list(outputs)
|
|
59
|
+
else:
|
|
60
|
+
logger.error(f"Unexpected output type: {type(outputs)}")
|
|
61
|
+
return ToolResult(content=f"Unexpected output type: {type(outputs)}")
|
|
62
|
+
|
|
63
|
+
images = []
|
|
64
|
+
videos = []
|
|
65
|
+
results = []
|
|
66
|
+
|
|
67
|
+
for output in outputs:
|
|
68
|
+
if not isinstance(output, FileOutput):
|
|
69
|
+
logger.error(f"Unexpected output type: {type(output)}")
|
|
70
|
+
return ToolResult(content=f"Unexpected output type: {type(output)}")
|
|
71
|
+
|
|
72
|
+
result_msg, media_artifact = self._parse_output(output)
|
|
73
|
+
results.append(result_msg)
|
|
74
|
+
|
|
75
|
+
if isinstance(media_artifact, Image):
|
|
76
|
+
images.append(media_artifact)
|
|
77
|
+
elif isinstance(media_artifact, Video):
|
|
78
|
+
videos.append(media_artifact)
|
|
79
|
+
|
|
80
|
+
content = "\n".join(results)
|
|
81
|
+
return ToolResult(
|
|
82
|
+
content=content,
|
|
83
|
+
images=images if images else None,
|
|
84
|
+
videos=videos if videos else None,
|
|
85
|
+
)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Failed to generate media: {e}")
|
|
88
|
+
return ToolResult(content=f"Error: {e}")
|
|
89
|
+
|
|
90
|
+
def _parse_output(self, output: FileOutput) -> Tuple[str, Union[Image, Video]]:
|
|
91
|
+
"""
|
|
92
|
+
Parse the outputs from the replicate model.
|
|
93
|
+
"""
|
|
94
|
+
# Parse the URL to extract the file extension
|
|
95
|
+
parsed_url = urlparse(output.url)
|
|
96
|
+
path = parsed_url.path
|
|
97
|
+
ext = Path(path).suffix.lower()
|
|
98
|
+
|
|
99
|
+
# Define supported extensions
|
|
100
|
+
image_extensions = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp"}
|
|
101
|
+
video_extensions = {".mp4", ".mov", ".avi", ".mkv", ".flv", ".wmv", ".webm"}
|
|
102
|
+
|
|
103
|
+
media_id = str(uuid4())
|
|
104
|
+
artifact: Union[Image, Video]
|
|
105
|
+
media_type: str
|
|
106
|
+
|
|
107
|
+
if ext in image_extensions:
|
|
108
|
+
artifact = Image(id=media_id, url=output.url)
|
|
109
|
+
media_type = "image"
|
|
110
|
+
elif ext in video_extensions:
|
|
111
|
+
artifact = Video(id=media_id, url=output.url)
|
|
112
|
+
media_type = "video"
|
|
113
|
+
else:
|
|
114
|
+
logger.error(f"Unsupported media type with extension '{ext}' for URL: {output.url}")
|
|
115
|
+
raise ValueError(f"Unsupported media type with extension '{ext}'.")
|
|
116
|
+
|
|
117
|
+
return f"{media_type.capitalize()} generated successfully at {output.url}", artifact
|
agno/tools/resend.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from os import getenv
|
|
2
|
+
from typing import Any, List, Optional
|
|
3
|
+
|
|
4
|
+
from agno.tools import Toolkit
|
|
5
|
+
from agno.utils.log import log_info, logger
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import resend # type: ignore
|
|
9
|
+
except ImportError:
|
|
10
|
+
raise ImportError("`resend` not installed. Please install using `pip install resend`.")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ResendTools(Toolkit):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
api_key: Optional[str] = None,
|
|
17
|
+
from_email: Optional[str] = None,
|
|
18
|
+
enable_send_email: bool = True,
|
|
19
|
+
all: bool = False,
|
|
20
|
+
**kwargs,
|
|
21
|
+
):
|
|
22
|
+
self.from_email = from_email
|
|
23
|
+
self.api_key = api_key or getenv("RESEND_API_KEY")
|
|
24
|
+
if not self.api_key:
|
|
25
|
+
logger.error("No Resend API key provided")
|
|
26
|
+
|
|
27
|
+
tools: List[Any] = []
|
|
28
|
+
if all or enable_send_email:
|
|
29
|
+
tools.append(self.send_email)
|
|
30
|
+
|
|
31
|
+
super().__init__(name="resend_tools", tools=tools, **kwargs)
|
|
32
|
+
|
|
33
|
+
def send_email(self, to_email: str, subject: str, body: str) -> str:
|
|
34
|
+
"""Send an email using the Resend API. Returns if the email was sent successfully or an error message.
|
|
35
|
+
|
|
36
|
+
:to_email: The email address to send the email to.
|
|
37
|
+
:subject: The subject of the email.
|
|
38
|
+
:body: The body of the email.
|
|
39
|
+
:return: A string indicating if the email was sent successfully or an error message.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
if not self.api_key:
|
|
43
|
+
return "Please provide an API key"
|
|
44
|
+
if not to_email:
|
|
45
|
+
return "Please provide an email address to send the email to"
|
|
46
|
+
|
|
47
|
+
log_info(f"Sending email to: {to_email}")
|
|
48
|
+
|
|
49
|
+
resend.api_key = self.api_key
|
|
50
|
+
try:
|
|
51
|
+
params = {
|
|
52
|
+
"from": self.from_email,
|
|
53
|
+
"to": to_email,
|
|
54
|
+
"subject": subject,
|
|
55
|
+
"html": body,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
resend.Emails.send(params)
|
|
59
|
+
return f"Email sent to {to_email} successfully."
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.error(f"Failed to send email {e}")
|
|
62
|
+
return f"Error: {e}"
|