-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(examples): add LangGraph integration with CubeSandbox via E2B SDK #710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
FAUST-BENCHOU
wants to merge
1
commit into
TencentCloud:master
from
FAUST-BENCHOU:feat/langgraph-example
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| OPENAI_API_KEY="<your-api-key>" | ||
|
|
||
| # Pick ONE OpenAI-compatible endpoint: | ||
| # DeepSeek: | ||
| OPENAI_BASE_URL="https://api.deepseek.com/v1" | ||
| # TokenHub: | ||
| # OPENAI_BASE_URL="https://tokenhub.tencentmaas.com/v1" | ||
|
|
||
| E2B_API_URL="http://<your-cube-sandbox-ip>:3000" | ||
| E2B_API_KEY="<your-e2b-api-key>" | ||
| CUBE_TEMPLATE_ID="<your-template-id>" | ||
|
|
||
| # mkcert root CA for E2B data-plane HTTPS (*.cube.app) | ||
| CUBE_SSL_CERT_FILE="/etc/pki/ca-trust/source/anchors/mkcert_<id>.pem" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| .venv/ | ||
| .env | ||
| __pycache__/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| # LangGraph + CubeSandbox Example | ||
|
|
||
| [中文](README_zh.md) | ||
|
|
||
| This directory shows how to connect a [LangGraph](https://github.com/langchain-ai/langgraph) agent to [CubeSandbox](https://github.com/TencentCloud/CubeSandbox). | ||
|
|
||
| It uses [Deep Agents](https://github.com/langchain-ai/deepagents) `create_deep_agent()` with the E2B-compatible API so tool calls run inside CubeSandbox MicroVMs. | ||
|
|
||
| ```text | ||
| LangGraph Agent (Deep Agents) | ||
| │ | ||
| └── langchain-e2b.E2BSandbox | ||
| └── e2b.Sandbox ──► CubeAPI (:3000) | ||
| │ | ||
| ▼ | ||
| CubeSandbox MicroVM (envd) | ||
| ``` | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - Python 3.11+ | ||
| - Running CubeSandbox with CubeAPI reachable | ||
| - A sandbox template | ||
| - An OpenAI-compatible LLM API key | ||
|
|
||
| ## Quick start | ||
|
|
||
| ```bash | ||
| pip install -r requirements.txt | ||
| cp .env.example .env # API keys, CubeAPI URL, template ID, mkcert CA path | ||
|
|
||
| # Sandbox connectivity only (no LLM) | ||
| python e2b_demo.py --sandbox-only | ||
|
|
||
| # Full agent | ||
| python e2b_demo.py | ||
| python e2b_demo.py --model deepseek-chat --question "What Linux distro is this?" | ||
| ``` | ||
|
|
||
| ## Environment variables | ||
|
|
||
| | Variable | Description | | ||
| |----------|-------------| | ||
| | `OPENAI_API_KEY` | LLM API key | | ||
| | `OPENAI_BASE_URL` | LLM endpoint (e.g. DeepSeek) | | ||
| | `E2B_API_URL` | CubeAPI URL | | ||
| | `E2B_API_KEY` | CubeAPI auth key | | ||
| | `CUBE_TEMPLATE_ID` | Sandbox template ID | | ||
| | `CUBE_SSL_CERT_FILE` | mkcert root CA for E2B data-plane HTTPS | | ||
|
|
||
| ## Files | ||
|
|
||
| | File | Description | | ||
| |------|-------------| | ||
| | `e2b_demo.py` | Entry script | | ||
| | `demo_common.py` | Agent and sandbox connectivity helpers | | ||
| | `cube_patches.py` | envd patches (root user, stdin) | | ||
| | `env_utils.py` | Load `.env`, validate variables | | ||
| | `llm_utils.py` | OpenAI-compatible LLM client | | ||
|
|
||
| ## CubeSandbox adaptations | ||
|
|
||
| `cube_patches.py` runs before the E2B SDK is used: | ||
|
|
||
| | Patch | Reason | | ||
| |-------|--------| | ||
| | `default_username = "root"` | Cube envd only supports root | | ||
| | Drop `stdin` kwarg | Older envd rejects it | | ||
| | `workdir = "/root"` | Cube images use root | | ||
| | `CUBE_SSL_CERT_FILE` → `SSL_CERT_FILE` | Trust mkcert CA for E2B HTTPS (same as openai-agents-example) | | ||
|
|
||
| The LLM client uses certifi + `trust_env=False` so `SSL_CERT_FILE` does not affect DeepSeek and other public HTTPS APIs. | ||
|
|
||
| Point `CUBE_SSL_CERT_FILE` at the **mkcert root CA**, not `cube-root-ca.crt`. | ||
|
|
||
| ## Core code | ||
|
|
||
| ```python | ||
| from cube_patches import apply_cube_envd_patches | ||
| apply_cube_envd_patches() | ||
|
|
||
| from deepagents import create_deep_agent | ||
| from e2b import Sandbox | ||
| from langchain_e2b import E2BSandbox | ||
|
|
||
| sandbox = Sandbox.create(template=template_id, timeout=300) | ||
| backend = E2BSandbox(sandbox=sandbox, workdir="/root", timeout=300) | ||
|
|
||
| agent = create_deep_agent(model=chat_model, backend=backend, system_prompt="...") | ||
| result = agent.invoke({"messages": [{"role": "user", "content": question}]}) | ||
| ``` | ||
|
|
||
| ## Related docs | ||
|
|
||
| - [LangGraph docs](https://docs.langchain.com/oss/python/langgraph/overview) | ||
| - [Deep Agents sandboxes](https://docs.langchain.com/oss/python/deepagents/sandboxes) | ||
| - [OpenAI Agents example](../openai-agents-example/README.md) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| # LangGraph + CubeSandbox 示例 | ||
|
|
||
| [English](README.md) | ||
|
|
||
| 本目录演示如何将 [LangGraph](https://github.com/langchain-ai/langgraph) Agent 接入 [CubeSandbox](https://github.com/TencentCloud/CubeSandbox) 沙箱。 | ||
|
|
||
| 使用 [Deep Agents](https://github.com/langchain-ai/deepagents) 的 `create_deep_agent()` 构建 LangGraph Agent,通过 E2B 兼容 API 在 CubeSandbox MicroVM 内执行工具。 | ||
|
|
||
| ```text | ||
| LangGraph Agent (Deep Agents) | ||
| │ | ||
| └── langchain-e2b.E2BSandbox | ||
| └── e2b.Sandbox ──► CubeAPI (:3000) | ||
| │ | ||
| ▼ | ||
| CubeSandbox MicroVM (envd) | ||
| ``` | ||
|
|
||
| ## 前置条件 | ||
|
|
||
| - Python 3.11+ | ||
| - CubeSandbox 已部署,CubeAPI 可访问 | ||
| - 已创建沙箱模板 | ||
| - OpenAI 兼容 LLM API Key | ||
|
|
||
| ## 快速开始 | ||
|
|
||
| ```bash | ||
| pip install -r requirements.txt | ||
| cp .env.example .env # 填入 API Key、CubeAPI 地址、模板 ID、mkcert 证书路径 | ||
|
|
||
| # 仅测沙箱(无需 LLM) | ||
| python e2b_demo.py --sandbox-only | ||
|
|
||
| # 完整 Agent | ||
| python e2b_demo.py | ||
| python e2b_demo.py --model deepseek-chat --question "What Linux distro is this?" | ||
| ``` | ||
|
|
||
| ## 环境变量 | ||
|
|
||
| | 变量 | 说明 | | ||
| |------|------| | ||
| | `OPENAI_API_KEY` | LLM API Key | | ||
| | `OPENAI_BASE_URL` | LLM 地址(如 DeepSeek) | | ||
| | `E2B_API_URL` | CubeAPI 地址 | | ||
| | `E2B_API_KEY` | CubeAPI 鉴权 Key | | ||
| | `CUBE_TEMPLATE_ID` | 沙箱模板 ID | | ||
| | `CUBE_SSL_CERT_FILE` | E2B 数据面 HTTPS 所需 mkcert 根证书 | | ||
|
|
||
| ## 文件说明 | ||
|
|
||
| | 文件 | 说明 | | ||
| |------|------| | ||
| | `e2b_demo.py` | 入口脚本 | | ||
| | `demo_common.py` | Agent / 沙箱连通性测试 | | ||
| | `cube_patches.py` | envd 兼容补丁(root 用户、stdin) | | ||
| | `env_utils.py` | 加载 `.env`、校验环境变量 | | ||
| | `llm_utils.py` | 构建 OpenAI 兼容 LLM 客户端 | | ||
|
|
||
| ## CubeSandbox 适配 | ||
|
|
||
| `cube_patches.py` 在导入 E2B SDK 前应用: | ||
|
|
||
| | 补丁 | 原因 | | ||
| |------|------| | ||
| | `default_username = "root"` | Cube envd 只支持 root | | ||
| | 移除 `stdin` 参数 | 旧版 envd 不支持 | | ||
| | `workdir = "/root"` | Cube 镜像以 root 为主 | | ||
| | `CUBE_SSL_CERT_FILE` → `SSL_CERT_FILE` | E2B 数据面 HTTPS 信任 mkcert CA(同 openai-agents-example) | | ||
|
|
||
| LLM 客户端使用 certifi 公网 CA + `trust_env=False`,避免 `SSL_CERT_FILE` 污染 DeepSeek 等 HTTPS 请求。 | ||
|
|
||
| `CUBE_SSL_CERT_FILE` 须指向 **mkcert 根证书**,不要用 `cube-root-ca.crt`。 | ||
|
|
||
| ## 核心代码 | ||
|
|
||
| ```python | ||
| from cube_patches import apply_cube_envd_patches | ||
| apply_cube_envd_patches() | ||
|
|
||
| from deepagents import create_deep_agent | ||
| from e2b import Sandbox | ||
| from langchain_e2b import E2BSandbox | ||
|
|
||
| sandbox = Sandbox.create(template=template_id, timeout=300) | ||
| backend = E2BSandbox(sandbox=sandbox, workdir="/root", timeout=300) | ||
|
|
||
| agent = create_deep_agent(model=chat_model, backend=backend, system_prompt="...") | ||
| result = agent.invoke({"messages": [{"role": "user", "content": question}]}) | ||
| ``` | ||
|
|
||
| ## 相关文档 | ||
|
|
||
| - [LangGraph 文档](https://docs.langchain.com/oss/python/langgraph/overview) | ||
| - [Deep Agents 沙箱文档](https://docs.langchain.com/oss/python/deepagents/sandboxes) | ||
| - [OpenAI Agents 集成示例](../openai-agents-example/README_zh.md) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # Copyright (c) 2026 Tencent Inc. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """CubeSandbox envd compatibility patches for the E2B Python SDK. | ||
|
|
||
| Cube envd only supports the ``root`` user, and older envd builds reject the | ||
| ``stdin`` keyword on ``commands.run()``. Apply these patches before creating | ||
| sandboxes or importing langchain-e2b backends. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import functools | ||
| import inspect | ||
| from typing import Any, Callable | ||
|
|
||
| _PATCHED = False | ||
|
|
||
|
|
||
| def _patch_filesystem(fs_cls: type) -> None: | ||
| for name in ( | ||
| "read", | ||
| "write", | ||
| "write_files", | ||
| "list", | ||
| "exists", | ||
| "get_info", | ||
| "remove", | ||
| "rename", | ||
| "make_dir", | ||
| "watch_dir", | ||
| ): | ||
| original = getattr(fs_cls, name, None) | ||
| if original is None: | ||
| continue | ||
|
|
||
| params = list(inspect.signature(original).parameters.keys()) | ||
| user_pos = params.index("user") - 1 if "user" in params else None | ||
|
|
||
| def _make(fn: Callable[..., Any], user_index: int | None = user_pos) -> Callable[..., Any]: | ||
| @functools.wraps(fn) | ||
| def _wrapper(self: Any, *args: Any, **kwargs: Any) -> Any: | ||
| if user_index is not None and len(args) > user_index: | ||
| args_list = list(args) | ||
| if args_list[user_index] is None: | ||
| args_list[user_index] = "root" | ||
| args = tuple(args_list) | ||
| else: | ||
| kwargs.setdefault("user", "root") | ||
| return fn(self, *args, **kwargs) | ||
|
|
||
| return _wrapper | ||
|
|
||
| setattr(fs_cls, name, _make(original)) | ||
|
|
||
|
|
||
| def _patch_commands(cmd_cls: type) -> None: | ||
| original_run = cmd_cls.run | ||
|
|
||
| @functools.wraps(original_run) | ||
| def _patched_run(self: Any, *args: Any, **kwargs: Any) -> Any: | ||
| if hasattr(self, "_envd_version"): | ||
| from e2b.sandbox_sync.commands.command import ENVD_COMMANDS_STDIN | ||
|
|
||
| if self._envd_version < ENVD_COMMANDS_STDIN: | ||
| kwargs.pop("stdin", None) | ||
| return original_run(self, *args, **kwargs) | ||
|
|
||
| cmd_cls.run = _patched_run | ||
|
|
||
|
|
||
| def apply_cube_envd_patches() -> None: | ||
| """Patch sync E2B SDK classes for CubeSandbox envd.""" | ||
| global _PATCHED | ||
| if _PATCHED: | ||
| return | ||
|
|
||
| import e2b.envd.rpc as e2b_rpc | ||
| from e2b.sandbox_sync.commands.command import Commands as SyncCommands | ||
| from e2b.sandbox_sync.filesystem.filesystem import Filesystem as SyncFilesystem | ||
|
|
||
| e2b_rpc.default_username = "root" | ||
| _patch_filesystem(SyncFilesystem) | ||
| _patch_commands(SyncCommands) | ||
| _PATCHED = True |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # Copyright (c) 2026 Tencent Inc. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """Shared helpers for LangGraph / Deep Agents CubeSandbox demos.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| import time | ||
| from typing import Protocol | ||
|
|
||
| from deepagents import create_deep_agent | ||
| from deepagents.backends.sandbox import BaseSandbox | ||
|
|
||
| from llm_utils import make_chat_model | ||
|
|
||
| CUBE_WORKDIR = "/root" | ||
| MARKER_PATH = "/tmp/langgraph-cube-marker.txt" | ||
| MARKER_CONTENT = "langgraph + cubesandbox\n" | ||
|
|
||
| SYSTEM_PROMPT = ( | ||
| "You are a helpful assistant running inside a CubeSandbox MicroVM. " | ||
| "Use the execute tool and filesystem tools to inspect the environment. " | ||
| "Be concise and show command output when relevant." | ||
| ) | ||
|
|
||
|
|
||
| class SandboxHandle(Protocol): | ||
| def kill(self) -> None: ... | ||
|
|
||
|
|
||
| def run_sandbox_check(*, backend: BaseSandbox, sandbox: SandboxHandle, label: str) -> None: | ||
| print(f"Backend: {label}") | ||
| print(f"CubeAPI: {os.environ['E2B_API_URL']}\n") | ||
|
|
||
| t0 = time.monotonic() | ||
| print(f"[create] sandbox_id={backend.id}") | ||
|
|
||
| result = backend.execute("uname -a && cat /etc/os-release | head -3") | ||
| print(f"[execute] exit={result.exit_code} {(time.monotonic() - t0) * 1000:.0f} ms") | ||
| print(result.output.strip()) | ||
|
|
||
| backend.upload_files([(MARKER_PATH, MARKER_CONTENT.encode("utf-8"))]) | ||
| read_back = backend.download_files([MARKER_PATH])[0] | ||
| if read_back.content is None: | ||
| raise RuntimeError(f"failed to read marker file: {read_back.error}") | ||
|
|
||
| text = read_back.content.decode("utf-8") | ||
| if text.strip() != MARKER_CONTENT.strip(): | ||
| raise RuntimeError(f"marker mismatch: {text!r}") | ||
|
|
||
| print(f"\n{'=' * 60}") | ||
| print("PASS: CubeSandbox backend is reachable.") | ||
|
|
||
|
|
||
| def run_agent_demo( | ||
| *, | ||
| backend: BaseSandbox, | ||
| sandbox: SandboxHandle, | ||
| label: str, | ||
| model: str, | ||
| question: str, | ||
| ) -> None: | ||
| print(f"Backend: {label}") | ||
| print(f"Model: {model}") | ||
| print(f"CubeAPI: {os.environ['E2B_API_URL']}") | ||
| print(f"Question: {question}\n") | ||
| print(f"[sandbox] sandbox_id={backend.id}") | ||
|
|
||
| agent = create_deep_agent( | ||
| model=make_chat_model(model), | ||
| backend=backend, | ||
| system_prompt=SYSTEM_PROMPT, | ||
| ) | ||
|
|
||
| print("[agent] running LangGraph agent (Deep Agents harness) ...\n") | ||
| t1 = time.monotonic() | ||
| result = agent.invoke({"messages": [{"role": "user", "content": question}]}) | ||
| elapsed_ms = (time.monotonic() - t1) * 1000 | ||
|
|
||
| messages = result.get("messages", []) | ||
| answer = messages[-1].content if messages else result | ||
| print(f"{'=' * 60}") | ||
| print(answer) | ||
| print(f"\n[done] {elapsed_ms:.0f} ms") | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.