From 70e7e8265824ad9f1a78300137690cd3d1ecc697 Mon Sep 17 00:00:00 2001 From: zhoujinyu <2319109590@qq.com> Date: Mon, 11 May 2026 16:02:41 +0800 Subject: [PATCH 1/5] Implement as langchain codeinterpreter sandbox provider Signed-off-by: zhoujinyu <2319109590@qq.com> --- .../langchain_agentcube/__init__.py | 8 ++ .../langchain_agentcube/sandbox.py | 117 ++++++++++++++++ .../langchain-agentcube/pyproject.toml | 23 ++++ .../clients/code_interpreter_data_plane.py | 39 ++++-- sdk-python/agentcube/code_interpreter.py | 8 +- test/e2e/run_e2e.sh | 6 + test/e2e/test_langchain_agentcube_sandbox.py | 126 ++++++++++++++++++ 7 files changed, 312 insertions(+), 15 deletions(-) create mode 100644 integrations/langchain-agentcube/langchain_agentcube/__init__.py create mode 100644 integrations/langchain-agentcube/langchain_agentcube/sandbox.py create mode 100644 integrations/langchain-agentcube/pyproject.toml create mode 100644 test/e2e/test_langchain_agentcube_sandbox.py diff --git a/integrations/langchain-agentcube/langchain_agentcube/__init__.py b/integrations/langchain-agentcube/langchain_agentcube/__init__.py new file mode 100644 index 00000000..dd9bc880 --- /dev/null +++ b/integrations/langchain-agentcube/langchain_agentcube/__init__.py @@ -0,0 +1,8 @@ +# Copyright The Volcano Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. + +from langchain_agentcube.sandbox import AgentcubeSandbox + +__all__ = ["AgentcubeSandbox"] diff --git a/integrations/langchain-agentcube/langchain_agentcube/sandbox.py b/integrations/langchain-agentcube/langchain_agentcube/sandbox.py new file mode 100644 index 00000000..192d3459 --- /dev/null +++ b/integrations/langchain-agentcube/langchain_agentcube/sandbox.py @@ -0,0 +1,117 @@ +# Copyright The Volcano Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. + +"""AgentCube Code Interpreter as a Deep Agents ``BaseSandbox`` backend.""" + +from __future__ import annotations + +import os +import tempfile +from typing import TYPE_CHECKING + +from deepagents.backends.protocol import ( + ExecuteResponse, + FileDownloadResponse, + FileUploadResponse, +) +from deepagents.backends.sandbox import BaseSandbox + +if TYPE_CHECKING: + from agentcube import CodeInterpreterClient + + +def _normalize_remote_path(path: str) -> str: + """Map paths from Deep Agents (often absolute) to session workspace-relative paths.""" + return path.replace("\\", "/").strip().lstrip("/") + + +class AgentcubeSandbox(BaseSandbox): + """Wraps an existing :class:`~agentcube.CodeInterpreterClient` session for ``create_deep_agent(..., backend=...)``.""" + + def __init__( + self, + *, + client: CodeInterpreterClient, + default_timeout: int | None = 30 * 60, + ) -> None: + self._client = client + self._default_timeout = default_timeout + + @property + def id(self) -> str: + sid = self._client.session_id + return sid if sid else "agentcube-unknown" + + def execute( + self, + command: str, + *, + timeout: int | None = None, + ) -> ExecuteResponse: + eff = timeout if timeout is not None else self._default_timeout + to = float(eff) if eff is not None else None + r = self._client.execute_command_result(command, timeout=to) + out = r.get("stdout") or "" + stderr = (r.get("stderr") or "").strip() + if stderr: + out += f"\n{stderr}" + return ExecuteResponse( + output=out, + exit_code=int(r.get("exit_code", -1)), + truncated=False, + ) + + def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]: + responses: list[FileUploadResponse] = [] + for path, content in files: + rel = _normalize_remote_path(path) + if not rel: + responses.append(FileUploadResponse(path=path, error="invalid_path")) + continue + tmp_path: str | None = None + try: + fd, tmp_path = tempfile.mkstemp(prefix="agentcube-upload-", suffix=".bin") + with os.fdopen(fd, "wb") as f: + f.write(content) + self._client.upload_file(tmp_path, rel) + responses.append(FileUploadResponse(path=path, error=None)) + except Exception as e: + responses.append(FileUploadResponse(path=path, error=str(e))) + finally: + if tmp_path: + try: + os.unlink(tmp_path) + except OSError: + pass + return responses + + def download_files(self, paths: list[str]) -> list[FileDownloadResponse]: + responses: list[FileDownloadResponse] = [] + for path in paths: + rel = _normalize_remote_path(path) + if not rel: + responses.append( + FileDownloadResponse(path=path, content=None, error="invalid_path") + ) + continue + fd, tmp_path = tempfile.mkstemp(prefix="agentcube-dl-", suffix=".bin") + os.close(fd) + try: + try: + self._client.download_file(rel, tmp_path) + except Exception as e: # noqa: BLE001 + responses.append( + FileDownloadResponse(path=path, content=None, error=str(e)) + ) + continue + with open(tmp_path, "rb") as f: + data = f.read() + responses.append(FileDownloadResponse(path=path, content=data, error=None)) + finally: + try: + os.unlink(tmp_path) + except OSError: + pass + return responses diff --git a/integrations/langchain-agentcube/pyproject.toml b/integrations/langchain-agentcube/pyproject.toml new file mode 100644 index 00000000..43be2e24 --- /dev/null +++ b/integrations/langchain-agentcube/pyproject.toml @@ -0,0 +1,23 @@ +# Copyright The Volcano Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. + +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "langchain-agentcube" +version = "0.1.0" +description = "LangChain Deep Agents sandbox backend for AgentCube Code Interpreter" +license = "Apache-2.0" +requires-python = ">=3.10" +dependencies = [ + "deepagents>=0.5.0,<0.6", + "agentcube-sdk>=0.1.0", +] + +[tool.setuptools.packages.find] +where = ["."] +include = ["langchain_agentcube*"] diff --git a/sdk-python/agentcube/clients/code_interpreter_data_plane.py b/sdk-python/agentcube/clients/code_interpreter_data_plane.py index a9350865..99f51d72 100644 --- a/sdk-python/agentcube/clients/code_interpreter_data_plane.py +++ b/sdk-python/agentcube/clients/code_interpreter_data_plane.py @@ -18,7 +18,7 @@ import os import ast import shlex -from typing import Optional, Any, List, Union +from typing import Optional, Any, Dict, List, Union from urllib.parse import urljoin import requests @@ -123,14 +123,13 @@ def _request(self, method: str, endpoint: str, body: Optional[bytes] = None, **k **kwargs ) - def execute_command(self, command: Union[str, List[str]], timeout: Optional[float] = None) -> str: - """Execute a shell command. + def execute_command_result( + self, command: Union[str, List[str]], timeout: Optional[float] = None + ) -> Dict[str, Any]: + """Execute a shell command and return stdout, stderr, and exit_code. - Args: - command: The command to execute, either as a single string or a list of arguments. - timeout: Optional timeout for the command execution. + Unlike ``execute_command``, this does not raise when ``exit_code != 0``. """ - # Convert timeout to string with 's' suffix as expected by PicoD timeout_value = timeout or self.timeout timeout_str = f"{timeout_value}s" if isinstance(timeout_value, (int, float)) else str(timeout_value) @@ -142,20 +141,32 @@ def execute_command(self, command: Union[str, List[str]], timeout: Optional[floa } body = json.dumps(payload).encode('utf-8') - # Add a buffer to the read timeout to allow PicoD to return the timeout response - # otherwise requests might raise ReadTimeout before we get the JSON response with exit_code 124 read_timeout = timeout_value + 2.0 if isinstance(timeout_value, (int, float)) else timeout_value resp = self._request("POST", "api/execute", body=body, timeout=read_timeout) resp.raise_for_status() result = resp.json() + return { + "stdout": result.get("stdout") or "", + "stderr": result.get("stderr") or "", + "exit_code": int(result.get("exit_code", -1)), + } + + def execute_command(self, command: Union[str, List[str]], timeout: Optional[float] = None) -> str: + """Execute a shell command. + + Args: + command: The command to execute, either as a single string or a list of arguments. + timeout: Optional timeout for the command execution. + """ + result = self.execute_command_result(command, timeout) if result["exit_code"] != 0: - raise CommandExecutionError( - exit_code=result["exit_code"], - stderr=result["stderr"], - command=command - ) + raise CommandExecutionError( + exit_code=result["exit_code"], + stderr=result["stderr"], + command=command + ) return result["stdout"] diff --git a/sdk-python/agentcube/code_interpreter.py b/sdk-python/agentcube/code_interpreter.py index d4f2fe5f..b0ccf5df 100644 --- a/sdk-python/agentcube/code_interpreter.py +++ b/sdk-python/agentcube/code_interpreter.py @@ -14,7 +14,7 @@ import os import logging -from typing import Optional +from typing import Any, Optional from agentcube.clients.control_plane import ControlPlaneClient from agentcube.clients.code_interpreter_data_plane import CodeInterpreterDataPlaneClient @@ -176,6 +176,12 @@ def execute_command(self, command: str, timeout: Optional[float] = None) -> str: """ return self.dp_client.execute_command(command, timeout) + def execute_command_result( + self, command: str, timeout: Optional[float] = None + ) -> dict[str, Any]: + """Run a shell command and return ``stdout``, ``stderr``, and ``exit_code`` (no raise on failure).""" + return self.dp_client.execute_command_result(command, timeout) + def run_code(self, language: str, code: str, timeout: Optional[float] = None) -> str: """ Execute a code snippet in the remote environment. diff --git a/test/e2e/run_e2e.sh b/test/e2e/run_e2e.sh index 3f6c053f..862bef7a 100755 --- a/test/e2e/run_e2e.sh +++ b/test/e2e/run_e2e.sh @@ -456,6 +456,7 @@ pip install --upgrade pip # We are currently in project root, sdk-python is at ./sdk-python pip install -e ./sdk-python pip install -e ./integrations/code-interpreter-mcp +pip install -e ./integrations/langchain-agentcube # Check if agentcube package is available after installation require_python @@ -475,6 +476,11 @@ if ! WORKLOAD_MANAGER_URL="http://localhost:${WORKLOAD_MANAGER_LOCAL_PORT}" ROUT TEST_FAILED=1 fi +echo "Running LangChain AgentcubeSandbox E2E..." +if ! WORKLOAD_MANAGER_URL="http://localhost:${WORKLOAD_MANAGER_LOCAL_PORT}" ROUTER_URL="http://localhost:${ROUTER_LOCAL_PORT}" API_TOKEN=$API_TOKEN AGENTCUBE_NAMESPACE="${AGENTCUBE_NAMESPACE}" "$E2E_VENV_DIR/bin/python" test_langchain_agentcube_sandbox.py; then + TEST_FAILED=1 +fi + echo "Running Python Code Interpreter MCP tests (streamable-http, local subprocess)..." if ! WORKLOAD_MANAGER_URL="http://localhost:${WORKLOAD_MANAGER_LOCAL_PORT}" ROUTER_URL="http://localhost:${ROUTER_LOCAL_PORT}" API_TOKEN=$API_TOKEN AGENTCUBE_NAMESPACE="${AGENTCUBE_NAMESPACE}" "$E2E_VENV_DIR/bin/python" test_mcp_code_interpreter.py; then TEST_FAILED=1 diff --git a/test/e2e/test_langchain_agentcube_sandbox.py b/test/e2e/test_langchain_agentcube_sandbox.py new file mode 100644 index 00000000..6183eae2 --- /dev/null +++ b/test/e2e/test_langchain_agentcube_sandbox.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# Copyright The Volcano Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""E2E: LangChain ``AgentcubeSandbox`` (Deep Agents BaseSandbox) against live Router/WM. + +Same prerequisites as ``test_codeinterpreter.py``: ``ROUTER_URL``, ``WORKLOAD_MANAGER_URL``, +``AGENTCUBE_NAMESPACE``, optional ``API_TOKEN``. Exercises ``execute``, non-zero exit, +and ``upload_files`` / ``download_files`` (mirrors MCP file roundtrip style). +""" + +from __future__ import annotations + +import os +import unittest + +from agentcube import CodeInterpreterClient +from langchain_agentcube import AgentcubeSandbox + + +class TestLangchainAgentcubeSandboxE2E(unittest.TestCase): + """E2E for ``AgentcubeSandbox`` backed by a real Code Interpreter session.""" + + def setUp(self): + self.namespace = os.getenv("AGENTCUBE_NAMESPACE", "agentcube") + self.workload_manager_url = os.getenv("WORKLOAD_MANAGER_URL") + self.router_url = os.getenv("ROUTER_URL") + self.api_token = os.getenv("API_TOKEN") + + if not self.workload_manager_url: + self.fail("WORKLOAD_MANAGER_URL environment variable not set") + if not self.router_url: + self.fail("ROUTER_URL environment variable not set") + + print( + f"[LangChain sandbox E2E] namespace={self.namespace}, " + f"wm={self.workload_manager_url}, router={self.router_url}" + ) + + def test_sandbox_execute_echo(self): + with CodeInterpreterClient( + name="e2e-code-interpreter", + namespace=self.namespace, + workload_manager_url=self.workload_manager_url, + router_url=self.router_url, + auth_token=self.api_token, + verbose=True, + ) as client: + backend = AgentcubeSandbox(client=client) + self.assertTrue(backend.id) + r = backend.execute("echo lc-sandbox-ok") + self.assertEqual(r.exit_code, 0, r.output) + self.assertIn("lc-sandbox-ok", r.output) + print(f"[LangChain sandbox E2E] execute ok: {r.output!r}") + + def test_sandbox_execute_nonzero_exit(self): + """``BaseSandbox.execute`` must return ``ExecuteResponse``, not raise.""" + with CodeInterpreterClient( + name="e2e-code-interpreter", + namespace=self.namespace, + workload_manager_url=self.workload_manager_url, + router_url=self.router_url, + auth_token=self.api_token, + verbose=True, + ) as client: + backend = AgentcubeSandbox(client=client) + r = backend.execute("sh -c 'exit 7'") + self.assertEqual(r.exit_code, 7, r.output) + print(f"[LangChain sandbox E2E] nonzero exit as expected: {r.exit_code}") + + def test_sandbox_upload_download_roundtrip(self): + marker = f"lc-roundtrip-{os.getpid()}\n".encode("utf-8") + remote = f"lc_e2e_roundtrip_{os.getpid()}.txt" + with CodeInterpreterClient( + name="e2e-code-interpreter", + namespace=self.namespace, + workload_manager_url=self.workload_manager_url, + router_url=self.router_url, + auth_token=self.api_token, + verbose=True, + ) as client: + backend = AgentcubeSandbox(client=client) + up = backend.upload_files([(remote, marker)]) + self.assertEqual(len(up), 1) + self.assertIsNone(up[0].error, up[0]) + + dl = backend.download_files([remote]) + self.assertEqual(len(dl), 1) + self.assertIsNone(dl[0].error, dl[0]) + self.assertEqual(dl[0].content, marker) + print(f"[LangChain sandbox E2E] upload/download ok remote={remote!r}") + + def test_sandbox_absolute_path_normalized(self): + """Deep Agents often use absolute paths; ensure strip + upload/download works.""" + marker = b"abs-path-ok\n" + remote_abs = f"/lc_abs_{os.getpid()}.txt" + with CodeInterpreterClient( + name="e2e-code-interpreter", + namespace=self.namespace, + workload_manager_url=self.workload_manager_url, + router_url=self.router_url, + auth_token=self.api_token, + verbose=True, + ) as client: + backend = AgentcubeSandbox(client=client) + up = backend.upload_files([(remote_abs, marker)]) + self.assertIsNone(up[0].error, up[0]) + dl = backend.download_files([remote_abs]) + self.assertIsNone(dl[0].error, dl[0]) + self.assertEqual(dl[0].content, marker) + print("[LangChain sandbox E2E] absolute path normalized ok") + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 360935b67777ea0d908a9d45c77fc52f3f37f49d Mon Sep 17 00:00:00 2001 From: zhoujinyu <2319109590@qq.com> Date: Mon, 11 May 2026 16:11:31 +0800 Subject: [PATCH 2/5] use python 3.11 Signed-off-by: zhoujinyu <2319109590@qq.com> --- .github/workflows/e2e.yml | 5 +++++ integrations/langchain-agentcube/pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 43c602db..6e084a78 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -13,6 +13,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Set up Go uses: actions/setup-go@v5 with: diff --git a/integrations/langchain-agentcube/pyproject.toml b/integrations/langchain-agentcube/pyproject.toml index 43be2e24..95bec25b 100644 --- a/integrations/langchain-agentcube/pyproject.toml +++ b/integrations/langchain-agentcube/pyproject.toml @@ -12,7 +12,7 @@ name = "langchain-agentcube" version = "0.1.0" description = "LangChain Deep Agents sandbox backend for AgentCube Code Interpreter" license = "Apache-2.0" -requires-python = ">=3.10" +requires-python = ">=3.11" dependencies = [ "deepagents>=0.5.0,<0.6", "agentcube-sdk>=0.1.0", From 7a00eed8ed8e3c6f1426669465f20ce4bdb285f1 Mon Sep 17 00:00:00 2001 From: zhoujinyu <2319109590@qq.com> Date: Mon, 11 May 2026 21:38:47 +0800 Subject: [PATCH 3/5] add official demo Signed-off-by: zhoujinyu <2319109590@qq.com> --- integrations/langchain-agentcube/README.md | 75 ++++++++++++ .../example/deep_agent_sandbox.py | 109 ++++++++++++++++++ .../langchain-agentcube/pyproject.toml | 1 + 3 files changed, 185 insertions(+) create mode 100644 integrations/langchain-agentcube/README.md create mode 100644 integrations/langchain-agentcube/example/deep_agent_sandbox.py diff --git a/integrations/langchain-agentcube/README.md b/integrations/langchain-agentcube/README.md new file mode 100644 index 00000000..de1cbc0c --- /dev/null +++ b/integrations/langchain-agentcube/README.md @@ -0,0 +1,75 @@ +# LangChain + AgentCube sandbox + +Wire AgentCube **Code Interpreter** to [LangChain Deep Agents](https://docs.langchain.com/oss/python/deepagents/sandboxes) as the `backend`. + +**Prerequisites**: AgentCube cluster with a `CodeInterpreter` CR deployed; see [getting-started](../../docs/getting-started.md). Local **Python >= 3.11**. + +--- + +## 1. Install + +From the repository root: + +```bash +pip install -e ./sdk-python +pip install -e ./integrations/langchain-agentcube +pip install langchain-openai # DeepSeek and OpenAI-compatible APIs +# pip install langchain-anthropic # only if you use Anthropic +``` + +--- + +## 2. Cluster environment variables + +After `kubectl port-forward` to `workloadmanager` and `agentcube-router`: + +```bash +export WORKLOAD_MANAGER_URL="http://localhost:8080" +export ROUTER_URL="http://localhost:8081" +export AGENTCUBE_NAMESPACE="default" # same namespace as the CodeInterpreter CR +# export API_TOKEN="..." # if your cluster requires it +# export CODE_INTERPRETER_NAME=my-ci # optional; default my-interpreter +``` + +--- + +## 3. LLM API keys + +**DeepSeek** (OpenAI-compatible; `pip install langchain-openai`): + +```bash +export DEEPSEEK_API_KEY="sk-..." +# Optional: DEEPSEEK_API_BASE (default https://api.deepseek.com/v1), DEEPSEEK_MODEL (default deepseek-chat) +``` + +**Anthropic Claude** (`pip install langchain-anthropic`): + +```bash +export ANTHROPIC_API_KEY="sk-ant-..." +# Optional: ANTHROPIC_MODEL (default claude-haiku-4-5-20251001) +``` + +**OpenAI GPT** (same `langchain-openai` package): + +```bash +export OPENAI_API_KEY="sk-..." +# Optional: OPENAI_MODEL (default gpt-4o-mini); set OPENAI_API_BASE for proxies or compatible gateways +``` + +If several keys are set, the example script prefers **DeepSeek, then Claude, then GPT**. For DeepSeek you can instead set only `OPENAI_API_KEY` + `OPENAI_API_BASE=https://api.deepseek.com/v1` + `OPENAI_MODEL=deepseek-chat` without `DEEPSEEK_API_KEY`. + +--- + +## 4. Run the example + +`agentcube-cli` and the SDK both use the top-level package name `agentcube`. If you installed the CLI in editable mode, `import agentcube` from the repo root may resolve to the CLI. The example inserts this repo's `sdk-python` first on `sys.path` before importing the SDK. **Do not substitute the CLI package for the SDK**: the CLI does not expose `CodeInterpreterClient`. + +```bash +python integrations/langchain-agentcube/example/deep_agent_sandbox.py +``` + +Flags: `--interpreter`, `--namespace`, `--prompt`. A full fix is to rename the CLI top-level package (e.g. `agentcube_cli`) in the future. + +--- + +Everything below uses **agentcube-sdk** plus Router / Workload Manager. diff --git a/integrations/langchain-agentcube/example/deep_agent_sandbox.py b/integrations/langchain-agentcube/example/deep_agent_sandbox.py new file mode 100644 index 00000000..d7f5dd58 --- /dev/null +++ b/integrations/langchain-agentcube/example/deep_agent_sandbox.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# Copyright The Volcano Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. + +"""Example: create_deep_agent with AgentcubeSandbox. See ../README.md.""" + +from __future__ import annotations + +import argparse +import os +import sys +from pathlib import Path +from typing import Any + + +def _require_env(name: str) -> str: + v = os.environ.get(name) + if not v: + print(f"Error: environment variable {name} is not set", file=sys.stderr) + sys.exit(1) + return v + + +def _make_chat_model() -> object: + if os.environ.get("DEEPSEEK_API_KEY"): + from langchain_openai import ChatOpenAI + + return ChatOpenAI( + model=os.environ.get("DEEPSEEK_MODEL", "deepseek-chat"), + api_key=os.environ["DEEPSEEK_API_KEY"], + base_url=os.environ.get("DEEPSEEK_API_BASE", "https://api.deepseek.com/v1"), + temperature=0, + ) + if os.environ.get("ANTHROPIC_API_KEY"): + from langchain_anthropic import ChatAnthropic + + return ChatAnthropic( + model=os.environ.get("ANTHROPIC_MODEL", "claude-haiku-4-5-20251001"), + temperature=0, + ) + if os.environ.get("OPENAI_API_KEY"): + from langchain_openai import ChatOpenAI + + kwargs: dict[str, Any] = { + "model": os.environ.get("OPENAI_MODEL", "gpt-4o-mini"), + "api_key": os.environ["OPENAI_API_KEY"], + "temperature": 0, + } + if os.environ.get("OPENAI_API_BASE"): + kwargs["base_url"] = os.environ["OPENAI_API_BASE"] + return ChatOpenAI(**kwargs) + print( + "Error: set DEEPSEEK_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY.", + file=sys.stderr, + ) + sys.exit(1) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Deep Agents + AgentcubeSandbox") + parser.add_argument( + "--interpreter", + default=os.environ.get("CODE_INTERPRETER_NAME", "my-interpreter"), + ) + parser.add_argument( + "--namespace", + default=os.environ.get("AGENTCUBE_NAMESPACE", "default"), + ) + parser.add_argument( + "--prompt", + default="Write and run a Python script to print 'Hello, World!'", + ) + args = parser.parse_args() + + _require_env("WORKLOAD_MANAGER_URL") + _require_env("ROUTER_URL") + sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "sdk-python")) + + from agentcube import CodeInterpreterClient + from deepagents import create_deep_agent + from langchain_agentcube import AgentcubeSandbox + + model = _make_chat_model() + with CodeInterpreterClient( + name=args.interpreter, + namespace=args.namespace, + router_url=os.environ["ROUTER_URL"], + workload_manager_url=os.environ["WORKLOAD_MANAGER_URL"], + auth_token=os.environ.get("API_TOKEN"), + ) as client: + agent = create_deep_agent( + model=model, + system_prompt="You are a coding assistant with sandbox access.", + backend=AgentcubeSandbox(client=client), + ) + try: + result = agent.invoke( + {"messages": [{"role": "user", "content": args.prompt}]}, + ) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + print(result) + + +if __name__ == "__main__": + main() diff --git a/integrations/langchain-agentcube/pyproject.toml b/integrations/langchain-agentcube/pyproject.toml index 95bec25b..3cad6aa0 100644 --- a/integrations/langchain-agentcube/pyproject.toml +++ b/integrations/langchain-agentcube/pyproject.toml @@ -11,6 +11,7 @@ build-backend = "setuptools.build_meta" name = "langchain-agentcube" version = "0.1.0" description = "LangChain Deep Agents sandbox backend for AgentCube Code Interpreter" +readme = "README.md" license = "Apache-2.0" requires-python = ">=3.11" dependencies = [ From cc897303cf914a25bc1cc1fe15ce5d9d742fc7e0 Mon Sep 17 00:00:00 2001 From: zhoujinyu <2319109590@qq.com> Date: Mon, 11 May 2026 21:44:58 +0800 Subject: [PATCH 4/5] lint Signed-off-by: zhoujinyu <2319109590@qq.com> --- integrations/langchain-agentcube/langchain_agentcube/sandbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/langchain-agentcube/langchain_agentcube/sandbox.py b/integrations/langchain-agentcube/langchain_agentcube/sandbox.py index 192d3459..a1a7410f 100644 --- a/integrations/langchain-agentcube/langchain_agentcube/sandbox.py +++ b/integrations/langchain-agentcube/langchain_agentcube/sandbox.py @@ -28,7 +28,7 @@ def _normalize_remote_path(path: str) -> str: class AgentcubeSandbox(BaseSandbox): - """Wraps an existing :class:`~agentcube.CodeInterpreterClient` session for ``create_deep_agent(..., backend=...)``.""" + """Wraps :class:`~agentcube.CodeInterpreterClient` for ``create_deep_agent(..., backend=...)``.""" def __init__( self, From 2ac342fe90e7803c11c2578d961eaaa7f225e7e0 Mon Sep 17 00:00:00 2001 From: zhoujinyu <2319109590@qq.com> Date: Tue, 12 May 2026 10:03:34 +0800 Subject: [PATCH 5/5] upd readme Signed-off-by: zhoujinyu <2319109590@qq.com> --- integrations/langchain-agentcube/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/integrations/langchain-agentcube/README.md b/integrations/langchain-agentcube/README.md index de1cbc0c..d7ba02b5 100644 --- a/integrations/langchain-agentcube/README.md +++ b/integrations/langchain-agentcube/README.md @@ -62,8 +62,6 @@ If several keys are set, the example script prefers **DeepSeek, then Claude, the ## 4. Run the example -`agentcube-cli` and the SDK both use the top-level package name `agentcube`. If you installed the CLI in editable mode, `import agentcube` from the repo root may resolve to the CLI. The example inserts this repo's `sdk-python` first on `sys.path` before importing the SDK. **Do not substitute the CLI package for the SDK**: the CLI does not expose `CodeInterpreterClient`. - ```bash python integrations/langchain-agentcube/example/deep_agent_sandbox.py ```