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/dalle.py CHANGED
@@ -1,10 +1,8 @@
1
1
  from os import getenv
2
- from typing import Any, List, Literal, Optional, Union
2
+ from typing import Any, List, Literal, Optional
3
3
  from uuid import uuid4
4
4
 
5
- from agno.agent import Agent
6
5
  from agno.media import Image
7
- from agno.team.team import Team
8
6
  from agno.tools import Toolkit
9
7
  from agno.tools.function import ToolResult
10
8
  from agno.utils.log import log_debug, logger
@@ -64,7 +62,7 @@ class DalleTools(Toolkit):
64
62
  # - Add support for saving images
65
63
  # - Add support for editing images
66
64
 
67
- def create_image(self, agent: Union[Agent, Team], prompt: str) -> ToolResult:
65
+ def create_image(self, prompt: str) -> ToolResult:
68
66
  """Use this function to generate an image for a prompt.
69
67
 
70
68
  Args:
agno/tools/decorator.py CHANGED
@@ -250,8 +250,10 @@ def tool(*args, **kwargs) -> Union[Function, Callable[[F], Function]]:
250
250
  if kwargs.get("stop_after_tool_call") is True:
251
251
  if "show_result" not in kwargs or kwargs.get("show_result") is None:
252
252
  tool_config["show_result"] = True
253
-
254
- return Function(**tool_config)
253
+ function = Function(**tool_config)
254
+ # Determine parameters for the function
255
+ function.process_entrypoint()
256
+ return function
255
257
 
256
258
  # Handle both @tool and @tool() cases
257
259
  if len(args) == 1 and callable(args[0]) and not kwargs:
agno/tools/duckduckgo.py CHANGED
@@ -12,14 +12,16 @@ except ImportError:
12
12
 
13
13
  class DuckDuckGoTools(Toolkit):
14
14
  """
15
- DuckDuckGo is a toolkit for searching DuckDuckGo easily.
15
+ DuckDuckGo is a toolkit for searching using DuckDuckGo easily.
16
+ It uses the meta-search library DDGS, so it also has access to other backends.
16
17
  Args:
17
- search (bool): Enable DuckDuckGo search function.
18
- news (bool): Enable DuckDuckGo news function.
18
+ enable_search (bool): Enable DDGS search function.
19
+ enable_news (bool): Enable DDGS news function.
19
20
  modifier (Optional[str]): A modifier to be used in the search request.
20
21
  fixed_max_results (Optional[int]): A fixed number of maximum results.
21
22
  proxy (Optional[str]): Proxy to be used in the search request.
22
23
  timeout (Optional[int]): The maximum number of seconds to wait for a response.
24
+ backend (Optional[str]): The backend to be used in the search request.
23
25
 
24
26
  """
25
27
 
@@ -28,6 +30,7 @@ class DuckDuckGoTools(Toolkit):
28
30
  enable_search: bool = True,
29
31
  enable_news: bool = True,
30
32
  all: bool = False,
33
+ backend: str = "duckduckgo",
31
34
  modifier: Optional[str] = None,
32
35
  fixed_max_results: Optional[int] = None,
33
36
  proxy: Optional[str] = None,
@@ -40,6 +43,7 @@ class DuckDuckGoTools(Toolkit):
40
43
  self.fixed_max_results: Optional[int] = fixed_max_results
41
44
  self.modifier: Optional[str] = modifier
42
45
  self.verify_ssl: bool = verify_ssl
46
+ self.backend: str = backend
43
47
 
44
48
  tools: List[Any] = []
45
49
  if all or enable_search:
@@ -50,38 +54,38 @@ class DuckDuckGoTools(Toolkit):
50
54
  super().__init__(name="duckduckgo", tools=tools, **kwargs)
51
55
 
52
56
  def duckduckgo_search(self, query: str, max_results: int = 5) -> str:
53
- """Use this function to search DuckDuckGo for a query.
57
+ """Use this function to search DDGS for a query.
54
58
 
55
59
  Args:
56
60
  query(str): The query to search for.
57
61
  max_results (optional, default=5): The maximum number of results to return.
58
62
 
59
63
  Returns:
60
- The result from DuckDuckGo.
64
+ The result from DDGS.
61
65
  """
62
66
  actual_max_results = self.fixed_max_results or max_results
63
67
  search_query = f"{self.modifier} {query}" if self.modifier else query
64
68
 
