agno 2.2.13__py3-none-any.whl → 2.4.3__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/agent/__init__.py +6 -0
- agno/agent/agent.py +5252 -3145
- agno/agent/remote.py +525 -0
- agno/api/api.py +2 -0
- agno/client/__init__.py +3 -0
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/client/os.py +2669 -0
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/manager.py +2 -2
- agno/db/base.py +927 -6
- agno/db/dynamo/dynamo.py +788 -2
- agno/db/dynamo/schemas.py +128 -0
- agno/db/dynamo/utils.py +26 -3
- agno/db/firestore/firestore.py +674 -50
- agno/db/firestore/schemas.py +41 -0
- agno/db/firestore/utils.py +25 -10
- agno/db/gcs_json/gcs_json_db.py +506 -3
- agno/db/gcs_json/utils.py +14 -2
- agno/db/in_memory/in_memory_db.py +203 -4
- agno/db/in_memory/utils.py +14 -2
- agno/db/json/json_db.py +498 -2
- agno/db/json/utils.py +14 -2
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +977 -0
- agno/db/mongo/async_mongo.py +1013 -39
- agno/db/mongo/mongo.py +684 -4
- agno/db/mongo/schemas.py +48 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2958 -0
- agno/db/mysql/mysql.py +722 -53
- agno/db/mysql/schemas.py +77 -11
- agno/db/mysql/utils.py +151 -8
- agno/db/postgres/async_postgres.py +1254 -137
- agno/db/postgres/postgres.py +2316 -93
- agno/db/postgres/schemas.py +153 -21
- agno/db/postgres/utils.py +22 -7
- agno/db/redis/redis.py +531 -3
- agno/db/redis/schemas.py +36 -0
- agno/db/redis/utils.py +31 -15
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +20 -9
- agno/db/singlestore/schemas.py +70 -1
- agno/db/singlestore/singlestore.py +737 -74
- agno/db/singlestore/utils.py +13 -3
- agno/db/sqlite/async_sqlite.py +1069 -89
- agno/db/sqlite/schemas.py +133 -1
- agno/db/sqlite/sqlite.py +2203 -165
- agno/db/sqlite/utils.py +21 -11
- agno/db/surrealdb/models.py +25 -0
- agno/db/surrealdb/surrealdb.py +603 -1
- agno/db/utils.py +60 -0
- agno/eval/__init__.py +26 -3
- agno/eval/accuracy.py +25 -12
- agno/eval/agent_as_judge.py +871 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +10 -4
- agno/eval/reliability.py +22 -13
- agno/eval/utils.py +2 -1
- agno/exceptions.py +42 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +13 -2
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +90 -0
- agno/knowledge/chunking/document.py +65 -4
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/markdown.py +102 -11
- agno/knowledge/chunking/recursive.py +2 -2
- agno/knowledge/chunking/semantic.py +130 -48
- agno/knowledge/chunking/strategy.py +18 -0
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/knowledge/embedder/mistral.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/openai.py +16 -12
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +4261 -1199
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +3 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +91 -42
- agno/knowledge/reader/docx_reader.py +9 -10
- agno/knowledge/reader/excel_reader.py +225 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
- agno/knowledge/reader/firecrawl_reader.py +3 -2
- agno/knowledge/reader/json_reader.py +16 -22
- agno/knowledge/reader/markdown_reader.py +15 -14
- agno/knowledge/reader/pdf_reader.py +33 -28
- agno/knowledge/reader/pptx_reader.py +9 -10
- agno/knowledge/reader/reader_factory.py +135 -1
- agno/knowledge/reader/s3_reader.py +8 -16
- agno/knowledge/reader/tavily_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +15 -14
- agno/knowledge/reader/utils/__init__.py +17 -0
- agno/knowledge/reader/utils/spreadsheet.py +114 -0
- agno/knowledge/reader/web_search_reader.py +8 -65
- agno/knowledge/reader/website_reader.py +16 -13
- agno/knowledge/reader/wikipedia_reader.py +36 -3
- agno/knowledge/reader/youtube_reader.py +3 -2
- agno/knowledge/remote_content/__init__.py +33 -0
- agno/knowledge/remote_content/config.py +266 -0
- agno/knowledge/remote_content/remote_content.py +105 -17
- agno/knowledge/utils.py +76 -22
- agno/learn/__init__.py +71 -0
- agno/learn/config.py +463 -0
- agno/learn/curate.py +185 -0
- agno/learn/machine.py +725 -0
- agno/learn/schemas.py +1114 -0
- agno/learn/stores/__init__.py +38 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/entity_memory.py +3275 -0
- agno/learn/stores/learned_knowledge.py +1583 -0
- agno/learn/stores/protocol.py +117 -0
- agno/learn/stores/session_context.py +1217 -0
- agno/learn/stores/user_memory.py +1495 -0
- agno/learn/stores/user_profile.py +1220 -0
- agno/learn/utils.py +209 -0
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +223 -8
- 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/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +434 -59
- agno/models/aws/bedrock.py +121 -20
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +10 -6
- agno/models/azure/openai_chat.py +33 -10
- agno/models/base.py +1162 -561
- agno/models/cerebras/cerebras.py +120 -24
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +65 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +959 -89
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +48 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +88 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +24 -5
- agno/models/meta/llama.py +40 -13
- agno/models/meta/llama_openai.py +22 -21
- agno/models/metrics.py +12 -0
- agno/models/mistral/mistral.py +8 -4
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/__init__.py +2 -0
- agno/models/ollama/chat.py +17 -6
- agno/models/ollama/responses.py +100 -0
- agno/models/openai/__init__.py +2 -0
- agno/models/openai/chat.py +117 -26
- agno/models/openai/open_responses.py +46 -0
- agno/models/openai/responses.py +110 -32
- agno/models/openrouter/__init__.py +2 -0
- agno/models/openrouter/openrouter.py +67 -2
- agno/models/openrouter/responses.py +146 -0
- agno/models/perplexity/perplexity.py +19 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +19 -2
- agno/models/response.py +20 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/claude.py +124 -4
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +467 -137
- agno/os/auth.py +253 -5
- agno/os/config.py +22 -0
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +635 -26
- agno/os/interfaces/a2a/utils.py +32 -33
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +26 -16
- agno/os/interfaces/agui/utils.py +97 -57
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +16 -7
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +35 -7
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +652 -79
- agno/os/middleware/__init__.py +4 -0
- agno/os/middleware/jwt.py +718 -115
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +105 -1558
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +655 -0
- agno/os/routers/agents/schema.py +288 -0
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/database.py +155 -0
- agno/os/routers/evals/evals.py +111 -18
- agno/os/routers/evals/schemas.py +38 -5
- agno/os/routers/evals/utils.py +80 -11
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +284 -35
- agno/os/routers/knowledge/schemas.py +14 -2
- agno/os/routers/memory/memory.py +274 -11
- agno/os/routers/memory/schemas.py +44 -3
- agno/os/routers/metrics/metrics.py +30 -15
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/session/session.py +143 -14
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +550 -0
- agno/os/routers/teams/schema.py +280 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +549 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +757 -0
- agno/os/routers/workflows/schema.py +139 -0
- agno/os/schema.py +157 -584
- agno/os/scopes.py +469 -0
- agno/os/settings.py +3 -0
- agno/os/utils.py +574 -185
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +102 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +87 -3
- agno/reasoning/groq.py +109 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +1238 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +115 -1
- agno/reasoning/vertexai.py +85 -1
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +581 -0
- agno/run/__init__.py +2 -4
- agno/run/agent.py +134 -19
- agno/run/base.py +49 -1
- agno/run/cancel.py +65 -52
- agno/run/cancellation_management/__init__.py +9 -0
- agno/run/cancellation_management/base.py +78 -0
- agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
- agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +111 -19
- agno/run/workflow.py +2 -1
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -115
- agno/session/workflow.py +353 -57
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +377 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/table.py +10 -0
- agno/team/__init__.py +5 -1
- agno/team/remote.py +447 -0
- agno/team/team.py +3769 -2202
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +225 -16
- agno/tools/crawl4ai.py +3 -0
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/file.py +14 -13
- agno/tools/file_generation.py +12 -6
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +94 -113
- agno/tools/google_bigquery.py +11 -2
- agno/tools/google_drive.py +4 -3
- agno/tools/knowledge.py +9 -4
- agno/tools/mcp/mcp.py +301 -18
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/parallel.py +0 -7
- agno/tools/postgres.py +76 -36
- agno/tools/python.py +14 -6
- agno/tools/reasoning.py +30 -23
- agno/tools/redshift.py +406 -0
- agno/tools/shopify.py +1519 -0
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +4 -1
- agno/tools/toolkit.py +253 -18
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +56 -48
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +161 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +112 -0
- agno/utils/agent.py +251 -10
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +264 -7
- agno/utils/hooks.py +111 -3
- agno/utils/http.py +161 -2
- agno/utils/mcp.py +49 -8
- agno/utils/media.py +22 -1
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +20 -5
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/os.py +0 -0
- agno/utils/print_response/agent.py +99 -16
- agno/utils/print_response/team.py +223 -24
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/prompts.py +8 -6
- agno/utils/remote.py +23 -0
- agno/utils/response.py +1 -13
- agno/utils/string.py +91 -2
- agno/utils/team.py +62 -12
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +15 -2
- agno/vectordb/cassandra/cassandra.py +1 -1
- agno/vectordb/chroma/__init__.py +2 -1
- agno/vectordb/chroma/chromadb.py +468 -23
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/vectordb/lancedb/lance_db.py +7 -38
- agno/vectordb/lightrag/lightrag.py +7 -6
- agno/vectordb/milvus/milvus.py +118 -84
- agno/vectordb/mongodb/__init__.py +2 -1
- agno/vectordb/mongodb/mongodb.py +14 -31
- agno/vectordb/pgvector/pgvector.py +120 -66
- agno/vectordb/pineconedb/pineconedb.py +2 -19
- agno/vectordb/qdrant/__init__.py +2 -1
- agno/vectordb/qdrant/qdrant.py +33 -56
- agno/vectordb/redis/__init__.py +2 -1
- agno/vectordb/redis/redisdb.py +19 -31
- agno/vectordb/singlestore/singlestore.py +17 -9
- agno/vectordb/surrealdb/surrealdb.py +2 -38
- agno/vectordb/weaviate/__init__.py +2 -1
- agno/vectordb/weaviate/weaviate.py +7 -3
- agno/workflow/__init__.py +5 -1
- agno/workflow/agent.py +2 -2
- agno/workflow/condition.py +12 -10
- agno/workflow/loop.py +28 -9
- agno/workflow/parallel.py +21 -13
- agno/workflow/remote.py +362 -0
- agno/workflow/router.py +12 -9
- agno/workflow/step.py +261 -36
- agno/workflow/steps.py +12 -8
- agno/workflow/types.py +40 -77
- agno/workflow/workflow.py +939 -213
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
- agno-2.4.3.dist-info/RECORD +677 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
- agno/tools/googlesearch.py +0 -98
- agno/tools/memori.py +0 -339
- agno-2.2.13.dist-info/RECORD +0 -575
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/tools/tavily.py
CHANGED
|
@@ -15,6 +15,7 @@ class TavilyTools(Toolkit):
|
|
|
15
15
|
def __init__(
|
|
16
16
|
self,
|
|
17
17
|
api_key: Optional[str] = None,
|
|
18
|
+
api_base_url: Optional[str] = None,
|
|
18
19
|
enable_search: bool = True,
|
|
19
20
|
enable_search_context: bool = False,
|
|
20
21
|
enable_extract: bool = False,
|
|
@@ -34,6 +35,7 @@ class TavilyTools(Toolkit):
|
|
|
34
35
|
|
|
35
36
|
Args:
|
|
36
37
|
api_key: Tavily API key. If not provided, will use TAVILY_API_KEY env var.
|
|
38
|
+
api_base_url: Tavily API base URL. If not provided, will use TAVILY_API_BASE_URL env var. Defaults to None. If None - will use https://api.tavily.com.
|
|
37
39
|
enable_search: Enable web search functionality. Defaults to True.
|
|
38
40
|
enable_search_context: Use search context mode instead of regular search. Defaults to False.
|
|
39
41
|
enable_extract: Enable URL content extraction functionality. Defaults to False.
|
|
@@ -52,8 +54,9 @@ class TavilyTools(Toolkit):
|
|
|
52
54
|
self.api_key = api_key or getenv("TAVILY_API_KEY")
|
|
53
55
|
if not self.api_key:
|
|
54
56
|
logger.error("TAVILY_API_KEY not provided")
|
|
57
|
+
self.api_base_url = api_base_url or getenv("TAVILY_API_BASE_URL")
|
|
55
58
|
|
|
56
|
-
self.client: TavilyClient = TavilyClient(api_key=self.api_key)
|
|
59
|
+
self.client: TavilyClient = TavilyClient(api_key=self.api_key, api_base_url=self.api_base_url)
|
|
57
60
|
self.search_depth: Literal["basic", "advanced"] = search_depth
|
|
58
61
|
self.extract_depth: Literal["basic", "advanced"] = extract_depth
|
|
59
62
|
self.max_tokens: int = max_tokens
|
agno/tools/toolkit.py
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
from collections import OrderedDict
|
|
2
|
-
from
|
|
2
|
+
from inspect import iscoroutinefunction
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union
|
|
3
5
|
|
|
4
6
|
from agno.tools.function import Function
|
|
5
|
-
from agno.utils.log import log_debug, log_warning, logger
|
|
7
|
+
from agno.utils.log import log_debug, log_error, log_warning, logger
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class Toolkit:
|
|
11
|
+
# Set to True for toolkits that require connection management (e.g., database connections)
|
|
12
|
+
# When True, the Agent will automatically call connect() before using tools and close() after
|
|
13
|
+
_requires_connect: bool = False
|
|
14
|
+
|
|
9
15
|
def __init__(
|
|
10
16
|
self,
|
|
11
17
|
name: str = "toolkit",
|
|
12
|
-
tools:
|
|
18
|
+
tools: Sequence[Union[Callable[..., Any], Function]] = [],
|
|
19
|
+
async_tools: Optional[Sequence[tuple[Callable[..., Any], str]]] = None,
|
|
13
20
|
instructions: Optional[str] = None,
|
|
14
21
|
add_instructions: bool = False,
|
|
15
22
|
include_tools: Optional[list[str]] = None,
|
|
@@ -27,7 +34,10 @@ class Toolkit:
|
|
|
27
34
|
|
|
28
35
|
Args:
|
|
29
36
|
name: A descriptive name for the toolkit
|
|
30
|
-
tools: List of tools to include in the toolkit
|
|
37
|
+
tools: List of tools to include in the toolkit (can be callables or Function objects from @tool decorator)
|
|
38
|
+
async_tools: List of (async_callable, tool_name) tuples for async variants.
|
|
39
|
+
Used when async methods have different names than sync methods.
|
|
40
|
+
Example: [(self.anavigate_to, "navigate_to"), (self.ascreenshot, "screenshot")]
|
|
31
41
|
instructions: Instructions for the toolkit
|
|
32
42
|
add_instructions: Whether to add instructions to the toolkit
|
|
33
43
|
include_tools: List of tool names to include in the toolkit
|
|
@@ -42,8 +52,12 @@ class Toolkit:
|
|
|
42
52
|
show_result_tools (Optional[List[str]]): List of function names whose results should be shown.
|
|
43
53
|
"""
|
|
44
54
|
self.name: str = name
|
|
45
|
-
self.tools:
|
|
55
|
+
self.tools: Sequence[Union[Callable[..., Any], Function]] = tools
|
|
56
|
+
self._async_tools: Sequence[tuple[Callable[..., Any], str]] = async_tools or []
|
|
57
|
+
# Functions dict - used by agent.run() and agent.print_response()
|
|
46
58
|
self.functions: Dict[str, Function] = OrderedDict()
|
|
59
|
+
# Async functions dict - used by agent.arun() and agent.aprint_response()
|
|
60
|
+
self.async_functions: Dict[str, Function] = OrderedDict()
|
|
47
61
|
self.instructions: Optional[str] = instructions
|
|
48
62
|
self.add_instructions: bool = add_instructions
|
|
49
63
|
|
|
@@ -54,7 +68,9 @@ class Toolkit:
|
|
|
54
68
|
self.show_result_tools: list[str] = show_result_tools or []
|
|
55
69
|
|
|
56
70
|
self._check_tools_filters(
|
|
57
|
-
available_tools=[tool
|
|
71
|
+
available_tools=[self._get_tool_name(tool) for tool in tools],
|
|
72
|
+
include_tools=include_tools,
|
|
73
|
+
exclude_tools=exclude_tools,
|
|
58
74
|
)
|
|
59
75
|
|
|
60
76
|
self.include_tools = include_tools
|
|
@@ -65,8 +81,17 @@ class Toolkit:
|
|
|
65
81
|
self.cache_dir: Optional[str] = cache_dir
|
|
66
82
|
|
|
67
83
|
# Automatically register all methods if auto_register is True
|
|
68
|
-
if auto_register
|
|
69
|
-
self.
|
|
84
|
+
if auto_register:
|
|
85
|
+
if self.tools:
|
|
86
|
+
self._register_tools()
|
|
87
|
+
if self._async_tools:
|
|
88
|
+
self._register_async_tools()
|
|
89
|
+
|
|
90
|
+
def _get_tool_name(self, tool: Union[Callable[..., Any], Function]) -> str:
|
|
91
|
+
"""Get the name of a tool, whether it's a Function or callable."""
|
|
92
|
+
if isinstance(tool, Function):
|
|
93
|
+
return tool.name
|
|
94
|
+
return tool.__name__
|
|
70
95
|
|
|
71
96
|
def _check_tools_filters(
|
|
72
97
|
self,
|
|
@@ -100,22 +125,57 @@ class Toolkit:
|
|
|
100
125
|
f"External execution required tool(s) not present in the toolkit: {', '.join(missing_external_execution_required)}"
|
|
101
126
|
)
|
|
102
127
|
|
|
128
|
+
if self.stop_after_tool_call_tools:
|
|
129
|
+
missing_stop_after_tool_call = set(self.stop_after_tool_call_tools) - set(available_tools)
|
|
130
|
+
if missing_stop_after_tool_call:
|
|
131
|
+
log_warning(
|
|
132
|
+
f"Stop after tool call tool(s) not present in the toolkit: {', '.join(missing_stop_after_tool_call)}"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if self.show_result_tools:
|
|
136
|
+
missing_show_result = set(self.show_result_tools) - set(available_tools)
|
|
137
|
+
if missing_show_result:
|
|
138
|
+
log_warning(f"Show result tool(s) not present in the toolkit: {', '.join(missing_show_result)}")
|
|
139
|
+
|
|
103
140
|
def _register_tools(self) -> None:
|
|
104
|
-
"""Register all tools."""
|
|
141
|
+
"""Register all sync tools."""
|
|
105
142
|
for tool in self.tools:
|
|
106
143
|
self.register(tool)
|
|
107
144
|
|
|
108
|
-
def
|
|
145
|
+
def _register_async_tools(self) -> None:
|
|
146
|
+
"""Register all async tools with their mapped names.
|
|
147
|
+
|
|
148
|
+
Async detection is automatic via iscoroutinefunction.
|
|
149
|
+
"""
|
|
150
|
+
for async_func, tool_name in self._async_tools:
|
|
151
|
+
self.register(async_func, name=tool_name)
|
|
152
|
+
|
|
153
|
+
def register(self, function: Union[Callable[..., Any], Function], name: Optional[str] = None) -> None:
|
|
109
154
|
"""Register a function with the toolkit.
|
|
110
155
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
156
|
+
This method supports both regular callables and Function objects (from @tool decorator).
|
|
157
|
+
Automatically detects if the function is async (using iscoroutinefunction) and registers
|
|
158
|
+
it to the appropriate dict (functions for sync, async_functions for async).
|
|
114
159
|
|
|
115
|
-
|
|
116
|
-
|
|
160
|
+
When a Function object is passed (e.g., from a @tool decorated method), it will:
|
|
161
|
+
1. Extract the configuration from the Function object
|
|
162
|
+
2. Look for a bound method with the same name on `self`
|
|
163
|
+
3. Create a new Function with the bound method as entrypoint, preserving decorator settings
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
function: The callable or Function object to register
|
|
167
|
+
name: Optional custom name for the function (useful for aliasing)
|
|
117
168
|
"""
|
|
118
169
|
try:
|
|
170
|
+
# Handle Function objects (from @tool decorator)
|
|
171
|
+
if isinstance(function, Function):
|
|
172
|
+
# Auto-detect if this is an async function
|
|
173
|
+
is_async = function.entrypoint is not None and iscoroutinefunction(function.entrypoint)
|
|
174
|
+
return self._register_decorated_tool(function, name, is_async=is_async)
|
|
175
|
+
|
|
176
|
+
# Handle regular callables - auto-detect async
|
|
177
|
+
is_async = iscoroutinefunction(function)
|
|
178
|
+
|
|
119
179
|
tool_name = name or function.__name__
|
|
120
180
|
if self.include_tools is not None and tool_name not in self.include_tools:
|
|
121
181
|
return
|
|
@@ -133,12 +193,187 @@ class Toolkit:
|
|
|
133
193
|
stop_after_tool_call=tool_name in self.stop_after_tool_call_tools,
|
|
134
194
|
show_result=tool_name in self.show_result_tools or tool_name in self.stop_after_tool_call_tools,
|
|
135
195
|
)
|
|
136
|
-
|
|
137
|
-
|
|
196
|
+
|
|
197
|
+
if is_async:
|
|
198
|
+
self.async_functions[f.name] = f
|
|
199
|
+
log_debug(f"Async function: {f.name} registered with {self.name}")
|
|
200
|
+
else:
|
|
201
|
+
self.functions[f.name] = f
|
|
202
|
+
log_debug(f"Function: {f.name} registered with {self.name}")
|
|
138
203
|
except Exception as e:
|
|
139
|
-
|
|
204
|
+
func_name = self._get_tool_name(function)
|
|
205
|
+
logger.warning(f"Failed to create Function for: {func_name}")
|
|
140
206
|
raise e
|
|
141
207
|
|
|
208
|
+
def _register_decorated_tool(self, function: Function, name: Optional[str] = None, is_async: bool = False) -> None:
|
|
209
|
+
"""Register a Function object from @tool decorator, binding it to self.
|
|
210
|
+
|
|
211
|
+
When @tool decorator is used on a class method, it creates a Function with an unbound
|
|
212
|
+
method as entrypoint. This method creates a bound version of the entrypoint that
|
|
213
|
+
includes `self`, preserving all decorator settings.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
function: The Function object from @tool decorator
|
|
217
|
+
name: Optional custom name override
|
|
218
|
+
is_async: If True, register to async_functions dict instead of functions
|
|
219
|
+
"""
|
|
220
|
+
import inspect
|
|
221
|
+
|
|
222
|
+
tool_name = name or function.name
|
|
223
|
+
if self.include_tools is not None and len(self.include_tools) > 0 and tool_name not in self.include_tools:
|
|
224
|
+
return
|
|
225
|
+
if self.exclude_tools is not None and len(self.exclude_tools) > 0 and tool_name in self.exclude_tools:
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
# Get the original entrypoint from the Function
|
|
229
|
+
if function.entrypoint is None:
|
|
230
|
+
log_warning(f"Function '{tool_name}' has no entrypoint, skipping registration")
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
original_func = function.entrypoint
|
|
234
|
+
|
|
235
|
+
# Check if the function expects 'self' as first argument (i.e., it's an unbound method)
|
|
236
|
+
sig = inspect.signature(original_func)
|
|
237
|
+
params = list(sig.parameters.keys())
|
|
238
|
+
|
|
239
|
+
if params and params[0] == "self":
|
|
240
|
+
# Create a bound method by wrapping the function to include self
|
|
241
|
+
if is_async:
|
|
242
|
+
|
|
243
|
+
def make_bound_method(func, instance):
|
|
244
|
+
async def bound(*args, **kwargs):
|
|
245
|
+
return await func(instance, *args, **kwargs)
|
|
246
|
+
|
|
247
|
+
bound.__name__ = getattr(func, "__name__", tool_name)
|
|
248
|
+
bound.__doc__ = getattr(func, "__doc__", None)
|
|
249
|
+
return bound
|
|
250
|
+
else:
|
|
251
|
+
|
|
252
|
+
def make_bound_method(func, instance):
|
|
253
|
+
def bound(*args, **kwargs):
|
|
254
|
+
return func(instance, *args, **kwargs)
|
|
255
|
+
|
|
256
|
+
bound.__name__ = getattr(func, "__name__", tool_name)
|
|
257
|
+
bound.__doc__ = getattr(func, "__doc__", None)
|
|
258
|
+
return bound
|
|
259
|
+
|
|
260
|
+
bound_method = make_bound_method(original_func, self)
|
|
261
|
+
else:
|
|
262
|
+
# Function doesn't expect self (e.g., static method or already bound)
|
|
263
|
+
bound_method = original_func
|
|
264
|
+
|
|
265
|
+
# decorator settings take precedence, then toolkit settings
|
|
266
|
+
stop_after = function.stop_after_tool_call or tool_name in self.stop_after_tool_call_tools
|
|
267
|
+
show_result = function.show_result or tool_name in self.show_result_tools or stop_after
|
|
268
|
+
requires_confirmation = function.requires_confirmation or tool_name in self.requires_confirmation_tools
|
|
269
|
+
external_execution = function.external_execution or tool_name in self.external_execution_required_tools
|
|
270
|
+
|
|
271
|
+
# Create new Function with bound method, preserving decorator settings
|
|
272
|
+
f = Function(
|
|
273
|
+
name=tool_name,
|
|
274
|
+
description=function.description,
|
|
275
|
+
parameters=function.parameters,
|
|
276
|
+
strict=function.strict,
|
|
277
|
+
instructions=function.instructions,
|
|
278
|
+
add_instructions=function.add_instructions,
|
|
279
|
+
entrypoint=bound_method,
|
|
280
|
+
skip_entrypoint_processing=True, # Parameters already processed by decorator
|
|
281
|
+
show_result=show_result,
|
|
282
|
+
stop_after_tool_call=stop_after,
|
|
283
|
+
pre_hook=function.pre_hook,
|
|
284
|
+
post_hook=function.post_hook,
|
|
285
|
+
tool_hooks=function.tool_hooks,
|
|
286
|
+
requires_confirmation=requires_confirmation,
|
|
287
|
+
requires_user_input=function.requires_user_input,
|
|
288
|
+
user_input_fields=function.user_input_fields,
|
|
289
|
+
user_input_schema=function.user_input_schema,
|
|
290
|
+
external_execution=external_execution,
|
|
291
|
+
cache_results=function.cache_results if function.cache_results else self.cache_results,
|
|
292
|
+
cache_dir=function.cache_dir if function.cache_dir else self.cache_dir,
|
|
293
|
+
cache_ttl=function.cache_ttl if function.cache_ttl != 3600 else self.cache_ttl,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if is_async:
|
|
297
|
+
self.async_functions[f.name] = f
|
|
298
|
+
log_debug(f"Async function: {f.name} registered with {self.name} (from @tool decorator)")
|
|
299
|
+
else:
|
|
300
|
+
self.functions[f.name] = f
|
|
301
|
+
log_debug(f"Function: {f.name} registered with {self.name} (from @tool decorator)")
|
|
302
|
+
|
|
303
|
+
def get_functions(self) -> Dict[str, Function]:
|
|
304
|
+
"""Get sync functions dict.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Dict of function name to Function for sync execution
|
|
308
|
+
"""
|
|
309
|
+
return self.functions
|
|
310
|
+
|
|
311
|
+
def get_async_functions(self) -> Dict[str, Function]:
|
|
312
|
+
"""Get functions dict optimized for async execution.
|
|
313
|
+
|
|
314
|
+
Returns a merged dict where async_functions take precedence over functions.
|
|
315
|
+
This allows async-optimized implementations to be automatically used in async contexts,
|
|
316
|
+
while falling back to sync implementations for tools without async variants.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Dict of function name to Function, with async variants preferred
|
|
320
|
+
"""
|
|
321
|
+
# Merge: start with sync functions, override with async variants
|
|
322
|
+
merged = OrderedDict(self.functions)
|
|
323
|
+
merged.update(self.async_functions)
|
|
324
|
+
return merged
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def requires_connect(self) -> bool:
|
|
328
|
+
"""Whether the toolkit requires connection management."""
|
|
329
|
+
return self._requires_connect
|
|
330
|
+
|
|
331
|
+
def connect(self) -> None:
|
|
332
|
+
"""
|
|
333
|
+
Establish any required connections for the toolkit.
|
|
334
|
+
Override this method in subclasses that require connection management.
|
|
335
|
+
Called automatically by the Agent when _requires_connect is True.
|
|
336
|
+
"""
|
|
337
|
+
pass
|
|
338
|
+
|
|
339
|
+
def close(self) -> None:
|
|
340
|
+
"""
|
|
341
|
+
Close any open connections for the toolkit.
|
|
342
|
+
Override this method in subclasses that require connection management.
|
|
343
|
+
Called automatically by the Agent when _requires_connect is True.
|
|
344
|
+
"""
|
|
345
|
+
pass
|
|
346
|
+
|
|
347
|
+
def _check_path(self, file_name: str, base_dir: Path, restrict_to_base_dir: bool = True) -> Tuple[bool, Path]:
|
|
348
|
+
"""Check if the file path is within the base directory.
|
|
349
|
+
|
|
350
|
+
This method validates that a given file path resolves to a location
|
|
351
|
+
within the specified base_dir, preventing directory traversal attacks.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
file_name: The file name or relative path to check.
|
|
355
|
+
base_dir: The base directory to validate against.
|
|
356
|
+
restrict_to_base_dir: If True, reject paths outside base_dir.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Tuple of (is_safe, resolved_path). If not safe, returns base_dir as the path.
|
|
360
|
+
"""
|
|
361
|
+
file_path = base_dir.joinpath(file_name).resolve()
|
|
362
|
+
|
|
363
|
+
if not restrict_to_base_dir:
|
|
364
|
+
return True, file_path
|
|
365
|
+
|
|
366
|
+
if base_dir == file_path:
|
|
367
|
+
return True, file_path
|
|
368
|
+
|
|
369
|
+
try:
|
|
370
|
+
file_path.relative_to(base_dir)
|
|
371
|
+
except ValueError:
|
|
372
|
+
log_error(f"Path escapes base directory: {file_name}")
|
|
373
|
+
return False, base_dir
|
|
374
|
+
|
|
375
|
+
return True, file_path
|
|
376
|
+
|
|
142
377
|
def __repr__(self):
|
|
143
378
|
return f"<{self.__class__.__name__} name={self.name} functions={list(self.functions.keys())}>"
|
|
144
379
|
|
agno/tools/websearch.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, List, Optional
|
|
3
|
+
|
|
4
|
+
from agno.tools import Toolkit
|
|
5
|
+
from agno.utils.log import log_debug
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from ddgs import DDGS
|
|
9
|
+
except ImportError:
|
|
10
|
+
raise ImportError("`ddgs` not installed. Please install using `pip install ddgs`")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WebSearchTools(Toolkit):
|
|
14
|
+
"""
|
|
15
|
+
Toolkit for searching the web. Uses the meta-search library DDGS.
|
|
16
|
+
Multiple search backends (e.g. google, bing, duckduckgo) are available.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
enable_search (bool): Enable web search function.
|
|
20
|
+
enable_news (bool): Enable news search function.
|
|
21
|
+
backend (str): The backend to use for searching. Defaults to "auto" which
|
|
22
|
+
automatically selects available backends. Other options include:
|
|
23
|
+
"duckduckgo", "google", "bing", "brave", "yandex", "yahoo", etc.
|
|
24
|
+
modifier (Optional[str]): A modifier to be prepended to search queries.
|
|
25
|
+
fixed_max_results (Optional[int]): A fixed number of maximum results.
|
|
26
|
+
proxy (Optional[str]): Proxy to be used for requests.
|
|
27
|
+
timeout (Optional[int]): The maximum number of seconds to wait for a response.
|
|
28
|
+
verify_ssl (bool): Whether to verify SSL certificates.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
enable_search: bool = True,
|
|
34
|
+
enable_news: bool = True,
|
|
35
|
+
backend: str = "auto",
|
|
36
|
+
modifier: Optional[str] = None,
|
|
37
|
+
fixed_max_results: Optional[int] = None,
|
|
38
|
+
proxy: Optional[str] = None,
|
|
39
|
+
timeout: Optional[int] = 10,
|
|
40
|
+
verify_ssl: bool = True,
|
|
41
|
+
**kwargs,
|
|
42
|
+
):
|
|
43
|
+
self.proxy: Optional[str] = proxy
|
|
44
|
+
self.timeout: Optional[int] = timeout
|
|
45
|
+
self.fixed_max_results: Optional[int] = fixed_max_results
|
|
46
|
+
self.modifier: Optional[str] = modifier
|
|
47
|
+
self.verify_ssl: bool = verify_ssl
|
|
48
|
+
self.backend: str = backend
|
|
49
|
+
|
|
50
|
+
tools: List[Any] = []
|
|
51
|
+
if enable_search:
|
|
52
|
+
tools.append(self.web_search)
|
|
53
|
+
if enable_news:
|
|
54
|
+
tools.append(self.search_news)
|
|
55
|
+
|
|
56
|
+
super().__init__(name="websearch", tools=tools, **kwargs)
|
|
57
|
+
|
|
58
|
+
def web_search(self, query: str, max_results: int = 5) -> str:
|
|
59
|
+
"""Use this function to search the web for a query.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
query(str): The query to search for.
|
|
63
|
+
max_results (optional, default=5): The maximum number of results to return.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
The search results from the web.
|
|
67
|
+
"""
|
|
68
|
+
actual_max_results = self.fixed_max_results or max_results
|
|
69
|
+
search_query = f"{self.modifier} {query}" if self.modifier else query
|
|
70
|
+
|
|
71
|
+
log_debug(f"Searching web for: {search_query} using backend: {self.backend}")
|
|
72
|
+
with DDGS(proxy=self.proxy, timeout=self.timeout, verify=self.verify_ssl) as ddgs:
|
|
73
|
+
results = ddgs.text(query=search_query, max_results=actual_max_results, backend=self.backend)
|
|
74
|
+
|
|
75
|
+
return json.dumps(results, indent=2)
|
|
76
|
+
|
|
77
|
+
def search_news(self, query: str, max_results: int = 5) -> str:
|
|
78
|
+
"""Use this function to get the latest news from the web.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
query(str): The query to search for.
|
|
82
|
+
max_results (optional, default=5): The maximum number of results to return.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The latest news from the web.
|
|
86
|
+
"""
|
|
87
|
+
actual_max_results = self.fixed_max_results or max_results
|
|
88
|
+
|
|
89
|
+
log_debug(f"Searching web news for: {query} using backend: {self.backend}")
|
|
90
|
+
with DDGS(proxy=self.proxy, timeout=self.timeout, verify=self.verify_ssl) as ddgs:
|
|
91
|
+
results = ddgs.news(query=query, max_results=actual_max_results, backend=self.backend)
|
|
92
|
+
|
|
93
|
+
return json.dumps(results, indent=2)
|
agno/tools/website.py
CHANGED