Strona główna / Artykuły

Uruchamianie agentów Running Deep w lokalnym backendzie OpenSandbox

Jak połączyć OpenSandbox z Deep Agents za pośrednictwem BaseSandbox, skonfigurować synchroniczny i asynchroniczny kod, oraz bezpiecznie uruchomić agent do analizy danych w lokalnej przestrzeni sandbox Docker.

Wydawca: REACTAPP.TOP15 min czytania3403 słów

Źródła: Upgrade Your Deep Agent With a Local Open-Source Sandbox

Dlaczego agenci potrzebują bezpiecznego miejsca do uruchamiania kodu

Asystenci czatowi takie jak ChatGPT, Claude, Gemini i Qwen przyjmują pliki w formie obrazów, PDF-ów oraz arkuszy kalkulacyjnych. Wiele zapytań dotyczących tych plików, np. opisanie obrazu, streszczenie PDF-a lub wydobycie fragmentu z dokumentu, mieści się w naturalnych możliwościach modelu językowego. Jeśli jednak podasz mu plik .csv i poprosisz o pełną analizę statystyczną wraz z wykresami, zwykłe rozumowanie już nie wystarczy – konieczna jest faktyczna manipulacja danymi, wykonywanie obliczeń oraz tworzenie wykresów.

Model językowy sam nie potrafi wykonać tych obliczeń ani narysować wykresu, ale może napisać skrypt w Pythonie, który wykorzystuje biblioteki pandas, NumPy i Matplotlib do tego celu. Po uruchomieniu skryptu zadaniem modelu jest przekształcenie uzyskanych wyników w czytelną raportację. To właśnie narzędzia typu agentic umożliwiają wykonywanie takich skryptów.

Jednak bezpośrednie uruchamianie kodu napisanego przez AI na własnym komputerze to zła decyzja. Uruchamianie bez nadzoru stanowi poważne zagrożenie dla bezpieczeństwa i stabilności: może zmienić konfigurację systemu, usunąć kluczowe pliki lub ujawnić dane osobowe. Dlatego istnieją piaskownice – bezpieczne, izolowane środowiska, w których agent może uruchamiać swój kod bez dostępu do podstawowego systemu operacyjnego. Ten materiał koncentruje się na piaskownicach dostępnych w bibliotece Deep Agents Python.

Czym jest piaskownica i dlaczego agenci jej potrzebują

Tutaj „sandbox” oznacza zamykane środowisko, zwykle pojemnik, w którym kod jest wykonywany bez żadnego kontaktu z resztą maszyny. Jego system plików i procesy są oddzielne, a nic nie łączy się z plikami, danymi dostępowymi ani siecią hosta, chyba że dostęp został specjalnie udzielony. Programiści mogą to traktować jako wygodę, ale dla autonomicznego agenta jest to niemal wymóg, a powody są praktyczne:

  • To agent wybiera kod, a nie użytkownik. Nawet dobrze zachowujący się agent czasami usunie niewłaściwy plik lub wykona polecenie, którego nikt nie zamierzał, a bez izolacji szkody dotkną rzeczywistej maszyny.
  • Iniekcja poleceń jest rzeczywistością. Jeśli agent czyta niezaufany treść, taką jak strona internetowa lub plik od kogoś innego, sprytnie sformułowana ukryta instrukcja może próbować zmienić jego dalsze działanie. Sandbox ogranicza skutki uszkodzeń do tymczasowego kontenera.
  • Czyste środowiska pomagają. Każde zadanie jest łatwiejsze do zrozumienia, gdy rozpoczyna się w znanych, pustych warunkach i nie odziedzicza stanu z poprzednich wykonywań.
  • Agenci równoległe. Dzięki izolacji pip install jednego agenta nie może kolidować z tym drugiego, a awaria sandboxu nie wpłynie na cały proces.
  • Limity zasobów. Agent w nieskończonej pętli lub taki, który dwukrotnie ładuje cały zbiór danych do pamięci, powinien zużywać zasoby tymczasowego kontenera, a nie twojego komputera.

Dlatego właśnie frameworki przeznaczone do tworzenia agentów, w tym Deep Agents, traktują sandbox jako koncepcję pierwszorzędną. Deep Agents dostarcza abstrakcję o nazwie BaseSandbox oraz oficjalne integracje z kilkoma dostawcami chmurowych sandboxów. Wskazując agentowi jeden z nich, otrzymuje on rzeczywiste, izolowane środowisko do uruchamiania kodu.

Wszyscy ci dostawcy są hostowani w chmurze i pobierane są od nich opłaty za minutę lub w zależności od ilości używanych zasobów. To nadaje się do środowiska produkcyjnego, ale jest to więcej, niż potrzebne do lokalnego budowania, testowania lub demonstracji agenta.

OpenSandbox jako opcja lokalna

OpenSandbox to bezpłatny, otwarty sandbox działający w Twoim własnym Dockerze. Oferuje te same podstawowe funkcje co wersje płatne:

  • Rozpoczęcie izolowanego kontenera.
  • Uruchamianie w nim poleceń.
  • Czytanie i zapisywanie plików w nim.
  • Zniszczenie go później.

Zainstaluj lokalnie serwer warstwy sterującej, skieruj go na Docker, a pojawi się lokalny punkt końcowy, który tworzy i zarządza sandboxami na żądanie. Nikt nie sprzedaje go jako produktu hostowanego. To infrastruktura, którą sam zarządzasz, nadająca się do lokalnego rozwoju, testowania oraz prezentacji takich jak ta. Pozostaje już tylko połączenie jej z Deep Agents, które nie zawiera integracji z OpenSandbox.

Umowa BaseSandbox

BaseSandbox to klasa abstrakcyjna, a integracja sprowadza się do czterech jej elementów.

class BaseSandbox(ABC):
    def execute(self, command: str, *, timeout: int | None = None) -> ExecuteResponse: ...
    @property
    def id(self) -> str: ...
    def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]: ...
    def download_files(self, paths: list[str]) -> list[FileDownloadResponse]: ...
  1. execute uruchamia polecenie w sandboxzie i zwraca ExecuteResponse.
  2. id to unikalny identyfikator dla backendu sandboxu.
  3. upload_files wysyła kilka plików do sandboxu.
  4. download_files pobiera kilka plików z niego.

Gdy te cztery elementy już istnieją, Deep Agents sam tworzy funkcje ls, read_file, write_file, edit_file, glob oraz grep na bazie execute(). Nie trzeba już nic więcej dla nich programować. To jest pełna powierzchnia integracji, co oznacza, że kwalifikuje się każdy sandbox zdolny do uruchamiania poleceń w shellu oraz przenoszenia plików do środka i na zewnątrz. Schemat wydaje się prosty, dopóki nie rozpocznie się rzeczywiste programowanie i nie pojawią się błędy.

Jak działa OpenSandbox

OpenSandbox wykorzystuje kontenery Docker do tworzenia i zarządzania środowiskami testowymi. Jego interfejs SDK w języku Python umożliwia tworzenie i zarządzanie takimi środowiskami, wykonywanie poleceń w wierszu poleceń, obsługę plików oraz monitorowanie zasobów. Aby to uruchomić, konieczne jest zainstalowane i działające Docker, a także sam serwer:

# Generate a starter config
uvx opensandbox-server init-config ~/.sandbox.toml --example docker

# Start the server
uvx opensandbox-server

Następnie SDK łączy się z uruchomionym serwerem i udostępnia wszystko, co może robić OpenSandbox.

Klasa integracyjna

To szkielet, który należy wypełnić, kierując się abstrakcją BaseSandbox. Metody dodawane są po jednej.

create()

class MinimalOpenSandboxBackend(BaseSandbox):
    def __init__(self, sandbox: Sandbox, runner: AsyncRunner):
        self._sandbox = sandbox
        self._runner = runner

__init__ jest prosty. Zawiera dwa obiekty, od których zależy wszystko inne: żywe połączenie Sandbox z serwerem oraz AsyncRunner, który je obsługuje. Żaden z nich nie jest tworzony ręcznie, ponieważ oba powstają za pomocą metody fabrycznej. Klasa ta ma również więcej metod niż wymaga to BaseSandbox – w szczególności metodę klasową .create(). Powód staje się jaśniejszy, gdy zobaczymy, w jaki sposób obiekt sandbox powstaje w rzeczywistości.

Połączenie Sandbox oraz tło wykonawcze muszą istnieć przed tym, jak __init__ będzie mógł je wykorzystać, i dlatego create() stanowi odrębny krok. Oto kod.

from opensandbox import Sandbox
from opensandbox.config import ConnectionConfig

IMAGE = "opensandbox/code-interpreter:v1.1.0"
ENTRYPOINT = ["/opt/code-interpreter/code-interpreter.sh"]

@classmethod
def create(cls, api_key: str | None = None) -> "MinimalOpenSandboxBackend":
    runner = AsyncRunner()
    config = ConnectionConfig(domain="localhost:8080", api_key=api_key)
    sandbox = runner.run(
        Sandbox.create(IMAGE, entrypoint=ENTRYPOINT, connection_config=config, timeout=timedelta(minutes=30))
    )
    return cls(sandbox, runner)

Dwa elementy wyróżniają się. domain="localhost:8080" łączy to z serwerem uruchomionym wcześniej za pomocą uvx opensandbox-server. timeout=timedelta(minutes=30) pełni rolę zabezpieczenia: nawet jeśli zapomnisz wywołać kill(), sandbox i tak zostanie zakończony, zamiast pozostać w tle. Zwróć również uwagę na IMAGE i ENTRYPOINT – muszą być kompatybilne z używaną wersją OpenSandbox.

O wykonywaczu: Sandbox.create jest koretą, jednak BaseSandbox.create musi zwrócić zwykły obiekt zamiast czegoś, na co trzeba czekać. Dopóki nie zostaną ujawnione szczegóły, traktuj runner.run(coro) jako „poczekaj, aż ta asynchroniczna funkcja się zakończy, a następnie daj mi to, co zwróciła”. To wyjaśnia, dlaczego Sandbox.create(...) jest przekazywane do runner.run(...), zamiast być bezpośrednio oczekiwane. Sposób działania AsyncRunner zostanie omówiony nieco później.

id

Proces tworzenia został zakończony, więc pozostałe metody obsługują już działające środowisko sandbox.

@property
def id(self) -> str:
    return self._sandbox.id

Zwraca identyfikator podstawowej przestrzeni sandbox. BaseSandbox wymaga go jako właściwości, a jest on przydatny w logach.

execute()

from deepagents.backends.protocol import ExecuteResponse

def execute(self, command: str, *, timeout: int | None = None) -> ExecuteResponse:
    execution = self._runner.run(self._sandbox.commands.run(command))
    stdout = "\n".join(c.text for c in execution.logs.stdout)
    stderr = "\n".join(c.text for c in execution.logs.stderr)
    output = "\n".join(p for p in (stdout, stderr) if p)
    return ExecuteResponse(output=output, exit_code=execution.exit_code or 0)

Wydaje się proste, ale w rzeczywistości nie. OpenSandbox dostarcza wyniki poleceń w postaci listy fragmentów, po jednym na linii, przy czym znak nowej linii jest już usunięty. Dlatego odpowiedź musi zostać prawidłowo odbudowana, aby agent mógł nią później zarządzać. Funkcja execute() również nie jest wywoływana bezpośrednio – ls, glob i grep w Deep Agentsach opierają się na niej i czytają wyniki linijka po linijce. Jeśli dostaną nieprawidłową odpowiedź, nic nie znajdą i cicho zwrócą puste wyniki. ls zwraca [], nie ma żadnego śladu błędu wskazującego na błędny join, a sandbox wydaje się pusty, mimo że tak nie jest.

Ma to większe znaczenie, niż się wydaje, ponieważ execute() służy nie tylko do bezpośredniego użycia. Jest podstawą dla ls, glob i grep w Deep Agents, a wszystkie one analizują wyniki linijka po linijce. Źle sformatowana odpowiedź sprawia, że nic nie zostanie dopasowane i funkcje te w milczeniu zwracają puste wyniki. ls wtedy zwraca [] bez żadnych informacji o błędzie wskazujących na problematyczną funkcję join, a sandbox wygląda na pusty, mimo że tak nie jest.

Pozostałe kwestie są prostsze: stdout i stderr są łączone, pomijając ten z pustymi danymi, a brak kodu wyjścia jest traktowany jako sukces (0) zamiast pozostać na poziomie None.

upload_files()

from opensandbox.models import WriteEntry
from deepagents.backends.protocol import FileUploadResponse

def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
    entries = [WriteEntry(path=path, data=data, mode=644) for path, data in files]
    try:
        self._runner.run(self._sandbox.files.write_files(entries))
        return [FileUploadResponse(path=p) for p, _ in files]
    except Exception as exc:
        return [FileUploadResponse(path=p, error=str(exc)) for p, _ in files]

Każda para (path, bytes) jest umieszczana w obiektu WriteEntry z zwykłymi uprawnieniami (właściciel może czytać i pisać, inni mogą tylko czytać), a cała partia jest wysyłana za pomocą jednej wywołania write_files().

download_files()

from deepagents.backends.protocol import FileDownloadResponse

def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
    results = []
    for path in paths:
        try:
            content = self._runner.run(self._sandbox.files.read_bytes(path))
            results.append(FileDownloadResponse(path=path, content=content))
        except Exception as exc:
            results.append(FileDownloadResponse(path=path, error=str(exc)))
    return results

Decydującym elementem jest użycie read_bytes zamiast read_file. SDK oferuje obie opcje, ale read_file zakłada tekst w formacie UTF-8. Sandbox uruchamiający kod LLM nie może wiedzieć, czy następny żądany plik to plik CSV, czy świeżo wygenerowana wykres, dlatego read_bytes jest jedynym bezpiecznym wyborem w obu przypadkach.

kill()

def kill(self) -> None:
    self._runner.run(self._sandbox.kill())
    self._runner.shutdown()

Procedura rozbiórki wykonuje dwie ważne czynności. Najpierw zamyka kontener sandbox, a następnie wyraźnie wyłącza wątek w tle uruchamiacza. Jeśli pominąć drugi krok, wątek wraz z pętlą zdarzeń w jego wnętrzu pozostaje aktywny nawet po zniknięciu sandboxu.

To jest cała klasa: dwa metody cyklu życia (create, kill) otaczające cztery wymagane przez BaseSandbox metody (id, execute, upload_files, download_files).

Most synchroniczny/asynchroniczny

Jedna kwestia pozostała niewyjaśniona: self._runner.run(...) – to właśnie on sprawia, że każda asynchroniczna wywołanie SDK wygląda dla BaseSandbox jak synchroniczna.

BaseSandbox jest synchroniczny. Funkcje execute(), upload_files() oraz download_files() to zwykłe wywołania funkcji bez użycia await. SDK OpenSandbox natomiast jest asynchroniczne: Sandbox.create(), sandbox.commands.run() oraz sandbox.files.write_files() to wszystko koretyny. AsyncRunner został stworzony po to, aby zamknąć tę lukę.

Podejście jest proste. Tworzony jest jeden pętla zdarzeń, przekazywany on jest własnemu wątkowi w tle i utrzymywany w aktywności tak długo, jak długo istnieje sandbox. Wszystko inne wykorzystuje jedynie funkcję run(), która przesyła koretynę do tej pętli za pomocą run_coroutine_threadsafe, blokuje kod wywołujący aż do jej zakończenia i zwraca wynik. W przypadku funkcji execute() wyrażenie self._runner.run(some_coroutine) wygląda dokładnie jak zwykłe wywołanie funkcji.

Jedna długo działająca pętla, zamiast nowej przy każdym wywołaniu, pozwala również połączeniu funkcjonować jako jedna ciągła sesja, a nie jako coś, co jest rozbierane i odbudowywane przy każdym wywołaniu execute(). Funkcja shutdown() zajmuje się sprzątaniem poprzez zatrzymanie pętli i czyste zakończenie wątku, więc kill() po prostu ją wywołuje.

Agent do analizy danych w sandboxzie

Aby przetestować integrację, notatnik Jupyter rozpoczyna od ustawienia zmiennych środowiskowych i stałych, importowania pakietów, a co najważniejsze – tworzenia obiektu MinimalOpenSandboxBackend. Wymaga to klucza API, który sam musisz stworzyć i umieścić w pliku ~/.sandbox.toml.

import asyncio
import nest_asyncio
import threading
from datetime import timedelta
from pathlib import Path

from deepagents import create_deep_agent
from deepagents.backends.protocol import ExecuteResponse, FileDownloadResponse, FileUploadResponse
from deepagents.backends.sandbox import BaseSandbox
from langchain.chat_models import init_chat_model

from opensandbox import Sandbox
from opensandbox.config import ConnectionConfig
from opensandbox.models import WriteEntry

# nest_asyncio for running async functions in Jupyter.
nest_asyncio.apply()

IMAGE = "opensandbox/code-interpreter:v1.1.0"
ENTRYPOINT = ["/opt/code-interpreter/code-interpreter.sh"]

backend = MinimalOpenSandboxBackend.create(api_key="SANDBOX_API_KEY")
print("Sandbox ready:", backend.id)

Następnie mamy model do czatowania. Modeli lokalne za pośrednictwem Ollamy również mogłyby zadziałać, ale model musi nadal radzić sobie z zadaniami typu agentic. W tym przypadku użyto modelu Gemini Flash.

llm = init_chat_model(
    model="gemini-3.5-flash",
    model_provider="google_genai",
    api_key=os.environ["GOOGLE_API_KEY"],
    max_tokens=14750,
    max_retries=5,
)

Następnie sam agent.

agent = create_deep_agent(
    model=llm,
    system_prompt=(
        "You are a Python coding assistant with sandbox access. "
        "You specialize in performing data analysis and data visualization with python,"
        "you generate clear reports with good looking charts using seaborn."
    ),
    backend=backend,
)

Wszystko, co zostało dotychczas zbudowane, znajduje odzwierciedlenie w argumencie backend=backend. Na podstawie tego jednego obiektu Deep Agents generuje funkcje ls, read_file, write_file, glob oraz grep sam, wykorzystując do tego celu specjalną funkcję execute() jako bazę.

Następnie zostają załadowane rzeczywiste dane, bezpośrednio za pomocą funkcji upload_files(). Zbiór danych to fałszywy zbiór obejmujący 1000 klientów.

csv_bytes = Path("customers-1000.csv").read_bytes()
results = backend.upload_files([("/workspace/customers-1000.csv", csv_bytes)])
for r in results:
    if r.error:
        print(f"Upload failed for {r.path}: {r.error}")
    else:
        print(f"Uploaded {r.path}")

Następnie agent otrzymuje rzeczywiste zadanie.

result = agent.invoke({
    "messages": "Perform a deep exploratory data analysis on the customers-1000.csv file "
                "and summarize the findings in a markdown report with clear charts."
})

Na tym etapie agent działa samodzielnie. Dzięki swoim narzędziom i infrastrukturze może pisać kod w pandas i seaborn, uruchamiać go w sandboxzie za pomocą execute(), przechowywać wykresy w systemie plików sandboxu i kontynuować iteracje, aż powstanie kompletny raport w formacie markdown.

Pozostaje jeden problem: część raportu znajduje się wewnątrz sandboxu. Jego plik markdown odwołuje się do wykresów za pomocą lokalnych ścieżek sandboxu, które nie istnieją na serwerze hostowym. Aby uczynić go czytelnym, konieczne są dwa kroki: pobranie obrazków wykresów za pomocą download_files() oraz przepisanie ścieżek obrazków w pliku markdown tak, aby wskazywały one na lokalne kopie. Mniej problematycznym podejściem jest zwracanie przez agenta ustrukturyzowanych wyników, na przykład pola z raportem oraz wyraźnej listy ścieżek do obrazków, zamiast wyszukiwania linków w swobodnie skonstruowanym tekście markdown. Postprocesing polega wtedy jedynie na pobieraniu każdej wymienionej ścieżki i jej zastąpieniu.

Po tym oto co sandbox wygenerował na podstawie surowego pliku CSV.