65
- log_debug(f"Searching DDG for: {search_query}")
69
+ log_debug(f"Searching DDG for: {search_query} using backend: {self.backend}")
66
70
  with DDGS(proxy=self.proxy, timeout=self.timeout, verify=self.verify_ssl) as ddgs:
67
- results = ddgs.text(query=search_query, max_results=actual_max_results)
71
+ results = ddgs.text(query=search_query, max_results=actual_max_results, backend=self.backend)
68
72
 
69
73
  return json.dumps(results, indent=2)
70
74
 
71
75
  def duckduckgo_news(self, query: str, max_results: int = 5) -> str:
72
- """Use this function to get the latest news from DuckDuckGo.
76
+ """Use this function to get the latest news from DDGS.
73
77
 
74
78
  Args:
75
79
  query(str): The query to search for.
76
80
  max_results (optional, default=5): The maximum number of results to return.
77
81
 
78
82
  Returns:
79
- The latest news from DuckDuckGo.
83
+ The latest news from DDGS.
80
84
  """
81
85
  actual_max_results = self.fixed_max_results or max_results
82
86
 
83
- log_debug(f"Searching DDG news for: {query}")
87
+ log_debug(f"Searching DDG news for: {query} using backend: {self.backend}")
84
88
  with DDGS(proxy=self.proxy, timeout=self.timeout, verify=self.verify_ssl) as ddgs:
85
- results = ddgs.news(query=query, max_results=actual_max_results)
89
+ results = ddgs.news(query=query, max_results=actual_max_results, backend=self.backend)
86
90
 
87
91
  return json.dumps(results, indent=2)
agno/tools/e2b.py CHANGED
@@ -464,7 +464,7 @@ class E2BTools(Toolkit):
464
464
 
465
465
  result = f"Contents of {directory_path}:\n"
466
466
  for file in files:
467
- file_type = "Directory" if file.is_dir else "File"
467
+ file_type = "Directory" if file.type == "directory" else "File"
468
468
  size = f"{file.size} bytes" if file.size is not None else "Unknown size"
469
469
  result += f"- {file.name} ({file_type}, {size})\n"
470
470
 
@@ -486,12 +486,19 @@ class E2BTools(Toolkit):
486
486
  try:
487
487
  content = self.sandbox.files.read(file_path)
488
488
 
489
- # Try to decode as text if encoding is provided
490
- try:
491
- text_content = content.decode(encoding)
492
- return text_content
493
- except UnicodeDecodeError:
494
- return f"File read successfully but contains binary data ({len(content)} bytes). Use download_file_from_sandbox to save it."
489
+ # Check if content is already a string or if it's bytes that need decoding
490
+ if isinstance(content, str):
491
+ return content
492
+ elif isinstance(content, bytes):
493
+ # Try to decode as text if encoding is provided
494
+ try:
495
+ text_content = content.decode(encoding)
496
+ return text_content
497
+ except UnicodeDecodeError:
498
+ return f"File read successfully but contains binary data ({len(content)} bytes). Use download_file_from_sandbox to save it."
499
+ else:
500
+ # Handle unexpected content type
501
+ return f"Unexpected content type: {type(content)}. Expected str or bytes."
495
502
 
496
503
  except Exception as e:
497
504
  return json.dumps({"status": "error", "message": f"Error reading file: {str(e)}"})
agno/tools/eleven_labs.py CHANGED
@@ -1,4 +1,3 @@
1
- from base64 import b64encode
2
1
  from io import BytesIO
3
2
  from os import getenv, path
4
3
  from pathlib import Path
@@ -10,7 +9,7 @@ from agno.media import Audio
10
9
  from agno.team.team import Team
11
10
  from agno.tools import Toolkit
12
11
  from agno.tools.function import ToolResult
13
- from agno.utils.log import logger
12
+ from agno.utils.log import log_error, log_info
14
13
 
15
14
  try:
16
15
  from elevenlabs import ElevenLabs # type: ignore
@@ -48,7 +47,7 @@ class ElevenLabsTools(Toolkit):
48
47
  ):
49
48
  self.api_key = api_key or getenv("ELEVEN_LABS_API_KEY")
50
49
  if not self.api_key:
51
- logger.error("ELEVEN_LABS_API_KEY not set. Please set the ELEVEN_LABS_API_KEY environment variable.")
50
+ log_error("ELEVEN_LABS_API_KEY not set. Please set the ELEVEN_LABS_API_KEY environment variable.")
52
51
 
53
52
  self.target_directory = target_directory
54
53
  self.voice_id = voice_id
@@ -73,7 +72,7 @@ class ElevenLabsTools(Toolkit):
73
72
 
74
73
  def get_voices(self) -> str:
75
74
  """
