e2b-haystack 1.0.0__tar.gz → 1.1.0__tar.gz
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.
- e2b_haystack-1.1.0/CHANGELOG.md +9 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/PKG-INFO +4 -3
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/README.md +2 -1
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/pyproject.toml +1 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/bash_tool.py +8 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/e2b_sandbox.py +1 -13
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/list_directory_tool.py +8 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/read_file_tool.py +8 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/write_file_tool.py +8 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/tests/test_integration.py +7 -1
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/tests/test_sandbox_toolset.py +49 -30
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/.gitignore +0 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/LICENSE.txt +0 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/examples/e2b_agent_example.py +0 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/examples/e2b_pipeline_example.py +0 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/pydoc/config_docusaurus.yml +0 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/__init__.py +0 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/sandbox_toolset.py +0 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/py.typed +0 -0
- {e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: e2b-haystack
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Haystack integration for E2B cloud sandboxes
|
|
5
5
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/e2b#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
|
|
@@ -29,6 +29,7 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
[](https://pypi.org/project/e2b-haystack)
|
|
30
30
|
[](https://pypi.org/project/e2b-haystack)
|
|
31
31
|
|
|
32
|
+
- [Integration page](https://haystack.deepset.ai/integrations/e2b)
|
|
32
33
|
- [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/e2b/CHANGELOG.md)
|
|
33
34
|
|
|
34
35
|
---
|
|
@@ -37,4 +38,4 @@ Description-Content-Type: text/markdown
|
|
|
37
38
|
|
|
38
39
|
Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
|
|
39
40
|
|
|
40
|
-
To run integration tests locally, set the `E2B_API_KEY` environment variable (get one at <https://e2b.dev/>).
|
|
41
|
+
To run integration tests locally, set the `E2B_API_KEY` environment variable (get one at <https://e2b.dev/>).
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://pypi.org/project/e2b-haystack)
|
|
4
4
|
[](https://pypi.org/project/e2b-haystack)
|
|
5
5
|
|
|
6
|
+
- [Integration page](https://haystack.deepset.ai/integrations/e2b)
|
|
6
7
|
- [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/e2b/CHANGELOG.md)
|
|
7
8
|
|
|
8
9
|
---
|
|
@@ -11,4 +12,4 @@
|
|
|
11
12
|
|
|
12
13
|
Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
|
|
13
14
|
|
|
14
|
-
To run integration tests locally, set the `E2B_API_KEY` environment variable (get one at <https://e2b.dev/>).
|
|
15
|
+
To run integration tests locally, set the `E2B_API_KEY` environment variable (get one at <https://e2b.dev/>).
|
|
@@ -80,6 +80,14 @@ class RunBashCommandTool(Tool):
|
|
|
80
80
|
)
|
|
81
81
|
self._e2b_sandbox = sandbox
|
|
82
82
|
|
|
83
|
+
def warm_up(self) -> None:
|
|
84
|
+
"""Start the sandbox if needed."""
|
|
85
|
+
self._e2b_sandbox.warm_up()
|
|
86
|
+
|
|
87
|
+
def close(self) -> None:
|
|
88
|
+
"""Close the sandbox."""
|
|
89
|
+
self._e2b_sandbox.close()
|
|
90
|
+
|
|
83
91
|
def to_dict(self) -> dict[str, Any]:
|
|
84
92
|
"""Serialize this tool to a dictionary."""
|
|
85
93
|
return {
|
{e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/e2b_sandbox.py
RENAMED
|
@@ -8,11 +8,9 @@ from typing import Any, ClassVar
|
|
|
8
8
|
|
|
9
9
|
from haystack import logging
|
|
10
10
|
from haystack.core.serialization import generate_qualified_class_name
|
|
11
|
-
from haystack.lazy_imports import LazyImport
|
|
12
11
|
from haystack.utils import Secret, deserialize_secrets_inplace
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
from e2b import Sandbox
|
|
13
|
+
from e2b import Sandbox
|
|
16
14
|
|
|
17
15
|
logger = logging.getLogger(__name__)
|
|
18
16
|
|
|
@@ -52,15 +50,6 @@ class E2BSandbox:
|
|
|
52
50
|
],
|
|
53
51
|
)
|
|
54
52
|
```
|
|
55
|
-
|
|
56
|
-
Lifecycle is handled automatically by the Agent's pipeline. If you use the
|
|
57
|
-
tools standalone, call :meth:`warm_up` before the first tool invocation:
|
|
58
|
-
|
|
59
|
-
```python
|
|
60
|
-
sandbox.warm_up()
|
|
61
|
-
# ... use tools ...
|
|
62
|
-
sandbox.close()
|
|
63
|
-
```
|
|
64
53
|
"""
|
|
65
54
|
|
|
66
55
|
# Process-wide cache used during deserialization to keep tools that
|
|
@@ -115,7 +104,6 @@ class E2BSandbox:
|
|
|
115
104
|
if self._sandbox is not None:
|
|
116
105
|
return
|
|
117
106
|
|
|
118
|
-
e2b_import.check()
|
|
119
107
|
resolved_key = self.api_key.resolve_value()
|
|
120
108
|
try:
|
|
121
109
|
logger.info(
|
{e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/list_directory_tool.py
RENAMED
|
@@ -67,6 +67,14 @@ class ListDirectoryTool(Tool):
|
|
|
67
67
|
)
|
|
68
68
|
self._e2b_sandbox = sandbox
|
|
69
69
|
|
|
70
|
+
def warm_up(self) -> None:
|
|
71
|
+
"""Start the sandbox if needed."""
|
|
72
|
+
self._e2b_sandbox.warm_up()
|
|
73
|
+
|
|
74
|
+
def close(self) -> None:
|
|
75
|
+
"""Close the sandbox."""
|
|
76
|
+
self._e2b_sandbox.close()
|
|
77
|
+
|
|
70
78
|
def to_dict(self) -> dict[str, Any]:
|
|
71
79
|
"""Serialize this tool to a dictionary."""
|
|
72
80
|
return {
|
{e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/read_file_tool.py
RENAMED
|
@@ -62,6 +62,14 @@ class ReadFileTool(Tool):
|
|
|
62
62
|
)
|
|
63
63
|
self._e2b_sandbox = sandbox
|
|
64
64
|
|
|
65
|
+
def warm_up(self) -> None:
|
|
66
|
+
"""Start the sandbox if needed."""
|
|
67
|
+
self._e2b_sandbox.warm_up()
|
|
68
|
+
|
|
69
|
+
def close(self) -> None:
|
|
70
|
+
"""Close the sandbox."""
|
|
71
|
+
self._e2b_sandbox.close()
|
|
72
|
+
|
|
65
73
|
def to_dict(self) -> dict[str, Any]:
|
|
66
74
|
"""Serialize this tool to a dictionary."""
|
|
67
75
|
return {
|
{e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/write_file_tool.py
RENAMED
|
@@ -62,6 +62,14 @@ class WriteFileTool(Tool):
|
|
|
62
62
|
)
|
|
63
63
|
self._e2b_sandbox = sandbox
|
|
64
64
|
|
|
65
|
+
def warm_up(self) -> None:
|
|
66
|
+
"""Start the sandbox if needed."""
|
|
67
|
+
self._e2b_sandbox.warm_up()
|
|
68
|
+
|
|
69
|
+
def close(self) -> None:
|
|
70
|
+
"""Close the sandbox."""
|
|
71
|
+
self._e2b_sandbox.close()
|
|
72
|
+
|
|
65
73
|
def to_dict(self) -> dict[str, Any]:
|
|
66
74
|
"""Serialize this tool to a dictionary."""
|
|
67
75
|
return {
|
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
Integration tests for the E2B sandbox tools.
|
|
7
7
|
|
|
8
8
|
These tests require a valid E2B_API_KEY environment variable and will
|
|
9
|
-
spin up a real cloud sandbox on each run.
|
|
9
|
+
spin up a real cloud sandbox on each run. Every test in this module skips when
|
|
10
|
+
the variable is unset, so the suite can always be invoked — including on fork
|
|
11
|
+
PRs, which have no access to repository secrets.
|
|
10
12
|
"""
|
|
11
13
|
|
|
14
|
+
import os
|
|
15
|
+
|
|
12
16
|
import pytest
|
|
13
17
|
|
|
14
18
|
from haystack_integrations.tools.e2b import (
|
|
@@ -20,6 +24,8 @@ from haystack_integrations.tools.e2b import (
|
|
|
20
24
|
WriteFileTool,
|
|
21
25
|
)
|
|
22
26
|
|
|
27
|
+
pytestmark = pytest.mark.skipif(not os.environ.get("E2B_API_KEY"), reason="E2B_API_KEY not set")
|
|
28
|
+
|
|
23
29
|
|
|
24
30
|
@pytest.fixture(scope="module")
|
|
25
31
|
def sandbox():
|
|
@@ -73,10 +73,8 @@ class TestE2BSandboxInit:
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
class TestE2BSandboxWarmUp:
|
|
76
|
-
@patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
|
|
77
76
|
@patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
|
|
78
|
-
def test_warm_up_creates_sandbox(self, mock_sandbox_create
|
|
79
|
-
mock_e2b_import.check.return_value = None
|
|
77
|
+
def test_warm_up_creates_sandbox(self, mock_sandbox_create):
|
|
80
78
|
mock_instance = _make_sandbox_mock()
|
|
81
79
|
mock_sandbox_create.return_value = mock_instance
|
|
82
80
|
|
|
@@ -91,10 +89,8 @@ class TestE2BSandboxWarmUp:
|
|
|
91
89
|
)
|
|
92
90
|
assert sb._sandbox is mock_instance
|
|
93
91
|
|
|
94
|
-
@patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
|
|
95
92
|
@patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
|
|
96
|
-
def test_warm_up_passes_environment_vars(self, mock_sandbox_create
|
|
97
|
-
mock_e2b_import.check.return_value = None
|
|
93
|
+
def test_warm_up_passes_environment_vars(self, mock_sandbox_create):
|
|
98
94
|
mock_sandbox_create.return_value = _make_sandbox_mock()
|
|
99
95
|
|
|
100
96
|
sb = _make_sandbox(environment_vars={"MY_VAR": "value"})
|
|
@@ -103,10 +99,8 @@ class TestE2BSandboxWarmUp:
|
|
|
103
99
|
_, kwargs = mock_sandbox_create.call_args
|
|
104
100
|
assert kwargs["envs"] == {"MY_VAR": "value"}
|
|
105
101
|
|
|
106
|
-
@patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
|
|
107
102
|
@patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
|
|
108
|
-
def test_warm_up_is_idempotent(self, mock_sandbox_create
|
|
109
|
-
mock_e2b_import.check.return_value = None
|
|
103
|
+
def test_warm_up_is_idempotent(self, mock_sandbox_create):
|
|
110
104
|
mock_sandbox_create.return_value = _make_sandbox_mock()
|
|
111
105
|
|
|
112
106
|
sb = _make_sandbox()
|
|
@@ -115,10 +109,8 @@ class TestE2BSandboxWarmUp:
|
|
|
115
109
|
|
|
116
110
|
mock_sandbox_create.assert_called_once()
|
|
117
111
|
|
|
118
|
-
@patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
|
|
119
112
|
@patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
|
|
120
|
-
def test_warm_up_raises_on_sandbox_error(self, mock_sandbox_create
|
|
121
|
-
mock_e2b_import.check.return_value = None
|
|
113
|
+
def test_warm_up_raises_on_sandbox_error(self, mock_sandbox_create):
|
|
122
114
|
mock_sandbox_create.side_effect = Exception("connection refused")
|
|
123
115
|
|
|
124
116
|
sb = _make_sandbox()
|
|
@@ -343,12 +335,47 @@ class TestToolClasses:
|
|
|
343
335
|
ts = E2BToolset()
|
|
344
336
|
assert ts.sandbox.api_key is not None
|
|
345
337
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
338
|
+
@pytest.mark.parametrize("tool_class", [RunBashCommandTool, ReadFileTool, WriteFileTool, ListDirectoryTool])
|
|
339
|
+
def test_warm_up_and_close(self, tool_class):
|
|
340
|
+
sandbox = MagicMock(spec=E2BSandbox)
|
|
341
|
+
tool = tool_class(sandbox=sandbox)
|
|
342
|
+
|
|
343
|
+
tool.warm_up()
|
|
344
|
+
sandbox.warm_up.assert_called_once_with()
|
|
345
|
+
tool.close()
|
|
346
|
+
sandbox.close.assert_called_once_with()
|
|
347
|
+
|
|
348
|
+
@patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
|
|
349
|
+
def test_tools_share_sandbox_lifecycle(self, create):
|
|
350
|
+
first, second = _make_sandbox_mock(), _make_sandbox_mock()
|
|
351
|
+
create.side_effect = [first, second]
|
|
352
|
+
sandbox = _make_sandbox()
|
|
353
|
+
tools = [ReadFileTool(sandbox=sandbox), ListDirectoryTool(sandbox=sandbox)]
|
|
354
|
+
|
|
355
|
+
for tool in tools:
|
|
356
|
+
tool.close()
|
|
357
|
+
create.assert_not_called()
|
|
358
|
+
|
|
359
|
+
tools[1].invoke(path="/")
|
|
360
|
+
first.files.list.assert_called_once_with("/")
|
|
361
|
+
for tool in tools:
|
|
362
|
+
tool.warm_up()
|
|
363
|
+
create.assert_called_once()
|
|
364
|
+
|
|
365
|
+
for tool in tools:
|
|
366
|
+
tool.close()
|
|
367
|
+
first.kill.assert_called_once_with()
|
|
368
|
+
assert sandbox._sandbox is None
|
|
369
|
+
|
|
370
|
+
for tool in tools:
|
|
371
|
+
tool.warm_up()
|
|
372
|
+
assert create.call_count == 2
|
|
373
|
+
assert sandbox._sandbox is second
|
|
374
|
+
|
|
375
|
+
for tool in tools:
|
|
376
|
+
tool.close()
|
|
377
|
+
first.kill.assert_called_once_with()
|
|
378
|
+
second.kill.assert_called_once_with()
|
|
352
379
|
|
|
353
380
|
|
|
354
381
|
# ---------------------------------------------------------------------------
|
|
@@ -378,10 +405,8 @@ class TestRunBashCommandTool:
|
|
|
378
405
|
|
|
379
406
|
mock.commands.run.assert_called_once_with("sleep 5", timeout=30)
|
|
380
407
|
|
|
381
|
-
@patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
|
|
382
408
|
@patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
|
|
383
|
-
def test_wraps_warm_up_failure(self, mock_sandbox_create
|
|
384
|
-
mock_e2b_import.check.return_value = None
|
|
409
|
+
def test_wraps_warm_up_failure(self, mock_sandbox_create):
|
|
385
410
|
mock_sandbox_create.side_effect = Exception("connection refused")
|
|
386
411
|
sb = _make_sandbox()
|
|
387
412
|
tool = RunBashCommandTool(sandbox=sb)
|
|
@@ -421,10 +446,8 @@ class TestReadFileTool:
|
|
|
421
446
|
|
|
422
447
|
assert result == "binary content"
|
|
423
448
|
|
|
424
|
-
@patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
|
|
425
449
|
@patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
|
|
426
|
-
def test_wraps_warm_up_failure(self, mock_sandbox_create
|
|
427
|
-
mock_e2b_import.check.return_value = None
|
|
450
|
+
def test_wraps_warm_up_failure(self, mock_sandbox_create):
|
|
428
451
|
mock_sandbox_create.side_effect = Exception("connection refused")
|
|
429
452
|
sb = _make_sandbox()
|
|
430
453
|
tool = ReadFileTool(sandbox=sb)
|
|
@@ -454,10 +477,8 @@ class TestWriteFileTool:
|
|
|
454
477
|
assert "/output/result.txt" in result
|
|
455
478
|
mock.files.write.assert_called_once_with("/output/result.txt", "hello")
|
|
456
479
|
|
|
457
|
-
@patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
|
|
458
480
|
@patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
|
|
459
|
-
def test_wraps_warm_up_failure(self, mock_sandbox_create
|
|
460
|
-
mock_e2b_import.check.return_value = None
|
|
481
|
+
def test_wraps_warm_up_failure(self, mock_sandbox_create):
|
|
461
482
|
mock_sandbox_create.side_effect = Exception("connection refused")
|
|
462
483
|
sb = _make_sandbox()
|
|
463
484
|
tool = WriteFileTool(sandbox=sb)
|
|
@@ -507,10 +528,8 @@ class TestListDirectoryTool:
|
|
|
507
528
|
|
|
508
529
|
assert result == "(empty directory)"
|
|
509
530
|
|
|
510
|
-
@patch("haystack_integrations.tools.e2b.e2b_sandbox.e2b_import")
|
|
511
531
|
@patch("haystack_integrations.tools.e2b.e2b_sandbox.Sandbox.create")
|
|
512
|
-
def test_wraps_warm_up_failure(self, mock_sandbox_create
|
|
513
|
-
mock_e2b_import.check.return_value = None
|
|
532
|
+
def test_wraps_warm_up_failure(self, mock_sandbox_create):
|
|
514
533
|
mock_sandbox_create.side_effect = Exception("connection refused")
|
|
515
534
|
sb = _make_sandbox()
|
|
516
535
|
tool = ListDirectoryTool(sandbox=sb)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{e2b_haystack-1.0.0 → e2b_haystack-1.1.0}/src/haystack_integrations/tools/e2b/sandbox_toolset.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|