Appearance
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
@cfimage 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
| Binding | Type | Resource | Notes |
|---|---|---|---|
AI | Workers-AI | — | No API key — authenticates via the platform. Always hits the remote account (remote: true silences the dev warning). |
VECTORIZE_CORPUS | Vectorize | nanaselect-corpus-text | Corpus passage index — 768-dim, cosine. remote: true required (no local emulation). |
VECTORIZE_MEDIA | Vectorize | nanaselect-media-sim | Media 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_CORPUS→nanaselect-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_MEDIA→nanaselect-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 sameMediaEmbedderseam, gated on #1190's consent-driven media-egress governance. The write side isrunMediaEmbeddingPass(src/lib/server/media-embedding-pass.ts,npm run media:embed); the read side issimilarAssets(src/lib/server/media-analysis.ts), which prefers this index and degrades to the brute-force D1 cosine whenMEDIA_SIM_VECTORIZEis off or the binding is unavailable (AC-05). The vector is upserted to Vectorize (primary) and kept inmedia_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:
- Embed a passage with
@cf/baai/bge-base-en-v1.5and assert 768 dimensions. - Upsert the vector into
nanaselect-corpus-text. - Embed a related query and confirm the upserted vector returns as the top match (polls through Vectorize's eventual consistency).
- 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 PASSEDLocal-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=cosineVerify: 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).