Saltar a contenido

LangChain y Vercel AI

Rayito como herramienta de un agente: el modelo escribe Python, la herramienta lo ejecuta en un sandbox con estado y le devuelve el resultado. Dos adaptadores de unas cincuenta líneas, listos para copiar.

Cuándo usarlo

  • Tu agente usa LangChain (Python) o el Vercel AI SDK (TypeScript) y quieres darle un intérprete de código seguro.
  • Para hosts que hablan MCP (Claude Code, Claude Desktop, Cursor, VS Code) no hace falta código: usa el servidor MCP.
  • Si lo que quieres es que el agente entero (el bucle del modelo y sus herramientas) corra dentro del sandbox, con el modelo detrás de la pasarela de secretos, no necesitas estos adaptadores: usa sbx.agent, con OpenCode o con un grafo de deepagents (LangChain).

Ejemplo

Cada adaptador crea el sandbox en la primera llamada, lo reutiliza (las variables sobreviven entre llamadas, como en un notebook) y lo destruye al salir del proceso. Devuelve al modelo un JSON con text, stdout, stderr y error.

Instalación: pip install rayito langchain y el paquete del proveedor de tu modelo.

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

Instalación: pnpm add rayito ai zod y el paquete del proveedor de tu modelo.

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

Los dos leen la imagen de RAYITO_TEMPLATE y las credenciales de la cadena estándar de AWS (AWS_PROFILE, AWS_REGION).

Paso a paso

  1. Un sandbox por proceso. La primera llamada a la herramienta crea el sandbox con un timeout de 900 s; las siguientes reutilizan el mismo kernel.
  2. Errores como datos. Si el código del modelo lanza una excepción, la herramienta devuelve error con su nombre y valor en vez de fallar: el modelo puede leerlo y corregirse.
  3. Limpieza. El sandbox se mata al salir del proceso (atexit en Python, beforeExit en Node). Si el proceso muere de golpe, el sandbox vive hasta su timeout.

Ajustes habituales

  • Un sandbox por conversación: guarda el sandbox en el estado de la sesión en vez de en una variable global, y mátalo al cerrar la sesión.
  • Latencia: con un pool, la primera llamada tarda < 1 s en vez de 5–6 s; cada plaza ociosa tiene un coste fijo al mes (Precios).
  • Sin internet: crea el sandbox en rayito-base-caps con allow_internet_access=False (Red saliente).
  • Gráficos: devuelve también execution.results[i].png (base64) si tu modelo acepta imágenes.

Ver también