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.
Files changed (314) hide show
  1. agno/agent/agent.py +6015 -2823
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +594 -186
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +2 -8
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +72 -0
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +999 -519
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +103 -31
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +139 -0
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +59 -5
  142. agno/models/openai/chat.py +69 -29
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +77 -1
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -178
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +248 -94
  205. agno/run/base.py +44 -5
  206. agno/run/team.py +238 -97
  207. agno/run/workflow.py +144 -33
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1610
  213. agno/tools/dalle.py +2 -4
  214. agno/tools/decorator.py +4 -2
  215. agno/tools/duckduckgo.py +15 -11
  216. agno/tools/e2b.py +14 -7
  217. agno/tools/eleven_labs.py +23 -25
  218. agno/tools/exa.py +21 -16
  219. agno/tools/file.py +153 -23
  220. agno/tools/file_generation.py +350 -0
  221. agno/tools/firecrawl.py +4 -4
  222. agno/tools/function.py +250 -30
  223. agno/tools/gmail.py +238 -14
  224. agno/tools/google_drive.py +270 -0
  225. agno/tools/googlecalendar.py +36 -8
  226. agno/tools/googlesheets.py +20 -5
  227. agno/tools/jira.py +20 -0
  228. agno/tools/knowledge.py +3 -3
  229. agno/tools/mcp/__init__.py +10 -0
  230. agno/tools/mcp/mcp.py +331 -0
  231. agno/tools/mcp/multi_mcp.py +347 -0
  232. agno/tools/mcp/params.py +24 -0
  233. agno/tools/mcp_toolbox.py +284 -0
  234. agno/tools/mem0.py +11 -17
  235. agno/tools/memori.py +1 -53
  236. agno/tools/memory.py +419 -0
  237. agno/tools/models/nebius.py +5 -5
  238. agno/tools/models_labs.py +20 -10
  239. agno/tools/notion.py +204 -0
  240. agno/tools/parallel.py +314 -0
  241. agno/tools/scrapegraph.py +58 -31
  242. agno/tools/searxng.py +2 -2
  243. agno/tools/serper.py +2 -2
  244. agno/tools/slack.py +18 -3
  245. agno/tools/spider.py +2 -2
  246. agno/tools/tavily.py +146 -0
  247. agno/tools/whatsapp.py +1 -1
  248. agno/tools/workflow.py +278 -0
  249. agno/tools/yfinance.py +12 -11
  250. agno/utils/agent.py +820 -0
  251. agno/utils/audio.py +27 -0
  252. agno/utils/common.py +90 -1
  253. agno/utils/events.py +217 -2
  254. agno/utils/gemini.py +180 -22
  255. agno/utils/hooks.py +57 -0
  256. agno/utils/http.py +111 -0
  257. agno/utils/knowledge.py +12 -5
  258. agno/utils/log.py +1 -0
  259. agno/utils/mcp.py +92 -2
  260. agno/utils/media.py +188 -10
  261. agno/utils/merge_dict.py +22 -1
  262. agno/utils/message.py +60 -0
  263. agno/utils/models/claude.py +40 -11
  264. agno/utils/print_response/agent.py +105 -21
  265. agno/utils/print_response/team.py +103 -38
  266. agno/utils/print_response/workflow.py +251 -34
  267. agno/utils/reasoning.py +22 -1
  268. agno/utils/serialize.py +32 -0
  269. agno/utils/streamlit.py +16 -10
  270. agno/utils/string.py +41 -0
  271. agno/utils/team.py +98 -9
  272. agno/utils/tools.py +1 -1
  273. agno/vectordb/base.py +23 -4
  274. agno/vectordb/cassandra/cassandra.py +65 -9
  275. agno/vectordb/chroma/chromadb.py +182 -38
  276. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  277. agno/vectordb/couchbase/couchbase.py +105 -10
  278. agno/vectordb/lancedb/lance_db.py +124 -133
  279. agno/vectordb/langchaindb/langchaindb.py +25 -7
  280. agno/vectordb/lightrag/lightrag.py +17 -3
  281. agno/vectordb/llamaindex/__init__.py +3 -0
  282. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  283. agno/vectordb/milvus/milvus.py +126 -9
  284. agno/vectordb/mongodb/__init__.py +7 -1
  285. agno/vectordb/mongodb/mongodb.py +112 -7
  286. agno/vectordb/pgvector/pgvector.py +142 -21
  287. agno/vectordb/pineconedb/pineconedb.py +80 -8
  288. agno/vectordb/qdrant/qdrant.py +125 -39
  289. agno/vectordb/redis/__init__.py +9 -0
  290. agno/vectordb/redis/redisdb.py +694 -0
  291. agno/vectordb/singlestore/singlestore.py +111 -25
  292. agno/vectordb/surrealdb/surrealdb.py +31 -5
  293. agno/vectordb/upstashdb/upstashdb.py +76 -8
  294. agno/vectordb/weaviate/weaviate.py +86 -15
  295. agno/workflow/__init__.py +2 -0
  296. agno/workflow/agent.py +299 -0
  297. agno/workflow/condition.py +112 -18
  298. agno/workflow/loop.py +69 -10
  299. agno/workflow/parallel.py +266 -118
  300. agno/workflow/router.py +110 -17
  301. agno/workflow/step.py +638 -129
  302. agno/workflow/steps.py +65 -6
  303. agno/workflow/types.py +61 -23
  304. agno/workflow/workflow.py +2085 -272
  305. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
  306. agno-2.3.0.dist-info/RECORD +577 -0
  307. agno/knowledge/reader/url_reader.py +0 -128
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -610
  310. agno/utils/models/aws_claude.py +0 -170
  311. agno-2.0.1.dist-info/RECORD +0 -515
  312. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  313. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
  314. {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
- return json.dumps({"error": str(e)})
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
- return f"Error converting to markdown: {str(e)}"
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
- schema=schema,
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
- return json.dumps({"error": str(e)})
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
- # Validate required parameters for AI extraction
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
- return json.dumps({"error": str(e)})
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, prompt: str) -> str:
181
+ def searchscraper(self, user_prompt: str) -> str:
180
182
  """Search the web and extract information from the web.
181
183
  Args:
182
- prompt (str): Search query
184
+ user_prompt (str): Search query
183
185
  Returns:
184
186
  JSON of the search results
185
187
  """
186
188
  try:
187
- response = self.client.searchscraper(user_prompt=prompt)
188
- if hasattr(response, "result"):
189
- return json.dumps(response.result)
190
- elif isinstance(response, dict) and "result" in response:
191
- return json.dumps(response["result"])
192
- else:
193
- return json.dumps(response)
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
- return json.dumps({"error": str(e)})
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.search,
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 search(self, query: str, max_results: int = 5) -> str:
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.search)
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 search(
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(channel=channel, text=text, thread_ts=thread_ts)
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.search)
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 search(self, query: str, max_results: int = 5) -> str:
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
@@ -16,7 +16,7 @@ class WhatsAppTools(Toolkit):
16
16
  self,
17
17
  access_token: Optional[str] = None,
18
18
  phone_number_id: Optional[str] = None,
19
- version: str = "v22.0",
19
+ version: Optional[str] = None,
20
20
  recipient_waid: Optional[str] = None,
21
21
  async_mode: bool = False,
22
22
  ):