76
- Use this function to get all the voices available.
75
+ Get all the voices available.
77
76
 
78
77
  Returns:
79
78
  result (list): A list of voices that have an ID, name and description.
@@ -94,20 +93,19 @@ class ElevenLabsTools(Toolkit):
94
93
  return str(response)
95
94
 
96
95
  except Exception as e:
97
- logger.error(f"Failed to fetch voices: {e}")
96
+ log_error(f"Failed to fetch voices: {e}")
98
97
  return f"Error: {e}"
99
98
 
100
- def _process_audio(self, audio_generator: Iterator[bytes]) -> str:
101
- # Step 1: Write audio data to BytesIO
99
+ def _process_audio(self, audio_generator: Iterator[bytes]) -> bytes:
102
100
  audio_bytes = BytesIO()
103
101
  for chunk in audio_generator:
104
102
  audio_bytes.write(chunk)
105
- audio_bytes.seek(0) # Rewind the stream
106
103
 
107
- # Step 2: Encode as Base64
108
- base64_audio = b64encode(audio_bytes.read()).decode("utf-8")
104
+ # Read bytes
105
+ audio_bytes.seek(0)
106
+ audio_data = audio_bytes.read()
109
107
 
110
- # Step 3: Optionally save to disk if target_directory exists
108
+ # Save to disk if target_directory exists
111
109
  if self.target_directory:
112
110
  # Determine file extension based on output format
113
111
  if self.output_format.startswith("mp3"):
@@ -122,19 +120,19 @@ class ElevenLabsTools(Toolkit):
122
120
  output_filename = f"{uuid4()}.{extension}"
123
121
  output_path = path.join(self.target_directory, output_filename)
124
122
 
125
- # Write from BytesIO to disk
126
- audio_bytes.seek(0) # Reset the BytesIO stream again
127
123
  with open(output_path, "wb") as f:
128
- f.write(audio_bytes.read())
124
+ f.write(audio_data)
129
125
 
130
- return base64_audio
126
+ log_info(f"Audio saved to: {output_path}")
127
+
128
+ return audio_data
131
129
 
132
130
  def generate_sound_effect(self, prompt: str, duration_seconds: Optional[float] = None) -> ToolResult:
133
131
  """
134
- Use this function to generate sound effect audio from a text prompt.
132
+ Generate a sound effect from a text description.
135
133
 
136
134
  Args:
137
- prompt (str): Text to generate audio from.
135
+ prompt (str): Description of the sound effect
138
136
  duration_seconds (Optional[float]): Duration in seconds to generate audio from. Has to be between 0.5 and 22.
139
137
  Returns:
140
138
  ToolResult: A ToolResult containing the generated audio or error message.
@@ -144,27 +142,27 @@ class ElevenLabsTools(Toolkit):
144
142
  text=prompt, duration_seconds=duration_seconds
145
143
  )
146
144
 
147
- base64_audio = self._process_audio(audio_generator)
145
+ audio_data = self._process_audio(audio_generator)
148
146
 
149
147
  # Create AudioArtifact
150
148
  audio_artifact = Audio(
151
149
  id=str(uuid4()),
152
- base64_audio=base64_audio,
150
+ content=audio_data,
153
151
  mime_type="audio/mpeg",
154
152
  )
155
153
 
156
154
  return ToolResult(
157
- content="Audio generated successfully",
155
+ content="Sound effect generated successfully",
158
156
  audios=[audio_artifact],
159
157
  )
160
158
 
161
159
  except Exception as e:
162
- logger.error(f"Failed to generate audio: {e}")
160
+ log_error(f"Failed to generate sound effect: {e}")
163
161
  return ToolResult(content=f"Error: {e}")
164
162
 
165
163
  def text_to_speech(self, agent: Union[Agent, Team], prompt: str) -> ToolResult:
166
164
  """
167
- Use this function to convert text to speech audio.
165
+ Convert text to speech.
168
166
 
169
167
  Args:
170
168
  prompt (str): Text to generate audio from.
@@ -179,12 +177,12 @@ class ElevenLabsTools(Toolkit):
179
177
  output_format=self.output_format,
