← Přehled / ai-search / how-to
Multitenancy
V multitenantní aplikaci musí každý tenant vidět pouze svá vlastní data. AI Search nabízí dva způsoby, jak vyhledávání izolovat podle tenanta: buď každému tenantovi přidělit vlastní instanci, nebo sdílet jednu instanci a filtrovat podle tenanta v době dotazu.
Zvolte přístup
| Přístup | Jak to izoluje | Zvolte jej, když |
|---|---|---|
| Instance na tenanta (doporučeno) | Každý tenant získá samostatnou instanci s vlastním úložištěm a indexem | Potřebujete silnou izolaci nebo za běhu vytváříte a mažete tenanty |
| Sdílená instance s filtrováním | Jedna instance obsahuje všechny tenanty; filtr metadat omezuje rozsah každého dotazu | Máte mnoho malých tenantů a chcete co nejjednodušší nastavení |
Předpoklady
Oba přístupy využívají Cloudflare Worker. Nejprve vytvořte projekt a poté postupujte podle zvolené možnosti.
- Zaregistrujte si účet Cloudflare ↗.
- Nainstalujte
Node.js↗.
Správce verzí Node.js
Použijte správce verzí Node, jako je Volta ↗ nebo nvm ↗ abyste se vyhnuli problémům s oprávněními a mohli měnit verze Node.js. Wrangler, o kterém se dozvíte dále v této příručce, vyžaduje verzi Node 16.17.0 nebo novější.
Vytvořte projekt Worker
Vytvořte nový projekt Worker pomocí create-cloudflare CLI (C3). C3 ↗ je nástroj příkazové řádky, který vám pomůže nastavit a nasadit nové aplikace na Cloudflare.
Vytvořte nový projekt s názvem tenant-search spuštěním:
npm create cloudflare@latest -- tenant-searchPři nastavení vyberte následující možnosti:
- Pro S čím byste chtěli začít?, vyberte
Hello World example. - Pro Jakou šablonu chcete použít?, vyberte
Worker only. - Pro Jaký jazyk chcete použít?, vyberte
TypeScript. - Pro Chcete používat git pro správu verzí?, vyberte
Yes. - Pro Chcete nasadit svou aplikaci?, vyberte
No(před nasazením provedeme ještě několik změn).
Přejděte do adresáře své aplikace:
cd tenant-searchMožnost 1: Jedna instance na tenanta
Toto je doporučeno přístup. Každý tenant získá vlastní instanci s vlastním úložištěm a vyhledávacím indexem, takže jeden tenant nikdy nemůže získat dokumenty jiného tenanta.
Za běhu vytvořte pro každého tenanta izolovanou instanci AI Search pomocí vazba jmenného prostoru.
Přidejte vazbu jmenného prostoru do svého Konfigurační soubor Wrangler:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"ai_search_namespaces": [
{
"binding": "TENANTS",
"namespace": "default",
"remote": true
}
]
}[[ai_search_namespaces]]
binding = "TENANTS"
namespace = "default"
remote = true remote volba umožňuje wrangler dev předávají požadavky na vaše nasazené instance, protože AI Search neběží lokálně.
Vestavěné úložiště
Instance každého tenanta obsahuje dokumenty, které do ní nahrajete přímo, bez externího zdroje dat.
Aktualizujte src/index.ts. Tento Worker určí tenanta podle hlavičky požadavku a poté vytvoří, naplní, prohledá a smaže jeho instanci.
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Identify the tenant from the request header.
const tenantId = request.headers.get("x-tenant-id");
if (!tenantId) {
return new Response("Missing x-tenant-id header", { status: 400 });
}
// Create a new instance for the tenant.
if (url.pathname === "/onboard" && request.method === "POST") {
const instance = await env.TENANTS.create({
id: `tenant-${tenantId}`,
});
return Response.json({ success: true, instance: await instance.info() });
}
// Upload a document to the tenant's instance.
if (url.pathname === "/upload" && request.method === "POST") {
const formData = await request.formData();
const file = formData.get("file");
const item = await env.TENANTS.get(`tenant-${tenantId}`).items.upload(
file.name,
await file.arrayBuffer(),
);
return Response.json({ success: true, item });
}
// Search the tenant's instance. Search is isolated to their instance.
if (url.pathname === "/search") {
const query = url.searchParams.get("q") || "";
const results = await env.TENANTS.get(`tenant-${tenantId}`).search({
messages: [{ role: "user", content: query }],
});
return Response.json(results);
}
// Delete the tenant's instance and all its data.
if (url.pathname === "/offboard" && request.method === "DELETE") {
await env.TENANTS.delete(`tenant-${tenantId}`);
return Response.json({ success: true });
}
return new Response("Not found", { status: 404 });
},
};export type Env = {
TENANTS: AiSearchNamespace;
};
export default {
async fetch(request, env): Promise<Response> {
const url = new URL(request.url);
// Identify the tenant from the request header.
const tenantId = request.headers.get("x-tenant-id");
if (!tenantId) {
return new Response("Missing x-tenant-id header", { status: 400 });
}
// Create a new instance for the tenant.
if (url.pathname === "/onboard" && request.method === "POST") {
const instance = await env.TENANTS.create({
id: `tenant-${tenantId}`,
});
return Response.json({ success: true, instance: await instance.info() });
}
// Upload a document to the tenant's instance.
if (url.pathname === "/upload" && request.method === "POST") {
const formData = await request.formData();
const file = formData.get("file") as File;
const item = await env.TENANTS.get(`tenant-${tenantId}`).items.upload(
file.name,
await file.arrayBuffer(),
);
return Response.json({ success: true, item });
}
// Search the tenant's instance. Search is isolated to their instance.
if (url.pathname === "/search") {
const query = url.searchParams.get("q") || "";
const results = await env.TENANTS.get(`tenant-${tenantId}`).search({
messages: [{ role: "user", content: query }],
});
return Response.json(results);
}
// Delete the tenant's instance and all its data.
if (url.pathname === "/offboard" && request.method === "DELETE") {
await env.TENANTS.delete(`tenant-${tenantId}`);
return Response.json({ success: true });
}
return new Response("Not found", { status: 404 });
},
} satisfies ExportedHandler<Env>;Spusťte místní vývojový server:
npx wrangler devPoté tenanta onboardujte, nahrajte dokument do jeho instance, vyhledejte v ní a nakonec ho odeberte. x-tenant-id hlavička omezuje každý požadavek na instanci daného tenanta:
# Create an isolated instance for tenant "acme"
curl -X POST http://localhost:8787/onboard -H "x-tenant-id: acme"
# Upload a document to acme's instance
curl -X POST http://localhost:8787/upload -H "x-tenant-id: acme" -F "file=@./handbook.pdf"
# Search acme's instance
curl "http://localhost:8787/search?q=vacation+policy" -H "x-tenant-id: acme"
# Delete acme's instance and all of its data
curl -X DELETE http://localhost:8787/offboard -H "x-tenant-id: acme"AI Search indexuje nahrané soubory asynchronně, počkejte tedy chvíli po nahrání, než začnete vyhledávat.
R2
Pokud data vašich tenantů již existují v R2, zajistěte instanci každého tenanta úložištěm R2 místo nahrávání dokumentů do vestavěného úložiště. Změňte /onboard trasu z vestavěné úložiště příklad pro vytvoření instance založené na R2. Způsob konfigurace závisí na tom, jak jsou data uspořádána.
Jeden bucket na tenanta: pokud jsou data každého tenanta již ve vlastním bucketu, nasměrujte instanci na celý tento bucket:
const instance = await env.TENANTS.create({
id: `tenant-${tenantId}`,
type: "r2",
source: `tenant-${tenantId}-bucket`,
});Sdílený bucket: pokud jsou data všech tenantů v jednom bucketu, uspořádaná podle složek:
- my-bucket
- customers/
- acme/
- globex/
- customers/
Omezte každou instanci na složku daného tenanta pomocí filtrování cest, takže vždy indexuje a prohledává pouze objekty daného tenanta:
const instance = await env.TENANTS.create({
id: `tenant-${tenantId}`,
type: "r2",
source: "my-bucket",
source_params: {
include_items: [`/customers/${tenantId}/**`],
},
});V obou uspořádáních AI Search zaindexuje objekty daného tenanta při příštím synchronizace. Dokumenty přidávejte zápisem do R2 místo nahrávání přes Worker a ponechte trasy pro vyhledávání a odebrání z příkladu s vestavěným úložištěm: každá instance vrací vždy jen výsledky vlastního tenanta a smazání instance odstraní její vyhledávací index, zatímco objekty v R2 zůstanou nedotčené.
Chcete-li si to vyzkoušet, spusťte npx wrangler dev a použijte stejný onboard, search, a offboard požadavky jako vestavěné úložiště. Krok s nahráváním přeskočte, protože AI Search indexuje objekty jednotlivých tenantů přímo z R2.
Možnost 2: Sdílená instance s filtrováním metadat při vyhledávání
Použijte jednu instanci AI Search a organizujte obsah podle tenanta pomocí cest ke složkám. Tento přístup funguje s oběma R2 buckety a vestavěné úložiště. Použijte filtry metadat v době dotazu, aby si každý tenant načítal pouze vlastní dokumenty.
Tato možnost prohledává existující instanci, proto si nejprve vytvořte instanci s názvem shared-instance a nejprve přidejte svůj obsah. Více informací najdete v Začínáme.
Přidejte vazbu instance do svého Konfigurační soubor Wrangler:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"ai_search": [
{
"binding": "SHARED_INSTANCE",
"instance_name": "shared-instance",
"remote": true
}
]
}[[ai_search]]
binding = "SHARED_INSTANCE"
instance_name = "shared-instance"
remote = trueOrganizujte svůj obsah podle tenanta pomocí jedinečných cest ke složkám:
- customer-a
- logs/
- contracts/
- customer-b
- contracts/
Aktualizujte src/index.ts a filtrujte podle složky tenanta při dotazu:
export default {
async fetch(request, env) {
const tenantId = request.headers.get("x-tenant-id");
if (!tenantId) {
return new Response("Missing x-tenant-id header", { status: 400 });
}
// Filter results to only return documents from this tenant's folder.
const results = await env.SHARED_INSTANCE.search({
messages: [{ role: "user", content: "When did I sign my agreement?" }],
ai_search_options: {
retrieval: {
filters: {
folder: { $gte: `${tenantId}/`, $lt: `${tenantId}0` },
},
},
},
});
return Response.json(results);
},
};export type Env = {
SHARED_INSTANCE: AiSearchInstance;
};
export default {
async fetch(request, env): Promise<Response> {
const tenantId = request.headers.get("x-tenant-id");
if (!tenantId) {
return new Response("Missing x-tenant-id header", { status: 400 });
}
// Filter results to only return documents from this tenant's folder.
const results = await env.SHARED_INSTANCE.search({
messages: [{ role: "user", content: "When did I sign my agreement?" }],
ai_search_options: {
retrieval: {
filters: {
folder: { $gte: `${tenantId}/`, $lt: `${tenantId}0` },
},
},
},
});
return Response.json(results);
},
} satisfies ExportedHandler<Env>;Tento příklad používá filtr „starts with“ aby odpovídal všem souborům ve složce tenanta, včetně podsložek.
Spusťte místní vývojový server:
npx wrangler devOdešlete požadavek jako jednotlivý tenant. Worker omezí vyhledávání na složku daného tenanta, takže se výsledky nikdy nepřekrývají:
curl http://localhost:8787/ -H "x-tenant-id: customer-a"
curl http://localhost:8787/ -H "x-tenant-id: customer-b"Nasadit
Přihlaste se pomocí svého účtu Cloudflare, poté nasaďte svůj Worker, aby byl dostupný na internetu:
npx wrangler login
npx wrangler deploy