Галоўная / Артыкулы

Deep Agents у лакальнай пясочніцы OpenSandbox

Як падключыць OpenSandbox да Deep Agents праз BaseSandbox, звязаць сінхронны і асінхронны код і бяспечна запусціць агента аналізу даных у лакальнай Docker-пясочніцы.

Выдавец: REACTAPP.TOP15 хв чытання3403 слоў

Крыніцы: Upgrade Your Deep Agent With a Local Open-Source Sandbox

Чаму агентам патрэбна безпечная атмосфера для запуску коду

Асістэнты для чату, такія як ChatGPT, Claude, Gemini і Qwen, прымоюць завантажаныя файлы — зображэнні, PDF-файлы і табліцы. Большасць запітоў да гэтых файлоў, напрыклад, апісанне картыны, стварэнне статыстычнага падсумку з PDF-файлу або выкарыстоўванне часткі тэксту з дакумента, належыць да стандартных можлівасцей мовных модэляў. Аднак як толькі да яго падае файл .csv і прасіць пра повную статыстычную аналіз з графікамі, проста логіка вядомае стае недастатней. Для гэтага патрэбна рэальная апрацоўка дадзеных, выконанне вычыслаў і стварэнне графікаў.

Мовны мадэль сама не можа выконваць гэтыя вычысленні чы рэсэрваваць, але можа напісаць скрыпт на Python, які выкарыстоўвае бібліятэкі pandas, NumPy і Matplotlib для гэтага. Пасля запуску скрыпта заставаемая задача модэля — ператворыць рэзультаты на чытальны атлас. Самэ агентскія інструменты дазволяюць запускаты такія скрыпты.

Адзінакоўна, запуск коду, напісанага сяброўскімі ІІ, безпосерэдні на вашай сабе машыне — гэта паслабыя ідэя. Запуск без нагляду ўтварае серьёзную апасцерогу для безпекі і стабільнасці: гэта можа змяніць настройкі сістэмы, выдаліць важлівыя файлы або абкрасіць персональныя даны. Таму і існуюць сэндбоксы — захішчаныя ізольаваныя асяроддзя, дзе агент можа запускаць свой код без доступу да базовай операцыйной сістэмы. У этым матэрыяле рассматрываюцца сэндбоксы для бібліятэкі Deep Agents Python.

Што такое сэндбокс і чаму агентам яго патрэбны