180
178
  )
181
179
 
182
- base64_audio = self._process_audio(audio_generator)
180
+ audio_data = self._process_audio(audio_generator)
183
181
 
184
182
  # Create AudioArtifact
185
183
  audio_artifact = Audio(
186
184
  id=str(uuid4()),
187
- base64_audio=base64_audio,
185
+ content=audio_data,
188
186
  mime_type="audio/mpeg",
189
187
  )
190
188
 
@@ -194,5 +192,5 @@ class ElevenLabsTools(Toolkit):
194
192
  )
195
193
 
196
194
  except Exception as e:
197
- logger.error(f"Failed to generate audio: {e}")
195
+ log_error(f"Failed to generate audio: {e}")
198
196
  return ToolResult(content=f"Error: {e}")
agno/tools/exa.py CHANGED
@@ -27,14 +27,14 @@ class ExaTools(Toolkit):
27
27
  all (bool): Enable all tools. Overrides individual flags when True. Default is False.
28
28
  text (bool): Retrieve text content from results. Default is True.
29
29
  text_length_limit (int): Max length of text content per result. Default is 1000.
30
- highlights (bool): Include highlighted snippets. Default is True.
30
+ highlights (bool): Include highlighted snippets. Deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.
31
31
  api_key (Optional[str]): Exa API key. Retrieved from `EXA_API_KEY` env variable if not provided.
32
32
  num_results (Optional[int]): Default number of search results. Overrides individual searches if set.
33
33
  start_crawl_date (Optional[str]): Include results crawled on/after this date (`YYYY-MM-DD`).
34
34
  end_crawl_date (Optional[str]): Include results crawled on/before this date (`YYYY-MM-DD`).
35
35
  start_published_date (Optional[str]): Include results published on/after this date (`YYYY-MM-DD`).
36
36
  end_published_date (Optional[str]): Include results published on/before this date (`YYYY-MM-DD`).
37
- use_autoprompt (Optional[bool]): Enable autoprompt features in queries.
37
+ use_autoprompt (Optional[bool]): Enable autoprompt features in queries. Deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.
38
38
  type (Optional[str]): Specify content type (e.g., article, blog, video).
39
39
  category (Optional[str]): Filter results by category. Options are "company", "research paper", "news", "pdf", "github", "tweet", "personal site", "linkedin profile", "financial report".
40
40
  include_domains (Optional[List[str]]): Restrict results to these domains.
@@ -54,7 +54,7 @@ class ExaTools(Toolkit):
54
54
  all: bool = False,
55
55
  text: bool = True,
56
56
  text_length_limit: int = 1000,
57
- highlights: bool = True,
57
+ highlights: Optional[bool] = None, # Deprecated
58
58
  summary: bool = False,
59
59
  api_key: Optional[str] = None,
60
60
  num_results: Optional[int] = None,
@@ -84,7 +84,24 @@ class ExaTools(Toolkit):
84
84
 
85
85
  self.text: bool = text
86
86
  self.text_length_limit: int = text_length_limit
87
- self.highlights: bool = highlights
87
+
88
+ if highlights:
89
+ import warnings
90
+
91
+ warnings.warn(
92
+ "The 'highlights' parameter is deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.",
93
+ DeprecationWarning,
94
+ stacklevel=2,
95
+ )
96
+ if use_autoprompt:
97
+ import warnings
98
+
99
+ warnings.warn(
100
+ "The 'use_autoprompt' parameter is deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.",
101
+ DeprecationWarning,
102
+ stacklevel=2,
103
+ )
104
+
88
105
  self.summary: bool = summary
89
106
  self.num_results: Optional[int] = num_results
90
107
  self.livecrawl: str = livecrawl
@@ -92,7 +109,6 @@ class ExaTools(Toolkit):
92
109
  self.end_crawl_date: Optional[str] = end_crawl_date
93
110
  self.start_published_date: Optional[str] = start_published_date
94
111
  self.end_published_date: Optional[str] = end_published_date
95
- self.use_autoprompt: Optional[bool] = use_autoprompt
96
112
  self.type: Optional[str] = type
97
113
  self.category: Optional[str] = category
98
114
  self.include_domains: Optional[List[str]] = include_domains
@@ -140,13 +156,6 @@ class ExaTools(Toolkit):
140
156
  if self.text_length_limit:
141
157
  _text = _text[: self.text_length_limit]
