mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-04 18:22:10 +00:00
feat: add configurable sandbox template and fix sandbox lifecycle
This commit is contained in:
parent
db2a755463
commit
841aa536f5
1 changed files with 68 additions and 18 deletions
|
|
@ -28,14 +28,10 @@ import asyncio
|
||||||
warnings.filterwarnings("ignore", message=".*Pydantic V1.*", category=UserWarning)
|
warnings.filterwarnings("ignore", message=".*Pydantic V1.*", category=UserWarning)
|
||||||
|
|
||||||
# Now safe to import agent (which imports LangChain modules)
|
# Now safe to import agent (which imports LangChain modules)
|
||||||
# Async wrapper for create_sandbox
|
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
|
|
||||||
from deepagents import create_deep_agent
|
from deepagents import create_deep_agent
|
||||||
from deepagents.backends.sandbox import SandboxBackendProtocol
|
from deepagents.backends.sandbox import SandboxBackendProtocol
|
||||||
from deepagents_cli.agent import get_system_prompt
|
from deepagents_cli.agent import get_system_prompt
|
||||||
from deepagents_cli.config import config, settings
|
from deepagents_cli.config import config, settings
|
||||||
from deepagents_cli.integrations.sandbox_factory import create_sandbox
|
|
||||||
from deepagents_cli.tools import fetch_url, http_request, web_search
|
from deepagents_cli.tools import fetch_url, http_request, web_search
|
||||||
|
|
||||||
# Local import for encryption
|
# Local import for encryption
|
||||||
|
|
@ -51,14 +47,45 @@ def _get_langsmith_api_key() -> str | None:
|
||||||
return os.environ.get("LANGSMITH_API_KEY") or os.environ.get("LANGSMITH_API_KEY_PROD")
|
return os.environ.get("LANGSMITH_API_KEY") or os.environ.get("LANGSMITH_API_KEY_PROD")
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
def _get_sandbox_template_config() -> tuple[str | None, str | None]:
|
||||||
async def create_sandbox_async(provider: str, **kwargs):
|
"""Get sandbox template configuration from environment.
|
||||||
"""Async wrapper around create_sandbox for compatibility."""
|
|
||||||
# For langsmith provider, pass the API key explicitly
|
Returns:
|
||||||
if provider == "langsmith" and "api_key" not in kwargs:
|
Tuple of (template_name, template_image) from environment variables.
|
||||||
kwargs["api_key"] = _get_langsmith_api_key()
|
Values are None if not set in environment.
|
||||||
with create_sandbox(provider, **kwargs) as sandbox:
|
"""
|
||||||
yield sandbox
|
template_name = os.environ.get("DEFAULT_SANDBOX_TEMPLATE_NAME")
|
||||||
|
template_image = os.environ.get("DEFAULT_SANDBOX_TEMPLATE_IMAGE")
|
||||||
|
return template_name, template_image
|
||||||
|
|
||||||
|
|
||||||
|
def _create_langsmith_sandbox(
|
||||||
|
sandbox_id: str | None = None,
|
||||||
|
) -> 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.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SandboxBackendProtocol instance
|
||||||
|
"""
|
||||||
|
from deepagents_cli.integrations.langsmith import LangSmithProvider
|
||||||
|
|
||||||
|
api_key = _get_langsmith_api_key()
|
||||||
|
template_name, template_image = _get_sandbox_template_config()
|
||||||
|
|
||||||
|
provider = LangSmithProvider(api_key=api_key)
|
||||||
|
return provider.get_or_create(
|
||||||
|
sandbox_id=sandbox_id,
|
||||||
|
template=template_name,
|
||||||
|
template_image=template_image,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_server_agent(
|
def create_server_agent(
|
||||||
|
|
@ -876,8 +903,8 @@ async def get_agent(config: RunnableConfig) -> Pregel: # noqa: PLR0915
|
||||||
await client.threads.update(thread_id=thread_id, metadata={"sandbox_id": SANDBOX_CREATING})
|
await client.threads.update(thread_id=thread_id, metadata={"sandbox_id": SANDBOX_CREATING})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sandbox_cm = create_sandbox_async("langsmith")
|
# Create sandbox without context manager cleanup (sandbox persists)
|
||||||
sandbox_backend = await sandbox_cm.__aenter__()
|
sandbox_backend = await asyncio.to_thread(_create_langsmith_sandbox)
|
||||||
logger.info("Sandbox created: %s", sandbox_backend.id)
|
logger.info("Sandbox created: %s", sandbox_backend.id)
|
||||||
|
|
||||||
# Update metadata immediately after sandbox creation so other callers
|
# Update metadata immediately after sandbox creation so other callers
|
||||||
|
|
@ -910,12 +937,35 @@ async def get_agent(config: RunnableConfig) -> Pregel: # noqa: PLR0915
|
||||||
else:
|
else:
|
||||||
logger.info("Connecting to existing sandbox %s", sandbox_id)
|
logger.info("Connecting to existing sandbox %s", sandbox_id)
|
||||||
try:
|
try:
|
||||||
sandbox_cm = create_sandbox_async("langsmith", sandbox_id=sandbox_id)
|
# Connect to existing sandbox without context manager cleanup
|
||||||
sandbox_backend = await sandbox_cm.__aenter__()
|
sandbox_backend = await asyncio.to_thread(
|
||||||
|
_create_langsmith_sandbox, sandbox_id
|
||||||
|
)
|
||||||
logger.info("Connected to existing sandbox %s", sandbox_id)
|
logger.info("Connected to existing sandbox %s", sandbox_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Failed to connect to existing sandbox %s", sandbox_id)
|
logger.warning(
|
||||||
raise
|
"Failed to connect to existing sandbox %s, creating new one", sandbox_id
|
||||||
|
)
|
||||||
|
# Reset sandbox_id and create a new sandbox
|
||||||
|
await client.threads.update(
|
||||||
|
thread_id=thread_id,
|
||||||
|
metadata={"sandbox_id": SANDBOX_CREATING},
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sandbox_backend = await asyncio.to_thread(_create_langsmith_sandbox)
|
||||||
|
logger.info("New sandbox created: %s", sandbox_backend.id)
|
||||||
|
|
||||||
|
await client.threads.update(
|
||||||
|
thread_id=thread_id,
|
||||||
|
metadata={"sandbox_id": sandbox_backend.id},
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to create replacement sandbox")
|
||||||
|
await client.threads.update(
|
||||||
|
thread_id=thread_id, metadata={"sandbox_id": None}
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
thread = await client.threads.get(thread_id=thread_id)
|
thread = await client.threads.get(thread_id=thread_id)
|
||||||
repo_dir = thread.get("metadata", {}).get("repo_dir")
|
repo_dir = thread.get("metadata", {}).get("repo_dir")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue