agno 2.3.22__py3-none-any.whl → 2.3.24__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/agent.py +28 -1
- agno/agent/remote.py +1 -1
- agno/db/mongo/mongo.py +9 -1
- agno/db/mysql/async_mysql.py +5 -7
- agno/db/mysql/mysql.py +5 -7
- agno/db/mysql/schemas.py +39 -21
- agno/db/postgres/async_postgres.py +10 -2
- agno/db/postgres/postgres.py +5 -7
- agno/db/postgres/schemas.py +39 -21
- agno/db/singlestore/schemas.py +41 -21
- agno/db/singlestore/singlestore.py +14 -3
- agno/db/sqlite/async_sqlite.py +7 -2
- agno/db/sqlite/schemas.py +36 -21
- agno/db/sqlite/sqlite.py +3 -7
- agno/knowledge/chunking/markdown.py +94 -8
- agno/knowledge/chunking/semantic.py +2 -2
- agno/knowledge/knowledge.py +215 -207
- agno/models/base.py +32 -8
- agno/models/google/gemini.py +27 -4
- agno/os/routers/agents/router.py +1 -1
- agno/os/routers/evals/evals.py +2 -2
- agno/os/routers/knowledge/knowledge.py +21 -5
- agno/os/routers/knowledge/schemas.py +1 -1
- agno/os/routers/memory/memory.py +4 -4
- agno/os/routers/session/session.py +2 -2
- agno/os/routers/teams/router.py +2 -2
- agno/os/routers/traces/traces.py +3 -3
- agno/os/routers/workflows/router.py +1 -1
- agno/os/schema.py +1 -1
- agno/os/utils.py +1 -1
- agno/remote/base.py +1 -1
- agno/team/remote.py +1 -1
- agno/team/team.py +24 -4
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +150 -13
- agno/tools/crawl4ai.py +3 -0
- agno/tools/file.py +14 -13
- agno/tools/function.py +15 -2
- agno/tools/mcp/mcp.py +1 -0
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/python.py +14 -6
- agno/tools/toolkit.py +122 -23
- agno/vectordb/cassandra/cassandra.py +1 -1
- agno/vectordb/chroma/chromadb.py +1 -1
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/couchbase/couchbase.py +1 -1
- agno/vectordb/milvus/milvus.py +1 -1
- agno/vectordb/mongodb/mongodb.py +13 -3
- agno/vectordb/pgvector/pgvector.py +1 -1
- agno/vectordb/pineconedb/pineconedb.py +2 -2
- agno/vectordb/qdrant/qdrant.py +1 -1
- agno/vectordb/redis/redisdb.py +2 -2
- agno/vectordb/singlestore/singlestore.py +1 -1
- agno/vectordb/surrealdb/surrealdb.py +2 -2
- agno/vectordb/weaviate/weaviate.py +1 -1
- agno/workflow/remote.py +1 -1
- agno/workflow/workflow.py +14 -0
- {agno-2.3.22.dist-info → agno-2.3.24.dist-info}/METADATA +1 -1
- {agno-2.3.22.dist-info → agno-2.3.24.dist-info}/RECORD +62 -62
- {agno-2.3.22.dist-info → agno-2.3.24.dist-info}/WHEEL +0 -0
- {agno-2.3.22.dist-info → agno-2.3.24.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.22.dist-info → agno-2.3.24.dist-info}/top_level.txt +0 -0
agno/tools/browserbase.py
CHANGED
|
@@ -10,13 +10,6 @@ try:
|
|
|
10
10
|
except ImportError:
|
|
11
11
|
raise ImportError("`browserbase` not installed. Please install using `pip install browserbase`")
|
|
12
12
|
|
|
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
13
|
|
|
21
14
|
class BrowserbaseTools(Toolkit):
|
|
22
15
|
def __init__(
|
|
@@ -36,7 +29,13 @@ class BrowserbaseTools(Toolkit):
|
|
|
36
29
|
Args:
|
|
37
30
|
api_key (str, optional): Browserbase API key.
|
|
38
31
|
project_id (str, optional): Browserbase project ID.
|
|
39
|
-
base_url (str, optional): Custom Browserbase API endpoint URL (NOT the target website URL).
|
|
32
|
+
base_url (str, optional): Custom Browserbase API endpoint URL (NOT the target website URL).
|
|
33
|
+
Only use this if you're using a self-hosted Browserbase instance or need to connect to a different region.
|
|
34
|
+
enable_navigate_to (bool): Enable the navigate_to tool. Defaults to True.
|
|
35
|
+
enable_screenshot (bool): Enable the screenshot tool. Defaults to True.
|
|
36
|
+
enable_get_page_content (bool): Enable the get_page_content tool. Defaults to True.
|
|
37
|
+
enable_close_session (bool): Enable the close_session tool. Defaults to True.
|
|
38
|
+
all (bool): Enable all tools. Defaults to False.
|
|
40
39
|
"""
|
|
41
40
|
self.api_key = api_key or getenv("BROWSERBASE_API_KEY")
|
|
42
41
|
if not self.api_key:
|
|
@@ -59,23 +58,40 @@ class BrowserbaseTools(Toolkit):
|
|
|
59
58
|
else:
|
|
60
59
|
self.app = Browserbase(api_key=self.api_key)
|
|
61
60
|
|
|
61
|
+
# Sync playwright state
|
|
62
62
|
self._playwright = None
|
|
63
63
|
self._browser = None
|
|
64
64
|
self._page = None
|
|
65
|
+
|
|
66
|
+
# Async playwright state
|
|
67
|
+
self._async_playwright = None
|
|
68
|
+
self._async_browser = None
|
|
69
|
+
self._async_page = None
|
|
70
|
+
|
|
71
|
+
# Shared session state
|
|
65
72
|
self._session = None
|
|
66
73
|
self._connect_url = None
|
|
67
74
|
|
|
75
|
+
# Build tools lists
|
|
76
|
+
# sync tools: used by agent.run() and agent.print_response()
|
|
77
|
+
# async tools: used by agent.arun() and agent.aprint_response()
|
|
68
78
|
tools: List[Any] = []
|
|
79
|
+
async_tools: List[tuple] = []
|
|
80
|
+
|
|
69
81
|
if all or enable_navigate_to:
|
|
70
82
|
tools.append(self.navigate_to)
|
|
83
|
+
async_tools.append((self.anavigate_to, "navigate_to"))
|
|
71
84
|
if all or enable_screenshot:
|
|
72
85
|
tools.append(self.screenshot)
|
|
86
|
+
async_tools.append((self.ascreenshot, "screenshot"))
|
|
73
87
|
if all or enable_get_page_content:
|
|
74
88
|
tools.append(self.get_page_content)
|
|
89
|
+
async_tools.append((self.aget_page_content, "get_page_content"))
|
|
75
90
|
if all or enable_close_session:
|
|
76
91
|
tools.append(self.close_session)
|
|
92
|
+
async_tools.append((self.aclose_session, "close_session"))
|
|
77
93
|
|
|
78
|
-
super().__init__(name="browserbase_tools", tools=tools, **kwargs)
|
|
94
|
+
super().__init__(name="browserbase_tools", tools=tools, async_tools=async_tools, **kwargs)
|
|
79
95
|
|
|
80
96
|
def _ensure_session(self):
|
|
81
97
|
"""Ensures a session exists, creating one if needed."""
|
|
@@ -91,9 +107,16 @@ class BrowserbaseTools(Toolkit):
|
|
|
91
107
|
|
|
92
108
|
def _initialize_browser(self, connect_url: Optional[str] = None):
|
|
93
109
|
"""
|
|
94
|
-
Initialize browser connection if not already initialized.
|
|
110
|
+
Initialize sync browser connection if not already initialized.
|
|
95
111
|
Use provided connect_url or ensure we have a session with a connect_url
|
|
96
112
|
"""
|
|
113
|
+
try:
|
|
114
|
+
from playwright.sync_api import sync_playwright # type: ignore[import-not-found]
|
|
115
|
+
except ImportError:
|
|
116
|
+
raise ImportError(
|
|
117
|
+
"`playwright` not installed. Please install using `pip install playwright` and run `playwright install`"
|
|
118
|
+
)
|
|
119
|
+
|
|
97
120
|
if connect_url:
|
|
98
121
|
self._connect_url = connect_url if connect_url else "" # type: ignore
|
|
99
122
|
elif not self._connect_url:
|
|
@@ -107,7 +130,7 @@ class BrowserbaseTools(Toolkit):
|
|
|
107
130
|
self._page = context.pages[0] or context.new_page() # type: ignore
|
|
108
131
|
|
|
109
132
|
def _cleanup(self):
|
|
110
|
-
"""Clean up browser resources."""
|
|
133
|
+
"""Clean up sync browser resources."""
|
|
111
134
|
if self._browser:
|
|
112
135
|
self._browser.close()
|
|
113
136
|
self._browser = None
|
|
@@ -186,8 +209,7 @@ class BrowserbaseTools(Toolkit):
|
|
|
186
209
|
|
|
187
210
|
def close_session(self) -> str:
|
|
188
211
|
"""Closes a browser session.
|
|
189
|
-
|
|
190
|
-
session_id (str, optional): The session ID to close. If not provided, will use the current session.
|
|
212
|
+
|
|
191
213
|
Returns:
|
|
192
214
|
JSON string with closure status
|
|
193
215
|
"""
|
|
@@ -207,3 +229,118 @@ class BrowserbaseTools(Toolkit):
|
|
|
207
229
|
)
|
|
208
230
|
except Exception as e:
|
|
209
231
|
return json.dumps({"status": "warning", "message": f"Cleanup completed with warning: {str(e)}"})
|
|
232
|
+
|
|
233
|
+
async def _ainitialize_browser(self, connect_url: Optional[str] = None):
|
|
234
|
+
"""
|
|
235
|
+
Initialize async browser connection if not already initialized.
|
|
236
|
+
Use provided connect_url or ensure we have a session with a connect_url
|
|
237
|
+
"""
|
|
238
|
+
try:
|
|
239
|
+
from playwright.async_api import async_playwright # type: ignore[import-not-found]
|
|
240
|
+
except ImportError:
|
|
241
|
+
raise ImportError(
|
|
242
|
+
"`playwright` not installed. Please install using `pip install playwright` and run `playwright install`"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
if connect_url:
|
|
246
|
+
self._connect_url = connect_url if connect_url else "" # type: ignore
|
|
247
|
+
elif not self._connect_url:
|
|
248
|
+
self._ensure_session()
|
|
249
|
+
|
|
250
|
+
if not self._async_playwright:
|
|
251
|
+
self._async_playwright = await async_playwright().start() # type: ignore
|
|
252
|
+
if self._async_playwright:
|
|
253
|
+
self._async_browser = await self._async_playwright.chromium.connect_over_cdp(self._connect_url)
|
|
254
|
+
context = self._async_browser.contexts[0] if self._async_browser else None
|
|
255
|
+
if context:
|
|
256
|
+
self._async_page = context.pages[0] if context.pages else await context.new_page()
|
|
257
|
+
|
|
258
|
+
async def _acleanup(self):
|
|
259
|
+
"""Clean up async browser resources."""
|
|
260
|
+
if self._async_browser:
|
|
261
|
+
await self._async_browser.close()
|
|
262
|
+
self._async_browser = None
|
|
263
|
+
if self._async_playwright:
|
|
264
|
+
await self._async_playwright.stop()
|
|
265
|
+
self._async_playwright = None
|
|
266
|
+
self._async_page = None
|
|
267
|
+
|
|
268
|
+
async def anavigate_to(self, url: str, connect_url: Optional[str] = None) -> str:
|
|
269
|
+
"""Navigates to a URL asynchronously.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
url (str): The URL to navigate to
|
|
273
|
+
connect_url (str, optional): The connection URL from an existing session
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
JSON string with navigation status
|
|
277
|
+
"""
|
|
278
|
+
try:
|
|
279
|
+
await self._ainitialize_browser(connect_url)
|
|
280
|
+
if self._async_page:
|
|
281
|
+
await self._async_page.goto(url, wait_until="networkidle")
|
|
282
|
+
title = await self._async_page.title() if self._async_page else ""
|
|
283
|
+
result = {"status": "complete", "title": title, "url": url}
|
|
284
|
+
return json.dumps(result)
|
|
285
|
+
except Exception as e:
|
|
286
|
+
await self._acleanup()
|
|
287
|
+
raise e
|
|
288
|
+
|
|
289
|
+
async def ascreenshot(self, path: str, full_page: bool = True, connect_url: Optional[str] = None) -> str:
|
|
290
|
+
"""Takes a screenshot of the current page asynchronously.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
path (str): Where to save the screenshot
|
|
294
|
+
full_page (bool): Whether to capture the full page
|
|
295
|
+
connect_url (str, optional): The connection URL from an existing session
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
JSON string confirming screenshot was saved
|
|
299
|
+
"""
|
|
300
|
+
try:
|
|
301
|
+
await self._ainitialize_browser(connect_url)
|
|
302
|
+
if self._async_page:
|
|
303
|
+
await self._async_page.screenshot(path=path, full_page=full_page)
|
|
304
|
+
return json.dumps({"status": "success", "path": path})
|
|
305
|
+
except Exception as e:
|
|
306
|
+
await self._acleanup()
|
|
307
|
+
raise e
|
|
308
|
+
|
|
309
|
+
async def aget_page_content(self, connect_url: Optional[str] = None) -> str:
|
|
310
|
+
"""Gets the HTML content of the current page asynchronously.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
connect_url (str, optional): The connection URL from an existing session
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
The page HTML content
|
|
317
|
+
"""
|
|
318
|
+
try:
|
|
319
|
+
await self._ainitialize_browser(connect_url)
|
|
320
|
+
return await self._async_page.content() if self._async_page else ""
|
|
321
|
+
except Exception as e:
|
|
322
|
+
await self._acleanup()
|
|
323
|
+
raise e
|
|
324
|
+
|
|
325
|
+
async def aclose_session(self) -> str:
|
|
326
|
+
"""Closes a browser session asynchronously.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
JSON string with closure status
|
|
330
|
+
"""
|
|
331
|
+
try:
|
|
332
|
+
# First cleanup our local browser resources
|
|
333
|
+
await self._acleanup()
|
|
334
|
+
|
|
335
|
+
# Reset session state
|
|
336
|
+
self._session = None
|
|
337
|
+
self._connect_url = None
|
|
338
|
+
|
|
339
|
+
return json.dumps(
|
|
340
|
+
{
|
|
341
|
+
"status": "closed",
|
|
342
|
+
"message": "Browser resources cleaned up. Session will auto-close if not already closed.",
|
|
343
|
+
}
|
|
344
|
+
)
|
|
345
|
+
except Exception as e:
|
|
346
|
+
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/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.
|
agno/tools/function.py
CHANGED
|
@@ -54,9 +54,17 @@ class UserInputField:
|
|
|
54
54
|
|
|
55
55
|
@classmethod
|
|
56
56
|
def from_dict(cls, data: Dict[str, Any]) -> "UserInputField":
|
|
57
|
+
type_mapping = {"str": str, "int": int, "float": float, "bool": bool, "list": list, "dict": dict}
|
|
58
|
+
field_type_raw = data["field_type"]
|
|
59
|
+
if isinstance(field_type_raw, str):
|
|
60
|
+
field_type = type_mapping.get(field_type_raw, str)
|
|
61
|
+
elif isinstance(field_type_raw, type):
|
|
62
|
+
field_type = field_type_raw
|
|
63
|
+
else:
|
|
64
|
+
field_type = str
|
|
57
65
|
return cls(
|
|
58
66
|
name=data["name"],
|
|
59
|
-
field_type=
|
|
67
|
+
field_type=field_type,
|
|
60
68
|
description=data["description"],
|
|
61
69
|
value=data["value"],
|
|
62
70
|
)
|
|
@@ -1139,10 +1147,15 @@ class FunctionCall(BaseModel):
|
|
|
1139
1147
|
else:
|
|
1140
1148
|
result = self.function.entrypoint(**entrypoint_args, **self.arguments)
|
|
1141
1149
|
|
|
1150
|
+
# Handle both sync and async entrypoints
|
|
1142
1151
|
if isasyncgenfunction(self.function.entrypoint):
|
|
1143
1152
|
self.result = result # Store async generator directly
|
|
1153
|
+
elif iscoroutinefunction(self.function.entrypoint):
|
|
1154
|
+
self.result = await result # Await coroutine result
|
|
1155
|
+
elif isgeneratorfunction(self.function.entrypoint):
|
|
1156
|
+
self.result = result # Store sync generator directly
|
|
1144
1157
|
else:
|
|
1145
|
-
self.result =
|
|
1158
|
+
self.result = result # Sync function, result is already computed
|
|
1146
1159
|
|
|
1147
1160
|
# Only cache if not a generator
|
|
1148
1161
|
if self.function.cache_results and not (isgenerator(self.result) or isasyncgen(self.result)):
|
agno/tools/mcp/mcp.py
CHANGED
agno/tools/mlx_transcribe.py
CHANGED
|
@@ -17,7 +17,7 @@ provides high-quality transcription capabilities.
|
|
|
17
17
|
|
|
18
18
|
import json
|
|
19
19
|
from pathlib import Path
|
|
20
|
-
from typing import Any, Dict, List, Optional,
|
|
20
|
+
from typing import Any, Dict, List, Optional, Union
|
|
21
21
|
|
|
22
22
|
from agno.tools import Toolkit
|
|
23
23
|
from agno.utils.log import log_info, logger
|
|
@@ -33,9 +33,10 @@ class MLXTranscribeTools(Toolkit):
|
|
|
33
33
|
self,
|
|
34
34
|
base_dir: Optional[Path] = None,
|
|
35
35
|
enable_read_files_in_base_dir: bool = True,
|
|
36
|
+
restrict_to_base_dir: bool = True,
|
|
36
37
|
path_or_hf_repo: str = "mlx-community/whisper-large-v3-turbo",
|
|
37
38
|
verbose: Optional[bool] = None,
|
|
38
|
-
temperature: Optional[Union[float,
|
|
39
|
+
temperature: Optional[Union[float, tuple[float, ...]]] = None,
|
|
39
40
|
compression_ratio_threshold: Optional[float] = None,
|
|
40
41
|
logprob_threshold: Optional[float] = None,
|
|
41
42
|
no_speech_threshold: Optional[float] = None,
|
|
@@ -50,10 +51,11 @@ class MLXTranscribeTools(Toolkit):
|
|
|
50
51
|
all: bool = False,
|
|
51
52
|
**kwargs,
|
|
52
53
|
):
|
|
53
|
-
self.base_dir: Path = base_dir or Path.cwd()
|
|
54
|
+
self.base_dir: Path = (base_dir or Path.cwd()).resolve()
|
|
55
|
+
self.restrict_to_base_dir = restrict_to_base_dir
|
|
54
56
|
self.path_or_hf_repo: str = path_or_hf_repo
|
|
55
57
|
self.verbose: Optional[bool] = verbose
|
|
56
|
-
self.temperature: Optional[Union[float,
|
|
58
|
+
self.temperature: Optional[Union[float, tuple[float, ...]]] = temperature
|
|
57
59
|
self.compression_ratio_threshold: Optional[float] = compression_ratio_threshold
|
|
58
60
|
self.logprob_threshold: Optional[float] = logprob_threshold
|
|
59
61
|
self.no_speech_threshold: Optional[float] = no_speech_threshold
|
|
@@ -83,9 +85,10 @@ class MLXTranscribeTools(Toolkit):
|
|
|
83
85
|
str: The transcribed text or an error message if the transcription fails.
|
|
84
86
|
"""
|
|
85
87
|
try:
|
|
86
|
-
|
|
87
|
-
if
|
|
88
|
-
return "
|
|
88
|
+
safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
|
|
89
|
+
if not safe:
|
|
90
|
+
return f"Error: Path '{file_name}' is outside the allowed base directory"
|
|
91
|
+
audio_file_path = str(file_path)
|
|
89
92
|
|
|
90
93
|
log_info(f"Transcribing audio file {audio_file_path}")
|
|
91
94
|
transcription_kwargs: Dict[str, Any] = {
|
agno/tools/python.py
CHANGED
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Any, List, Optional
|
|
5
5
|
|
|
6
6
|
from agno.tools import Toolkit
|
|
7
|
-
from agno.utils.log import log_debug, log_info, logger
|
|
7
|
+
from agno.utils.log import log_debug, log_error, log_info, logger
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@functools.lru_cache(maxsize=None)
|
|
@@ -18,9 +18,11 @@ class PythonTools(Toolkit):
|
|
|
18
18
|
base_dir: Optional[Path] = None,
|
|
19
19
|
safe_globals: Optional[dict] = None,
|
|
20
20
|
safe_locals: Optional[dict] = None,
|
|
21
|
+
restrict_to_base_dir: bool = True,
|
|
21
22
|
**kwargs,
|
|
22
23
|
):
|
|
23
|
-
self.base_dir: Path = base_dir or Path.cwd()
|
|
24
|
+
self.base_dir: Path = (base_dir or Path.cwd()).resolve()
|
|
25
|
+
self.restrict_to_base_dir = restrict_to_base_dir
|
|
24
26
|
|
|
25
27
|
# Restricted global and local scope
|
|
26
28
|
self.safe_globals: dict = safe_globals or globals()
|
|
@@ -55,7 +57,9 @@ class PythonTools(Toolkit):
|
|
|
55
57
|
"""
|
|
56
58
|
try:
|
|
57
59
|
warn()
|
|
58
|
-
file_path = self.base_dir.
|
|
60
|
+
safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
|
|
61
|
+
if not safe:
|
|
62
|
+
return f"Error: Path '{file_name}' is outside the allowed base directory"
|
|
59
63
|
log_debug(f"Saving code to {file_path}")
|
|
60
64
|
if not file_path.parent.exists():
|
|
61
65
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -89,8 +93,9 @@ class PythonTools(Toolkit):
|
|
|
89
93
|
"""
|
|
90
94
|
try:
|
|
91
95
|
warn()
|
|
92
|
-
file_path = self.base_dir.
|
|
93
|
-
|
|
96
|
+
safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
|
|
97
|
+
if not safe:
|
|
98
|
+
return f"Error: Path '{file_name}' is outside the allowed base directory"
|
|
94
99
|
log_info(f"Running {file_path}")
|
|
95
100
|
globals_after_run = runpy.run_path(str(file_path), init_globals=self.safe_globals, run_name="__main__")
|
|
96
101
|
if variable_to_return:
|
|
@@ -113,7 +118,10 @@ class PythonTools(Toolkit):
|
|
|
113
118
|
"""
|
|
114
119
|
try:
|
|
115
120
|
log_info(f"Reading file: {file_name}")
|
|
116
|
-
file_path = self.base_dir.
|
|
121
|
+
safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
|
|
122
|
+
if not safe:
|
|
123
|
+
log_error(f"Attempted to read file outside base directory: {file_name}")
|
|
124
|
+
return "Error reading file: path outside allowed directory"
|
|
117
125
|
contents = file_path.read_text(encoding="utf-8")
|
|
118
126
|
return str(contents)
|
|
119
127
|
except Exception as e:
|