mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-01 13:13:14 +00:00
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:
parent
d3506598fe
commit
24a6343352
6 changed files with 30 additions and 123 deletions
|
|
@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
27
uv.lock
generated
|
|
@ -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" },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue