Accueil / Articles

Exécuter des agents Running Deep dans un backend OpenSandbox local

Comment connecter OpenSandbox à Deep Agents via BaseSandbox, relier du code synchrone et asynchrone, et exécuter en toute sécurité un agent d’analyse de données dans un environnement Docker local.

Éditeur : REACTAPP.TOP15 min de lecture3403 mots

Sources: Upgrade Your Deep Agent With a Local Open-Source Sandbox

Pourquoi les agents ont besoin d’un endroit sûr pour exécuter du code

Les assistants de chat tels que ChatGPT, Claude, Gemini et Qwen acceptent des téléchargements tels que des images, des PDF et des feuilles de calcul. De nombreuses demandes concernant ces fichiers, comme décrire une image, résumer un PDF ou extraire un extrait d’un document, relèvent des capacités natives d’un modèle de langage. Cependant, si l’on donne à un tel modèle un fichier .csv et qu’on lui demande une analyse statistique complète accompagnée de graphiques, la simple raison ne suffit plus : il faut alors effectuer une véritable manipulation de données, des calculs et la création de graphiques.

Un LLM ne peut pas effectuer lui-même ces calculs ou ces dessins, mais il peut écrire un script Python utilisant pandas, NumPy et Matplotlib pour le faire. Une fois le script exécuté, la tâche restante du modèle est de transformer les résultats en un rapport lisible. Ce sont les outils d’agent qui rendent possible l’exécution de ces scripts.

Cependant, exécuter directement sur sa propre machine du code écrit par une IA n’est pas une bonne idée. L’exécution non supervisée représente un risque sérieux pour la sécurité et la stabilité : elle peut modifier la configuration du système, supprimer des fichiers essentiels ou exposer des données personnelles. C’est pourquoi il existe des boîtes de sable, des environnements isolés et sécurisés où l’agent peut exécuter son code sans accéder au système d’exploitation sous-jacent. Ce document se concentre sur les boîtes de sable pour la bibliothèque Deep Agents Python.

Qu’est-ce qu’une boîte de sable et pourquoi les agents en ont-ils besoin

Ici, un environnement de type sandbox désigne un environnement isolé, généralement un conteneur, dans lequel le code s’exécute sans aucun contact avec le reste de la machine. Son système de fichiers et ses processus lui appartiennent exclusivement, et rien ne permet d’accéder aux fichiers, identifiants ou réseau du hôte, sauf si l’accès est accordé délibérément. Les développeurs peuvent y voir un avantage pratique, mais pour un agent autonome c’est presque une exigence, et les raisons sont pratiques :

  • C’est l’agent qui choisit le code, et non l’utilisateur. Même un agent bien conçu peut parfois supprimer le mauvais fichier ou exécuter une commande inattendue, et sans isolation, les dommages affectent la machine réelle.
  • L’injection de commandes est réelle. Si l’agent lit du contenu non fiable, comme une page web ou un fichier provenant d’une autre personne, une instruction cachée bien formulée peut tenter de rediriger ses actions suivantes. Un environnement sandbox limite les dommages à un conteneur jetable.
  • Des environnements propres aident. Chaque tâche est plus facile à gérer lorsqu’elle débute dans un environnement connu et vide, sans hériter de l’état des exécutions précédentes.
  • Agents parallèles. Grâce à l’isolation, un pip install d’un agent ne peut pas entrer en conflit avec celui d’un autre, et une sandbox en panne ne perturbera pas l’ensemble du processus.
  • Limites de ressources. Un agent bloqué dans une boucle infinie, ou celui qui charge un ensemble de données entier en mémoire à deux reprises, devrait épuiser les ressources d’un conteneur jetable et non celles de votre ordinateur.

C’est pourquoi les frameworks conçus pour coder des agents, y compris Deep Agents, traitent le sandbox comme un concept de premier ordre. Deep Agents fournit une abstraction appelée BaseSandbox, ainsi que des intégrations officielles avec quelques fournisseurs de sandboxes cloud. En orientant l’agent vers l’un d’eux, celui-ci dispose d’un environnement réel et isolé pour exécuter du code.

Tous ces fournisseurs sont hébergés dans le cloud et facturés par minute ou en fonction de l’utilisation. Cela convient pour le déploiement en production, mais c’est plus que nécessaire pour développer, tester ou démontrer un agent localement.

OpenSandbox en tant qu’option locale

OpenSandbox est un environnement de sandbox gratuit et open source qui fonctionne sur votre propre Docker. Il offre les mêmes fonctionnalités de base que les options payantes :

  • Démarrer un conteneur isolé.
  • Exécuter des commandes à l’intérieur.
  • Lire et écrire des fichiers dessus.
  • Le détruire par la suite.