Хутчэй, «пісочніця» — гэта закрыта асяроддзе, зазвычай кантэйнер, у яком код запускаецца без жадных контактаў з рэштой апаратуры. Яго файлавая сістэма і працэсы ўсё там, і ніч не вяло да файлаў, пароляў чы сеткі хоста, як толькі не будзе спецыяльна надана дазвола. Распрацоўшчыкі можаць спрыяць гэта як зручнасць, але для автонамнага агента гэта практычна неабходнасць, і прычыны ў гэтым практычныя:

  • Агент сам выбірае код, а не выкарыстоўвальнік. Нават агент, які правільна працуе, іноды можа выдаліць неправільны файл чы запусціць каманду, якой ніхто не планаваў, і без ізоляціі нашкоджэння паўтарыцца на рэальнай апаратуры.
  • Ін’екцыя запытку ў рэальнасці існуе. Калі агент чытае ненадзеяны кантэнт, такі як веб-стораніцу або файл ад каго-небудзь іншага, хутка сформульаваная супакрытая інструкцыя можа спробаваць змяніць яго дальнейшыя дзеянні. Сандбокс обмежвае парадксы, якія можа спрычыніць агент, у врэменным кантэйнерэ.
  • Чыстыя сераўныя умовы дапамагаюць. Кожны заданні легча аналізаваць, калі ён пачынаецца ў вядомай, порожней асяроддзі і не успадковуе стан з пакульніх запускаў.
  • Паралельныя агенты. Дзякуючы ізоляцыі pip install аднаго агента не можа стварыць суперскупніцтва з аналогічным камандам іншага, а зламаны сандбокс не будзе пашкодзіць цэлы працэс.
  • Ліміты рысаў. Агент, які застраг у бесканцовым цыкле, або той, які два разы завантажае весь набор дадзеных у памяць, павінен выкарыстоўваць рысы врэменнага кантэйнера, а не рысы вашага ПК.
  • Самэтна прычына, чаму фрэймворкі, прызначаныя для стварэння агентаў, укладзеных у канцэпцыю Deep Agents, спрыяюць выкарыстоўванню сэндбокса як ключовага элемента. Deep Agents аддае абстракцыю пад назвай BaseSandbox, а таксама офіцыйныя інтэграцыі з калькама прадаўцоў хмарных сэндбоксаў. Як толькі агент праб’юеся да аднаго з іх, ён отрымае рэальнае, ізольаванае месца для запуску коду.

    Усі этыя прадаўцы розмешаны ў воблаку і беруць плата за хвіліну або за розмер выкарыстоўвання. Гэта падходзіць для працы ў працэсе віробніцтва, але ўсё гэта занадта для стварэння, тэставання або дэманстрацыі агента локальна.

    OpenSandbox як локальная альтэратыва

    OpenSandbox — гэта бесплатны, адкрытыя коду сэндбокс-рантайм, які працюе на вашам сэрвэры Docker. Ён праставляе сабою тыя ж базовыя можлівасці, што і платныя варыянты:

    • Запуск ізольаванага кантэйнера.
    • Выкананне каманд у ўсім.
    • Чытанне і запіс файлаў там.
    • Зняць яго пасля завершэння працы.

    Запускайце сервер кантрол-плэна локальна, націсьце яго на Docker, і з’явіцца локальны канцэнтр, які стварае і керуе сэндбоксамі кожны раз, калі гэта запатрэбна. Ніхто не продае яго як продукт на хостынгу. Гэта інфраструктура, якую вы самі керуеце, і яна падходзіць для локальнага развіцця, тэставання і дэманаў, такіх як гэты. Заставало толькі пад’язаць яго да Deep Agents, у якога няма інтэграціі з OpenSandbox.

    Контракт BaseSandbox

    BaseSandbox — абстрактны клас, і інтэграцыя складаецца з чатырох элементаў.

    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 выкананяе команду ў sandbox-е і вяртае ExecuteResponse.
    2. id — унікальны ідэнтыфікатор для бэкэнду sandbox-а.
    3. upload_files адправляе калькі лістоў у sandbox.
    4. download_files выцягвае калькі лістоў з sandbox.

    Калі гэтыя чатыры элементы ўжо ўсёце ў наявнасці, Deep Agents сам стварае ls, read_file, write_file, edit_file, glob і grep на адной заснове execute(). Для іх не трэба больш нічога кодаваць. Гэта ўсё, што трэба для інтэграціі, тое значыць, што будзь-які sandbox, які можа выкананяць команды шэлу і перамешчаць лісты, падходзіць. Спосаб працы здаецца простым, але калі пачынаецца рэальны кодаванне, з’яўляюцца бягі.

    Як працюе OpenSandbox

    OpenSandbox выкарыстоўвае контэйнеры Docker для стварэння і керавання сэндбоксамі. Яго Python SDK можа ствараць і кераваць сэндбоксовымі сераўамі, запускаць команды шэлу, кераваць файламі і сочыць за рэсурсамі. Для наладкі патрэбны Docker, установлены і працюючы, а таксама сам сервер:

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

    Пасля чаго SDK падключаецца да працюючага сервера і адкрывае вялікі можлівасці для працы OpenSandbox.

    Клас інтэграцыі

    Гэта каркас, які трэба запаўніць, следуючы абстракцыі BaseSandbox. Метады прыкладзяюцца адна за адной.

    create()

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

    __init__ — гэта простая функцыя. У яе зберагаюцься два об’екты, ад каліх залежыць усё іншае: жывыя з’язкі Sandbox з серверам і AsyncRunner, які імі керуе. Ніхто з гэтых об’ектаў не ствараецца вручную, адколі оба являюцься рэзультатам выканання фабрычнага метода. Клас таксама мае больш методаў, чым тыя чатыры, якія трэбае BaseSandbox, зокнічна — метод .create(). Прычына гэтага стае ясней, калі разумеце, як на самай працэ падмет Sandbox ствараецца.

    З’язкі Sandbox і фонавы экзэкватары должны існаваць яшчэ до таго, калі __init__ зможа іх выкарыстоўваць, і самэ гэта ўскладніла структуру класа, таму create() ёсць самастоятны крок. Ось гэты код.

    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)
    

    Выдзеляюця два аспекты. domain="localhost:8080" налучвае гэта з серверам, які быў запусцаны ранейша за дапамогою uvx opensandbox-server. timeout=timedelta(minutes=30) служыць мерой безпекі: калі забыць вызваць kill(), сандбокс усё раве будзе знішчаны, а не застаецца працаваць у фоне. Таксама зверніце увагу на IMAGE і ENTRYPOINT — яны павінны быць сумесныя з версіяю OpenSandbox, якая выкарыстоўваецца.

    Праўда пра аб’екты, якія выконваюцца: Sandbox.create ўособліва корутына, але BaseSandbox.create павінен вярнуць звычайны об’ект, а не ўсё тое, чакаецца. Пакуль не будуць вядомы дэталі, трэба спрыятаць runner.run(coro) як „чакаць, пакуль гэты асінхронны вызов завершыцца, а потым даць мне тое, што ён вернуў“. Гэта і адказвае на пытанне, чаму Sandbox.create(...) пасылаецца ў runner.run(...), а не чакаецца безпосерэдна. Тое, як працюе AsyncRunner унутро, будзе розглянута пазней.

    id

    Працэс стварэння вяршыўся, таму застаўшыяся методы працююць з сандбоксам, які ужо працюе.

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

    Ён вяртае ідэнтыфікатор адпраўнай асяроддзя. BaseSandbox выкарыстоўвае яго як атрыбут, і ён стаўціцьы ў логах.

    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)
    

    Гэты прыем на першы погляд просты. OpenSandbox выдае рэзультаты запуску команд усьмоўкамі, по аднай на лінію, пры чым знак новай лініі ўжо быў выдален. Таму адпаведны адпаведны рэсультат павінен быць правільна перзбудаваны, каб агент мог усунуць яго пазней. Функцыя execute() таксама не вызываецца безпосерэдна. Функцыі ls, glob і grep у Deep Agents таксама базуюцца на яй і чытаюць выходны тэкст лінія за лініяй. Калі даць ім некарэктны рэсультат, яны нічога не знайдуць і тыха прынесуць порожнія рэзультаты. ls вяртае [], няма жадных паведамленняў пра прычыну бяды, а сандбокс выглядае порожнім, хоць на самай працы ён не такі.

    Гэта мае большое значэнне, чым здаецца, таму што execute() не прызначан толькі для прымітнага вжытку. Ён являецца аднай з базаў для ls, glob і grep у Deep Agents, і ўсе яны аналізуюць выходны тэкст па лініях. Некорэктна сформаваны адпаведзень не дазволяе ўсем ім знайсці нічога, і яны тыха вяртаюць порожнія рэзультаты. ls тады вяртае [] без жадных пазнак збою, якія б вказвалі на проблемную функцыю join, і сандбокс выглядае порожнім, калі на самай працэ ён не такі.

    Рэшта простае: stdout і stderr з’едынаюцца, пры чым ігнаруецца тое, што ў яго няма тэксту, а відсутны код завершэння лічыцца як успех (0), а не як 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]
    

    Кожна пара (path, bytes) абсарбуецца ў элемент WriteEntry з звычайнымі правамі (власнік можа чытаць і запісваць, іншыя можаць толькі чытаць), а весь набор выканацца за дапамою адной вызову 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
    

    Важным моментам ёсць read_bytes працоўвае лепшая за read_file. SDK аддае і адны, і другі, але read_file прыпускае, што тэкст знаходзіцца у фармаце UTF-8. Сандбокс, які запускае код LLM, не можа знайсці, чы рэгулярны файл — гэта CSV чы няўжоўваны графік, таму read_bytes ёсць едынны безпечны выбар у будзь-якім случае.

    kill()

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

    Працэс разбіркі выкананы два важныя дзеянні. Ён зупіняе кантэйнер песочніцы, а таксама явна ўсунеў заўантажаныя нейтрансляцыйныя потокі. Якшто прыхіліцца толькі да першага крока, потокі, а таксама цыкл здарэнняў у іх, застаюцца актываўымі пасля таго, як песочніца знікне.

    Гэта ўсё, што ўключае гэты клас: два методы жыцёвага цыклу (create, kill), якія абгрунтаваны чатырмая методамі, якія выкалікае BaseSandbox (id, execute, upload_files, download_files).

    Мост між сінхронным і асінхронным рэжымам

    Ёсць адна частка, якая застала нез'ясаванай: self._runner.run(...) — элемент, які робіць кожны асінхронны вызов SDK сінхронным з точкі зору BaseSandbox.

    BaseSandbox ўсунуты сінхронна. Функцыі execute(), upload_files() і download_files() — гэта звычныя вызовы функцый без await. Аднак SDK OpenSandbox ўсунуты асінхронна: Sandbox.create(), sandbox.commands.run() і sandbox.files.write_files() — усе гэта корутыны. AsyncRunner створаны, каб заполніць гэты прыемак.

    Падход просты. Ствараецца адзін цыкл змагання, які перадаецца у сае фонавое вырабніце і застаецца актыўным пакуль існуе сандбокс. Усе іншае выкарыстоўвае толькі run(), які адправляе корутыну ў гэты цыкл за дапамогою run_coroutine_threadsafe, блакуе код, які яе вызваў, пакуль яна не завершыцца, і вяртае рэзультат. Для execute() выраз self._runner.run(some_coroutine) выглядае абавесці як звычны вызов функцый.

    Адзін дзейнавы цыкл, замест новага за кожны вызов, таксама дазваляе з’яўленню працаваць як адна непаўзламная сесія, а не як што-то, што раз на раз знімаецца і перабудовваецца праз кожны execute(). Функцыя shutdown() абрабатвае чыставанне, зупініваючы цыкл і адмовіваючы ніць чыста, таму kill() проста вызывае яе.

    Агент для аналізу дадзэнняў у песчанай коробцы

    Для тэставання інтэграцыі Jupyter notebook спачатку задае зменныя сераўысу і сталярныя значэнні, імпортуе пакеты, а, што найважнейша, стварае об’ект MinimalOpenSandboxBackend. Для этага патрэбны ключ API, які вы самі ствараеце і задаеце ў файле ~/.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)
    

    Далей йдзе мадэль для чату. Модэлі, які работаюць локальна праз Ollama, таксама падходзяць, але модэль все рава должна выконваць задачы з агентным падходам. У гэтым запуске вікорыстоўваецца модэль 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,
    )
    

    Потым сам агент.

    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,
    )
    

    Усе, што было створана даўжэй, дае рэзультат у параметры backend=backend. З гэтага аднаго об’екта Deep Agents генеруе самыя функцыі ls, read_file, write_file, glob і grep, выкорыстоўваючы як адназначку спецыяльную функцыю execute().

    Пасля таго прыносзяцца рэальныя даны, якія завантажваюцца безпасова за дапамогою upload_files(). Набор даных — гэта фальшывы набор, які складаецца з 1 000 кліяў.

    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}")
    

    Потым агент атрымляе сама задача.

    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."
    })
    

    У гэтам этапе агент працуе сам. За дапамою сваіх інструментаў і інфраструктуры ён можа пісаць код на pandas і seaborn, запускаць яго ў сандбоксе за дапамою execute(), зберагаць графікі на файлавай сістэме сандбокса і продовжваць працаваць, пакуль не будзе створана цэлая адчынніка-документацыя.

    Застаецца адна проблема: частка документацыі застаецца ўсерэдзіні сандбокса. Яе адчыннік-документацыя паказвае на графікі за дапамою месцаўкі, спецыфічных для сандбокса, якія не існуе на хосце. Ёй зробіць чытабельной трэба два крокі: спакаваць зображэнні графікаў за дапамою download_files() і перапісаць месцаўкі зображэнняў у адчынніку-документацыі так, каб яны вядалі на месцаўкі ў сандбоксе. Альтэрнатыўны, менш проблематычны падход — калі агент вяртае структураваны выхід, напрыклад, поле з дакументацыяй і чысты спіс месцаўак зображэнняў, замест таго, каб шукаць лінкі ў неструктураваным адчынніку-документацыі. Пасляобработка тады складаецца толькі з спакавання кожнай з указаных месцаўак і ўсунення старых знакоў.

    Пасля таго, гэта тое, што сандбокс стварыў з неапранутага 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))
    

    Зменшаны відблік гэтаго звярнутага атласа:

    З ноутбука да пакета PyPI

    Ноутбук паказвае, як усе складаецца разам, але пакет, які кожны можа запусціць за дапамою pip, ўжо корыстней. Стварэнне deepagents_opensandbox_backend абяраў рэмонт усьго таго, што перастаў працаваць толькі тады, калі іншыя людзі запускалі яго ў налаштаваннях, якія аўтар ніколі не пробаваў. Публічны інтэрфейс застаўся незменным: тыя ж методы BaseSandbox, а таксама тыя ж функцыі create() і kill(). Змяны адбыліся ўнутранне:

    • Адны спяльны цыкл. Бэкенды цяпер выкарыстоўваюць аднаго модульнага _BackgroundLoop і не запускаюць адны ў аднам окрэмыя потыкі.
    • Стандартны ўпавяджэнні пра безпеку. API не атрыбутаваны, таму калі не падае api_key, create() цяпер выклікае InsecureSandboxWarning, тады калі раней ён з’яўляўся без жадных упавяджэнняў.
    • Рашэнне для Docker Desktop. У Docker Desktop падключэнне безпосерэдня да порту кантэйнера часта завершаецца з тайм-аутам. Установка use_server_proxy=True дазволяе трафіку праходзіць праз сервер і такім чынам ухіліцца ад гэтай проблемы.
    • Параболка шляхоў. Функцыі upload_files і download_files з самага пачатку адхоўляюць абсалютныя шляхі, таму сервер не можа таямніча пераканаць іх у неправільных каталогах.
    • Яснейшыя памылкі. Кліент пераглядае інфармацыю файлу, ўпэўніваючыся, чы гэта „заборона на выкананне“ чы „файл не знайдзены“, і прызначае кожную з іх да адпаведнага коду памылкі, замест таго каб перадаваць вадзівы нечыткія, необработаныя памылкі.
    • Функцыяя прыпынення чакання, якая працуе. Аргумент timeout раней быў ігнараваны. Цяпер яго перадаюць на сервер, каб той зупініў неконтрольаваныя запускі команд, замест таго каб тыя засталіся без прыгляду пасля таго, як кліент здаеся.
    • Удаленыя ручныя асінхронныя методы. Рэальныя асінхронныя версіі, такія як aexecute, спрычынялі бесшумныя, порожнія рэзультаты ў ToolNode LangGraph, таму яны былі выдалены, і цяпер код вяртаецца да звычных сінхронных методаў, якія выкананыя ў пуле задач.

    Гэтыя разлікі выдзеляюць дэмаверсію, якая працуе на аднам лэптопе, ад пакета, які можа выкарыстоўваць кожны за дапамою каманды pip install. Разам з пакетам прыходзіць набор тэстаў на адпаведнасць, які запускаецца на адной з стандартных тэстовых срэдаў самага Deep Agents, а працэс інсталяцыі выглядае так:

    !pip install deepagents-opensandbox-backend
    

    Гэта тая ж самая інтэграцыя, якая была опісана вышэй, толькі гострыя канты былі згладжаны.

    Спадневае чытанне

  • AgentSRE: Агенты на базе Composable AI для автонамнага усунення інцыдэнтав на AWS — Як платформа AWS ператварае інцыдэнт, заявлены ў аларме CloudWatch, на рашэння за дапамогою агента DevOps, агентаў для усунення проблем LangGraph, пацверджэння чалавека і тыкетаў у ServiceNow.
  • Што схема не можа сказаць вашаму агенту: фармат адкрытых ведамаў у BigQuery — Эксперымент з BigQuery і ADK, які паказвае, як пакеты ведамаў у фармаце Markdown у Git стабілізавалі адпаведзі агента і вылічылі памылку GMV на 29%.
  • Порэйнаванне CrewAI, AutoGen, LangGraph, OpenAI Agents SDK і smolagents — Порэйнаванне інжынера пяці фрэймворкаў агентаў на адкрытым кодзе па такімі параметрамі, як оркестрацыя, памяць, інструменты, ацэнка і можлівасці абзірнасці, а таксама часта з’яўляючыяся патэрны неудач.