agno 2.1.2__py3-none-any.whl → 2.3.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. agno/agent/agent.py +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.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/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
 
@@ -108,15 +108,17 @@ class FileGenerationTools(Toolkit):
108
108
  # Save file to disk (if output_directory is set)
109
109
  file_path = self._save_file_to_disk(json_content, filename)
110
110
 
111
+ content_bytes = json_content.encode("utf-8")
112
+
111
113
  # Create FileArtifact
112
114
  file_artifact = File(
113
115
  id=str(uuid4()),
114
- content=json_content,
116
+ content=content_bytes,
115
117
  mime_type="application/json",
116
118
  file_type="json",
117
119
  filename=filename,
118
- size=len(json_content.encode("utf-8")),
119
- url=f"file://{file_path}" if file_path else None,
120
+ size=len(content_bytes),
121
+ filepath=file_path if file_path else None,
120
122
  )
121
123
 
122
124
  log_debug("JSON file generated successfully")
@@ -195,15 +197,17 @@ class FileGenerationTools(Toolkit):
195
197
  # Save file to disk (if output_directory is set)
196
198
  file_path = self._save_file_to_disk(csv_content, filename)
197
199
 
200
+ content_bytes = csv_content.encode("utf-8")
201
+
198
202
  # Create FileArtifact
199
203
  file_artifact = File(
200
204
  id=str(uuid4()),
201
- content=csv_content,
205
+ content=content_bytes,
202
206
  mime_type="text/csv",
203
207
  file_type="csv",
204
208
  filename=filename,
205
- size=len(csv_content.encode("utf-8")),
206
- url=f"file://{file_path}" if file_path else None,
209
+ size=len(content_bytes),
210
+ filepath=file_path if file_path else None,
207
211
  )
208
212
 
209
213
  log_debug("CSV file generated successfully")
@@ -287,7 +291,7 @@ class FileGenerationTools(Toolkit):
287
291
  file_type="pdf",
288
292
  filename=filename,
289
293
  size=len(pdf_content),
290
- url=f"file://{file_path}" if file_path else None,
294
+ filepath=file_path if file_path else None,
291
295
  )
292
296
 
293
297
  log_debug("PDF file generated successfully")
@@ -325,15 +329,17 @@ class FileGenerationTools(Toolkit):
325
329
  # Save file to disk (if output_directory is set)
326
330
  file_path = self._save_file_to_disk(content, filename)
327
331
 
332
+ content_bytes = content.encode("utf-8")
333
+
328
334
  # Create FileArtifact
329
335
  file_artifact = File(
330
336
  id=str(uuid4()),
331
- content=content,
337
+ content=content_bytes,
332
338
  mime_type="text/plain",
333
339
  file_type="txt",
334
340
  filename=filename,
335
- size=len(content.encode("utf-8")),
336
- url=f"file://{file_path}" if file_path else None,
341
+ size=len(content_bytes),
342
+ filepath=file_path if file_path else None,
337
343
  )
338
344
 
339
345
  log_debug("Text file generated successfully")
agno/tools/firecrawl.py CHANGED
@@ -101,8 +101,10 @@ class FirecrawlTools(Toolkit):
101
101
  The results of the crawling.
102
102
  """
103
103
  params: Dict[str, Any] = {}
104
- if self.limit or limit:
105
- params["limit"] = self.limit or limit
104
+ if self.limit is not None:
105
+ params["limit"] = self.limit
106
+ elif limit is not None:
107
+ params["limit"] = limit
106
108
  if self.formats:
107
109
  params["scrape_options"] = ScrapeOptions(formats=self.formats) # type: ignore
108
110
 
@@ -129,15 +131,21 @@ class FirecrawlTools(Toolkit):
129
131
  limit (int): The maximum number of results to return.
130
132
  """
131
133
  params: Dict[str, Any] = {}
132
- if self.limit or limit:
133
- params["limit"] = self.limit or limit
134
+ if self.limit is not None:
135
+ params["limit"] = self.limit
136
+ elif limit is not None:
137
+ params["limit"] = limit
134
138
  if self.formats:
135
139
  params["scrape_options"] = ScrapeOptions(formats=self.formats) # type: ignore
136
140
  if self.search_params:
137
141
  params.update(self.search_params)
138
142
 
139
143
  search_result = self.app.search(query, **params)
140
- if search_result.success:
141
- return json.dumps(search_result.data, cls=CustomJSONEncoder)
144
+
145
+ if hasattr(search_result, "success"):
146
+ if search_result.success:
147
+ return json.dumps(search_result.data, cls=CustomJSONEncoder)
148
+ else:
149
+ return f"Error searching with the Firecrawl tool: {search_result.error}"
142
150
  else:
143
- return "Error searching with the Firecrawl tool: " + search_result.error
151
+ return json.dumps(search_result.model_dump(), cls=CustomJSONEncoder)