Démarrez son serveur de plane de contrôle localement, orientez-le vers Docker, et un point d’entrée local apparaît qui crée et gère des sandbox chaque fois que c’est demandé. Personne ne le vend en tant que produit hébergé. Il s’agit d’une infrastructure que vous gérez vous-même, ce qui convient au développement local, aux tests et aux démos comme celle-ci. Il ne reste plus qu’à le connecter à Deep Agents, ce qui ne comprend pas d’intégration OpenSandbox.

Le contrat BaseSandbox

BaseSandbox est une classe abstraite, et l’intégration se résume à quatre membres.

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 exécute une commande dans le sandbox et renvoie une ExecuteResponse.
  2. id est un identifiant unique pour le backend du sandbox.
  3. upload_files envoie plusieurs fichiers dans le sandbox.
  4. download_files récupère plusieurs fichiers en sortant.

Lorsque ces quatre éléments existent, Deep Agents crée automatiquement ls, read_file, write_file, edit_file, glob et grep en s’appuyant sur execute(). Aucun code supplémentaire n’est nécessaire pour eux. Il s’agit de l’ensemble complet des fonctionnalités d’intégration, ce qui signifie que tout sandbox capable d’exécuter des commandes de shell et de déplacer des fichiers entre eux est éligible. La méthode semble simple, jusqu’à ce que le codage réel commence et que des bugs apparaissent.

Comment fonctionne OpenSandbox

OpenSandbox utilise des conteneurs Docker pour créer et gérer des environnements de sandbox. Son SDK Python permet de créer et de gérer ces environnements, d’exécuter des commandes shell, de gérer des fichiers et de surveiller les ressources. L’installation nécessite que Docker soit installé et en cours d’exécution, ainsi que le serveur lui-même :

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

# Start the server
uvx opensandbox-server

Par la suite, le SDK se connecte au serveur en cours d’exécution et met à disposition toutes les fonctionnalités d’OpenSandbox.

La classe d’intégration

C’est le squelette à compléter, en suivant l’abstraction BaseSandbox. Les méthodes doivent être ajoutées une par une.

create()

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

__init__ est simple. Il contient deux objets dont tout le reste dépend : la connexion en temps réel Sandbox au serveur et le AsyncRunner qui la gère. Aucun de ces objets n’est créé manuellement, car ils proviennent tous deux d’une méthode factory. La classe dispose également de plus de méthodes que les quatre exigées par BaseSandbox, notamment une méthode de classe .create(). La raison devient plus claire en comprenant comment un objet sandbox est réellement créé.

La connexion Sandbox ainsi que le processus d’exécution en arrière-plan doivent exister avant que __init__ ne puisse les utiliser, c’est pourquoi create() constitue une étape distincte. Voici le code correspondant.

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)

Deux détails se détachent. domain="localhost:8080" relie ce dernier au serveur démarré précédemment avec uvx opensandbox-server. timeout=timedelta(minutes=30) sert de filet de sécurité : même en oubliant d’appeler kill(), le sandbox se détruit malgré tout au lieu de rester en arrière-plan. Prêtez également attention à IMAGE et ENTRYPOINT, qui doivent être compatibles avec la version d’OpenSandbox en usage.

À propos du gestionnaire d’exécution : Sandbox.create est une coroutine, mais BaseSandbox.create doit retourner un objet ordinaire plutôt que quelque chose à attendre. En attendant plus de détails, considérez runner.run(coro) comme « attendre que cette appel asynchrone se termine, puis me donner ce qu’il a retourné ». Cela explique pourquoi Sandbox.create(...) est passé à runner.run(...) au lieu d’être attendu directement. Le fonctionnement interne de AsyncRunner sera abordé un peu plus tard.

id

La création est terminée, donc les méthodes restantes s’occupent d’un environnement de test déjà en cours d’exécution.

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

Il renvoie l’ID du sandbox sous-jacent. BaseSandbox l’exige en tant que propriété, et c’est pratique pour les journaux.

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)

Apparemment simple, en réalité non. OpenSandbox fournit la sortie des commandes sous forme d’une liste de fragments, un par ligne, et le saut de ligne a déjà été supprimé. La réponse doit donc être reconstruite correctement afin que l’agent puisse la gérer ultérieurement. La fonction execute() n’est pas non plus appelée directement. Les commandes ls, glob et grep dans Deep Agents s’appuient toutes sur elle pour lire la sortie ligne par ligne. Si l’on leur fournit une réponse mal formatée, elles ne trouvent rien et renvoient silencieusement des résultats vides. ls renvoie [], aucune trace d’erreur ne pointe vers une erreur de join, et le sandbox semble vide alors qu’il ne l’est pas.

Cela est plus important qu’il n’y paraît, car execute() n’est pas seulement destiné à un usage direct. C’est la base de ls, glob et grep au sein des Deep Agents, et tous ces outils analysent les résultats ligne par ligne. Une réponse mal formatée les amène à ne rien trouver et à retourner silencieusement des résultats vides. ls affiche alors [] sans aucun retour d’erreur indiquant le problème lié à join, et le sandbox semble vide alors qu’il ne l’est pas.

Le reste est plus simple : stdout et stderr sont concaténés, en ignorant celui qui est vide, et l’absence de code de sortie est considérée comme un succès (0) au lieu de rester 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]

Chaque paire (chemin, octets) est enveloppée dans une WriteEntry dotée de permissions ordinaires (le propriétaire peut lire et écrire, les autres ne peuvent que lire), et l’ensemble du lot est envoyé via une seule appelation 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

Le détail important est que read_bytes est préférable à read_file. Le SDK propose les deux, mais read_file suppose que le texte est en UTF-8. Un environnement sandbox exécutant le code d’un LLM ne peut pas savoir si le fichier demandé suivant est un CSV ou un graphique fraîchement généré ; par conséquent, read_bytes est le seul choix sûr pour les deux cas.

kill()

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

Le processus de démontage effectue deux tâches importantes : il termine le conteneur de sandbox et il arrête explicitement le thread en arrière-plan du lanceur. En sautant cette deuxième étape, le thread, ainsi que le boucle d’événements qu’il contient, reste actif même après la disparition du sandbox.

C’est tout l’ensemble de la classe : deux méthodes de cycle de vie (create, kill) qui entourent les quatre méthodes exigées par BaseSandbox (id, execute, upload_files, download_files).

Le pont sync/async

Un élément reste à expliquer : self._runner.run(...), c’est-à-dire la fonction qui fait en sorte que chaque appel SDK asynchrone apparaisse synchrone aux yeux de BaseSandbox.

BaseSandbox est synchrone. execute(), upload_files() et download_files() ne sont que des appels de fonction ordinaires sans utilisation de await. En revanche, le SDK OpenSandbox est asynchrone : Sandbox.create(), sandbox.commands.run() et sandbox.files.write_files() sont tous des coroutines. AsyncRunner a été conçu pour combler cette lacune.

La méthode est simple. Un boucle d’événements est créée, attribuée à son propre thread en arrière-plan et maintenue active tant que le sandbox existe. Tout le reste utilise uniquement run(), qui soumet une coroutine à cette boucle via run_coroutine_threadsafe, bloque le code appelant jusqu’à ce qu’elle soit terminée et renvoie le résultat. Pour execute(), self._runner.run(some_coroutine) ressemble à un appel de fonction ordinaire.

Un seul boucle à longue durée de vie, au lieu d’en créer une nouvelle à chaque appel, permet également à la connexion de fonctionner comme une session continue et non comme quelque chose qui est démantelé puis reconstruit à chaque execute(). shutdown() s’occupe du nettoyage en arrêtant la boucle et en terminant proprement le thread, c’est pourquoi kill() se contente de l’appeler.

Un agent d’analyse de données dans le sandbox

Pour tester l’intégration, un notebook Jupyter commence par définir des variables d’environnement et des constantes, importer des packages et, ce qui est le plus important, créer un objet MinimalOpenSandboxBackend. Cela nécessite une clé API que vous créez vous-même et que vous définissez dans le fichier ~/.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)

Viens ensuite un modèle de chat. Les modèles locaux via Ollama fonctionneraient également, mais le modèle doit néanmoins gérer des tâches agentes. Cette exécution utilise un modèle 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,
)

Puis l’agent lui-même.

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

Tout ce qui a été construit jusqu’à présent se reflète dans l’argument backend=backend. À partir de cet objet unique, Deep Agents génère ls, read_file, write_file, glob ainsi que grep lui-même, en utilisant la fonction personnalisée execute() comme base.

Des données réelles suivent, chargées directement avec upload_files(). L’ensemble de données est fictif et comprend 1 000 clients.

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

Ensuite, l’agent reçoit la tâche réelle.

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

À ce stade, l’agent travaille seul. Grâce à ses outils et à son infrastructure, il peut écrire du code pandas et seaborn, le lancer dans le sandbox via execute(), stocker les graphiques dans le système de fichiers du sandbox et continuer à itérer jusqu’à ce qu’un rapport Markdown complet soit généré.

