Skip to content

LangChain and Vercel AI

Rayito as an agent tool: the model writes Python, the tool runs it in a stateful sandbox and returns the result. Two adapters of about fifty lines each, ready to copy.

When to use it

  • Your agent uses LangChain (Python) or the Vercel AI SDK (TypeScript) and you want to give it a safe code interpreter.
  • For hosts that speak MCP (Claude Code, Claude Desktop, Cursor, VS Code) you need no code: use the MCP server.
  • If you want the whole agent (the model loop and its tools) to run inside the sandbox, with the model behind the secrets gateway, you do not need these adapters: use sbx.agent, with OpenCode or with a deepagents (LangChain) graph.

Example

Each adapter creates the sandbox on the first call, reuses it (variables survive between calls, as in a notebook) and destroys it when the process exits. It returns to the model a JSON with text, stdout, stderr and error.

Install: pip install rayito langchain and the provider package for your model.

"""Un sandbox de Rayito como herramienta de LangChain, en cincuenta líneas.

Instalación: `pip install rayito langchain` (y el proveedor de tu modelo).
Entorno: `RAYITO_TEMPLATE` (nombre o ARN de la imagen), `AWS_REGION` y
`AWS_PROFILE` o las credenciales de boto3. El sandbox se crea en la primera
llamada, vive como mucho 900 s y se destruye al salir del proceso.
"""

from __future__ import annotations

import atexit
import json

from langchain.tools import tool
from rayito import Sandbox

SANDBOX_TIMEOUT_SECONDS = 900
_sandbox: Sandbox | None = None


def sandbox() -> Sandbox:
    global _sandbox
    if _sandbox is None:
        _sandbox = Sandbox.create(timeout=SANDBOX_TIMEOUT_SECONDS)
        atexit.register(_sandbox.kill)
    return _sandbox


@tool
def run_python(code: str) -> str:
    """Ejecuta código Python en un sandbox aislado con estado entre llamadas
    (Linux, internet, matplotlib y pandas instalados). Devuelve un JSON con
    `text` (valor de la última expresión), `stdout`, `stderr` y `error`
    (nombre y valor si la celda lanzó una excepción)."""
    execution = sandbox().run_code(code)
    error = execution.error
    summary = {
        "text": execution.text,
        "stdout": "".join(execution.logs.stdout),
        "stderr": "".join(execution.logs.stderr),
        "error": None if error is None else {"name": error.name, "value": error.value},
    }
    return json.dumps(summary, ensure_ascii=False)


if __name__ == "__main__":
    from langchain.agents import create_agent

    agent = create_agent("anthropic:claude-opus-5", tools=[run_python])
    print(agent.invoke({"messages": [("user", "Calcula 2**100 en Python.")]}))

Install: pnpm add rayito ai zod and the provider package for your model.

/**
 * Un sandbox de Rayito como herramienta del Vercel AI SDK, en cincuenta líneas.
 *
 * Instalación: `pnpm add ai zod rayito` (y el proveedor de tu modelo).
 * Entorno: `RAYITO_TEMPLATE` (nombre o ARN de la imagen), `AWS_REGION` y
 * `AWS_PROFILE` o las credenciales del SDK de AWS. El sandbox se crea en la
 * primera llamada, vive como mucho 900 s y se destruye al salir del proceso.
 */

import { tool } from "ai";
import { Sandbox } from "rayito";
import { z } from "zod";

const SANDBOX_TIMEOUT_MS = 900_000;
let pending: Promise<Sandbox> | undefined;

function sandbox(): Promise<Sandbox> {
  pending ??= Sandbox.create({ timeoutMs: SANDBOX_TIMEOUT_MS });
  return pending;
}

process.on("beforeExit", () => {
  void pending?.then((sbx) => sbx.kill());
});

export const runPython = tool({
  description:
    "Ejecuta código Python en un sandbox aislado con estado entre llamadas " +
    "(Linux, internet, matplotlib y pandas instalados). Devuelve text (valor de " +
    "la última expresión), stdout, stderr y error (nombre y valor si la celda " +
    "lanzó una excepción).",
  inputSchema: z.object({ code: z.string().describe("Código Python que ejecutar.") }),
  execute: async ({ code }) => {
    const execution = await (await sandbox()).runCode(code);
    return {
      text: execution.text ?? null,
      stdout: execution.logs.stdout.join(""),
      stderr: execution.logs.stderr.join(""),
      error: execution.error ? { name: execution.error.name, value: execution.error.value } : null,
    };
  },
});

// Uso: `generateText({ model, tools: { runPython }, prompt: "Calcula 2**100 en Python." })`.

Both read the image from RAYITO_TEMPLATE and the credentials from the standard AWS chain (AWS_PROFILE, AWS_REGION).

Step by step

  1. One sandbox per process. The first call to the tool creates the sandbox with a timeout of 900 s; later calls reuse the same kernel.
  2. Errors as data. If the model's code raises an exception, the tool returns error with its name and value instead of failing: the model can read it and correct itself.
  3. Cleanup. The sandbox is killed when the process exits (atexit in Python, beforeExit in Node). If the process dies abruptly, the sandbox lives until its timeout.

Common adjustments

  • One sandbox per conversation: keep the sandbox in the session state instead of a global variable, and kill it when the session closes.
  • Latency: with a pool, the first call takes < 1 s instead of 5-6 s; each idle slot has a fixed monthly cost (Pricing).
  • No internet: create the sandbox on rayito-base-caps with allow_internet_access=False (Outbound network).
  • Charts: also return execution.results[i].png (base64) if your model accepts images.

See also