Skip to content

RAG Intelligence Bindings

The platform bindings and secrets the RAG intelligence stack (capability #1185) runs on: Cloudflare Workers-AI for inference and Vectorize for similarity search. Requirement #1186 provisions them; this document is the topology, the two-index model, the model-selection decision, and the smoke-check procedure.

Requirement #1186 is the committed reversal of decision #1123's earlier infra deferral ("no new AI/Vectorize bindings"). The bindings live in wrangler.jsonc; the retrieval that consumes them is the #66 contract (see corpus-retrieval.md), and the media seam is the #1113 adapter in src/lib/server/media-analysis.ts.

Model-selection decision (#1195)

The provider choice is recorded in decision #1195 (interview 2026-07-11, confirmed by paul@iwpi.com), not a separate issue:

  • Provider — CF Workers-AI for both vision (media tagging) and embeddings (corpus retrieval + media similarity). Vision via a @cf image model; embeddings via @cf/baai/bge-*.
  • Why in-boundary matters: the image/text never leaves the Cloudflare perimeter, so #1190's media-egress/consent surface stays light (media-to-model still ships gated, egress-OFF by default — but there is no third-party egress to govern) and #1191's cost model is first-party (trivial unit cost under the $50/mo cap).

The choice deliberately scopes two downstream requirements: #1190 (egress governance) carries only in-boundary consent, and #1191 (cost model) is first-party — both lighter than the external-API alternative would have made them.

Bindings

BindingTypeResourceNotes
AIWorkers-AINo API key — authenticates via the platform. Always hits the remote account (remote: true silences the dev warning).
VECTORIZE_CORPUSVectorizenanaselect-corpus-textCorpus passage index — 768-dim, cosine. remote: true required (no local emulation).
VECTORIZE_MEDIAVectorizenanaselect-media-simMedia similarity index (#1189) — 768-dim, cosine, keyed by media asset id. remote: true required.

Workers-AI

env.AI.run('@cf/baai/bge-base-en-v1.5', { text: [...] }) returns { data: number[][] } — one 768-dim vector per input. Workers-AI is reachable in local dev: wrangler proxies inference to the remote account (there is no local model). It is paid per-inference; spend is governed by #1191 and every real-model swap is gated behind the #1193 feature flag before any prod flip.

The two-index model

A Vectorize index is single-dimension and its dimension is immutable after creation, so an index is only declared once its embedding model — and thus its dimension — is decided.

  • VECTORIZE_CORPUSnanaselect-corpus-text (created): the Product Expertise Corpus (#47) passage index, read through the #66 retrieval contract for the hybrid lexical + vector blend (#1187). Model @cf/baai/bge-base-en-v1.5, 768-dim, cosine. The dimension was confirmed against a live embed before the index was created, so the immutable value can never be wrong.

  • VECTORIZE_MEDIAnanaselect-media-sim (declared in #1189): the media "assets like this" index. #1189 picked the embedder — @cf/baai/bge-base-en-v1.5 (768-dim) over each asset's textual identity (filename / alt / title / caption) — deliberately reusing the corpus model so the immutable index inherits the corpus index's already-live-validated 768 dimension (no wrong-dim risk). It embeds text metadata only, so no media bytes leave the perimeter; true image/visual embedding is a later drop-in behind the same MediaEmbedder seam, gated on #1190's consent-driven media-egress governance. The write side is runMediaEmbeddingPass (src/lib/server/media-embedding-pass.ts, npm run media:embed); the read side is similarAssets (src/lib/server/media-analysis.ts), which prefers this index and degrades to the brute-force D1 cosine when MEDIA_SIM_VECTORIZE is off or the binding is unavailable (AC-05). The vector is upserted to Vectorize (primary) and kept in media_embeddings.vector (the degrade seed + provenance). Flag default OFF, gated behind #1193; relevance vs the stub baseline is measured by the #1192 eval harness.

Secrets — the shared model-provider seam

The RAG stack runs entirely on the AI binding, which needs no API key. So nothing is required in .dev.vars for #1187/#1188/#1189.

.dev.vars.example still documents the shared model-secret provisioning path (folds in the standing TODO #1183): any provider that does need a key is provisioned the same way — a Worker secret in prod (wrangler secret put <NAME>), mirrored in local .dev.vars, and its name only documented in .dev.vars.example (never a value; .dev.vars is git-ignored and never deployed). NanaSage (#760) inherits this seam: if its chat provider is an external API (e.g. Anthropic → ANTHROPIC_API_KEY), set the key that way; if NanaSage adopts Workers-AI text-gen (a candidate flagged on #1195), no key is needed and the seam stays unset.

Smoke check

scripts/rag-smoke-check.ts (npm run rag:smoke) proves the whole foundation with one real round-trip:

  1. Embed a passage with @cf/baai/bge-base-en-v1.5 and assert 768 dimensions.
  2. Upsert the vector into nanaselect-corpus-text.
  3. Embed a related query and confirm the upserted vector returns as the top match (polls through Vectorize's eventual consistency).
  4. Delete the test vector, leaving the index empty for real ingestion (#1187).
$ npm run rag:smoke
[1/4] embed  — model @cf/baai/bge-base-en-v1.5, dim 768
[2/4] upsert — id smoke-check:<ts>
[3/4] query  — matched id smoke-check:<ts> at cosine score 0.8451
[4/4] cleanup — deleted test vector smoke-check:<ts>
✅ RAG binding smoke check PASSED

Local-dev caveat (#1186 AC-05)

Vectorize has no local emulation under wrangler/miniflare. Every Vectorize call — including this smoke check and any app call under vite dev — therefore runs against the remote index. This is why VECTORIZE_CORPUS carries remote: true in wrangler.jsonc: without it, getPlatformProxy errors with "Binding VECTORIZE_CORPUS needs to be run remotely." remote: true routes local-dev calls to the real index and is inert on deploy (a deployed Worker always uses the real index). Consequence: local dev makes small, real (paid) Vectorize and Workers-AI calls — a known, documented limitation, not a bug, and not a blocker on local Vectorize emulation.

Provisioning (one-time)

bash
# corpus (already done)
wrangler vectorize create nanaselect-corpus-text --dimensions=768 --metric=cosine
# media similarity (#1189) — run once before the first `npm run media:embed` / prod flip
wrangler vectorize create nanaselect-media-sim --dimensions=768 --metric=cosine

Verify: wrangler vectorize get nanaselect-corpus-text → 768 / cosine (and likewise nanaselect-media-sim). The code ships flag-OFF and degrade-safe, so it is inert until the index exists and MEDIA_SIM_VECTORIZE is armed (#1193).