cocoonstack-sandbox-langchain 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cocoonsandbox_langchain/__init__.py +7 -0
- cocoonsandbox_langchain/toolkit.py +127 -0
- cocoonstack_sandbox_langchain-0.1.0.dist-info/METADATA +33 -0
- cocoonstack_sandbox_langchain-0.1.0.dist-info/RECORD +6 -0
- cocoonstack_sandbox_langchain-0.1.0.dist-info/WHEEL +5 -0
- cocoonstack_sandbox_langchain-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""LangChain toolkit over cocoon microVM sandboxes: one claimed sandbox
|
|
2
|
+
behind a set of StructuredTools (exec, file read/write, list). The claim is
|
|
3
|
+
lazy — building an agent costs nothing until a tool actually runs — and the
|
|
4
|
+
toolkit is a context manager whose exit releases the sandbox."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
from cocoonsandbox import Client, Sandbox
|
|
13
|
+
from langchain_core.tools import StructuredTool
|
|
14
|
+
from pydantic import BaseModel, Field
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ExecInput(BaseModel):
|
|
18
|
+
command: str = Field(description="shell command, run via sh -c inside the sandbox")
|
|
19
|
+
cwd: str = Field(default="", description="working directory (optional)")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WriteFileInput(BaseModel):
|
|
23
|
+
path: str = Field(description="absolute path in the sandbox")
|
|
24
|
+
content: str = Field(description="file content to write")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PathInput(BaseModel):
|
|
28
|
+
path: str = Field(description="absolute path in the sandbox")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CocoonToolkit:
|
|
32
|
+
"""LangChain tools backed by one sandbox, claimed on first tool use.
|
|
33
|
+
|
|
34
|
+
from_checkpoint branches a fresh sandbox from that checkpoint's captured
|
|
35
|
+
moment instead of claiming a clean template — agents resume from
|
|
36
|
+
prepared state.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, addr: str, api_token: str = "", template: str = "rt:24.04",
|
|
40
|
+
net: str = "", ttl_seconds: int = 0, from_checkpoint: str = ""):
|
|
41
|
+
self._client = Client(addr, api_token=api_token)
|
|
42
|
+
self._template = template
|
|
43
|
+
self._net = net
|
|
44
|
+
self._ttl = ttl_seconds
|
|
45
|
+
self._from_checkpoint = from_checkpoint
|
|
46
|
+
self._lock = threading.Lock()
|
|
47
|
+
self._sb: Sandbox | None = None
|
|
48
|
+
self._closed = False
|
|
49
|
+
|
|
50
|
+
def __enter__(self) -> CocoonToolkit:
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
def __exit__(self, *exc) -> None:
|
|
54
|
+
self.close()
|
|
55
|
+
|
|
56
|
+
def get_tools(self) -> list[StructuredTool]:
|
|
57
|
+
"""The sandbox tool set; sync-native (_run), async via to_thread."""
|
|
58
|
+
return [
|
|
59
|
+
self._tool("sandbox_exec",
|
|
60
|
+
"Run a shell command in the sandbox; returns stdout, stderr, "
|
|
61
|
+
"and the exit code. State on disk persists across calls.",
|
|
62
|
+
ExecInput, self._exec),
|
|
63
|
+
self._tool("sandbox_write_file",
|
|
64
|
+
"Write a text file in the sandbox (atomic).",
|
|
65
|
+
WriteFileInput, self._write_file),
|
|
66
|
+
self._tool("sandbox_read_file",
|
|
67
|
+
"Read a text file from the sandbox.",
|
|
68
|
+
PathInput, self._read_file),
|
|
69
|
+
self._tool("sandbox_list_dir",
|
|
70
|
+
"List a sandbox directory as JSON entries.",
|
|
71
|
+
PathInput, self._list_dir),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
def close(self) -> None:
|
|
75
|
+
"""Releases the sandbox; safe to call twice or before any claim.
|
|
76
|
+
Tools invoked after close raise instead of silently claiming a
|
|
77
|
+
sandbox nothing would ever release."""
|
|
78
|
+
with self._lock:
|
|
79
|
+
sb, self._sb = self._sb, None
|
|
80
|
+
self._closed = True
|
|
81
|
+
if sb is not None:
|
|
82
|
+
sb.close()
|
|
83
|
+
|
|
84
|
+
def sandbox(self) -> Sandbox:
|
|
85
|
+
"""The claimed sandbox, claiming (or branching) on first use."""
|
|
86
|
+
with self._lock:
|
|
87
|
+
if self._closed:
|
|
88
|
+
raise RuntimeError("toolkit is closed")
|
|
89
|
+
if self._sb is None:
|
|
90
|
+
self._sb = self._claim()
|
|
91
|
+
return self._sb
|
|
92
|
+
|
|
93
|
+
def _claim(self) -> Sandbox:
|
|
94
|
+
if self._from_checkpoint:
|
|
95
|
+
return self._client.checkpoint(self._from_checkpoint).new(ttl_seconds=self._ttl)
|
|
96
|
+
return self._client.new(self._template, net=self._net, ttl_seconds=self._ttl)
|
|
97
|
+
|
|
98
|
+
def _tool(self, name: str, description: str, schema: type[BaseModel], func) -> StructuredTool:
|
|
99
|
+
async def arun(**kwargs):
|
|
100
|
+
return await asyncio.to_thread(func, **kwargs)
|
|
101
|
+
|
|
102
|
+
return StructuredTool.from_function(func=func, coroutine=arun, name=name,
|
|
103
|
+
description=description, args_schema=schema)
|
|
104
|
+
|
|
105
|
+
def _exec(self, command: str, cwd: str = "") -> str:
|
|
106
|
+
out: list[bytes] = []
|
|
107
|
+
errs: list[bytes] = []
|
|
108
|
+
code = self.sandbox().run(["sh", "-c", command], cwd=cwd,
|
|
109
|
+
on_stdout=out.append, on_stderr=errs.append)
|
|
110
|
+
stdout = b"".join(out).decode(errors="replace")
|
|
111
|
+
stderr = b"".join(errs).decode(errors="replace")
|
|
112
|
+
result = stdout
|
|
113
|
+
if stderr:
|
|
114
|
+
result += ("\n" if result else "") + "stderr: " + stderr
|
|
115
|
+
if code != 0:
|
|
116
|
+
result += ("\n" if result else "") + f"exit code: {code}"
|
|
117
|
+
return result or "(no output)"
|
|
118
|
+
|
|
119
|
+
def _write_file(self, path: str, content: str) -> str:
|
|
120
|
+
self.sandbox().write_file(path, content.encode())
|
|
121
|
+
return f"wrote {path}"
|
|
122
|
+
|
|
123
|
+
def _read_file(self, path: str) -> str:
|
|
124
|
+
return self.sandbox().read_file(path).decode(errors="replace")
|
|
125
|
+
|
|
126
|
+
def _list_dir(self, path: str) -> str:
|
|
127
|
+
return json.dumps(self.sandbox().list_dir(path))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cocoonstack-sandbox-langchain
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LangChain tools backed by cocoon microVM sandboxes
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: langchain-core>=1.0
|
|
9
|
+
Requires-Dist: cocoonstack-sandbox>=0.1
|
|
10
|
+
|
|
11
|
+
# cocoonstack-sandbox-langchain
|
|
12
|
+
|
|
13
|
+
LangChain tools backed by cocoon microVM sandboxes: a `CocoonToolkit`
|
|
14
|
+
claims one sandbox lazily on first tool use and exposes it as
|
|
15
|
+
`StructuredTool`s an agent can call — shell exec, file read/write, and
|
|
16
|
+
directory listing, with disk state persisting across calls.
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from cocoonsandbox_langchain import CocoonToolkit
|
|
20
|
+
|
|
21
|
+
with CocoonToolkit("10.0.0.5:7777", api_token="...") as kit:
|
|
22
|
+
agent = create_react_agent(model, kit.get_tools())
|
|
23
|
+
agent.invoke({"messages": [("user", "run the tests in /work")]})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`from_checkpoint="ck_..."` branches the sandbox from a checkpoint's
|
|
27
|
+
captured moment instead of a clean template — agents resume from prepared
|
|
28
|
+
state (dependencies installed, repo cloned) in milliseconds.
|
|
29
|
+
|
|
30
|
+
Sync-native (`_run` calls the stdlib-only cocoonstack-sandbox SDK
|
|
31
|
+
directly); async agents get `_arun` bridged via `asyncio.to_thread`.
|
|
32
|
+
|
|
33
|
+
Full reference: https://cocoonstack.github.io/sandbox/langchain
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
cocoonsandbox_langchain/__init__.py,sha256=9_otWHxffnMJBm2_1XcZQdA2InXQParAM1Pha-SyXt4,130
|
|
2
|
+
cocoonsandbox_langchain/toolkit.py,sha256=hcCcp24ldUZz7iKd4H88EZiwaJ8AxuKf3SFOwUk63Ts,5008
|
|
3
|
+
cocoonstack_sandbox_langchain-0.1.0.dist-info/METADATA,sha256=HSPW_g5wCG_QN31LTH-QHiPv6-ueLGFeKUcHxFnjk2c,1249
|
|
4
|
+
cocoonstack_sandbox_langchain-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
5
|
+
cocoonstack_sandbox_langchain-0.1.0.dist-info/top_level.txt,sha256=H0cckpx__PMsnQoWLd94bpqIQtu8sB0qJxKmhib3ntI,24
|
|
6
|
+
cocoonstack_sandbox_langchain-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cocoonsandbox_langchain
|