Novedades de 0.10.0¶
Publicada el 2026-10-08: SDK Python rayito 0.10.0, SDK TypeScript
rayito 0.10.0 y rayd 0.10.0.
0.10.0 trae tres cosas para quien usa el
agente en el sandbox (sbx.agent):
- Más proveedores para el modelo. Hasta 0.9 el agente hablaba con Bedrock, Anthropic o una API compatible con OpenAI que configurabas a mano. Ahora hay un preset listo para OpenAI, Gemini, Azure OpenAI, OpenRouter, Groq, Mistral, DeepSeek, xAI y tu propio proxy de LiteLLM.
- El timeout y
abort()paran de verdad. Antes, un proceso que el agente lanzaba con su herramienta de shell (unsleep, un servidor) podía sobrevivir al timeout. Ahoraraydpara todo lo que lanzó la ejecución. - deepagents funciona fuera de Bedrock y cuenta sus tokens.
No hay cambios que rompan la API. Para el punto 2 y para deepagents hay que reconstruir la imagen: ver Cómo actualizar desde 0.9.x.
De un vistazo¶
| Cambio | Python | TypeScript | Necesita imagen nueva |
|---|---|---|---|
Nueve presets de proveedor y tres valores nuevos de AgentModel.provider |
openai_gateway()… litellm_gateway(); "openai", "google", "azure" |
openaiGateway()… litellmGateway(); los mismos provider |
sí, la del agente (AgentTemplate) |
El timeout y abort() del agente paran todo su árbol de procesos |
sbx.agent, commands.run(kill_tree=) |
sbx.agent, commands.run({ killTree }) |
sí (rayd 0.10.0) |
deepagents con modelos que no son de Bedrock (faltaba socksio) |
runtime deepagents |
runtime deepagents |
sí, la del agente |
| deepagents cuenta los tokens de las APIs compatibles con OpenAI | result.usage, AgentLimits.max_total_tokens |
result.usage, AgentLimits.maxTotalTokens |
sí, la del agente |
rayito doctor conoce la serie 0.10 |
CLI | — | — |
Más proveedores para el modelo del agente¶
El agente nunca ve la clave del modelo. La clave vive en Secrets Manager y
la pasarela de secretos
de rayd la pone en la cabecera de cada petición. Un preset es esa
pasarela ya configurada para un proveedor: sabe a qué dirección llamar, en
qué cabecera va la clave y qué rutas se permiten.
¿Cuál elijo?¶
| Tu situación | Elige | Por qué |
|---|---|---|
| Ya trabajas en AWS y quieres una sola factura | Bedrock (bedrock_gateway) |
la pasarela limita también qué modelos se pueden llamar; es la ruta más probada |
| Ya tienes clave de OpenAI | OpenAI (openai_gateway) |
Responses API nativa en OpenCode y deepagents |
| Tu empresa usa Azure | Azure OpenAI (azure_openai_gateway) |
el modelo es un despliegue de tu recurso, con su propia cuota |
| Quieres Gemini | Gemini (gemini_gateway) |
como en Bedrock, la pasarela limita los modelos |
| Quieres probar muchos modelos con una sola clave | OpenRouter (openrouter_gateway) |
un catálogo grande detrás de una API |
| Quieres velocidad o precio bajo | Groq, DeepSeek o Mistral | APIs compatibles con OpenAI |
| Ya tienes Grok | xAI (xai_gateway) |
habla la misma Responses API que OpenAI |
| Ya tienes un proxy de LiteLLM, o quieres modelos locales | LiteLLM (litellm_gateway) |
claves virtuales con presupuesto y cualquier modelo que el proxy sirva |
Si dudas, empieza por Bedrock o por LiteLLM delante de Bedrock: son las rutas que se han probado con un modelo real (ver qué se probó de verdad).
Configurarlo en tres pasos¶
- Guarda la clave en Secrets Manager, una sola vez, desde tu máquina o
tu backend. Para las APIs al estilo de OpenAI el valor es la cabecera
completa:
Bearer <clave>. Para Gemini y Azure, la clave tal cual. - Pasa el preset en
gateways=al crear el sandbox. El nombre que le des (aquí"openai") es el que usará el modelo. - Apunta el
AgentModela esa pasarela con elproviderdel proveedor y su id de modelo, y ejecuta.
from rayito import AgentModel, AgentSpec, Sandbox, SecretStore, openai_gateway
# 1. La clave, una sola vez
SecretStore().create("openai-key", "Bearer <clave de API de OpenAI>")
# 2. El preset, con el nombre "openai"
gateways = {"openai": openai_gateway("openai-key", rate_per_minute=60)}
# 3. El modelo apunta a esa pasarela
spec = AgentSpec(model=AgentModel(provider="openai", id="gpt-5-mini", gateway="openai"))
with Sandbox.create(template="rayito-agent", allow_internet_access=False, gateways=gateways) as sbx:
print(sbx.agent.run("Resume el README.", spec=spec).text)
from rayito import AgentModel, AgentSpec, AsyncSandbox, openai_gateway
async def main() -> None:
gateways = {"openai": openai_gateway("openai-key", rate_per_minute=60)}
spec = AgentSpec(model=AgentModel(provider="openai", id="gpt-5-mini", gateway="openai"))
async with await AsyncSandbox.create(
template="rayito-agent", allow_internet_access=False, gateways=gateways
) as sbx:
result = await sbx.agent.run("Resume el README.", spec=spec)
print(result.text)
import { AgentModel, AgentSpec, Sandbox, SecretStore, openaiGateway } from "rayito";
// 1. La clave, una sola vez
await new SecretStore().create("openai-key", "Bearer <clave de API de OpenAI>");
// 2. El preset, con el nombre "openai"
const gateways = { openai: openaiGateway("openai-key", { ratePerMinute: 60 }) };
// 3. El modelo apunta a esa pasarela
const spec = new AgentSpec({
model: new AgentModel({ provider: "openai", id: "gpt-5-mini", gateway: "openai" }),
});
await using sbx = await Sandbox.create({ template: "rayito-agent", allowInternetAccess: false, gateways });
console.log((await sbx.agent.run("Resume el README.", { spec })).text);
Para otro proveedor sólo cambian el preset y el AgentModel:
| Proveedor | Valor del secreto | Preset (Python) | AgentModel |
|---|---|---|---|
| OpenAI | Bearer sk-… |
openai_gateway(secret) |
provider="openai" |
| Gemini | la clave tal cual | gemini_gateway(secret, models=[…]) |
provider="google" |
| Azure OpenAI | la clave tal cual | azure_openai_gateway(secret, resource="mi-recurso") |
provider="azure", id = nombre del despliegue |
| OpenRouter | Bearer … |
openrouter_gateway(secret) |
provider="openai-compatible", base_path="/api/v1" |
| Groq | Bearer … |
groq_gateway(secret) |
provider="openai-compatible", base_path="/openai/v1" |
| Mistral | Bearer … |
mistral_gateway(secret) |
provider="openai-compatible", base_path="/v1" |
| DeepSeek | Bearer … |
deepseek_gateway(secret) |
provider="openai-compatible", sin base_path |
| xAI | Bearer xai-… |
xai_gateway(secret) |
provider="openai" |
| LiteLLM | Bearer <clave virtual> |
litellm_gateway(secret, upstream="https://…") |
provider="openai-compatible", base_path="/v1" |
En TypeScript los presets se llaman igual en camelCase (openaiGateway,
geminiGateway, azureOpenaiGateway, openrouterGateway, groqGateway,
mistralGateway, deepseekGateway, xaiGateway, litellmGateway) y las
opciones van en un objeto ({ models }, { resource }, { upstream }).
Cada proveedor, con su ejemplo completo en los dos lenguajes y el enlace a
su consola, está en
Proveedores del agente.
Fija el tope de gasto en el proveedor
En Bedrock y Gemini el modelo va en la ruta y la pasarela sólo deja
pasar los modelos que listes. En las APIs al estilo de OpenAI (OpenAI,
Azure OpenAI, xAI, OpenRouter, Groq, Mistral, DeepSeek, LiteLLM) el
modelo va en el cuerpo, que la pasarela no lee: cualquier código del
sandbox puede pedir cualquier modelo que permita la clave. Pon el tope en
el proveedor (presupuesto del proyecto, límite de crédito de la clave,
presupuesto de la clave virtual de LiteLLM) y usa rate_per_minute /
ratePerMinute. Detalle en
Límites del lado del proveedor.
Tu propio proxy de LiteLLM¶
Si ya tienes un proxy de LiteLLM, el agente puede usar cualquier modelo que sirva (de cualquier proveedor, o uno local como vLLM u Ollama) con una sola clave virtual. Tres condiciones:
- El proxy escucha en HTTPS y la VPC del sandbox lo alcanza. El
localhostde tu portátil no vale: el preset rechazahttp://,localhost, loopback,0.0.0.0y enlace local. - La clave virtual tiene presupuesto (
max_budget) y lista de modelos. Es tu único tope real de gasto. - El
base_pathdelAgentModeles el mismo que el del preset (/v1por defecto).
from rayito import AgentModel, SecretStore, litellm_gateway
SecretStore().create("litellm-key", "Bearer <clave virtual de LiteLLM>")
gateway = litellm_gateway("litellm-key", upstream="https://llm.example.com")
model = AgentModel(provider="openai-compatible", id="<modelo del proxy>", gateway="litellm", base_path="/v1")
import { AgentModel, SecretStore, litellmGateway } from "rayito";
await new SecretStore().create("litellm-key", "Bearer <clave virtual de LiteLLM>");
const gateway = litellmGateway("litellm-key", { upstream: "https://llm.example.com" });
const model = new AgentModel({ provider: "openai-compatible", id: "<modelo del proxy>", gateway: "litellm", basePath: "/v1" });
Qué se probó de verdad¶
| Ruta | Cómo se probó |
|---|---|
Bedrock (bedrock_gateway), OpenCode y deepagents |
con modelo real, en AWS real (MicroVMs) en la aceptación de esta release, desde Python y TypeScript |
API compatible con OpenAI de Bedrock (bedrock-runtime /openai/v1 y bedrock-mantle) por openai_compatible_gateway |
con modelo real, en el entorno local, con el egress cerrado: herramientas, sesión y uso de tokens |
provider="openai" (Responses API) contra bedrock-mantle |
con modelo real, en local, una vuelta sin herramientas (bedrock-mantle se aparta de la API de OpenAI con herramientas) |
LiteLLM (litellm_gateway) delante de Bedrock |
con modelo real, en local: herramientas, sesión y uso de tokens |
| OpenAI, Gemini, Azure OpenAI, OpenRouter, Groq, Mistral, DeepSeek y xAI | sólo contra un upstream falso en local: los nueve presets × OpenCode y deepagents × Python y TypeScript completan una vuelta, la clave llega en su cabecera, el marcador nunca sale y una ruta fuera de la lista recibe 403 |
Esas ocho rutas no se han probado con una clave real del proveedor. Si
tienes una, la prueba de humo
(make local-providers-smoke) hace una vuelta corta contra la API real por
menos de 0,01 USD por proveedor.
Proveedores que no se admiten, y por qué¶
Rayito sólo admite claves de API. Las suscripciones de consumo se revisaron y no se admiten:
| Opción | Por qué |
|---|---|
| Plan de ChatGPT (OAuth de OpenCode) | usa el client_id de otra aplicación y endpoints que OpenAI pide no usar (models-and-inference); además pondría el refresh token al alcance del agente |
| Plan de ChatGPT con "Sign in with ChatGPT" | aplazada: para una app alojada en remoto OpenAI pide un formulario de interés (overview) |
| Claude Pro/Max | los términos de consumo de Anthropic no permiten que terceros enruten peticiones con esas credenciales; usa una clave de Anthropic o Bedrock |
| GitHub Copilot | sin términos para flotas desatendidas (política de uso aceptable) |
| SuperGrok | sin términos para automatización; usa la clave de API de xAI |
La explicación completa está en Proveedores no admitidos.
El timeout y abort() del agente paran todo lo que lanzó¶
OpenCode y deepagents corren cada orden de su herramienta de shell en una
sesión propia (setsid). Hasta 0.9, el timeout sólo mataba el proceso del
runtime: si el agente había lanzado sleep 600 o un servidor, ese proceso
seguía vivo, y el siguiente run() encontraba el agente ocupado
(reason="busy").
Desde 0.10.0, con una imagen de rayd 0.10.0, esto queda garantizado:
- Al vencer el timeout (
timeout_seconds/timeoutMs),raydpide a todo el árbol que pare (SIGTERM) y, si a los 5 s queda algo, lo congela y lo mata. El stream acaba conreason="timeout"sólo cuando no queda nada vivo. abort(), unAbortSignaly los límites del SDK (max_steps,max_total_tokens) matan el árbol entero sin esperar.- Nada más se toca: ni otros comandos, ni la terminal, ni el kernel de
run_code. Y si la ejecución termina por sí sola, lo que dejó corriendo a propósito (un servidor) sigue vivo.
No tienes que cambiar tu código:
import { AgentLimits, type AgentSpec, type Sandbox } from "rayito";
export async function runWithDeadline(sbx: Sandbox, spec: AgentSpec): Promise<void> {
const controller = new AbortController();
setTimeout(() => controller.abort(), 60_000);
const result = await sbx.agent.run("Arranca el servidor de desarrollo y prueba la API.", {
spec,
limits: new AgentLimits({ timeoutMs: 120_000 }),
signal: controller.signal,
});
console.log(result.text);
}
La misma garantía está disponible para cualquier comando con
commands.run(..., kill_tree=True) / commands.run(cmd, { killTree: true })
(Comandos). Con una imagen anterior el
campo se ignora y el timeout sólo alcanza al grupo del runtime. Detalle en
Qué garantizan el timeout y abort().
Correcciones de deepagents¶
- Funciona con los modelos que no son de Bedrock.
raydexportaALL_PROXY=socks5h://…en el sandbox, y el cliente HTTP de deepagents fallaba al arrancar (AgentFailedconImportError) con OpenAI, xAI, Azure OpenAI, Gemini, Anthropic y las APIs compatibles con OpenAI. La imagen del agente lleva ahorasocksio. - Cuenta los tokens de las APIs compatibles con OpenAI. Con
provider="openai-compatible", cada paso llegaba con 0 tokens:result.usagequedaba a cero ymax_total_tokens/maxTotalTokensno cortaba nunca. Ahora se pide el uso en streaming, como ya hacía OpenCode.
Las dos necesitan reconstruir la imagen del agente con AgentTemplate
de 0.10.0.
Otros cambios¶
rayito doctorconoce la serie 0.10: exige elrayddel tagrayd-v0.10.0para el SDK 0.10 (Compatibilidad).rayd0.10.0:StartRequest.kill_tree(nuevo,falsepor defecto) hace al proceso child subreaper y lleva el timeout y las señales a todo su árbol.
Cómo actualizar desde 0.9.x¶
-
Actualiza el SDK.
-
Republica tu imagen sobre
rayd0.10.0 (Imágenes).rayito doctorexige elrayddel tagrayd-v0.10.0para el SDK 0.10 y da FAIL en la comprobacióncompatibilitycon una imagen 0.9. - Si usas el agente, reconstruye su imagen con
AgentTemplateorayito agent template build: la nueva llevarayd0.10.0,socksioy el runner de deepagents corregido.
No hay cambios incompatibles en la API pública de 0.9.x.