142
158
  result_dict["text"] = _text
143
- if self.highlights:
144
- try:
145
- if result.highlights: # type: ignore
146
- result_dict["highlights"] = result.highlights # type: ignore
147
- except Exception as e:
148
- log_debug(f"Failed to get highlights {e}")
149
- result_dict["highlights"] = f"Failed to get highlights {e}"
150
159
  exa_results_parsed.append(result_dict)
151
160
  return json.dumps(exa_results_parsed, indent=4, ensure_ascii=False)
152
161
 
@@ -168,14 +177,12 @@ class ExaTools(Toolkit):
168
177
  log_info(f"Searching exa for: {query}")
169
178
  search_kwargs: Dict[str, Any] = {
170
179
  "text": self.text,
171
- "highlights": self.highlights,
172
180
  "summary": self.summary,
173
181
  "num_results": self.num_results or num_results,
174
182
  "start_crawl_date": self.start_crawl_date,
175
183
  "end_crawl_date": self.end_crawl_date,
176
184
  "start_published_date": self.start_published_date,
177
185
  "end_published_date": self.end_published_date,
178
- "use_autoprompt": self.use_autoprompt,
179
186
  "type": self.type,
180
187
  "category": self.category or category, # Prefer a user-set category
181
188
  "include_domains": self.include_domains,
@@ -212,7 +219,6 @@ class ExaTools(Toolkit):
212
219
 
213
220
  query_kwargs: Dict[str, Any] = {
214
221
  "text": self.text,
215
- "highlights": self.highlights,
216
222
  "summary": self.summary,
217
223
  }
218
224
 
@@ -249,7 +255,6 @@ class ExaTools(Toolkit):
249
255
 
250
256
  query_kwargs: Dict[str, Any] = {
251
257
  "text": self.text,
252
- "highlights": self.highlights,
253
258
  "summary": self.summary,
254
259
  "include_domains": self.include_domains,
255
260
  "exclude_domains": self.exclude_domains,
agno/tools/file.py CHANGED
@@ -1,9 +1,9 @@
1
1
  import json
2
2
  from pathlib import Path
3
- from typing import Any, List, Optional
3
+ from typing import Any, List, Optional, Tuple
4
4
 
5
5
  from agno.tools import Toolkit
6
- from agno.utils.log import log_debug, log_error, log_info
6
+ from agno.utils.log import log_debug, log_error
7
7
 
8
8
 
9
9
  class FileTools(Toolkit):
@@ -12,14 +12,26 @@ class FileTools(Toolkit):
12
12
  base_dir: Optional[Path] = None,
13
13
  enable_save_file: bool = True,
14
14
  enable_read_file: bool = True,
15
+ enable_delete_file: bool = False,
15
16
  enable_list_files: bool = True,
16
17
  enable_search_files: bool = True,
18
+ enable_read_file_chunk: bool = True,
19
+ enable_replace_file_chunk: bool = True,
20
+ expose_base_directory: bool = False,
21
+ max_file_length: int = 10000000,
22
+ max_file_lines: int = 100000,
23
+ line_separator: str = "\n",
17
24
  all: bool = False,
18
25
  **kwargs,
19
26
  ):
20
27
  self.base_dir: Path = base_dir or Path.cwd()
28
+ self.base_dir = self.base_dir.resolve()
21
29
 
22
30
  tools: List[Any] = []
31
+ self.max_file_length = max_file_length
32
+ self.max_file_lines = max_file_lines
33
+ self.line_separator = line_separator
34
+ self.expose_base_directory = expose_base_directory
23
35
  if all or enable_save_file:
24
36
  tools.append(self.save_file)
25
37
  if all or enable_read_file:
@@ -28,10 +40,16 @@ class FileTools(Toolkit):
28
40
  tools.append(self.list_files)
29
41
  if all or enable_search_files:
30
42
  tools.append(self.search_files)
43
+ if all or enable_delete_file:
44
+ tools.append(self.delete_file)
45
+ if all or enable_read_file_chunk:
46
+ tools.append(self.read_file_chunk)
47
+ if all or enable_replace_file_chunk:
48
+ tools.append(self.replace_file_chunk)
31
49
 
32
50
  super().__init__(name="file_tools", tools=tools, **kwargs)
33
51
 
34
- def save_file(self, contents: str, file_name: str, overwrite: bool = True) -> str:
52
+ def save_file(self, contents: str, file_name: str, overwrite: bool = True, encoding: str = "utf-8") -> str:
35
53
  """Saves the contents to a file called `file_name` and returns the file name if successful.
36
54
 
37
55
  :param contents: The contents to save.
@@ -40,42 +58,146 @@ class FileTools(Toolkit):
40
58
  :return: The file name if successful, otherwise returns an error message.
41
59
  """
42
60
  try:
43
- file_path = self.base_dir.joinpath(file_name)
61
+ safe, file_path = self.check_escape(file_name)
62
+ if not (safe):
63
+ log_error(f"Attempted to save file: {file_name}")
64
+ return "Error saving file"
44
65
  log_debug(f"Saving contents to {file_path}")
45
66
  if not file_path.parent.exists():
46
67
  file_path.parent.mkdir(parents=True, exist_ok=True)
47
68
  if file_path.exists() and not overwrite:
48
69
  return f"File {file_name} already exists"
49
- file_path.write_text(contents)
50
- log_info(f"Saved: {file_path}")
70
+ file_path.write_text(contents, encoding=encoding)
71
+ log_debug(f"Saved: {file_path}")
51
72
  return str(file_name)
52
73
  except Exception as e:
53
74
  log_error(f"Error saving to file: {e}")
54
75
  return f"Error saving to file: {e}"
55
76
 
56
- def read_file(self, file_name: str) -> str:
77
+ def read_file_chunk(self, file_name: str, start_line: int, end_line: int, encoding: str = "utf-8") -> str:
78
+ """Reads the contents of the file `file_name` and returns lines from start_line to end_line.
79
+
80
+ :param file_name: The name of the file to read.
81
+ :param start_line: Number of first line in the returned chunk
82
+ :param end_line: Number of the last line in the returned chunk
83
+ :param encoding: Encoding to use, default - utf-8
84
+
85
+ :return: The contents of the selected chunk
86
+ """
87
+ try:
88
+ log_debug(f"Reading file: {file_name}")
89
+ safe, file_path = self.check_escape(file_name)
90
+ if not (safe):
91
+ log_error(f"Attempted to read file: {file_name}")
92
+ return "Error reading file"
93
+ contents = file_path.read_text(encoding=encoding)
94
+ lines = contents.split(self.line_separator)
95
+ return self.line_separator.join(lines[start_line : end_line + 1])
96
+ except Exception as e:
97
+ log_error(f"Error reading file: {e}")
98
+ return f"Error reading file: {e}"
99
+
100
+ def replace_file_chunk(
101
+ self, file_name: str, start_line: int, end_line: int, chunk: str, encoding: str = "utf-8"
102
+ ) -> str:
103
+ """Reads the contents of the file, replaces lines
104
+ between start_line and end_line with chunk and writes the file
105
+
106
+ :param file_name: The name of the file to process.
107
+ :param start_line: Number of first line in the replaced chunk
108
+ :param end_line: Number of the last line in the replaced chunk
109
+ :param chunk: String to be inserted instead of lines from start_line to end_line. Can have multiple lines.
110
+ :param encoding: Encoding to use, default - utf-8
111
+
112
+ :return: file name if successfull, error message otherwise
113
+ """
114
+ try:
115
+ log_debug(f"Patching file: {file_name}")
116
+ safe, file_path = self.check_escape(file_name)
117
+ if not (safe):
118
+ log_error(f"Attempted to read file: {file_name}")
119
+ return "Error reading file"
120
+ contents = file_path.read_text(encoding=encoding)
121
+ lines = contents.split(self.line_separator)
122
+ start = lines[0:start_line]
123
+ end = lines[end_line + 1 :]
124
+ return self.save_file(
125
+ file_name=file_name, contents=self.line_separator.join(start + [chunk] + end), encoding=encoding
126
+ )
127
+ except Exception as e:
128
+ log_error(f"Error patching file: {e}")
129
+ return f"Error patching file: {e}"
130
+
131
+ def read_file(self, file_name: str, encoding: str = "utf-8") -> str:
57
132
  """Reads the contents of the file `file_name` and returns the contents if successful.
58
133
 
59
134
  :param file_name: The name of the file to read.
135
+ :param encoding: Encoding to use, default - utf-8
60
136
  :return: The contents of the file if successful, otherwise returns an error message.
61
137
  """
62
138
  try:
63
- log_info(f"Reading file: {file_name}")
64
- file_path = self.base_dir.joinpath(file_name)
65
- contents = file_path.read_text(encoding="utf-8")
139
+ log_debug(f"Reading file: {file_name}")
140
+ safe, file_path = self.check_escape(file_name)
141
+ if not (safe):
142
+ log_error(f"Attempted to read file: {file_name}")
143
+ return "Error reading file"
144
+ contents = file_path.read_text(encoding=encoding)
145
+ if len(contents) > self.max_file_length:
146
+ return "Error reading file: file too long. Use read_file_chunk instead"
147
+ if len(contents.split(self.line_separator)) > self.max_file_lines:
148
+ return "Error reading file: file too long. Use read_file_chunk instead"
149
+
66
150
  return str(contents)
67
151
  except Exception as e:
68
152
  log_error(f"Error reading file: {e}")
69
153
  return f"Error reading file: {e}"
70
154
 
71
- def list_files(self) -> str:
72
- """Returns a list of files in the base directory
155
+ def delete_file(self, file_name: str) -> str:
156
+ """Deletes a file
157
+ :param file_name: Name of the file to delete
158
+
159
+ :return: Empty string, if operation succeeded, otherwise returns an error message
160
+ """
161
+ safe, path = self.check_escape(file_name)
162
+ try:
163
+ if safe:
164
+ if path.is_dir():
165
+ path.rmdir()
166
+ return ""
167
+ path.unlink()
168
+ return ""
169
+ else:
170
+ log_error(f"Attempt to delete file outside {self.base_dir}: {file_name}")
171
+ return "Incorrect file_name"
172
+ except Exception as e:
173
+ log_error(f"Error removing {file_name}: {e}")
174
+ return f"Error removing file: {e}"
175
+
176
+ def check_escape(self, relative_path: str) -> Tuple[bool, Path]:
177
+ d = self.base_dir.joinpath(Path(relative_path)).resolve()
178
+ if self.base_dir == d:
179
+ return True, d
180
+ try:
181
+ d.relative_to(self.base_dir)
182
+ except ValueError:
183
+ log_error("Attempted to escape base_dir")
184
+ return False, self.base_dir
185
+ return True, d
186
+
187
+ def list_files(self, **kwargs) -> str:
188
+ """Returns a list of files in directory
189
+ :param directory: (Optional) name of directory to list.
73
190
 
74
191
  :return: The contents of the file if successful, otherwise returns an error message.
75
192
  """
193
+ directory = kwargs.get("directory", ".")
76
194
  try:
77
- log_info(f"Reading files in : {self.base_dir}")
78
- return json.dumps([str(file_path) for file_path in self.base_dir.iterdir()], indent=4)
195
+ log_debug(f"Reading files in : {self.base_dir}/{directory}")
196
+ safe, d = self.check_escape(directory)
197
+ if safe:
198
+ return json.dumps([str(file_path.relative_to(self.base_dir)) for file_path in d.iterdir()], indent=4)
199
+ else:
200
+ return "{}"
79
201
  except Exception as e:
80
202
  log_error(f"Error reading files: {e}")
81
203
  return f"Error reading files: {e}"
@@ -92,15 +214,23 @@ class FileTools(Toolkit):
92
214
 
93
215
  log_debug(f"Searching files in {self.base_dir} with pattern {pattern}")
94
216
  matching_files = list(self.base_dir.glob(pattern))
95
-
96
- file_paths = [str(file_path) for file_path in matching_files]
97
-
98
- result = {
99
- "pattern": pattern,
100
- "base_directory": str(self.base_dir),
101
- "matches_found": len(file_paths),
102
- "files": file_paths,
103
- }
217
+ result = None
218
+ if self.expose_base_directory:
219
+ file_paths = [str(file_path) for file_path in matching_files]
220
+ result = {
221
+ "pattern": pattern,
222
+ "matches_found": len(file_paths),
223
+ "base_directory": str(self.base_dir),
224
+ "files": file_paths,
225
+ }
226
+ else:
227
+ file_paths = [str(file_path.relative_to(self.base_dir)) for file_path in matching_files]
228
+
229
+ result = {
230
+ "pattern": pattern,
231
+ "matches_found": len(file_paths),
232
+ "files": file_paths,
233
+ }
104
234
  log_debug(f"Found {len(file_paths)} files matching pattern {pattern}")
105
235
  return json.dumps(result, indent=2)
106
236