chore: upgrade deepagents to v0.5.0a4 (#1161)

Replace custom LangSmithBackend with built-in LangSmithSandbox from
deepagents. Update deprecated protocol method names in docs.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com>
This commit is contained in:
open-swe[bot] 2026-04-06 10:24:50 -07:00 • committed by GitHub
parent d3506598fe
commit 24a6343352
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 30 additions and 123 deletions

View file

@ -84,7 +84,7 @@ The factory must return an object implementing `SandboxBackendProtocol` from `de
If none of the built-in providers fit, you can build your own. The agent accepts any backend that implements `SandboxBackendProtocol` from `deepagents`. The protocol requires:
- **File operations**: `ls_info()`, `read()`, `write()`, `edit()`, `glob_info()`, `grep_raw()`
- **File operations**: `ls()`, `read()`, `write()`, `edit()`, `glob()`, `grep()`
- **Shell execution**: `execute(command, timeout=None) -> ExecuteResponse`
- **Identity**: `id` property returning a unique sandbox identifier
@ -111,7 +111,7 @@ class MySandbox(BaseSandbox):
)
```
See `agent/integrations/langsmith.py` (`LangSmithBackend` class) for a full reference implementation.
See `deepagents.backends.LangSmithSandbox` and `agent/integrations/langsmith.py` for a full reference implementation.
---

View file

@ -1,5 +1,7 @@
"""Sandbox provider integrations."""
from agent.integrations.langsmith import LangSmithBackend, LangSmithProvider
from deepagents.backends import LangSmithSandbox
__all__ = ["LangSmithBackend", "LangSmithProvider"]
from agent.integrations.langsmith import LangSmithProvider
__all__ = ["LangSmithProvider", "LangSmithSandbox"]

View file

@ -1,7 +1,4 @@
"""LangSmith sandbox backend implementation.
Copied from deepagents-cli to avoid requiring deepagents-cli as a dependency.
"""
"""LangSmith sandbox backend integration."""
from __future__ import annotations
@ -11,15 +8,9 @@ import time
from abc import ABC, abstractmethod
from typing import Any
from deepagents.backends.protocol import (
ExecuteResponse,
FileDownloadResponse,
FileUploadResponse,
SandboxBackendProtocol,
WriteResult,
)
from deepagents.backends.sandbox import BaseSandbox
from langsmith.sandbox import Sandbox, SandboxClient, SandboxTemplate
from deepagents.backends import LangSmithSandbox
from deepagents.backends.protocol import SandboxBackendProtocol
from langsmith.sandbox import SandboxClient, SandboxTemplate
def _get_langsmith_api_key() -> str | None:
@ -32,12 +23,7 @@ def _get_langsmith_api_key() -> str | None:
def _get_sandbox_template_config() -> tuple[str | None, str | None]:
"""Get sandbox template configuration from environment.
Returns:
Tuple of (template_name, template_image) from environment variables.
Values are None if not set in environment.
"""
"""Get sandbox template configuration from environment."""
template_name = os.environ.get("DEFAULT_SANDBOX_TEMPLATE_NAME")
template_image = os.environ.get("DEFAULT_SANDBOX_TEMPLATE_IMAGE")
return template_name, template_image
@ -48,10 +34,6 @@ def create_langsmith_sandbox(
) -> SandboxBackendProtocol:
"""Create or connect to a LangSmith sandbox without automatic cleanup.
This function directly uses the LangSmithProvider to create/connect to sandboxes
without the context manager cleanup, allowing sandboxes to persist across
multiple agent invocations.
Args:
sandbox_id: Optional existing sandbox ID to connect to.
If None, creates a new sandbox.
@ -99,7 +81,6 @@ def _update_thread_sandbox_metadata(sandbox_id: str) -> None:
else:
loop.create_task(_update())
except Exception:
# Best-effort: ignore failures (no config context, client unavailable, etc.)
pass
@ -127,87 +108,12 @@ class SandboxProvider(ABC):
raise NotImplementedError
# Default template configuration
DEFAULT_TEMPLATE_NAME = "open-swe"
DEFAULT_TEMPLATE_IMAGE = "python:3"
class LangSmithBackend(BaseSandbox):
"""LangSmith backend implementation conforming to SandboxBackendProtocol.
This implementation inherits all file operation methods from BaseSandbox
and only implements the execute() method using LangSmith's API.
"""
def __init__(self, sandbox: Sandbox) -> None:
self._sandbox = sandbox
self._default_timeout: int = 30 * 5 # 5 minute default
@property
def id(self) -> str:
"""Unique identifier for the sandbox backend."""
return self._sandbox.name
def execute(self, command: str, *, timeout: int | None = None) -> ExecuteResponse:
"""Execute a command in the sandbox and return ExecuteResponse.
Args:
command: Full shell command string to execute.
timeout: Maximum time in seconds to wait for the command to complete.
If None, uses the default timeout of 5 minutes.
Returns:
ExecuteResponse with combined output, exit code, and truncation flag.
"""
effective_timeout = timeout if timeout is not None else self._default_timeout
result = self._sandbox.run(command, timeout=effective_timeout)
# Combine stdout and stderr (matching other backends' approach)
output = result.stdout or ""
if result.stderr:
output += "\n" + result.stderr if output else result.stderr
return ExecuteResponse(
output=output,
exit_code=result.exit_code,
truncated=False,
)
def write(self, file_path: str, content: str) -> WriteResult:
"""Write content using the LangSmith SDK to avoid ARG_MAX.
BaseSandbox.write() sends the full content in a shell command, which
can exceed ARG_MAX for large content. This override uses the SDK's
native write(), which sends content in the HTTP body.
"""
try:
self._sandbox.write(file_path, content.encode("utf-8"))
return WriteResult(path=file_path, files_update=None)
except Exception as e:
return WriteResult(error=f"Failed to write file '{file_path}': {e}")
def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
"""Download multiple files from the LangSmith sandbox."""
responses: list[FileDownloadResponse] = []
for path in paths:
content = self._sandbox.read(path)
responses.append(FileDownloadResponse(path=path, content=content, error=None))
return responses
def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
"""Upload multiple files to the LangSmith sandbox."""
responses: list[FileUploadResponse] = []
for path, content in files:
self._sandbox.write(path, content)
responses.append(FileUploadResponse(path=path, error=None))
return responses
class LangSmithProvider(SandboxProvider):
"""LangSmith sandbox provider implementation.
Manages LangSmith sandbox lifecycle using the LangSmith SDK.
"""
"""LangSmith sandbox provider implementation."""
def __init__(self, api_key: str | None = None) -> None:
from langsmith import sandbox
@ -237,7 +143,7 @@ class LangSmithProvider(SandboxProvider):
except Exception as e:
msg = f"Failed to connect to existing sandbox '{sandbox_id}': {e}"
raise RuntimeError(msg) from e
return LangSmithBackend(sandbox)
return LangSmithSandbox(sandbox)
resolved_template_name, resolved_image_name = self._resolve_template(
template, template_image
@ -253,7 +159,6 @@ class LangSmithProvider(SandboxProvider):
msg = f"Failed to create sandbox from template '{resolved_template_name}': {e}"
raise RuntimeError(msg) from e
# Verify sandbox is ready by polling
for _ in range(timeout // 2):
try:
result = sandbox.run("echo ready", timeout=5)
@ -268,7 +173,7 @@ class LangSmithProvider(SandboxProvider):
msg = f"LangSmith sandbox failed to start within {timeout} seconds"
raise RuntimeError(msg)
return LangSmithBackend(sandbox)
return LangSmithSandbox(sandbox)
def delete(self, *, sandbox_id: str, **kwargs: Any) -> None:
"""Delete a LangSmith sandbox."""
@ -285,7 +190,6 @@ class LangSmithProvider(SandboxProvider):
return DEFAULT_TEMPLATE_NAME, resolved_image
if isinstance(template, str):
return template, resolved_image
# SandboxTemplate object
if template_image is None and template.image:
resolved_image = template.image
return template.name, resolved_image

View file

@ -90,7 +90,7 @@ async def _clone_or_pull_repo_in_sandbox( # noqa: PLR0915
"""Clone a GitHub repo into the sandbox, or pull if it already exists.
Args:
sandbox_backend: The sandbox backend to execute commands in (LangSmithBackend)
sandbox_backend: The sandbox backend to execute commands in
owner: GitHub repo owner
repo: GitHub repo name
github_token: GitHub access token (from agent auth or env var)

View file

@ -6,7 +6,7 @@ readme = "README.md"
requires-python = ">=3.11"
license = { text = "MIT" }
dependencies = [
"deepagents>=0.4.12",
"deepagents>=0.5.0a4",
"fastapi>=0.104.0",
"uvicorn>=0.24.0",
"httpx>=0.25.0",

27
uv.lock generated
View file

@ -171,7 +171,7 @@ wheels = [
[[package]]
name = "anthropic"
version = "0.78.0"
version = "0.88.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@ -183,9 +183,9 @@ dependencies = [
{ name = "sniffio" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ec/51/32849a48f9b1cfe80a508fd269b20bd8f0b1357c70ba092890fde5a6a10b/anthropic-0.78.0.tar.gz", hash = "sha256:55fd978ab9b049c61857463f4c4e9e092b24f892519c6d8078cee1713d8af06e", size = 509136, upload-time = "2026-02-05T17:52:04.986Z" }
sdist = { url = "https://files.pythonhosted.org/packages/86/68/565f13059c0a6a6fd5f96f306f2a0fb478a0e1174ec18a4df16b5fac9379/anthropic-0.88.0.tar.gz", hash = "sha256:f4c7f6863d08c869913516f08d658fe53caaf8bcc4fbea3218df343d2a876c58", size = 596654, upload-time = "2026-04-01T19:59:05.287Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/03/2f50931a942e5e13f80e24d83406714672c57964be593fc046d81369335b/anthropic-0.78.0-py3-none-any.whl", hash = "sha256:2a9887d2e99d1b0f9fe08857a1e9fe5d2d4030455dbf9ac65aab052e2efaeac4", size = 405485, upload-time = "2026-02-05T17:52:03.674Z" },
{ url = "https://files.pythonhosted.org/packages/ad/ac/68f646998160c9f2e6f9353a31dd87292ef02b915b455aaf70a52a059a75/anthropic-0.88.0-py3-none-any.whl", hash = "sha256:71898b32332bc75d9739bc10095288d40a29605da6d00da2fe832b1aa036552f", size = 478338, upload-time = "2026-04-01T19:59:03.832Z" },
]
[[package]]
@ -629,18 +629,19 @@ wheels = [
[[package]]
name = "deepagents"
version = "0.4.12"
version = "0.5.0a4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain" },
{ name = "langchain-anthropic" },
{ name = "langchain-core" },
{ name = "langchain-google-genai" },
{ name = "langsmith" },
{ name = "wcmatch" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5a/ef/0b2ccd5e4f40c1554145de17a7f3ee41994de1dda3ea36abe28600f1a3cf/deepagents-0.4.12.tar.gz", hash = "sha256:fc24a691e5cba00920ac4fa1d94f8147d6081fe513ed22bdba7da469288681c3", size = 91870, upload-time = "2026-03-20T14:54:29.904Z" }
sdist = { url = "https://files.pythonhosted.org/packages/da/52/8fc1fc65fcfa34e54d79ba40cbf0205f68f81f97d716f12c51ef91e91f1f/deepagents-0.5.0a4.tar.gz", hash = "sha256:4f2993fffd7ac3092ac534e72362dca511d4a965fa4669da45502ec2c96cf580", size = 108345, upload-time = "2026-04-03T14:41:18.734Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/77/63a5cb4e3a8871c4a52d600661a232c26b35f52931ee551c3adc38eeacf6/deepagents-0.4.12-py3-none-any.whl", hash = "sha256:76a272bac25607c5ef8c5adc876e391da945f1107b504686964dfdb6afdc1ebb", size = 104455, upload-time = "2026-03-20T14:54:28.786Z" },
{ url = "https://files.pythonhosted.org/packages/ca/79/d997eefa2a2a46e02f64d4efe8321fff61bb8204fda99379c6451b8f1987/deepagents-0.5.0a4-py3-none-any.whl", hash = "sha256:c8d996209d0bb738b7ae02f90c4d4c8905276bcae6778aa26d383d16c5c46b5a", size = 121698, upload-time = "2026-04-03T14:41:17.745Z" },
]
[[package]]
@ -1271,16 +1272,16 @@ wheels = [
[[package]]
name = "langchain-anthropic"
version = "1.3.4"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anthropic" },
{ name = "langchain-core" },
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/30/4e/7c1ffac126f5e62b0b9066f331f91ae69361e73476fd3ca1b19f8d8a3cc3/langchain_anthropic-1.3.4.tar.gz", hash = "sha256:000ed4c2d6fb8842b4ffeed22a74a3e84f9e9bcb63638e4abbb4a1d8ffa07211", size = 671858, upload-time = "2026-02-24T13:54:01.738Z" }
sdist = { url = "https://files.pythonhosted.org/packages/98/c7/259d4d805c6ac90c8695714fc15498a4557bb515eb24f692fd611966e383/langchain_anthropic-1.4.0.tar.gz", hash = "sha256:bbf64e99f9149a34ba67813e9582b2160a0968de9e9f54f7ba8d1658f253c2e5", size = 674360, upload-time = "2026-03-17T18:42:20.751Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/cf/b7c7b7270efbb3db2edbf14b09ba9110a41628f3a85a11cae9527a35641c/langchain_anthropic-1.3.4-py3-none-any.whl", hash = "sha256:cd112dcc8049aef09f58b3c4338b2c9db5ee98105e08664954a4e40d8bf120b9", size = 47454, upload-time = "2026-02-24T13:54:00.53Z" },
{ url = "https://files.pythonhosted.org/packages/2e/c0/77f99373276d4f06c38a887ef6023f101cfc7ba3b2bf9af37064cdbadde5/langchain_anthropic-1.4.0-py3-none-any.whl", hash = "sha256:c84f55722336935f7574d5771598e674f3959fdca0b51de14c9788dbf52761be", size = 48463, upload-time = "2026-03-17T18:42:19.742Z" },
]
[[package]]
@ -1317,7 +1318,7 @@ wheels = [
[[package]]
name = "langchain-google-genai"
version = "4.2.0"
version = "4.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "filetype" },
@ -1325,9 +1326,9 @@ dependencies = [
{ name = "langchain-core" },
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d8/0b/eae2305e207574dc633983a8a82a745e0ede1bce1f3a9daff24d2341fadc/langchain_google_genai-4.2.0.tar.gz", hash = "sha256:9a8d9bfc35354983ed29079cefff53c3e7c9c2a44b6ba75cc8f13a0cf8b55c33", size = 277361, upload-time = "2026-01-13T20:41:17.63Z" }
sdist = { url = "https://files.pythonhosted.org/packages/14/63/e7d148f903cebfef50109da71378f411166f068d66f79b9e16a62dbacf41/langchain_google_genai-4.2.1.tar.gz", hash = "sha256:7f44487a0337535897e3bba9a1d6605d722629e034f757ffa8755af0aa85daa8", size = 278288, upload-time = "2026-02-19T19:29:19.416Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/22/51/39942c0083139652494bb354dddf0ed397703a4882302f7b48aeca531c96/langchain_google_genai-4.2.0-py3-none-any.whl", hash = "sha256:856041aaafceff65a4ef0d5acf5731f2db95229ff041132af011aec51e8279d9", size = 66452, upload-time = "2026-01-13T20:41:16.296Z" },
{ url = "https://files.pythonhosted.org/packages/ec/7e/46c5973bd8b10a5c4c8a77136cf536e658796380a17c740246074901b038/langchain_google_genai-4.2.1-py3-none-any.whl", hash = "sha256:a7735289cf94ca3a684d830e09196aac8f6e75e647e3a0a1c3c9dc534ceb985e", size = 66500, upload-time = "2026-02-19T19:29:18.002Z" },
]
[[package]]
@ -1812,7 +1813,7 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "cryptography", specifier = ">=41.0.0" },
{ name = "deepagents", specifier = ">=0.4.12" },
{ name = "deepagents", specifier = ">=0.5.0a4" },
{ name = "exa-py", specifier = ">=2.10.1" },
{ name = "fastapi", specifier = ">=0.104.0" },
{ name = "httpx", specifier = ">=0.25.0" },