@langchain/modal 0.0.1
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.
- package/README.md +344 -0
- package/dist/index.cjs +636 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +514 -0
- package/dist/index.d.ts +514 -0
- package/dist/index.js +633 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# @langchain/modal
|
|
2
|
+
|
|
3
|
+
Modal Sandbox backend for [deepagents](https://www.npmjs.com/package/deepagents). This package provides a `ModalSandbox` implementation of the `SandboxBackendProtocol`, enabling agents to execute commands, read/write files, and manage isolated container environments using Modal's serverless infrastructure.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@langchain/modal)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Isolated Execution**: Run commands in secure, isolated containers on Modal's serverless infrastructure
|
|
11
|
+
- **GPU Support**: Access NVIDIA GPUs (T4, L4, A10G, A100, H100) for ML/AI workloads
|
|
12
|
+
- **Custom Images**: Use any Docker image from public registries
|
|
13
|
+
- **File Operations**: Upload and download files with full filesystem access
|
|
14
|
+
- **Volume Mounts**: Mount Modal Volumes for persistent storage
|
|
15
|
+
- **Secrets Injection**: Securely inject Modal Secrets as environment variables
|
|
16
|
+
- **BaseSandbox Integration**: All inherited methods (`read`, `write`, `edit`, `ls`, `grep`, `glob`) work out of the box
|
|
17
|
+
- **Factory Pattern**: Compatible with deepagents' middleware architecture
|
|
18
|
+
- **Full SDK Access**: Access the underlying Modal SDK via the `sandbox` property for advanced features
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# npm
|
|
24
|
+
npm install @langchain/modal
|
|
25
|
+
|
|
26
|
+
# yarn
|
|
27
|
+
yarn add @langchain/modal
|
|
28
|
+
|
|
29
|
+
# pnpm
|
|
30
|
+
pnpm add @langchain/modal
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Authentication Setup
|
|
34
|
+
|
|
35
|
+
The package requires Modal authentication:
|
|
36
|
+
|
|
37
|
+
### Environment Variables (Recommended)
|
|
38
|
+
|
|
39
|
+
1. Go to [https://modal.com/settings/tokens](https://modal.com/settings/tokens)
|
|
40
|
+
2. Create a new token and set the environment variables:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
export MODAL_TOKEN_ID=your_token_id
|
|
44
|
+
export MODAL_TOKEN_SECRET=your_token_secret
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Explicit Credentials in Code
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const sandbox = await ModalSandbox.create({
|
|
51
|
+
auth: {
|
|
52
|
+
tokenId: "your-token-id",
|
|
53
|
+
tokenSecret: "your-token-secret",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Basic Usage
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { createDeepAgent } from "deepagents";
|
|
62
|
+
import { ChatAnthropic } from "@langchain/anthropic";
|
|
63
|
+
import { ModalSandbox } from "@langchain/modal";
|
|
64
|
+
|
|
65
|
+
// Create and initialize the sandbox
|
|
66
|
+
const sandbox = await ModalSandbox.create({
|
|
67
|
+
imageName: "python:3.12-slim",
|
|
68
|
+
timeoutMs: 600_000, // 10 minutes
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const agent = createDeepAgent({
|
|
73
|
+
model: new ChatAnthropic({ model: "claude-sonnet-4-20250514" }),
|
|
74
|
+
systemPrompt: "You are a coding assistant with access to a sandbox.",
|
|
75
|
+
backend: sandbox,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const result = await agent.invoke({
|
|
79
|
+
messages: [
|
|
80
|
+
{ role: "user", content: "Create a hello world Python app and run it" },
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
} finally {
|
|
84
|
+
await sandbox.close();
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Configuration Options
|
|
89
|
+
|
|
90
|
+
`ModalSandboxOptions` extends the Modal SDK's `SandboxCreateParams` directly, so you can use any SDK option. We only wrap `volumes` and `secrets` to accept names instead of objects.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
interface ModalSandboxOptions extends SandboxCreateParams {
|
|
94
|
+
/** Modal App name. @default "deepagents-sandbox" */
|
|
95
|
+
appName?: string;
|
|
96
|
+
|
|
97
|
+
/** Docker image to use. @default "alpine:3.21" */
|
|
98
|
+
imageName?: string;
|
|
99
|
+
|
|
100
|
+
/** Initial files to populate the sandbox with. */
|
|
101
|
+
initialFiles?: Record<string, string | Uint8Array>;
|
|
102
|
+
|
|
103
|
+
/** Authentication credentials (or use env vars). */
|
|
104
|
+
auth?: { tokenId?: string; tokenSecret?: string };
|
|
105
|
+
|
|
106
|
+
/** Modal Volume names to mount (keys are mount paths). */
|
|
107
|
+
volumes?: Record<string, string>;
|
|
108
|
+
|
|
109
|
+
/** Modal Secret names to inject. */
|
|
110
|
+
secrets?: string[];
|
|
111
|
+
|
|
112
|
+
// All SandboxCreateParams options are available:
|
|
113
|
+
timeoutMs?: number; // Max lifetime in milliseconds
|
|
114
|
+
idleTimeoutMs?: number; // Idle timeout in milliseconds
|
|
115
|
+
workdir?: string; // Working directory
|
|
116
|
+
gpu?: string; // GPU type (e.g., "T4", "A100")
|
|
117
|
+
cpu?: number; // CPU cores (fractional allowed)
|
|
118
|
+
memoryMiB?: number; // Memory in MiB
|
|
119
|
+
regions?: string[]; // Regions to run in
|
|
120
|
+
env?: Record<string, string>; // Environment variables
|
|
121
|
+
blockNetwork?: boolean; // Block network access
|
|
122
|
+
cidrAllowlist?: string[]; // Allowed CIDRs
|
|
123
|
+
verbose?: boolean; // Enable verbose logging
|
|
124
|
+
name?: string; // Sandbox name (unique within app)
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## GPU Support
|
|
129
|
+
|
|
130
|
+
Modal supports various NVIDIA GPUs for ML/AI workloads:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const sandbox = await ModalSandbox.create({
|
|
134
|
+
imageName: "python:3.12-slim",
|
|
135
|
+
gpu: "T4", // NVIDIA T4 (16GB VRAM)
|
|
136
|
+
// gpu: "L4", // NVIDIA L4 (24GB VRAM)
|
|
137
|
+
// gpu: "A10G", // NVIDIA A10G (24GB VRAM)
|
|
138
|
+
// gpu: "A100", // NVIDIA A100 (40/80GB VRAM)
|
|
139
|
+
// gpu: "H100", // NVIDIA H100 (80GB VRAM)
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Using Volumes
|
|
144
|
+
|
|
145
|
+
Mount Modal Volumes for persistent storage:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Volume must be created in Modal first
|
|
149
|
+
const sandbox = await ModalSandbox.create({
|
|
150
|
+
imageName: "python:3.12-slim",
|
|
151
|
+
volumes: {
|
|
152
|
+
"/data": "my-data-volume",
|
|
153
|
+
"/models": "my-models-volume",
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Files in /data and /models persist across sandbox restarts
|
|
158
|
+
await sandbox.execute("echo 'Hello' > /data/test.txt");
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Using Secrets
|
|
162
|
+
|
|
163
|
+
Inject Modal Secrets as environment variables:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// Secrets must be created in Modal first
|
|
167
|
+
const sandbox = await ModalSandbox.create({
|
|
168
|
+
imageName: "python:3.12-slim",
|
|
169
|
+
secrets: ["my-api-keys", "database-credentials"],
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Secrets are available as environment variables
|
|
173
|
+
await sandbox.execute("echo $API_KEY");
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Initial Files
|
|
177
|
+
|
|
178
|
+
Pre-populate the sandbox with files during creation:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const sandbox = await ModalSandbox.create({
|
|
182
|
+
imageName: "python:3.12-slim",
|
|
183
|
+
initialFiles: {
|
|
184
|
+
// String content
|
|
185
|
+
"/app/main.py": `
|
|
186
|
+
import json
|
|
187
|
+
|
|
188
|
+
def main():
|
|
189
|
+
print("Hello from Python!")
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
main()
|
|
193
|
+
`,
|
|
194
|
+
// JSON configuration
|
|
195
|
+
"/app/config.json": JSON.stringify(
|
|
196
|
+
{ name: "my-app", version: "1.0.0" },
|
|
197
|
+
null,
|
|
198
|
+
2,
|
|
199
|
+
),
|
|
200
|
+
|
|
201
|
+
// Uint8Array content also supported
|
|
202
|
+
"/app/data.bin": new Uint8Array([0x00, 0x01, 0x02]),
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Files are ready to use immediately
|
|
207
|
+
const result = await sandbox.execute("python /app/main.py");
|
|
208
|
+
console.log(result.output); // "Hello from Python!"
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
This is especially useful for:
|
|
212
|
+
|
|
213
|
+
- Setting up project scaffolding before agent execution
|
|
214
|
+
- Providing configuration files
|
|
215
|
+
- Pre-loading test data or fixtures
|
|
216
|
+
- Creating initial source code files
|
|
217
|
+
|
|
218
|
+
## Accessing the Modal SDK
|
|
219
|
+
|
|
220
|
+
For advanced features not exposed by `BaseSandbox`, you can access the underlying Modal SDK directly:
|
|
221
|
+
|
|
222
|
+
- `.client` - The `ModalClient` instance for accessing other Modal resources
|
|
223
|
+
- `.instance` - The `Sandbox` instance for direct sandbox operations
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const modalSandbox = await ModalSandbox.create();
|
|
227
|
+
|
|
228
|
+
// Access the Modal client for other Modal resources
|
|
229
|
+
const client = modalSandbox.client;
|
|
230
|
+
|
|
231
|
+
// Access the raw Modal Sandbox for direct operations
|
|
232
|
+
const instance = modalSandbox.instance;
|
|
233
|
+
|
|
234
|
+
// Execute commands with specific options
|
|
235
|
+
const process = await instance.exec(["python", "-c", "print('Hello')"], {
|
|
236
|
+
stdout: "pipe",
|
|
237
|
+
stderr: "pipe",
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Open files for reading/writing
|
|
241
|
+
const writeHandle = await instance.open("/tmp/file.txt", "w");
|
|
242
|
+
await writeHandle.write(new TextEncoder().encode("Hello"));
|
|
243
|
+
await writeHandle.close();
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Reconnecting to Existing Sandboxes
|
|
247
|
+
|
|
248
|
+
Resume working with a sandbox that is still running:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
// First session: create a sandbox
|
|
252
|
+
const sandbox = await ModalSandbox.create({
|
|
253
|
+
imageName: "python:3.12-slim",
|
|
254
|
+
timeout: 3600, // 1 hour
|
|
255
|
+
});
|
|
256
|
+
const sandboxId = sandbox.id;
|
|
257
|
+
|
|
258
|
+
// Later: reconnect to the same sandbox by ID
|
|
259
|
+
const reconnected = await ModalSandbox.fromId(sandboxId);
|
|
260
|
+
const result = await reconnected.execute("ls -la");
|
|
261
|
+
|
|
262
|
+
// Or reconnect by name (if sandbox has a name)
|
|
263
|
+
const sandbox2 = await ModalSandbox.create({
|
|
264
|
+
appName: "my-app",
|
|
265
|
+
sandboxName: "my-sandbox",
|
|
266
|
+
imageName: "python:3.12-slim",
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const reconnected2 = await ModalSandbox.fromName("my-app", "my-sandbox");
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Error Handling
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { ModalSandboxError } from "@langchain/modal";
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
await sandbox.execute("some command");
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (error instanceof ModalSandboxError) {
|
|
281
|
+
switch (error.code) {
|
|
282
|
+
case "NOT_INITIALIZED":
|
|
283
|
+
await sandbox.initialize();
|
|
284
|
+
break;
|
|
285
|
+
case "COMMAND_TIMEOUT":
|
|
286
|
+
console.error("Command took too long");
|
|
287
|
+
break;
|
|
288
|
+
case "AUTHENTICATION_FAILED":
|
|
289
|
+
console.error("Check your Modal token credentials");
|
|
290
|
+
break;
|
|
291
|
+
default:
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Error Codes
|
|
299
|
+
|
|
300
|
+
| Code | Description |
|
|
301
|
+
| ------------------------- | ------------------------------------------- |
|
|
302
|
+
| `NOT_INITIALIZED` | Sandbox not initialized - call initialize() |
|
|
303
|
+
| `ALREADY_INITIALIZED` | Cannot initialize twice |
|
|
304
|
+
| `AUTHENTICATION_FAILED` | Invalid or missing Modal tokens |
|
|
305
|
+
| `SANDBOX_CREATION_FAILED` | Failed to create sandbox |
|
|
306
|
+
| `SANDBOX_NOT_FOUND` | Sandbox ID/name not found or expired |
|
|
307
|
+
| `COMMAND_TIMEOUT` | Command execution timed out |
|
|
308
|
+
| `COMMAND_FAILED` | Command execution failed |
|
|
309
|
+
| `FILE_OPERATION_FAILED` | File read/write failed |
|
|
310
|
+
| `RESOURCE_LIMIT_EXCEEDED` | CPU, memory, or storage limits exceeded |
|
|
311
|
+
| `VOLUME_ERROR` | Volume operation failed |
|
|
312
|
+
|
|
313
|
+
## Inherited BaseSandbox Methods
|
|
314
|
+
|
|
315
|
+
`ModalSandbox` extends `BaseSandbox` and inherits these convenience methods:
|
|
316
|
+
|
|
317
|
+
| Method | Description |
|
|
318
|
+
| ------------ | ----------------------------- |
|
|
319
|
+
| `read()` | Read a file's contents |
|
|
320
|
+
| `write()` | Write content to a file |
|
|
321
|
+
| `edit()` | Replace text in a file |
|
|
322
|
+
| `lsInfo()` | List directory contents |
|
|
323
|
+
| `grepRaw()` | Search for patterns in files |
|
|
324
|
+
| `globInfo()` | Find files matching a pattern |
|
|
325
|
+
|
|
326
|
+
## Limits and Constraints
|
|
327
|
+
|
|
328
|
+
| Constraint | Value |
|
|
329
|
+
| --------------- | --------------------------------------- |
|
|
330
|
+
| Max timeout | 86400 seconds (24 hours) |
|
|
331
|
+
| Default timeout | 300 seconds (5 minutes) |
|
|
332
|
+
| Network access | Full (by default, can be blocked) |
|
|
333
|
+
| File API | Alpha (up to 100 MiB read, 1 GiB write) |
|
|
334
|
+
|
|
335
|
+
## Environment Variables
|
|
336
|
+
|
|
337
|
+
| Variable | Description |
|
|
338
|
+
| -------------------- | ---------------------- |
|
|
339
|
+
| `MODAL_TOKEN_ID` | Modal API token ID |
|
|
340
|
+
| `MODAL_TOKEN_SECRET` | Modal API token secret |
|
|
341
|
+
|
|
342
|
+
## License
|
|
343
|
+
|
|
344
|
+
MIT
|