Un problème persiste : une partie du rapport reste à l’intérieur du sandbox. Son format Markdown fait référence à des graphiques via des chemins locaux au sandbox, qui n’existent pas sur l’hôte. Pour le rendre lisible, il faut deux étapes : télécharger les images des graphiques avec download_files() et réécrire les chemins des images dans le Markdown de manière à ce qu’ils fassent référence aux copies locales. Une approche moins fragile consisterait pour l’agent à retourner des données structurées, par exemple un champ contenant le rapport et une liste explicite des chemins des images, au lieu de chercher des liens dans du Markdown non structuré. Le post-traitement consisterait alors simplement à télécharger chaque chemin indiqué et à le substituer.

Après cela, voici ce que le sandbox a généré à partir du CSV brut.

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

Vue agrandie du rapport généré :

De cahier de notes à un paquet PyPI

Un cahier de notes montre comment les éléments s’assemblent, mais un paquet que n’importe qui peut installer avec pip est bien plus utile. Créer deepagents_opensandbox_backend a nécessité de corriger tout ce qui ne fonctionnait pas une fois que d’autres personnes l’ont exécuté dans des configurations que l’auteur n’avait jamais testées. L’interface publique est restée inchangée, avec les mêmes méthodes BaseSandbox ainsi que les mêmes fonctions create() et kill(). Les modifications se sont produites en coulisses :

  • Une boucle partagée. Les backends partagent désormais un seul _BackgroundLoop au niveau du module et n’exécutent plus de threads distincts chacun.
  • Avertissement de sécurité par défaut. L’API n’est pas authentifiée, de sorte que create() génère désormais un InsecureSandboxWarning lorsqu’aucune api_key n’est fournie, alors qu’auparavant elle se connectait en silence.
  • Solution de contournement avec Docker Desktop. Sur Docker Desktop, se connecter directement à un port de conteneur provoque fréquemment un temps d’attente infini. En définissant use_server_proxy=True, le trafic est acheminé via le serveur, ce qui permet de contourner le problème.
  • Vérification des chemins. upload_files et download_files rejettent d’emblée les chemins relatifs, afin que le serveur ne puisse pas les résoudre silencieusement vers des dossiers incorrects.
  • Erreurs plus claires. Le client vérifie les informations du fichier pour distinguer les cas de « permission refusée » des cas de « fichier introuvable », et attribue à chacun le code d’erreur correspondant, au lieu de transmettre des erreurs brutes trompeuses.
  • Délais d’expiration fonctionnels. L’argument timeout était auparavant ignoré. Il est désormais transmis au serveur afin d’arrêter les commandes qui s’éternisent, plutôt que de les laisser en suspens une fois que le client abandonne.
  • Suppression des méthodes asynchrones manuelles. Les versions asynchrones réelles telles que aexecute provoquaient des sorties silencieuses et vides à l’intérieur de ToolNode de LangGraph ; elles ont donc été supprimées, et le code recourt désormais aux méthodes synchrones normales exécutées dans un pool de threads.

Cette différence sépare une démonstration fonctionnelle sur un seul ordinateur portable d’un paquet prêt à être installé par n’importe qui à l’aide de pip install. Une suite de tests de conformité, exécutée à l’aide des tests de sandbox standard fournis par Deep Agents lui-même, est incluse, et l’installation se fait de la manière suivante :

!pip install deepagents-opensandbox-backend

C’est la même intégration décrite ci-dessus, avec les bords tranchants affûtés.

Lectures complémentaires

  • AgentSRE : Agents d’IA composable pour la résolution autonome des incidents sur AWS — Comment une plateforme AWS transforme un incident déclenché par une alarme CloudWatch en solution grâce à un agent DevOps, des agents de résolution LangGraph, à l’approbation humaine et aux tickets ServiceNow.
  • Ce que le schéma ne peut pas dire à votre agent : le format Open Knowledge sur BigQuery — Une expérience menée avec BigQuery et ADK montrant comment les paquets de connaissances au format Markdown dans Git ont stabilisé les réponses de l’agent et corrigé une erreur de 29 % sur le GMV.
  • Comparaison de CrewAI, AutoGen, LangGraph, OpenAI Agents SDK et smolagents — Une comparaison réalisée par un ingénieur de cinq frameworks open-source d’agents en ce qui concerne l’orchestration, la mémoire, les outils, les évaluations et l’observabilité, ainsi que les schémas de défaillance récurrents.