# Deep Exploratory Data Analysis: Customer Acquisition and Profiling
**Dataset:** `customers-1000.csv`
**Analysis Period:** Jan 2020 – May 2022

---

## 1. Executive Summary

This report presents a comprehensive exploratory data analysis (EDA) of a customer database containing 1,000 unique records. The analysis delves into geographical distributions, sign-up temporal trends, domain & technical alignments, and name demographics to uncover actionable insights for strategic growth.

### Key Takeaways
1. **Unprecedented Global Reach:** The customer base is extraordinarily decentralized, spanning **240 countries** across all **7 continents** (including Antarctica). No single country represents more than 1.2% of the customer base. Africa (24.7%) and Asia (22.6%) are the leading regions, followed by Europe (18.5%) and North America (16.1%).
2. **Stable Acquisition Trends:** Customer subscriptions are remarkably stable, averaging roughly **34-35 new customers per month** across 2020 and 2021. This consistency is maintained across all continents year-over-year, indicating a highly standardized, globally distributed customer acquisition channel.
3. **Mid-Week and Weekend Consistency:** Subscriptions are evenly spread across the days of the week, with a minor peak on Friday and Saturday, and a minor trough on Thursday.
4. **B2B / Synthetic Profile Characteristics:** The dataset shows zero domain overlap between customer email domains and company websites (0.0% exact match across 923 unique domains). Combined with the near 1-to-1 ratio of customers to companies, this suggests a highly B2B-centric profile (one representative per enterprise) or synthetically generated profiles with randomized fields.
5. **Standardized TLD Footprint:** The `.com` top-level domain (TLD) dominates both emails (61.2%) and corporate websites (58.8%). The remaining distribution is evenly split among `.org`, `.net`, `.biz`, and `.info`.

---

## 2. Dataset Structure & Data Integrity

The initial dataset contains **1,000 rows** and **12 columns**. An inspection of data integrity reveals excellent completeness:
- **Zero Missing Values:** Every column is 100% populated.
- **Zero Duplicates:** There are no duplicate rows, and the `Customer Id` column contains 1,000 unique identifiers.
- **Data Types:** All columns are stored as object/string types except for `Index` (integer).

### Data Preprocessing & Feature Engineering
To enable deep exploratory analysis, several features were engineered:
1. **Temporal Features:** `Subscription Date` was parsed as a datetime object, allowing the extraction of `Sub_Year`, `Sub_Month`, `Sub_Month_Name`, `Sub_Day_of_Week`, and `Sub_Year_Month` (period).
2. **Geographical Mapping:** Using the `pycountry` and `pycountry-convert` libraries, coupled with a manual fallback dictionary for territories, each of the 240 countries was successfully mapped to its respective **Continent**.
3. **Domain & Technical Profiles:** Email domains (`Email_Domain`), email TLDs (`Email_TLD`), and website TLDs (`Website_TLD`) were extracted to analyze the technical profiling of users.

---

## 3. Geographical Analysis

### Continent-Level Distribution
The geographic reach of this customer base is truly global. Rather than being concentrated in a single dominant market like North America or Europe, customers are spread across all continents:

| Continent | Customer Count | Percentage |
| :--- | :---: | :---: |
| **Africa** | 247 | 24.7% |
| **Asia** | 226 | 22.6% |
| **Europe** | 185 | 18.5% |
| **North America** | 161 | 16.1% |
| **Oceania** | 107 | 10.7% |
| **South America** | 54 | 5.4% |
| **Antarctica** | 20 | 2.0% |

#### Chart 1: Customer Distribution by Continent
![Customer Distribution by Continent](downloads/customer_by_continent.png)

### Country-Level Distribution (Top 15 Countries)
The country-level distribution exhibits a heavy tail, with the 1,000 customers distributed across **240 distinct nations**. This indicates that the average number of customers per country is only **4.17**.

The top countries by customer density are:
- **Liechtenstein:** 12 customers (1.2%)
- **Gabon:** 10 customers (1.0%)
- **China, Bangladesh, Reunion, Nigeria, Luxembourg:** 9 customers each (0.9%)

This extreme dispersion suggests a borderless, digital-first product that appeals universally across jurisdictions without localized geographic bias.

