INTEGRITY Dokumentace

Vytvořte AI s Retrieval Augmented Generation (RAG)

Tento návod vás provede nastavením a nasazením první aplikace s Cloudflare AI. Vytvoříte plnohodnotnou aplikaci poháněnou umělou inteligencí s využitím nástrojů jako Workers AI, Vectorize, D1 a Cloudflare Workers.

Na konci tohoto tutoriálu budete mít vytvořený nástroj AI, který umožňuje ukládat informace a dotazovat se na ně pomocí velkého jazykového modelu. Tento postup, známý jako Retrieval Augmented Generation neboli RAG, je užitečný projekt, který lze sestavit kombinací více částí nástrojové sady AI od Cloudflare. Ke stavbě této aplikace nepotřebujete žádné zkušenosti s prací s nástroji AI.

  1. Zaregistrujte si účet Cloudflare.
  2. 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ší.

Budete také potřebovat přístup k Vectorize. V tomto tutoriálu vám ukážeme, jak lze volitelně integrovat Anthropic Claude také. Budete potřebovat API klíč Anthropic to udělat.

1. Vytvořte nový projekt Worker

C3 (create-cloudflare-cli) je nástroj příkazové řádky, který vám pomůže co nejrychleji nastavit a nasadit Workers do Cloudflare.

Otevřete okno terminálu a spusťte C3 pro vytvoření projektu Workeru:

npm create cloudflare@latest -- rag-ai-tutorial

Při nastavení vyberte následující možnosti:

Ve vašem adresáři projektu C3 vygenerovalo několik souborů.

Jaké soubory C3 vytvořil?

  1. wrangler.jsonc: Váš Wrangler konfigurační soubor.
  2. index.js (v /src): Minimální 'Hello World!' Worker napsaný v ES modul syntaxe.
  3. package.json: Minimální konfigurační soubor závislostí Node.
  4. package-lock.json: Viz npm dokumentaci k package-lock.json.
  5. node_modules: Viz npm dokumentace node_modules.

Nyní přejděte do nově vytvořeného adresáře:

cd rag-ai-tutorial

2. Vyvíjejte pomocí Wrangler CLI

Rozhraní příkazové řádky Workers, Wrangler, umožňuje vám vytvořit, test, a deploy vaše Workers projekty. C3 ve výchozím nastavení do projektů nainstaluje Wrangler.

Jakmile vytvoříte svůj první Worker, spusťte wrangler dev příkaz v adresáři projektu ke spuštění lokálního serveru pro vývoj Workeru. Díky tomu budete moci Worker během vývoje testovat lokálně.

npx wrangler dev

Nyní budete moci přejít na http://localhost:8787 a uvidíte svého Workera v provozu. Jakákoli změna kódu spustí nové sestavení a po obnovení stránky se zobrazí aktuální výstup vašeho Workeru.

3. Přidání AI bindingu

Chcete-li začít používat AI produkty Cloudflare, můžete přidat ai blok do Konfigurační soubor Wrangler jako vzdálený binding. Tím se ve vašem kódu nastaví vazba na modely AI Cloudflare, kterou můžete použít k práci s dostupnými modely AI na platformě.

Tento příklad využívá @cf/meta/llama-3-8b-instruct model, který generuje text.

{
	"ai": {
		"binding": "AI",
		"remote": true
	}
}
[ai]
binding = "AI"
remote = true

Nyní najděte soubor src/index.js soubor. Uvnitř souboru fetch handleru můžete dotazovat AI binding:

export default {
	async fetch(request, env, ctx) {
		const answer = await env.AI.run("@cf/meta/llama-3-8b-instruct", {
			messages: [{ role: "user", content: `What is the square root of 9?` }],
		});

		return new Response(JSON.stringify(answer));
	},
};

Dotazováním LLM prostřednictvím AI vazby můžeme přímo v kódu pracovat s velkými jazykovými modely Cloudflare AI. V tomto příkladu používáme @cf/meta/llama-3-8b-instruct model, který generuje text.

Nasaďte svůj Worker pomocí wrangler:

npx wrangler deploy

Požadavek na váš Worker nyní vygeneruje textovou odpověď z LLM a vrátí ji jako objekt JSON.

curl https://example.username.workers.dev
{"response":"Answer: The square root of 9 is 3."}

4. Přidejte embeddingy pomocí Cloudflare D1 a Vectorize

Embeddingy vám umožňují rozšířit jazykové modely, které používáte ve svých projektech Cloudflare AI, o další možnosti. Provádí se to přes Vectorize, vektorová databáze Cloudflare.

Chcete-li začít používat Vectorize, vytvořte nový index embeddingů pomocí wrangler. Tento index bude ukládat vektory s 768 dimenzemi a k určení, které vektory jsou si nejpodobnější, použije kosinovou podobnost:

npx wrangler vectorize create vector-index --dimensions=768 --metric=cosine

Poté přidejte konfigurační údaje nového indexu Vectorize do Konfigurační soubor Wrangler:

{
	// ... existing wrangler configuration
	"vectorize": [
		{
			"binding": "VECTOR_INDEX",
			"index_name": "vector-index"
		}
	]
}
[[vectorize]]
binding = "VECTOR_INDEX"
index_name = "vector-index"

Vektorový index umožňuje ukládat sadu dimenzí, což jsou desetinná čísla s pohyblivou řádovou čárkou reprezentující vaše data. Pokud chcete vektorovou databázi dotazovat, můžete svůj dotaz rovněž převést na dimenze. Vectorize je navržen tak, aby efektivně určil, které uložené vektory jsou nejpodobnější vašemu dotazu.

Chcete-li implementovat funkci vyhledávání, musíte nastavit databázi D1 od Cloudflare. V D1 můžete ukládat data své aplikace, která poté převedete do vektorového formátu. Když uživatel zadá dotaz odpovídající některému z vektorů, můžete mu zobrazit příslušná data.

Vytvořte novou databázi D1 pomocí wrangler:

npx wrangler d1 create database

Poté vložte konfigurační údaje z výstupu předchozího příkazu do Konfigurační soubor Wrangler:

{
	// ... existing wrangler configuration
	"d1_databases": [
		{
			"binding": "DB", // available in your Worker on env.DB
			"database_name": "database",
			"database_id": "abc-def-geh" // replace this with a real database_id (UUID)
		}
	]
}
[[d1_databases]]
binding = "DB"
database_name = "database"
database_id = "abc-def-geh"

V této aplikaci vytvoříme notes tabulku v D1, díky které budeme moci ukládat poznámky a později je načítat ve Vectorize. Tuto tabulku vytvoříte spuštěním příkazu SQL pomocí wrangler d1 execute:

npx wrangler d1 execute database --remote --command "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT NOT NULL)"

Nyní můžeme do naší databáze přidat novou poznámku pomocí wrangler d1 execute:

npx wrangler d1 execute database --remote --command "INSERT INTO notes (text) VALUES ('The best pizza topping is pepperoni')"

5. Vytvořte workflow

Než začneme vytvářet poznámky, představíme si Cloudflare Workflow. Díky tomu budeme moci definovat trvalé workflow, které bezpečně a spolehlivě provede všechny kroky procesu RAG.

Chcete-li začít, přidejte novou [[workflows]] blok do vašeho Konfigurační soubor Wrangler:

{
	// ... existing wrangler configuration
	"workflows": [
		{
			"name": "rag",
			"binding": "RAG_WORKFLOW",
			"class_name": "RAGWorkflow"
		}
	]
}
[[workflows]]
name = "rag"
binding = "RAG_WORKFLOW"
class_name = "RAGWorkflow"

V src/index.js, přidejte novou třídu s názvem RAGWorkflow která rozšiřuje WorkflowEntrypoint:

import { WorkflowEntrypoint } from "cloudflare:workers";

export class RAGWorkflow extends WorkflowEntrypoint {
	async run(event, step) {
		await step.do("example step", async () => {
			console.log("Hello World!");
		});
	}
}

Tato třída definuje jeden krok workflow, který do konzole zaloguje „Hello World!“. Do svého workflow můžete přidat libovolný počet kroků.

