agno 2.0.1__py3-none-any.whl → 2.3.0__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/agent.py +6015 -2823
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- 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 +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -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/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +594 -186
- agno/knowledge/reader/base.py +9 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +2 -8
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +72 -0
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +999 -519
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +103 -31
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +139 -0
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +59 -5
- agno/models/openai/chat.py +69 -29
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +77 -1
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -178
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -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/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +248 -94
- agno/run/base.py +44 -5
- agno/run/team.py +238 -97
- agno/run/workflow.py +144 -33
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1610
- agno/tools/dalle.py +2 -4
- agno/tools/decorator.py +4 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +14 -7
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +250 -30
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- 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 +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +217 -2
- agno/utils/gemini.py +180 -22
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +124 -133
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +638 -129
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +61 -23
- agno/workflow/workflow.py +2085 -272
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
- agno-2.3.0.dist-info/RECORD +577 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.1.dist-info/RECORD +0 -515
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/tools/scrapegraph.py
CHANGED
|
@@ -3,6 +3,7 @@ from os import getenv
|
|
|
3
3
|
from typing import Any, List, Optional
|
|
4
4
|
|
|
5
5
|
from agno.tools import Toolkit
|
|
6
|
+
from agno.utils.log import log_debug, log_error
|
|
6
7
|
|
|
7
8
|
try:
|
|
8
9
|
from scrapegraph_py import Client
|
|
@@ -23,11 +24,14 @@ class ScrapeGraphTools(Toolkit):
|
|
|
23
24
|
enable_crawl: bool = False,
|
|
24
25
|
enable_searchscraper: bool = False,
|
|
25
26
|
enable_agentic_crawler: bool = False,
|
|
27
|
+
enable_scrape: bool = False,
|
|
28
|
+
render_heavy_js: bool = False,
|
|
26
29
|
all: bool = False,
|
|
27
30
|
**kwargs,
|
|
28
31
|
):
|
|
29
32
|
self.api_key: Optional[str] = api_key or getenv("SGAI_API_KEY")
|
|
30
33
|
self.client = Client(api_key=self.api_key)
|
|
34
|
+
self.render_heavy_js = render_heavy_js
|
|
31
35
|
|
|
32
36
|
# Start with smartscraper by default
|
|
33
37
|
# Only enable markdownify if smartscraper is False
|
|
@@ -45,6 +49,8 @@ class ScrapeGraphTools(Toolkit):
|
|
|
45
49
|
tools.append(self.searchscraper)
|
|
46
50
|
if enable_agentic_crawler or all:
|
|
47
51
|
tools.append(self.agentic_crawler)
|
|
52
|
+
if enable_scrape or all:
|
|
53
|
+
tools.append(self.scrape)
|
|
48
54
|
|
|
49
55
|
super().__init__(name="scrapegraph_tools", tools=tools, **kwargs)
|
|
50
56
|
|
|
@@ -57,10 +63,13 @@ class ScrapeGraphTools(Toolkit):
|
|
|
57
63
|
The structured data extracted from the webpage
|
|
58
64
|
"""
|
|
59
65
|
try:
|
|
66
|
+
log_debug(f"ScrapeGraph smartscraper request for URL: {url}")
|
|
60
67
|
response = self.client.smartscraper(website_url=url, user_prompt=prompt)
|
|
61
68
|
return json.dumps(response["result"])
|
|
62
69
|
except Exception as e:
|
|
63
|
-
|
|
70
|
+
error_msg = f"Smartscraper failed: {str(e)}"
|
|
71
|
+
log_error(error_msg)
|
|
72
|
+
return f"Error: {error_msg}"
|
|
64
73
|
|
|
65
74
|
def markdownify(self, url: str) -> str:
|
|
66
75
|
"""Convert a webpage to markdown format.
|
|
@@ -70,10 +79,13 @@ class ScrapeGraphTools(Toolkit):
|
|
|
70
79
|
The markdown version of the webpage
|
|
71
80
|
"""
|
|
72
81
|
try:
|
|
82
|
+
log_debug(f"ScrapeGraph markdownify request for URL: {url}")
|
|
73
83
|
response = self.client.markdownify(website_url=url)
|
|
74
84
|
return response["result"]
|
|
75
85
|
except Exception as e:
|
|
76
|
-
|
|
86
|
+
error_msg = f"Markdownify failed: {str(e)}"
|
|
87
|
+
log_error(error_msg)
|
|
88
|
+
return f"Error: {error_msg}"
|
|
77
89
|
|
|
78
90
|
def crawl(
|
|
79
91
|
self,
|
|
@@ -100,10 +112,11 @@ class ScrapeGraphTools(Toolkit):
|
|
|
100
112
|
The structured data extracted from the website
|
|
101
113
|
"""
|
|
102
114
|
try:
|
|
115
|
+
log_debug(f"ScrapeGraph crawl request for URL: {url}")
|
|
103
116
|
response = self.client.crawl(
|
|
104
117
|
url=url,
|
|
105
118
|
prompt=prompt,
|
|
106
|
-
|
|
119
|
+
data_schema=schema,
|
|
107
120
|
cache_website=cache_website,
|
|
108
121
|
depth=depth,
|
|
109
122
|
max_pages=max_pages,
|
|
@@ -112,7 +125,9 @@ class ScrapeGraphTools(Toolkit):
|
|
|
112
125
|
)
|
|
113
126
|
return json.dumps(response, indent=2)
|
|
114
127
|
except Exception as e:
|
|
115
|
-
|
|
128
|
+
error_msg = f"Crawl failed: {str(e)}"
|
|
129
|
+
log_error(error_msg)
|
|
130
|
+
return f"Error: {error_msg}"
|
|
116
131
|
|
|
117
132
|
def agentic_crawler(
|
|
118
133
|
self,
|
|
@@ -143,21 +158,7 @@ class ScrapeGraphTools(Toolkit):
|
|
|
143
158
|
JSON string containing the scraping results, including request_id, status, and extracted data
|
|
144
159
|
"""
|
|
145
160
|
try:
|
|
146
|
-
|
|
147
|
-
if ai_extraction and not user_prompt:
|
|
148
|
-
return json.dumps({"error": "user_prompt is required when ai_extraction=True"})
|
|
149
|
-
|
|
150
|
-
# Validate URL format
|
|
151
|
-
if not url.strip():
|
|
152
|
-
return json.dumps({"error": "URL cannot be empty"})
|
|
153
|
-
if not (url.startswith("http://") or url.startswith("https://")):
|
|
154
|
-
return json.dumps({"error": "Invalid URL - must start with http:// or https://"})
|
|
155
|
-
|
|
156
|
-
# Validate steps
|
|
157
|
-
if not steps:
|
|
158
|
-
return json.dumps({"error": "Steps cannot be empty"})
|
|
159
|
-
if any(not step.strip() for step in steps):
|
|
160
|
-
return json.dumps({"error": "All steps must contain valid instructions"})
|
|
161
|
+
log_debug(f"ScrapeGraph agentic_crawler request for URL: {url}")
|
|
161
162
|
|
|
162
163
|
# Prepare parameters for the API call
|
|
163
164
|
params = {"url": url, "steps": steps, "use_session": use_session, "ai_extraction": ai_extraction}
|
|
@@ -170,26 +171,52 @@ class ScrapeGraphTools(Toolkit):
|
|
|
170
171
|
|
|
171
172
|
# Call the agentic scraper API
|
|
172
173
|
response = self.client.agenticscraper(**params)
|
|
173
|
-
|
|
174
174
|
return json.dumps(response, indent=2)
|
|
175
175
|
|
|
176
176
|
except Exception as e:
|
|
177
|
-
|
|
177
|
+
error_msg = f"Agentic crawler failed: {str(e)}"
|
|
178
|
+
log_error(error_msg)
|
|
179
|
+
return f"Error: {error_msg}"
|
|
178
180
|
|
|
179
|
-
def searchscraper(self,
|
|
181
|
+
def searchscraper(self, user_prompt: str) -> str:
|
|
180
182
|
"""Search the web and extract information from the web.
|
|
181
183
|
Args:
|
|
182
|
-
|
|
184
|
+
user_prompt (str): Search query
|
|
183
185
|
Returns:
|
|
184
186
|
JSON of the search results
|
|
185
187
|
"""
|
|
186
188
|
try:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
189
|
+
log_debug(f"ScrapeGraph searchscraper request with prompt: {user_prompt}")
|
|
190
|
+
response = self.client.searchscraper(user_prompt=user_prompt)
|
|
191
|
+
return json.dumps(response["result"])
|
|
192
|
+
except Exception as e:
|
|
193
|
+
error_msg = f"Searchscraper failed: {str(e)}"
|
|
194
|
+
log_error(error_msg)
|
|
195
|
+
return f"Error: {error_msg}"
|
|
196
|
+
|
|
197
|
+
def scrape(
|
|
198
|
+
self,
|
|
199
|
+
website_url: str,
|
|
200
|
+
headers: Optional[dict] = None,
|
|
201
|
+
) -> str:
|
|
202
|
+
"""Get raw HTML content from a website using the ScrapeGraphAI scrape API.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
website_url (str): The URL of the website to scrape
|
|
206
|
+
headers (Optional[dict]): Optional headers to send with the request
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
JSON string containing the HTML content and metadata
|
|
210
|
+
"""
|
|
211
|
+
try:
|
|
212
|
+
log_debug(f"ScrapeGraph scrape request for URL: {website_url}")
|
|
213
|
+
response = self.client.scrape(
|
|
214
|
+
website_url=website_url,
|
|
215
|
+
headers=headers,
|
|
216
|
+
render_heavy_js=self.render_heavy_js,
|
|
217
|
+
)
|
|
218
|
+
return json.dumps(response, indent=2)
|
|
194
219
|
except Exception as e:
|
|
195
|
-
|
|
220
|
+
error_msg = f"Scrape failed: {str(e)}"
|
|
221
|
+
log_error(error_msg)
|
|
222
|
+
return f"Error: {error_msg}"
|
agno/tools/searxng.py
CHANGED
|
@@ -21,7 +21,7 @@ class Searxng(Toolkit):
|
|
|
21
21
|
self.fixed_max_results = fixed_max_results
|
|
22
22
|
|
|
23
23
|
tools: List[Any] = [
|
|
24
|
-
self.
|
|
24
|
+
self.search_web,
|
|
25
25
|
self.image_search,
|
|
26
26
|
self.it_search,
|
|
27
27
|
self.map_search,
|
|
@@ -33,7 +33,7 @@ class Searxng(Toolkit):
|
|
|
33
33
|
|
|
34
34
|
super().__init__(name="searxng", tools=tools, **kwargs)
|
|
35
35
|
|
|
36
|
-
def
|
|
36
|
+
def search_web(self, query: str, max_results: int = 5) -> str:
|
|
37
37
|
"""Use this function to search the web.
|
|
38
38
|
|
|
39
39
|
Args:
|
agno/tools/serper.py
CHANGED
|
@@ -44,7 +44,7 @@ class SerperTools(Toolkit):
|
|
|
44
44
|
|
|
45
45
|
tools: List[Any] = []
|
|
46
46
|
if all or enable_search:
|
|
47
|
-
tools.append(self.
|
|
47
|
+
tools.append(self.search_web)
|
|
48
48
|
if all or enable_search_news:
|
|
49
49
|
tools.append(self.search_news)
|
|
50
50
|
if all or enable_search_scholar:
|
|
@@ -97,7 +97,7 @@ class SerperTools(Toolkit):
|
|
|
97
97
|
log_error(f"Serper API error: {str(e)}")
|
|
98
98
|
return {"success": False, "error": str(e)}
|
|
99
99
|
|
|
100
|
-
def
|
|
100
|
+
def search_web(
|
|
101
101
|
self,
|
|
102
102
|
query: str,
|
|
103
103
|
num_results: Optional[int] = None,
|
agno/tools/slack.py
CHANGED
|
@@ -16,6 +16,7 @@ class SlackTools(Toolkit):
|
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
18
|
token: Optional[str] = None,
|
|
19
|
+
markdown: bool = True,
|
|
19
20
|
enable_send_message: bool = True,
|
|
20
21
|
enable_send_message_thread: bool = True,
|
|
21
22
|
enable_list_channels: bool = True,
|
|
@@ -23,10 +24,22 @@ class SlackTools(Toolkit):
|
|
|
23
24
|
all: bool = False,
|
|
24
25
|
**kwargs,
|
|
25
26
|
):
|
|
27
|
+
"""
|
|
28
|
+
Initialize the SlackTools class.
|
|
29
|
+
Args:
|
|
30
|
+
token: The Slack API token. Defaults to the SLACK_TOKEN environment variable.
|
|
31
|
+
markdown: Whether to enable Slack markdown formatting. Defaults to True.
|
|
32
|
+
enable_send_message: Whether to enable the send_message tool. Defaults to True.
|
|
33
|
+
enable_send_message_thread: Whether to enable the send_message_thread tool. Defaults to True.
|
|
34
|
+
enable_list_channels: Whether to enable the list_channels tool. Defaults to True.
|
|
35
|
+
enable_get_channel_history: Whether to enable the get_channel_history tool. Defaults to True.
|
|
36
|
+
all: Whether to enable all tools. Defaults to False.
|
|
37
|
+
"""
|
|
26
38
|
self.token: Optional[str] = token or getenv("SLACK_TOKEN")
|
|
27
39
|
if self.token is None or self.token == "":
|
|
28
40
|
raise ValueError("SLACK_TOKEN is not set")
|
|
29
41
|
self.client = WebClient(token=self.token)
|
|
42
|
+
self.markdown = markdown
|
|
30
43
|
|
|
31
44
|
tools: List[Any] = []
|
|
32
45
|
if enable_send_message or all:
|
|
@@ -52,7 +65,7 @@ class SlackTools(Toolkit):
|
|
|
52
65
|
str: A JSON string containing the response from the Slack API.
|
|
53
66
|
"""
|
|
54
67
|
try:
|
|
55
|
-
response = self.client.chat_postMessage(channel=channel, text=text)
|
|
68
|
+
response = self.client.chat_postMessage(channel=channel, text=text, mrkdwn=self.markdown)
|
|
56
69
|
return json.dumps(response.data)
|
|
57
70
|
except SlackApiError as e:
|
|
58
71
|
logger.error(f"Error sending message: {e}")
|
|
@@ -65,13 +78,15 @@ class SlackTools(Toolkit):
|
|
|
65
78
|
Args:
|
|
66
79
|
channel (str): The channel ID or name to send the message to.
|
|
67
80
|
text (str): The text of the message to send.
|
|
68
|
-
thread_ts (ts): The thread to reply to
|
|
81
|
+
thread_ts (ts): The thread to reply to.
|
|
69
82
|
|
|
70
83
|
Returns:
|
|
71
84
|
str: A JSON string containing the response from the Slack API.
|
|
72
85
|
"""
|
|
73
86
|
try:
|
|
74
|
-
response = self.client.chat_postMessage(
|
|
87
|
+
response = self.client.chat_postMessage(
|
|
88
|
+
channel=channel, text=text, thread_ts=thread_ts, mrkdwn=self.markdown
|
|
89
|
+
)
|
|
75
90
|
return json.dumps(response.data)
|
|
76
91
|
except SlackApiError as e:
|
|
77
92
|
logger.error(f"Error sending message: {e}")
|
agno/tools/spider.py
CHANGED
|
@@ -42,7 +42,7 @@ class SpiderTools(Toolkit):
|
|
|
42
42
|
|
|
43
43
|
tools: List[Any] = []
|
|
44
44
|
if enable_search or all:
|
|
45
|
-
tools.append(self.
|
|
45
|
+
tools.append(self.search_web)
|
|
46
46
|
if enable_scrape or all:
|
|
47
47
|
tools.append(self.scrape)
|
|
48
48
|
if enable_crawl or all:
|
|
@@ -50,7 +50,7 @@ class SpiderTools(Toolkit):
|
|
|
50
50
|
|
|
51
51
|
super().__init__(name="spider", tools=tools, **kwargs)
|
|
52
52
|
|
|
53
|
-
def
|
|
53
|
+
def search_web(self, query: str, max_results: int = 5) -> str:
|
|
54
54
|
"""Use this function to search the web.
|
|
55
55
|
Args:
|
|
56
56
|
query (str): The query to search the web with.
|
agno/tools/tavily.py
CHANGED
|
@@ -17,21 +17,51 @@ class TavilyTools(Toolkit):
|
|
|
17
17
|
api_key: Optional[str] = None,
|
|
18
18
|
enable_search: bool = True,
|
|
19
19
|
enable_search_context: bool = False,
|
|
20
|
+
enable_extract: bool = False,
|
|
20
21
|
all: bool = False,
|
|
21
22
|
max_tokens: int = 6000,
|
|
22
23
|
include_answer: bool = True,
|
|
23
24
|
search_depth: Literal["basic", "advanced"] = "advanced",
|
|
25
|
+
extract_depth: Literal["basic", "advanced"] = "basic",
|
|
26
|
+
include_images: bool = False,
|
|
27
|
+
include_favicon: bool = False,
|
|
28
|
+
extract_timeout: Optional[int] = None,
|
|
29
|
+
extract_format: Literal["markdown", "text"] = "markdown",
|
|
24
30
|
format: Literal["json", "markdown"] = "markdown",
|
|
25
31
|
**kwargs,
|
|
26
32
|
):
|
|
33
|
+
"""Initialize TavilyTools with search and extract capabilities.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
api_key: Tavily API key. If not provided, will use TAVILY_API_KEY env var.
|
|
37
|
+
enable_search: Enable web search functionality. Defaults to True.
|
|
38
|
+
enable_search_context: Use search context mode instead of regular search. Defaults to False.
|
|
39
|
+
enable_extract: Enable URL content extraction functionality. Defaults to False.
|
|
40
|
+
all: Enable all available tools. Defaults to False.
|
|
41
|
+
max_tokens: Maximum tokens for search results. Defaults to 6000.
|
|
42
|
+
include_answer: Include AI-generated answer in search results. Defaults to True.
|
|
43
|
+
search_depth: Search depth level - basic (1 credit) or advanced (2 credits). Defaults to "advanced".
|
|
44
|
+
extract_depth: Extract depth level - basic (1 credit/5 URLs) or advanced (2 credits/5 URLs). Defaults to "basic".
|
|
45
|
+
include_images: Include images in extracted content. Defaults to False.
|
|
46
|
+
include_favicon: Include favicon in extracted content. Defaults to False.
|
|
47
|
+
extract_timeout: Timeout in seconds for extraction requests. Defaults to None.
|
|
48
|
+
extract_format: Output format for extracted content - markdown or text. Defaults to "markdown".
|
|
49
|
+
format: Output format for search results - json or markdown. Defaults to "markdown".
|
|
50
|
+
**kwargs: Additional arguments passed to Toolkit.
|
|
51
|
+
"""
|
|
27
52
|
self.api_key = api_key or getenv("TAVILY_API_KEY")
|
|
28
53
|
if not self.api_key:
|
|
29
54
|
logger.error("TAVILY_API_KEY not provided")
|
|
30
55
|
|
|
31
56
|
self.client: TavilyClient = TavilyClient(api_key=self.api_key)
|
|
32
57
|
self.search_depth: Literal["basic", "advanced"] = search_depth
|
|
58
|
+
self.extract_depth: Literal["basic", "advanced"] = extract_depth
|
|
33
59
|
self.max_tokens: int = max_tokens
|
|
34
60
|
self.include_answer: bool = include_answer
|
|
61
|
+
self.include_images: bool = include_images
|
|
62
|
+
self.include_favicon: bool = include_favicon
|
|
63
|
+
self.extract_timeout: Optional[int] = extract_timeout
|
|
64
|
+
self.extract_format: Literal["markdown", "text"] = extract_format
|
|
35
65
|
self.format: Literal["json", "markdown"] = format
|
|
36
66
|
|
|
37
67
|
tools: List[Any] = []
|
|
@@ -42,6 +72,9 @@ class TavilyTools(Toolkit):
|
|
|
42
72
|
else:
|
|
43
73
|
tools.append(self.web_search_using_tavily)
|
|
44
74
|
|
|
75
|
+
if enable_extract or all:
|
|
76
|
+
tools.append(self.extract_url_content)
|
|
77
|
+
|
|
45
78
|
super().__init__(name="tavily_tools", tools=tools, **kwargs)
|
|
46
79
|
|
|
47
80
|
def web_search_using_tavily(self, query: str, max_results: int = 5) -> str:
|
|
@@ -106,3 +139,116 @@ class TavilyTools(Toolkit):
|
|
|
106
139
|
return self.client.get_search_context(
|
|
107
140
|
query=query, search_depth=self.search_depth, max_tokens=self.max_tokens, include_answer=self.include_answer
|
|
108
141
|
)
|
|
142
|
+
|
|
143
|
+
def extract_url_content(self, urls: str) -> str:
|
|
144
|
+
"""Extract content from one or more URLs using Tavily's Extract API.
|
|
145
|
+
This function retrieves the main content from web pages in markdown or text format.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
urls (str): Single URL or multiple comma-separated URLs to extract content from.
|
|
149
|
+
Example: "https://example.com" or "https://example.com,https://another.com"
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
str: Extracted content in the specified format (markdown or text).
|
|
153
|
+
For multiple URLs, returns combined content with URL headers.
|
|
154
|
+
Failed extractions are noted in the output.
|
|
155
|
+
"""
|
|
156
|
+
# Parse URLs - handle both single and comma-separated multiple URLs
|
|
157
|
+
url_list = [url.strip() for url in urls.split(",") if url.strip()]
|
|
158
|
+
|
|
159
|
+
if not url_list:
|
|
160
|
+
return "Error: No valid URLs provided."
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
# Prepare extract parameters
|
|
164
|
+
extract_params: Dict[str, Any] = {
|
|
165
|
+
"urls": url_list,
|
|
166
|
+
"depth": self.extract_depth,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# Add optional parameters if specified
|
|
170
|
+
if self.include_images:
|
|
171
|
+
extract_params["include_images"] = True
|
|
172
|
+
if self.include_favicon:
|
|
173
|
+
extract_params["include_favicon"] = True
|
|
174
|
+
if self.extract_timeout is not None:
|
|
175
|
+
extract_params["timeout"] = self.extract_timeout
|
|
176
|
+
|
|
177
|
+
# Call Tavily Extract API
|
|
178
|
+
response = self.client.extract(**extract_params)
|
|
179
|
+
|
|
180
|
+
# Process response based on format preference
|
|
181
|
+
if not response or "results" not in response:
|
|
182
|
+
return "Error: No content could be extracted from the provided URL(s)."
|
|
183
|
+
|
|
184
|
+
results = response.get("results", [])
|
|
185
|
+
if not results:
|
|
186
|
+
return "Error: No content could be extracted from the provided URL(s)."
|
|
187
|
+
|
|
188
|
+
# Format output
|
|
189
|
+
if self.extract_format == "markdown":
|
|
190
|
+
return self._format_extract_markdown(results)
|
|
191
|
+
elif self.extract_format == "text":
|
|
192
|
+
return self._format_extract_text(results)
|
|
193
|
+
else:
|
|
194
|
+
# Fallback to JSON if format is unrecognized
|
|
195
|
+
return json.dumps(results, indent=2)
|
|
196
|
+
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logger.error(f"Error extracting content from URLs: {e}")
|
|
199
|
+
return f"Error extracting content: {str(e)}"
|
|
200
|
+
|
|
201
|
+
def _format_extract_markdown(self, results: List[Dict[str, Any]]) -> str:
|
|
202
|
+
"""Format extraction results as markdown.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
results: List of extraction result dictionaries from Tavily API.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
str: Formatted markdown content.
|
|
209
|
+
"""
|
|
210
|
+
output = []
|
|
211
|
+
|
|
212
|
+
for result in results:
|
|
213
|
+
url = result.get("url", "Unknown URL")
|
|
214
|
+
raw_content = result.get("raw_content", "")
|
|
215
|
+
failed_reason = result.get("failed_reason")
|
|
216
|
+
|
|
217
|
+
if failed_reason:
|
|
218
|
+
output.append(f"## {url}\n\n **Extraction Failed**: {failed_reason}\n\n")
|
|
219
|
+
elif raw_content:
|
|
220
|
+
output.append(f"## {url}\n\n{raw_content}\n\n")
|
|
221
|
+
else:
|
|
222
|
+
output.append(f"## {url}\n\n*No content available*\n\n")
|
|
223
|
+
|
|
224
|
+
return "".join(output) if output else "No content extracted."
|
|
225
|
+
|
|
226
|
+
def _format_extract_text(self, results: List[Dict[str, Any]]) -> str:
|
|
227
|
+
"""Format extraction results as plain text.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
results: List of extraction result dictionaries from Tavily API.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
str: Formatted plain text content.
|
|
234
|
+
"""
|
|
235
|
+
output = []
|
|
236
|
+
|
|
237
|
+
for result in results:
|
|
238
|
+
url = result.get("url", "Unknown URL")
|
|
239
|
+
raw_content = result.get("raw_content", "")
|
|
240
|
+
failed_reason = result.get("failed_reason")
|
|
241
|
+
|
|
242
|
+
output.append(f"URL: {url}")
|
|
243
|
+
output.append("-" * 80)
|
|
244
|
+
|
|
245
|
+
if failed_reason:
|
|
246
|
+
output.append(f"EXTRACTION FAILED: {failed_reason}")
|
|
247
|
+
elif raw_content:
|
|
248
|
+
output.append(raw_content)
|
|
249
|
+
else:
|
|
250
|
+
output.append("No content available")
|
|
251
|
+
|
|
252
|
+
output.append("\n")
|
|
253
|
+
|
|
254
|
+
return "\n".join(output) if output else "No content extracted."
|
agno/tools/whatsapp.py
CHANGED