#### Chart 2: Top 15 Countries by Customer Count
![Top 15 Countries by Customer Count](downloads/top_15_countries.png)

---

## 4. Temporal Analysis (Subscription Trends)

... (Trimmed to keep blog (estimated read time short))

Zbliżenie na wygenerowany raport:

Od notatnika do pakietu PyPI

Notatnik pokazuje, jak wszystko się łączy, ale pakiet, który każdy może zainstalować za pomocą pip, jest bardziej przydatny. Stworzenie deepagents_opensandbox_backend oznaczało naprawę wszystkiego, co zawodziło dopiero wtedy, gdy inni ludzie uruchamiali ten kod w środowiskach, których autor nigdy nie testował. Publiczna część pozostała bez zmian – te same metody BaseSandbox oraz te same funkcje create() i kill(). Zmiany zaszły wewnątrz:

  • Jeden wspólny pętla. Tła teraz dzielą się jednym modułem _BackgroundLoop i nie uruchamiają już osobnych wątków.
  • Standardowe ostrzeżenie bezpieczeństwa. API nie wymaga autoryzacji, więc funkcja create() teraz wywołuje błąd InsecureSandboxWarning, gdy nie podano api_key, podczas gdy wcześniej łączyło się bez żadnych komunikatów.
  • Rozwiązanie dla Docker Desktop. W środowisku Docker Desktop bezpośrednie połączenie z portem kontenera często kończy się timeoutem. Ustawienie use_server_proxy=True kieruje ruch przez serwer, co pomaga uniknąć tego problemu.
  • Walidacja ścieżek. Funkcje upload_files i download_files od razu odrzucają ścieżki względne, dzięki czemu serwer nie może w tajemnicy przekierować ich do niewłaściwych katalogów.
  • Jasniejsze błędy. Klient sprawdza informacje o pliku, aby odróżnić „brak uprawnień” od „plik nie został znaleziony” i przyporządkowuje każdy z nich odpowiedniemu kodowi błędu, zamiast przekazywać mylące, surowe błędy.
  • Funkcjonalne limity czasowe. Argument timeout wcześniej był ignorowany. Teraz jest przekazywany serwerowi w celu zatrzymania niekontrolowanych poleceń, zamiast pozostawiać je bez opieki po rezygnacji klienta.
  • Usunięte ręcznie napisane metody asynchroniczne. Prawdziwe wersje asynchroniczne, takie jak aexecute, powodowały ciche, puste wyniki w elementach ToolNode LangGraph, dlatego zostały usunięte, a kod teraz korzysta z normalnych metod synchronicznych uruchamianych w puli wątków.

To rozdzielenie oznacza różnicę pomiędzy wersją demonstracyjną działającą na jednym laptopie a pakietem gotowym do zainstalowania za pomocą polecenia pip install przez każdego. Dołączony jest zestaw testów zgodności, który jest wykonywany na podstawie standardowych testów w środowisku sandbox dostarczanym przez sam Deep Agents, a proces instalacji wygląda następująco:

!pip install deepagents-opensandbox-backend

To ta sama integracja opisana powyżej, z tylko bardziej zaokrąglonymi krawędziami.

Podobne artykuły

  • AgentSRE: Agenci Composable AI do autonomicznego naprawiania incydentów w AWS — Jak platforma AWS przekształca incydent wykryty przez alarm CloudWatch w rozwiązanie za pomocą agenta DevOps, agentów naprawczych LangGraph, zatwierdzenia przez człowieka oraz ticketów w ServiceNow.
  • Co schemat nie może powiedzieć twojemu agentowi: Format otwartej wiedzy w BigQuery — Eksperyment z BigQuery i ADK pokazujący, jak pakiety wiedzy w formacie Markdown w Git stabilizowały odpowiedzi agenta i naprawiły błąd GMV o 29%.
  • CrewAI, AutoGen, LangGraph, OpenAI Agents SDK i smolagents w porównaniu — Porównanie pięciu frameworki agentów open-source pod kątem orkiestracji, pamięci, narzędzi, ocen i możliwości obserwacji, wraz z często występującymi wzorcami awarii.