Ir al contenido

Edge deploys: latencia baja desde cualquier lugar

Tu RAG corre en local, latencia ~150ms por query. Movés a un servidor en Argentina, los usuarios en Argentina siguen viendo ~150ms. Los de Tokio ven ~600ms. Los de Frankfurt ven ~400ms. Latencia global: pésima.

Edge te corre el código en ~300 puntos de presencia distribuidos por el planeta. Latencia desde cualquier lugar del mundo: ~50ms para conectar, más el costo de tu pipeline. Tu RAG se siente igual de rápido para todos.

Tradeoff: edge runtime es V8 isolates, no Node.js. Hay reglas:

  • No fs: no podés leer archivos. (Tu corpus tiene que estar accesible vía HTTP — Qdrant Cloud, R2, KV.)
  • No process module: no podés leer env de la forma usual. Las vars vienen vía env argument.
  • No native deps (binarios .node): solo JavaScript / WASM puro.
  • No require(): solo ESM.
  • fetch() global: lo único que podés usar para network.
StageEdge?Por qué
Ingest (read corpus, chunk, embed, upsert)Toca filesystem. Queda en Node.
RefreshIgual que ingest.
Query — embedSolo fetch() al endpoint del embedder.
Query — retrievefetch() al REST API de Qdrant.
Query — generatefetch() al endpoint del LLM.
Query — rerank/multi-query/rewriteTambién fetch puro.

Patrón resultante: dos deploys. Un cron job en Node (Cloudflare Cron Trigger, Vercel Cron, Lambda scheduled) hace ingest+refresh. Un Worker en edge atiende las queries.

packages/01-vercel-ai-sdk/src/ops/edge.ts — ~120 líneas, sin imports al stack del repo (solo tipos Env y funciones helper que viven todas en el archivo):

edge.ts: shape
export interface Env {
OLLAMA_URL: string;
OLLAMA_LLM: string;
OLLAMA_EMBED: string;
QDRANT_URL: string;
QDRANT_API_KEY?: string;
COLLECTION?: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// 1. POST / → query path
// 2. GET /health → simple healthcheck
// ...
},
};

Env tiene strings con los endpoints públicos. Esto es la primera diferencia importante con el código local: no podés usar localhost:11434 desde edge — el worker no tiene acceso a tu máquina.

edge.ts: helpers
async function embed(env: Env, text: string): Promise<number[]> {
const res = await fetch(`${env.OLLAMA_URL}/v1/embeddings`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: env.OLLAMA_EMBED, input: text }),
});
if (!res.ok) throw new Error(`embed failed: ${res.status}`);
const data = await res.json();
return data.data[0].embedding;
}
async function retrieve(env: Env, vector: number[], topK: number): Promise<QdrantPoint[]> {
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (env.QDRANT_API_KEY) headers['api-key'] = env.QDRANT_API_KEY;
const res = await fetch(
`${env.QDRANT_URL}/collections/${env.COLLECTION ?? 'rag_vercel'}/points/query`,
{ method: 'POST', headers, body: JSON.stringify({ query: vector, limit: topK, with_payload: true }) },
);
if (!res.ok) throw new Error(`qdrant query failed: ${res.status}`);
const data = await res.json();
return data.result.points;
}
// generate(...) similar

Todo fetch puro. Sin embed() del AI SDK (que requiere Node bindings). Sin QdrantClient (que también). Esto es el AI SDK de bajo nivel: HTTP a una API.

Cuenta gratis en cloudflare.com. El free tier:

  • 100k requests/día gratis
  • 10ms CPU por request en free, 30ms en paid (paid: $5/mes)
  • Sin egress charges

Para self-study, free tier alcanza con sobra.

Ventana de terminal
pnpm wrangler login

Abre browser, pide autorización, listo. Token persiste en ~/.wrangler/.

Hay un sample en packages/01-vercel-ai-sdk/wrangler.toml.example. Copialo:

Ventana de terminal
cp packages/01-vercel-ai-sdk/wrangler.toml.example \
packages/01-vercel-ai-sdk/wrangler.toml

(wrangler.toml está gitignorado para que no commitees URLs y secretos por accidente; el .example queda tracked como referencia.)

Editá los valores:

wrangler.toml
name = "rag-vercel-edge"
main = "src/ops/edge.ts"
compatibility_date = "2026-05-01"
[vars]
OLLAMA_URL = "https://your-llm-host.example.com" # NO localhost
OLLAMA_LLM = "llama3.2:3b"
OLLAMA_EMBED = "nomic-embed-text"
QDRANT_URL = "https://your-qdrant-cluster.qdrant.io"
COLLECTION = "rag_vercel"

Y los secretos van por separado:

Ventana de terminal
pnpm wrangler secret put QDRANT_API_KEY
# pega el valor cuando pregunta
Ventana de terminal
cd packages/01-vercel-ai-sdk
pnpm wrangler deploy

Output:

Total Upload: 8.42 KiB
Worker Startup Time: 5 ms
Uploaded rag-vercel-edge (3.15 sec)
Deployed rag-vercel-edge triggers (0.83 sec)
https://rag-vercel-edge.<your-subdomain>.workers.dev

Y ya está. El worker está vivo en cada PoP de Cloudflare.

Ventana de terminal
curl -X POST https://rag-vercel-edge.<your-subdomain>.workers.dev/ \
-H 'Content-Type: application/json' \
-d '{"question": "¿Qué propone Clean Architecture?"}'

Output esperado:

{
"answer": "Clean Architecture propone separar las dependencias en capas concéntricas...",
"sources": [
{ "filename": "clean-architecture.md", "score": 0.7234 },
{ "filename": "clean-architecture.md", "score": 0.6118 },
{ "filename": "hexagonal-architecture.md", "score": 0.5891 },
{ "filename": "solid-principles.md", "score": 0.5102 }
]
}

Si tu modelo está en el catálogo de Cloudflare Workers AI, te ahorrás el host externo del LLM. Bind:

wrangler.toml: AI binding
[ai]
binding = "AI"

Y en el worker:

edge.ts: AI binding (si lo activás)
async function generate(env: Env, prompt: string): Promise<string> {
if (env.AI) {
const response = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', { prompt });
return response.response;
}
// fallback al fetch externo (default del archivo)
}

Ventaja: el LLM corre en la misma red que el worker. Latencia mínima.

Limitación: la cuota gratis de Workers AI es chica. Para producción real, pagás per-neuron.

  • Ingest: queda como Node script ejecutado en cron / build / scheduled worker que SÍ tiene fs. La regla mental: “ingest es batch, query es real-time”.
  • Refresh: idem. Cron una vez por día / hora / cuando aplique.
  • Eval harness (Nivel 2): queda en Node. Es batch, corre en CI o local.

Tu RAG es:

  • Inspectable (Nivel 1).
  • Medible (Nivel 2).
  • Robusto (Nivel 3).
  • Observable, económico, refrescable y deployable a edge (Nivel 4).

El último capítulo es la lista de chequeo antes de mandarlo a producción + el appendix de comandos del curso entero.