Samotný tento workflow nedělá nic. Abychom workflow spustili, zavoláme vazbu RAG_WORKFLOW vazbu a předejte jí libovolné parametry, které workflow potřebuje k úspěšnému dokončení. Zde je příklad, jak lze workflow zavolat:

env.RAG_WORKFLOW.create({ params: { text } });

6. Vytvořte poznámky a přidejte je do Vectorize

Abychom funkci Workers rozšířili o podporu více tras, přidáme hono, knihovnu pro směrování ve Workers. Díky ní budeme moci vytvořit novou cestu pro přidávání poznámek do naší databáze. Nainstalujte hono pomocí npm:

npm i hono

Poté importujte hono do vašeho src/index.js soubor. Měli byste také aktualizovat fetch handler tak, aby používal hono:

import { Hono } from "hono";
const app = new Hono();

app.get("/", async (c) => {
	const answer = await c.env.AI.run("@cf/meta/llama-3-8b-instruct", {
		messages: [{ role: "user", content: `What is the square root of 9?` }],
	});

	return c.json(answer);
});

export default app;

Tím se vytvoří trasa na kořenové cestě / která je funkčně shodná s předchozí verzí vaší aplikace.

Nyní můžeme workflow upravit tak, aby začal přidávat poznámky do naší databáze a generovat pro ně příslušné embeddingy.

Tento příklad využívá @cf/baai/bge-base-en-v1.5 model, které lze použít k vytvoření embeddingu. Embeddingy se ukládají a načítají uvnitř Vectorize, vektorová databáze Cloudflare. Uživatelský dotaz se také převede na embedding, aby ho bylo možné použít pro vyhledávání ve Vectorize.

import { WorkflowEntrypoint } from "cloudflare:workers";

export class RAGWorkflow extends WorkflowEntrypoint {
	async run(event, step) {
		const env = this.env;
		const { text } = event.payload;

		const record = await step.do(`create database record`, async () => {
			const query = "INSERT INTO notes (text) VALUES (?) RETURNING *";

			const { results } = await env.DB.prepare(query).bind(text).run();

			const record = results[0];
			if (!record) throw new Error("Failed to create note");
			return record;
		});

		const embedding = await step.do(`generate embedding`, async () => {
			const embeddings = await env.AI.run("@cf/baai/bge-base-en-v1.5", {
				text: text,
			});
			const values = embeddings.data[0];
			if (!values) throw new Error("Failed to generate vector embedding");
			return values;
		});

		await step.do(`insert vector`, async () => {
			return env.VECTOR_INDEX.upsert([
				{
					id: record.id.toString(),
					values: embedding,
				},
			]);
		});
	}
}

Workflow provádí následující:

  1. Přijímá text .
  2. Vložte nový řádek do notes tabulku v D1 a načíst id nového řádku.
  3. Převeďte text na vektor pomocí embeddings model bindingu LLM.
  4. Proveďte upsert id a vectors do vector-index index ve Vectorize.

Tímto vytvoříte novou vektorovou reprezentaci poznámky, kterou lze později použít k jejímu vyhledání.

Abychom kód dokončili, přidáme trasu, díky které budou moci uživatelé odesílat poznámky do databáze. Tato trasa zpracuje tělo požadavku JSON, získá note parametr a vytvořte novou instanci workflow, do které parametr předáte:

app.post("/notes", async (c) => {
	const { text } = await c.req.json();
	if (!text) return c.text("Missing text", 400);
	await c.env.RAG_WORKFLOW.create({ params: { text } });
	return c.text("Created note", 201);
});

7. Proveďte dotaz do Vectorize a načtěte poznámky

Kód dokončíte úpravou kořenové cesty (/) pro dotazování Vectorize. Dotaz převedete na vektor a poté použijete vector-index index k nalezení nejpodobnějších vektorů.

topK parametr omezuje počet vektorů, které funkce vrátí. Pokud například nastavíte topK rovna 1 vrátí pouze nejpodobnější vektor na základě dotazu. Pokud nastavíte topK na 5 vrátí 5 nejpodobnějších vektorů.

Když máte seznam podobných vektorů, můžete načíst poznámky odpovídající ID záznamů uložených spolu s těmito vektory. V tomto případě načítáme jen jednu poznámku, ale podle potřeby si to můžete upravit.

