agno 2.2.13__py3-none-any.whl → 2.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/tools/brandfetch.py CHANGED
@@ -1,9 +1,10 @@
1
1
  """
2
- Going to contribute this to agno toolkits.
2
+ Brandfetch API toolkit for retrieving brand data and searching brands.
3
3
  """
4
4
 
5
+ import warnings
5
6
  from os import getenv
6
- from typing import Any, Optional
7
+ from typing import Any, List, Optional
7
8
 
8
9
  try:
9
10
  import httpx
@@ -31,8 +32,6 @@ class BrandfetchTools(Toolkit):
31
32
  all: bool - if True, will use all tools
32
33
  enable_search_by_identifier: bool - if True, will use search by identifier
33
34
  enable_search_by_brand: bool - if True, will use search by brand
34
- enable_asearch_by_identifier: bool - if True, will use async search by identifier
35
- enable_asearch_by_brand: bool - if True, will use async search by brand
36
35
  """
37
36
 
38
37
  def __init__(
@@ -44,9 +43,18 @@ class BrandfetchTools(Toolkit):
44
43
  enable_search_by_identifier: bool = True,
45
44
  enable_search_by_brand: bool = False,
46
45
  all: bool = False,
47
- async_tools: bool = False,
46
+ async_tools: bool = False, # Deprecated
48
47
  **kwargs,
49
48
  ):
49
+ # Handle deprecated async_tools parameter
50
+ if async_tools:
51
+ warnings.warn(
52
+ "The 'async_tools' parameter is deprecated and will be removed in a future version. "
53
+ "Async tools are now automatically used when calling agent.arun() or agent.aprint_response().",
54
+ DeprecationWarning,
55
+ stacklevel=2,
56
+ )
57
+
50
58
  self.api_key = api_key or getenv("BRANDFETCH_API_KEY")
51
59
  self.client_id = client_id or getenv("BRANDFETCH_CLIENT_ID")
52
60
  self.base_url = base_url
@@ -54,20 +62,21 @@ class BrandfetchTools(Toolkit):
54
62
  self.search_url = f"{self.base_url}/search"
55
63
  self.brand_url = f"{self.base_url}/brands"
56
64
 
57
- tools: list[Any] = []
58
- # Backward-compat mapping: prefer new enable_* flags, but honor legacy toggles
59
- if async_tools:
60
- if all or enable_search_by_identifier:
61
- tools.append(self.asearch_by_identifier)
62
- if all or enable_search_by_brand:
63
- tools.append(self.asearch_by_brand)
64
- else:
65
- if all or enable_search_by_identifier:
66
- tools.append(self.search_by_identifier)
67
- if all or enable_search_by_brand:
68
- tools.append(self.search_by_brand)
65
+ # Build tools lists
66
+ # sync tools: used by agent.run() and agent.print_response()
67
+ # async tools: used by agent.arun() and agent.aprint_response()
68
+ tools: List[Any] = []
69
+ async_tools_list: List[tuple] = []
70
+
71
+ if all or enable_search_by_identifier:
72
+ tools.append(self.search_by_identifier)
73
+ async_tools_list.append((self.asearch_by_identifier, "search_by_identifier"))
74
+ if all or enable_search_by_brand:
75
+ tools.append(self.search_by_brand)
76
+ async_tools_list.append((self.asearch_by_brand, "search_by_brand"))
77
+
69
78
  name = kwargs.pop("name", "brandfetch_tools")
70
- super().__init__(name=name, tools=tools, **kwargs)
79
+ super().__init__(name=name, tools=tools, async_tools=async_tools_list, **kwargs)
71
80
 
72
81
  async def asearch_by_identifier(self, identifier: str) -> dict[str, Any]:
73
82
  """
agno/tools/browserbase.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import re
2
3
  from os import getenv
3
4
  from typing import Any, Dict, List, Optional
4
5
 
@@ -10,13 +11,6 @@ try:
10
11
  except ImportError:
11
12
  raise ImportError("`browserbase` not installed. Please install using `pip install browserbase`")
12
13
 
13
- try:
14
- from playwright.sync_api import sync_playwright
15
- except ImportError:
16
- raise ImportError(
17
- "`playwright` not installed. Please install using `pip install playwright` and run `playwright install`"
18
- )
19
-
20
14
 
21
15
  class BrowserbaseTools(Toolkit):
22
16
  def __init__(
@@ -29,6 +23,8 @@ class BrowserbaseTools(Toolkit):
29
23
  enable_get_page_content: bool = True,
30
24
  enable_close_session: bool = True,
31
25
  all: bool = False,
26
+ parse_html: bool = True,
27
+ max_content_length: Optional[int] = 100000,
32
28
  **kwargs,
33
29
  ):
34
30
  """Initialize BrowserbaseTools.
@@ -36,8 +32,21 @@ class BrowserbaseTools(Toolkit):
36
32
  Args:
37
33
  api_key (str, optional): Browserbase API key.
38
34
  project_id (str, optional): Browserbase project ID.
39
- base_url (str, optional): Custom Browserbase API endpoint URL (NOT the target website URL). Only use this if you're using a self-hosted Browserbase instance or need to connect to a different region.
35
+ base_url (str, optional): Custom Browserbase API endpoint URL (NOT the target website URL).
36
+ Only use this if you're using a self-hosted Browserbase instance or need to connect to a different region.
37
+ enable_navigate_to (bool): Enable the navigate_to tool. Defaults to True.
38
+ enable_screenshot (bool): Enable the screenshot tool. Defaults to True.
39
+ enable_get_page_content (bool): Enable the get_page_content tool. Defaults to True.
40
+ enable_close_session (bool): Enable the close_session tool. Defaults to True.
41
+ all (bool): Enable all tools. Defaults to False.
42
+ parse_html (bool): If True, extract only visible text content instead of raw HTML. Defaults to True.
43
+ This significantly reduces token usage and is recommended for most use cases.
44
+ max_content_length (int, optional): Maximum character length for page content. Defaults to 100000.
45
+ Content exceeding this limit will be truncated with a notice. Set to None for no limit.
40
46
  """
47
+ self.parse_html = parse_html
48
+ self.max_content_length = max_content_length
49
+
41
50
  self.api_key = api_key or getenv("BROWSERBASE_API_KEY")
42
51
  if not self.api_key:
43
52
  raise ValueError(
@@ -59,23 +68,40 @@ class BrowserbaseTools(Toolkit):
59
68
  else:
60
69
  self.app = Browserbase(api_key=self.api_key)
61
70
 
71
+ # Sync playwright state
62
72
  self._playwright = None
63
73
  self._browser = None
64
74
  self._page = None
75
+
76
+ # Async playwright state
77
+ self._async_playwright = None
78
+ self._async_browser = None
79
+ self._async_page = None
80
+
81
+ # Shared session state
65
82
  self._session = None
66
83
  self._connect_url = None
67
84
 
85
+ # Build tools lists
86
+ # sync tools: used by agent.run() and agent.print_response()
87
+ # async tools: used by agent.arun() and agent.aprint_response()
68
88
  tools: List[Any] = []
89
+ async_tools: List[tuple] = []
90
+
69
91
  if all or enable_navigate_to:
70
92
  tools.append(self.navigate_to)
93
+ async_tools.append((self.anavigate_to, "navigate_to"))
71
94
  if all or enable_screenshot:
72
95
  tools.append(self.screenshot)
96
+ async_tools.append((self.ascreenshot, "screenshot"))
73
97
  if all or enable_get_page_content:
74
98
  tools.append(self.get_page_content)
99
+ async_tools.append((self.aget_page_content, "get_page_content"))
75
100
  if all or enable_close_session:
76
101
  tools.append(self.close_session)
102
+ async_tools.append((self.aclose_session, "close_session"))
77
103
 
78
- super().__init__(name="browserbase_tools", tools=tools, **kwargs)
104
+ super().__init__(name="browserbase_tools", tools=tools, async_tools=async_tools, **kwargs)
79
105
 
80
106
  def _ensure_session(self):
81
107
  """Ensures a session exists, creating one if needed."""
@@ -91,9 +117,16 @@ class BrowserbaseTools(Toolkit):
91
117
 
92
118
  def _initialize_browser(self, connect_url: Optional[str] = None):
93
119
  """
94
- Initialize browser connection if not already initialized.
120
+ Initialize sync browser connection if not already initialized.
95
121
  Use provided connect_url or ensure we have a session with a connect_url
96
122
  """
123
+ try:
124
+ from playwright.sync_api import sync_playwright # type: ignore[import-not-found]
125
+ except ImportError:
126
+ raise ImportError(
127
+ "`playwright` not installed. Please install using `pip install playwright` and run `playwright install`"
128
+ )
129
+
97
130
  if connect_url:
98
131
  self._connect_url = connect_url if connect_url else "" # type: ignore
99
132
  elif not self._connect_url:
@@ -107,7 +140,7 @@ class BrowserbaseTools(Toolkit):
107
140
  self._page = context.pages[0] or context.new_page() # type: ignore
108
141
 
109
142
  def _cleanup(self):
110
- """Clean up browser resources."""
143
+ """Clean up sync browser resources."""
111
144
  if self._browser:
112
145
  self._browser.close()
113
146
  self._browser = None
@@ -168,26 +201,77 @@ class BrowserbaseTools(Toolkit):
168
201
  self._cleanup()
169
202
  raise e
170
203
 
204
+ def _extract_text_content(self, html: str) -> str:
205
+ """Extract visible text content from HTML, removing scripts, styles, and tags.
206
+
207
+ Args:
208
+ html: Raw HTML content
209
+
210
+ Returns:
211
+ Cleaned text content
212
+ """
213
+ # Remove script and style elements
214
+ html = re.sub(r"<script[^>]*>.*?</script>", "", html, flags=re.DOTALL | re.IGNORECASE)
215
+ html = re.sub(r"<style[^>]*>.*?</style>", "", html, flags=re.DOTALL | re.IGNORECASE)
216
+ # Remove HTML comments
217
+ html = re.sub(r"<!--.*?-->", "", html, flags=re.DOTALL)
218
+ # Remove all HTML tags
219
+ html = re.sub(r"<[^>]+>", " ", html)
220
+ # Decode common HTML entities
221
+ html = html.replace("&nbsp;", " ")
222
+ html = html.replace("&amp;", "&")
223
+ html = html.replace("&lt;", "<")
224
+ html = html.replace("&gt;", ">")
225
+ html = html.replace("&quot;", '"')
226
+ html = html.replace("&#39;", "'")
227
+ # Normalize whitespace
228
+ html = re.sub(r"\s+", " ", html)
229
+ return html.strip()
230
+
231
+ def _truncate_content(self, content: str) -> str:
232
+ """Truncate content if it exceeds max_content_length.
233
+
234
+ Args:
235
+ content: The content to potentially truncate
236
+
237
+ Returns:
238
+ Original or truncated content with notice
239
+ """
240
+ if self.max_content_length is None or len(content) <= self.max_content_length:
241
+ return content
242
+
243
+ truncated = content[: self.max_content_length]
244
+ return f"{truncated}\n\n[Content truncated. Original length: {len(content)} characters. Showing first {self.max_content_length} characters.]"
245
+
171
246
  def get_page_content(self, connect_url: Optional[str] = None) -> str:
172
- """Gets the HTML content of the current page.
247
+ """Gets the content of the current page.
173
248
 
174
249
  Args:
175
250
  connect_url (str, optional): The connection URL from an existing session
176
251
 
177
252
  Returns:
178
- The page HTML content
253
+ The page content (text-only if parse_html=True, otherwise raw HTML)
179
254
  """
180
255
  try:
181
256
  self._initialize_browser(connect_url)
182
- return self._page.content() if self._page else ""
257
+ if not self._page:
258
+ return ""
259
+
260
+ raw_content = self._page.content()
261
+
262
+ if self.parse_html:
263
+ content = self._extract_text_content(raw_content)
264
+ else:
265
+ content = raw_content
266
+
267
+ return self._truncate_content(content)
183
268
  except Exception as e:
184
269
  self._cleanup()
185
270
  raise e
186
271
 
187
272
  def close_session(self) -> str:
188
273
  """Closes a browser session.
189
- Args:
190
- session_id (str, optional): The session ID to close. If not provided, will use the current session.
274
+
191
275
  Returns:
192
276
  JSON string with closure status
193
277
  """
@@ -207,3 +291,128 @@ class BrowserbaseTools(Toolkit):
207
291
  )
208
292
  except Exception as e:
209
293
  return json.dumps({"status": "warning", "message": f"Cleanup completed with warning: {str(e)}"})
294
+
295
+ async def _ainitialize_browser(self, connect_url: Optional[str] = None):
296
+ """
297
+ Initialize async browser connection if not already initialized.
298
+ Use provided connect_url or ensure we have a session with a connect_url
299
+ """
300
+ try:
301
+ from playwright.async_api import async_playwright # type: ignore[import-not-found]
302
+ except ImportError:
303
+ raise ImportError(
304
+ "`playwright` not installed. Please install using `pip install playwright` and run `playwright install`"
305
+ )
306
+
307
+ if connect_url:
308
+ self._connect_url = connect_url if connect_url else "" # type: ignore
309
+ elif not self._connect_url:
310
+ self._ensure_session()
311
+
312
+ if not self._async_playwright:
313
+ self._async_playwright = await async_playwright().start() # type: ignore
314
+ if self._async_playwright:
315
+ self._async_browser = await self._async_playwright.chromium.connect_over_cdp(self._connect_url)
316
+ context = self._async_browser.contexts[0] if self._async_browser else None
317
+ if context:
318
+ self._async_page = context.pages[0] if context.pages else await context.new_page()
319
+
320
+ async def _acleanup(self):
321
+ """Clean up async browser resources."""
322
+ if self._async_browser:
323
+ await self._async_browser.close()
324
+ self._async_browser = None
325
+ if self._async_playwright:
326
+ await self._async_playwright.stop()
327
+ self._async_playwright = None
328
+ self._async_page = None
329
+
330
+ async def anavigate_to(self, url: str, connect_url: Optional[str] = None) -> str:
331
+ """Navigates to a URL asynchronously.
332
+
333
+ Args:
334
+ url (str): The URL to navigate to
335
+ connect_url (str, optional): The connection URL from an existing session
336
+
337
+ Returns:
338
+ JSON string with navigation status
339
+ """
340
+ try:
341
+ await self._ainitialize_browser(connect_url)
342
+ if self._async_page:
343
+ await self._async_page.goto(url, wait_until="networkidle")
344
+ title = await self._async_page.title() if self._async_page else ""
345
+ result = {"status": "complete", "title": title, "url": url}
346
+ return json.dumps(result)
347
+ except Exception as e:
348
+ await self._acleanup()
349
+ raise e
350
+
351
+ async def ascreenshot(self, path: str, full_page: bool = True, connect_url: Optional[str] = None) -> str:
352
+ """Takes a screenshot of the current page asynchronously.
353
+
354
+ Args:
355
+ path (str): Where to save the screenshot
356
+ full_page (bool): Whether to capture the full page
357
+ connect_url (str, optional): The connection URL from an existing session
358
+
359
+ Returns:
360
+ JSON string confirming screenshot was saved
361
+ """
362
+ try:
363
+ await self._ainitialize_browser(connect_url)
364
+ if self._async_page:
365
+ await self._async_page.screenshot(path=path, full_page=full_page)
366
+ return json.dumps({"status": "success", "path": path})
367
+ except Exception as e:
368
+ await self._acleanup()
369
+ raise e
370
+
371
+ async def aget_page_content(self, connect_url: Optional[str] = None) -> str:
372
+ """Gets the content of the current page asynchronously.
373
+
374
+ Args:
375
+ connect_url (str, optional): The connection URL from an existing session
376
+
377
+ Returns:
378
+ The page content (text-only if parse_html=True, otherwise raw HTML)
379
+ """
380
+ try:
381
+ await self._ainitialize_browser(connect_url)
382
+ if not self._async_page:
383
+ return ""
384
+
385
+ raw_content = await self._async_page.content()
386
+
387
+ if self.parse_html:
388
+ content = self._extract_text_content(raw_content)
389
+ else:
390
+ content = raw_content
391
+
392
+ return self._truncate_content(content)
393
+ except Exception as e:
394
+ await self._acleanup()
395
+ raise e
396
+
397
+ async def aclose_session(self) -> str:
398
+ """Closes a browser session asynchronously.
399
+
400
+ Returns:
401
+ JSON string with closure status
402
+ """
403
+ try:
404
+ # First cleanup our local browser resources
405
+ await self._acleanup()
406
+
407
+ # Reset session state
408
+ self._session = None
409
+ self._connect_url = None
410
+
411
+ return json.dumps(
412
+ {
413
+ "status": "closed",
414
+ "message": "Browser resources cleaned up. Session will auto-close if not already closed.",
415
+ }
416
+ )
417
+ except Exception as e:
418
+ return json.dumps({"status": "warning", "message": f"Cleanup completed with warning: {str(e)}"})
agno/tools/crawl4ai.py CHANGED
@@ -20,6 +20,7 @@ class Crawl4aiTools(Toolkit):
20
20
  bm25_threshold: float = 1.0,
21
21
  headless: bool = True,
22
22
  wait_until: str = "domcontentloaded",
23
+ proxy_config: Optional[Dict[str, Any]] = None,
23
24
  enable_crawl: bool = True,
24
25
  all: bool = False,
25
26
  **kwargs,
@@ -36,6 +37,7 @@ class Crawl4aiTools(Toolkit):
36
37
  self.bm25_threshold = bm25_threshold
37
38
  self.wait_until = wait_until
38
39
  self.headless = headless
40
+ self.proxy_config = proxy_config or {}
39
41
 
40
42
  def _build_config(self, search_query: Optional[str] = None) -> Dict[str, Any]:
41
43
  """Build CrawlerRunConfig parameters from toolkit settings."""
@@ -103,6 +105,7 @@ class Crawl4aiTools(Toolkit):
103
105
  browser_config = BrowserConfig(
104
106
  headless=self.headless,
105
107
  verbose=False,
108
+ **self.proxy_config,
106
109
  )
107
110
 
108
111
  async with AsyncWebCrawler(config=browser_config) as crawler:
agno/tools/duckduckgo.py CHANGED
@@ -1,36 +1,26 @@
1
- import json
2
- from typing import Any, List, Optional
1
+ from typing import Optional
3
2
 
4
- from agno.tools import Toolkit
5
- from agno.utils.log import log_debug
3
+ from agno.tools.websearch import WebSearchTools
6
4
 
7
- try:
8
- from ddgs import DDGS
9
- except ImportError:
10
- raise ImportError("`ddgs` not installed. Please install using `pip install ddgs`")
11
5
 
12
-
13
- class DuckDuckGoTools(Toolkit):
6
+ class DuckDuckGoTools(WebSearchTools):
14
7
  """
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.
8
+ DuckDuckGoTools is a convenience wrapper around WebSearchTools with the backend
9
+ defaulting to "duckduckgo".
17
10
  Args:
18
- enable_search (bool): Enable DDGS search function.
19
- enable_news (bool): Enable DDGS news function.
20
- modifier (Optional[str]): A modifier to be used in the search request.
11
+ enable_search (bool): Enable web search function.
12
+ enable_news (bool): Enable news search function.
13
+ modifier (Optional[str]): A modifier to be prepended to search queries.
21
14
  fixed_max_results (Optional[int]): A fixed number of maximum results.
22
- proxy (Optional[str]): Proxy to be used in the search request.
15
+ proxy (Optional[str]): Proxy to be used for requests.
23
16
  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.
25
-
17
+ verify_ssl (bool): Whether to verify SSL certificates.
26
18
  """
27
19
 
28
20
  def __init__(
29
21
  self,
30
22
  enable_search: bool = True,
31
23
  enable_news: bool = True,
32
- all: bool = False,
33
- backend: str = "duckduckgo",
34
24
  modifier: Optional[str] = None,
35
25
  fixed_max_results: Optional[int] = None,
36
26
  proxy: Optional[str] = None,
@@ -38,54 +28,18 @@ class DuckDuckGoTools(Toolkit):
38
28
  verify_ssl: bool = True,
39
29
  **kwargs,
40
30
  ):
41
- self.proxy: Optional[str] = proxy
42
- self.timeout: Optional[int] = timeout
43
- self.fixed_max_results: Optional[int] = fixed_max_results
44
- self.modifier: Optional[str] = modifier
45
- self.verify_ssl: bool = verify_ssl
46
- self.backend: str = backend
47
-
48
- tools: List[Any] = []
49
- if all or enable_search:
50
- tools.append(self.duckduckgo_search)
51
- if all or enable_news:
52
- tools.append(self.duckduckgo_news)
53
-
54
- super().__init__(name="duckduckgo", tools=tools, **kwargs)
55
-
56
- def duckduckgo_search(self, query: str, max_results: int = 5) -> str:
57
- """Use this function to search DDGS for a query.
58
-
59
- Args:
60
- query(str): The query to search for.
61
- max_results (optional, default=5): The maximum number of results to return.
62
-
63
- Returns:
64
- The result from DDGS.
65
- """
66
- actual_max_results = self.fixed_max_results or max_results
67
- search_query = f"{self.modifier} {query}" if self.modifier else query
68
-
69
- log_debug(f"Searching DDG for: {search_query} using backend: {self.backend}")
70
- with DDGS(proxy=self.proxy, timeout=self.timeout, verify=self.verify_ssl) as ddgs:
71
- results = ddgs.text(query=search_query, max_results=actual_max_results, backend=self.backend)
72
-
73
- return json.dumps(results, indent=2)
74
-
75
- def duckduckgo_news(self, query: str, max_results: int = 5) -> str:
76
- """Use this function to get the latest news from DDGS.
77
-
78
- Args:
79
- query(str): The query to search for.
80
- max_results (optional, default=5): The maximum number of results to return.
81
-
82
- Returns:
83
- The latest news from DDGS.
84
- """
85
- actual_max_results = self.fixed_max_results or max_results
86
-
87
- log_debug(f"Searching DDG news for: {query} using backend: {self.backend}")
88
- with DDGS(proxy=self.proxy, timeout=self.timeout, verify=self.verify_ssl) as ddgs:
89
- results = ddgs.news(query=query, max_results=actual_max_results, backend=self.backend)
90
-
91
- return json.dumps(results, indent=2)
31
+ super().__init__(
32
+ enable_search=enable_search,
33
+ enable_news=enable_news,
34
+ backend="duckduckgo",
35
+ modifier=modifier,
36
+ fixed_max_results=fixed_max_results,
37
+ proxy=proxy,
38
+ timeout=timeout,
39
+ verify_ssl=verify_ssl,
40
+ **kwargs,
41
+ )
42
+
43
+ # Backward compatibility aliases for old method names
44
+ self.duckduckgo_search = self.web_search
45
+ self.duckduckgo_news = self.search_news
agno/tools/exa.py CHANGED
@@ -27,14 +27,12 @@ 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. Deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.
31
30
  api_key (Optional[str]): Exa API key. Retrieved from `EXA_API_KEY` env variable if not provided.
32
31
  num_results (Optional[int]): Default number of search results. Overrides individual searches if set.
33
32
  start_crawl_date (Optional[str]): Include results crawled on/after this date (`YYYY-MM-DD`).
34
33
  end_crawl_date (Optional[str]): Include results crawled on/before this date (`YYYY-MM-DD`).
35
34
  start_published_date (Optional[str]): Include results published on/after this date (`YYYY-MM-DD`).
36
35
  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. Deprecated since it was removed in the Exa API. It will be removed from Agno in a future release.
38
36
  type (Optional[str]): Specify content type (e.g., article, blog, video).
39
37
  category (Optional[str]): Filter results by category. Options are "company", "research paper", "news", "pdf", "github", "tweet", "personal site", "linkedin profile", "financial report".
40
38
  include_domains (Optional[List[str]]): Restrict results to these domains.
@@ -54,7 +52,6 @@ class ExaTools(Toolkit):
54
52
  all: bool = False,
55
53
  text: bool = True,
56
54
  text_length_limit: int = 1000,
57
- highlights: Optional[bool] = None, # Deprecated
58
55
  summary: bool = False,
59
56
  api_key: Optional[str] = None,
60
57
  num_results: Optional[int] = None,
@@ -63,7 +60,6 @@ class ExaTools(Toolkit):
63
60
  end_crawl_date: Optional[str] = None,
64
61
  start_published_date: Optional[str] = None,
65
62
  end_published_date: Optional[str] = None,
66
- use_autoprompt: Optional[bool] = None,
67
63
  type: Optional[str] = None,
68
64
  category: Optional[str] = None,
69
65
  include_domains: Optional[List[str]] = None,
@@ -85,23 +81,6 @@ class ExaTools(Toolkit):
85
81
  self.text: bool = text
86
82
  self.text_length_limit: int = text_length_limit
87
83
 
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
-
105
84
  self.summary: bool = summary
106
85
  self.num_results: Optional[int] = num_results
107
86
  self.livecrawl: str = livecrawl
agno/tools/file.py CHANGED
@@ -24,8 +24,7 @@ class FileTools(Toolkit):
24
24
  all: bool = False,
25
25
  **kwargs,
26
26
  ):
27
- self.base_dir: Path = base_dir or Path.cwd()
28
- self.base_dir = self.base_dir.resolve()
27
+ self.base_dir: Path = (base_dir or Path.cwd()).resolve()
29
28
 
30
29
  tools: List[Any] = []
31
30
  self.max_file_length = max_file_length
@@ -49,6 +48,19 @@ class FileTools(Toolkit):
49
48
 
50
49
  super().__init__(name="file_tools", tools=tools, **kwargs)
51
50
 
51
+ def check_escape(self, relative_path: str) -> Tuple[bool, Path]:
52
+ """Check if the file path is within the base directory.
53
+
54
+ Alias for _check_path maintained for backward compatibility.
55
+
56
+ Args:
57
+ relative_path: The file name or relative path to check.
58
+
59
+ Returns:
60
+ Tuple of (is_safe, resolved_path). If not safe, returns base_dir as the path.
61
+ """
62
+ return self._check_path(relative_path, self.base_dir)
63
+
52
64
  def save_file(self, contents: str, file_name: str, overwrite: bool = True, encoding: str = "utf-8") -> str:
53
65
  """Saves the contents to a file called `file_name` and returns the file name if successful.
54
66
 
@@ -173,17 +185,6 @@ class FileTools(Toolkit):
173
185
  log_error(f"Error removing {file_name}: {e}")
174
186
  return f"Error removing file: {e}"
175
187
 
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
188
  def list_files(self, **kwargs) -> str:
188
189
  """Returns a list of files in directory
189
190
  :param directory: (Optional) name of directory to list.