Text těchto poznámek můžete vložit jako kontext do promptu pro binding LLM. To je základ metody Retrieval-Augmented Generation (RAG): poskytnutí dodatečného kontextu z dat mimo LLM, který vylepší text generovaný LLM.

Prompt upravíme tak, aby obsahoval kontext, a požádáme LLM, aby tento kontext při odpovědi použil:

import { Hono } from "hono";
const app = new Hono();

// Existing post route...
// app.post('/notes', async (c) => { ... })

app.get("/", async (c) => {
	const question = c.req.query("text") || "What is the square root of 9?";

	const embeddings = await c.env.AI.run("@cf/baai/bge-base-en-v1.5", {
		text: question,
	});
	const vectors = embeddings.data[0];

	const vectorQuery = await c.env.VECTOR_INDEX.query(vectors, { topK: 1 });
	let vecId;
	if (
		vectorQuery.matches &&
		vectorQuery.matches.length > 0 &&
		vectorQuery.matches[0]
	) {
		vecId = vectorQuery.matches[0].id;
	} else {
		console.log("No matching vector found or vectorQuery.matches is empty");
	}

	let notes = [];
	if (vecId) {
		const query = `SELECT * FROM notes WHERE id = ?`;
		const { results } = await c.env.DB.prepare(query).bind(vecId).run();
		if (results) notes = results.map((vec) => vec.text);
	}

	const contextMessage = notes.length
		? `Context:\n${notes.map((note) => `- ${note}`).join("\n")}`
		: "";

	const systemPrompt = `When answering the question or responding, use the context provided, if it is provided and relevant.`;

	const { response: answer } = await c.env.AI.run(
		"@cf/meta/llama-3-8b-instruct",
		{
			messages: [
				...(notes.length ? [{ role: "system", content: contextMessage }] : []),
				{ role: "system", content: systemPrompt },
				{ role: "user", content: question },
			],
		},
	);

	return c.text(answer);
});

app.onError((err, c) => {
	return c.text(err);
});

export default app;

8. Přidejte model Anthropic Claude (volitelné)

Pokud pracujete s rozsáhlejšími dokumenty, můžete využít funkci společnosti Anthropic nazvanou Modely Claude, které mají velká kontextová okna a dobře se hodí pro RAG workflow.

Chcete-li začít, nainstalujte @anthropic-ai/sdk balíček:

npm i @anthropic-ai/sdk

V src/index.js, můžete upravit GET / trasu, která zkontroluje přítomnost ANTHROPIC_API_KEY proměnnou prostředí. Pokud je nastavena, můžeme generovat text pomocí Anthropic SDK. Pokud nastavena není, použijeme jako záložní řešení stávající kód Workers AI:

import Anthropic from '@anthropic-ai/sdk';

app.get('/', async (c) => {
  // ... Existing code
	const systemPrompt = `When answering the question or responding, use the context provided, if it is provided and relevant.`

	let modelUsed = ""
	let response = null

	if (c.env.ANTHROPIC_API_KEY) {
		const anthropic = new Anthropic({
			apiKey: c.env.ANTHROPIC_API_KEY
		})

		const model = "claude-3-5-sonnet-latest"
		modelUsed = model

		const message = await anthropic.messages.create({
			max_tokens: 1024,
			model,
			messages: [
				{ role: 'user', content: question }
			],
			system: [systemPrompt, notes ? contextMessage : ''].join(" ")
		})

		response = {
			response: message.content.map(content => content.text).join("\n")
		}
	} else {
		const model = "@cf/meta/llama-3.1-8b-instruct"
		modelUsed = model

		response = await c.env.AI.run(
			model,
			{
				messages: [
					...(notes.length ? [{ role: 'system', content: contextMessage }] : []),
					{ role: 'system', content: systemPrompt },
					{ role: 'user', content: question }
				]
			}
		)
	}

	if (response) {
		c.header('x-model-used', modelUsed)
		return c.text(response.response)
	} else {
		return c.text("We were unable to generate output", 500)
	}
})

Nakonec je potřeba nastavit ANTHROPIC_API_KEY proměnnou prostředí ve vaší aplikaci Workers. Můžete to udělat pomocí wrangler secret put:

$ npx wrangler secret put ANTHROPIC_API_KEY

9. Odstraňte poznámky a vektory

Jakmile poznámku už nepotřebujete, můžete ji smazat z databáze. Při každém smazání poznámky musíte odstranit i odpovídající vektor z Vectorize. Toho dosáhnete vytvořením DELETE /notes/:id trasu ve svém src/index.js soubor:

app.delete("/notes/:id", async (c) => {
	const { id } = c.req.param();

	const query = `DELETE FROM notes WHERE id = ?`;
	await c.env.DB.prepare(query).bind(id).run();

	await c.env.VECTOR_INDEX.deleteByIds([id]);

	return c.status(204);
});

10. Rozdělte text (volitelné)

U rozsáhlejších textů doporučujeme rozdělit text na menší části (chunky). Díky tomu LLM efektivněji získávají relevantní kontext, aniž by musely načítat velké bloky textu.

Za tímto účelem přidáme do projektu nový balíček NPM, `@langchain/textsplitters':

npm i @langchain/textsplitters

RecursiveCharacterTextSplitter třída poskytovaná tímto balíčkem rozdělí text na menší části. Lze ji přizpůsobit podle potřeby, ale výchozí konfigurace funguje ve většině případů:

import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";

const text = "Some long piece of text...";

const splitter = new RecursiveCharacterTextSplitter({
	// These can be customized to change the chunking size
	// chunkSize: 1000,
	// chunkOverlap: 200,
});

const output = await splitter.createDocuments([text]);
console.log(output); // [{ pageContent: 'Some long piece of text...' }]

Chcete-li tento splitter použít, upravíme workflow tak, aby text rozdělil na menší části. Poté projdeme jednotlivé části a pro každou z nich spustíme zbytek workflow:

export class RAGWorkflow extends WorkflowEntrypoint {
	async run(event, step) {
		const env = this.env;
		const { text } = event.payload;
		let texts = await step.do("split text", async () => {
			const splitter = new RecursiveCharacterTextSplitter();
			const output = await splitter.createDocuments([text]);
			return output.map((doc) => doc.pageContent);
		});

		console.log(
			"RecursiveCharacterTextSplitter generated ${texts.length} chunks",
		);

		for (const index in texts) {
			const text = texts[index];
			const record = await step.do(
				`create database record: ${index}/${texts.length}`,
				async () => {
					const query = "INSERT INTO notes (text) VALUES (?) RETURNING *";

					const { results } = await env.DB.prepare(query).bind(text).run();

					const record = results[0];
					if (!record) throw new Error("Failed to create note");
					return record;
				},
			);

			const embedding = await step.do(
				`generate embedding: ${index}/${texts.length}`,
				async () => {
					const embeddings = await env.AI.run("@cf/baai/bge-base-en-v1.5", {
						text: text,
					});
					const values = embeddings.data[0];
					if (!values) throw new Error("Failed to generate vector embedding");
					return values;
				},
			);

			await step.do(`insert vector: ${index}/${texts.length}`, async () => {
				return env.VECTOR_INDEX.upsert([
					{
						id: record.id.toString(),
						values: embedding,
					},
				]);
			});
		}
	}
}

Když se do koncového bodu /notes endpoint, budou rozděleny na menší části a každá část bude zpracována workflow.

11. Nasaďte projekt

Pokud jste Worker nenasadili už během krok 1, nasaďte svého Workera pomocí Wrangleru na *.workers.dev subdoména, nebo Custom Domain, pokud jste nějakou nakonfigurovali. Pokud nemáte nastavenou žádnou subdoménu ani doménu, Wrangler vás při publikování vyzve k jejímu nastavení.

npx wrangler deploy

Zobrazte náhled svého Workeru na adrese <YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev.

Úplná verze tohoto kódu je k dispozici na GitHubu. Obsahuje frontendové uživatelské rozhraní pro dotazování, přidávání a mazání poznámek a backendové API pro práci s databází a vektorovým indexem. Najdete ji zde: github.com/kristianfreeman/cloudflare-retrieval-augmented-generation-example.

Chcete-li udělat více: