Skip to content

Commit 316d7a5

Browse files
committed
added tutorial for mastra
1 parent 24e0482 commit 316d7a5

File tree

3 files changed

+321
-0
lines changed

3 files changed

+321
-0
lines changed
1.59 MB
Loading
Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
# Building a RAG Application with Mastra and Couchbase: A Step-by-Step Tutorial
2+
3+
This tutorial will guide you through building a complete Retrieval-Augmented Generation (RAG) application from scratch using Next.js, the Mastra AI framework, and Couchbase for vector search. We'll start by getting the pre-built application running and then break down how each part works so you can build it yourself.
4+
5+
## Part 1: Quick Start
6+
7+
First, let's get the completed application running to see what we're building.
8+
9+
### 1.1 Clone and Install
10+
11+
Get the project code and install the necessary dependencies.
12+
13+
```bash
14+
git clone https://github.com/couchbase-examples/mastra-nextJS-quickstart.git
15+
cd mastra-nextJS-quickstart
16+
npm install
17+
```
18+
19+
### 1.2 Prerequisites & Environment Setup
20+
21+
Before running, you need to configure your environment.
22+
23+
1. **Couchbase:**
24+
* Sign up for a free Couchbase Capella account or run a local Couchbase cluster.
25+
* Create a Bucket, Scope, and Collection. Note down the names.
26+
* Get your database credentials (connection string, username, and password).
27+
28+
2. **OpenAI:**
29+
* Get an API key from the [OpenAI Platform](https://platform.openai.com/api-keys).
30+
31+
3. **`.env` File:**
32+
* Create a `.env` file in the root of the project.
33+
* Copy the contents from `.env.sample` (if available) or use the following template and fill in your credentials.
34+
35+
```bash
36+
# Couchbase Vector Store Configuration
37+
COUCHBASE_CONNECTION_STRING=couchbase://localhost
38+
COUCHBASE_USERNAME=Administrator
39+
COUCHBASE_PASSWORD=your_password
40+
COUCHBASE_BUCKET_NAME=your_bucket
41+
COUCHBASE_SCOPE_NAME=your_scope
42+
COUCHBASE_COLLECTION_NAME=your_collection
43+
44+
# Embedding Configuration
45+
EMBEDDING_MODEL=text-embedding-3-small
46+
EMBEDDING_DIMENSION=1536
47+
EMBEDDING_BATCH_SIZE=100
48+
49+
# Chunking Configuration
50+
CHUNK_SIZE=1000
51+
CHUNK_OVERLAP=200
52+
53+
# Vector Index Configuration
54+
VECTOR_INDEX_NAME=document-embeddings
55+
VECTOR_INDEX_METRIC=cosine
56+
57+
# OpenAI Configuration
58+
OPENAI_API_KEY=your_openai_api_key
59+
```
60+
61+
### 1.3 Run the Application
62+
63+
Start the Next.js development server.
64+
65+
```bash
66+
npm run dev
67+
```
68+
69+
Open your browser to `http://localhost:3000`. You should see the PDF upload interface.
70+
71+
### 1.4 Test the RAG Flow
72+
73+
1. **Upload a PDF:** Drag and drop a PDF file into the designated area or click to select one.
74+
2. **Processing:** The application will process the file. This involves extracting text, chunking it, generating vector embeddings, and storing them in your Couchbase database. The UI will navigate you to the chat page upon completion.
75+
3. **Chat:** Ask a question about the content of the PDF you just uploaded. The application will use vector search to find relevant information and generate an answer.
76+
77+
---
78+
79+
## Part 2: Building From Scratch - The Mastra Foundation
80+
81+
Mastra is the AI orchestration framework that powers our application's intelligence. It helps define agents, tools, and workflows for complex AI tasks.
82+
83+
### 2.1 What is Mastra?
84+
85+
Mastra provides a structured way to build AI applications. Instead of writing scattered functions, you define:
86+
* **Agents:** AI entities with a specific purpose, model, and set of instructions (e.g., a "Research Assistant").
87+
* **Tools:** Functions that an Agent can use to interact with the outside world (e.g., a tool to query a database).
88+
* **Workflows:** Sequences of operations that orchestrate agents and tools.
89+
90+
### 2.2 Setting Up the Mastra Research Agent
91+
92+
Our core AI component is the `researchAgent`. Let's create it in `src/mastra/agents/researchAgent.ts`.
93+
94+
```typescript
95+
// src/mastra/agents/researchAgent.ts
96+
import { Agent } from "@mastra/core/agent";
97+
import { openai } from "@ai-sdk/openai";
98+
import { vectorQueryTool } from "../tools/embed";
99+
import { Memory } from "@mastra/memory";
100+
import { LibSQLStore } from "@mastra/libsql";
101+
102+
export const researchAgent = new Agent({
103+
name: "Research Assistant",
104+
instructions: `You are a helpful research assistant... Base your responses only on the content provided.`,
105+
model: openai("gpt-4o-mini"),
106+
tools: {
107+
vectorQueryTool,
108+
},
109+
memory: new Memory({
110+
storage: new LibSQLStore({
111+
url: "file:../../memory.db"
112+
})
113+
}),
114+
});
115+
```
116+
Here, we define an `Agent` that uses the `gpt-4o-mini` model, has a clear set of instructions for its personality and task, and is equipped with a `vectorQueryTool` to find information. It also uses `LibSQLStore` for memory, allowing it to remember conversation history.
117+
118+
### 2.3 Creating a Mastra Tool for Vector Search
119+
120+
The agent needs a tool to search for information. We create this in `src/mastra/tools/embed.ts`. This tool is responsible for taking a user's query, embedding it, and searching the vector database.
121+
122+
```typescript
123+
// src/mastra/tools/embed.ts
124+
import { createTool } from "@mastra/core";
125+
import { openai } from "@ai-sdk/openai";
126+
import { embed } from "ai";
127+
import { z } from "zod";
128+
import { getVectorStore } from "./store";
129+
130+
// ... (Environment variable configuration)
131+
132+
export const vectorQueryTool = createTool({
133+
id: "vector_query",
134+
description: "Search for relevant document chunks...",
135+
inputSchema: z.object({
136+
query: z.string().describe("The search query..."),
137+
topK: z.number().optional().default(5),
138+
minScore: z.number().optional().default(0.1),
139+
}),
140+
execute: async (executionContext) => {
141+
const { query, topK, minScore } = executionContext.context;
142+
143+
// Generate embedding for the query
144+
const { embedding: queryEmbedding } = await embed({
145+
model: openai.embedding(EMBEDDING_CONFIG.model),
146+
value: query,
147+
});
148+
149+
// Perform vector search
150+
const vectorStore = getVectorStore();
151+
const results = await vectorStore.query({
152+
indexName: INDEX_CONFIG.indexName,
153+
queryVector: queryEmbedding,
154+
topK,
155+
});
156+
157+
// Filter and format results
158+
const relevantResults = results
159+
.filter(result => result.score >= minScore)
160+
.map(result => ({ /* ... format result ... */ }));
161+
162+
return { /* ... results ... */ };
163+
},
164+
});
165+
```
166+
This tool uses the `ai` SDK to create an embedding for the search query and then uses our Couchbase vector store to find the most relevant text chunks.
167+
168+
---
169+
170+
## Part 3: Couchbase Vector Database Integration
171+
172+
Couchbase serves as our high-performance vector database, storing the document embeddings and allowing for fast semantic search.
173+
174+
### 3.1 Why Couchbase for Vector Search?
175+
176+
Couchbase is an excellent choice for RAG applications because it combines a powerful, scalable NoSQL database with integrated vector search capabilities. This means you can store your unstructured metadata and structured vector embeddings in the same place, simplifying your architecture.
177+
178+
### 3.2 Connecting to Couchbase
179+
180+
We need a way to manage the connection to Couchbase. A singleton pattern is perfect for this, ensuring we don't create unnecessary connections. We'll write this in `src/mastra/tools/store.ts`.
181+
182+
```typescript
183+
// src/mastra/tools/store.ts
184+
import { CouchbaseVector } from "@mastra/couchbase";
185+
186+
function createCouchbaseConnection(): CouchbaseVector {
187+
// ... reads environment variables ...
188+
return new CouchbaseVector({ /* ... connection config ... */ });
189+
}
190+
191+
let vectorStoreInstance: CouchbaseVector | null = null;
192+
193+
export function getVectorStore(): CouchbaseVector {
194+
if (!vectorStoreInstance) {
195+
vectorStoreInstance = createCouchbaseConnection();
196+
}
197+
return vectorStoreInstance;
198+
}
199+
```
200+
The `@mastra/couchbase` package provides the `CouchbaseVector` class, which handles all the complexities of interacting with Couchbase for vector operations.
201+
202+
### 3.3 Automatic Vector Index Creation
203+
204+
A key feature of our application is that it automatically creates the necessary vector search index if it doesn't already exist. This logic resides in our PDF ingestion API route.
205+
206+
```typescript
207+
// src/app/api/ingestPdf/route.ts
208+
209+
// Inside createDocumentEmbeddings function:
210+
const vectorStore = connectToCouchbase();
211+
212+
try {
213+
await vectorStore.createIndex({
214+
indexName: INDEX_CONFIG.indexName,
215+
dimension: EMBEDDING_CONFIG.dimension,
216+
metric: INDEX_CONFIG.metric,
217+
});
218+
console.info('Successfully created search index');
219+
} catch (error) {
220+
// Continue anyway - index might already exist
221+
console.warn(`Index creation warning: ${error}`);
222+
}
223+
```
224+
This ensures that the application is ready for vector search as soon as the first document is uploaded, without any manual setup required in Couchbase.
225+
226+
---
227+
228+
## Part 4: The Full RAG Pipeline
229+
230+
Now let's connect everything to build the full Retrieval-Augmented Generation pipeline.
231+
232+
### 4.1 Ingestion: From PDF to Vector Embeddings
233+
234+
The ingestion process is handled by the `/api/ingestPdf` API route. Here's the step-by-step flow defined in `src/app/api/ingestPdf/route.ts`:
235+
236+
1. **Receive File:** The `POST` handler receives the uploaded PDF from the frontend.
237+
2. **Save File:** The file is saved locally to the `public/assets` directory.
238+
3. **Extract Text:** The server uses the `pdf-parse` library to extract raw text from the PDF buffer.
239+
4. **Chunk Text:** The extracted text is chunked into smaller, overlapping pieces using Mastra's `MDocument` utility. This is crucial for providing focused context to the AI.
240+
```typescript
241+
const doc = MDocument.fromText(documentText);
242+
const chunks = await doc.chunk({ /* ... chunking config ... */ });
243+
```
244+
5. **Generate Embeddings:** The text chunks are sent to the OpenAI API to be converted into vector embeddings using `embedMany`.
245+
6. **Upsert to Couchbase:** The embeddings, along with their corresponding text and metadata, are saved to Couchbase using `vectorStore.upsert()`.
246+
247+
### 4.2 Retrieval & Generation: From Query to Answer
248+
249+
When a user sends a message in the chat, the `/api/chat` route takes over.
250+
251+
1. **Receive Query:** The `POST` handler in `src/app/api/chat/route.ts` receives the user's message history.
252+
2. **Invoke Agent:** It calls our `researchAgent` with the conversation history.
253+
```typescript
254+
// src/app/api/chat/route.ts
255+
const stream = await researchAgent.stream(matraMessages);
256+
```
257+
3. **Use Tool:** The `researchAgent`, guided by its instructions, determines that it needs to find information and calls its `vectorQueryTool`.
258+
4. **Vector Search:** The tool embeds the user's query and searches Couchbase for the most relevant document chunks.
259+
5. **Augment Prompt:** The retrieved chunks are added to the context of the agent's prompt to the LLM.
260+
6. **Generate Response:** The agent sends the augmented prompt to OpenAI's `gpt-4o-mini`, which generates a response based *only* on the provided information.
261+
7. **Stream Response:** The response is streamed back to the user for a real-time chat experience.
262+
263+
---
264+
265+
## Part 5: Next.js Frontend Integration
266+
267+
The frontend is a Next.js application using React Server Components and Client Components for a modern, responsive user experience.
268+
269+
### 5.1 PDF Upload Component
270+
271+
The file upload functionality is handled by `src/components/PDFUploader.tsx`.
272+
273+
* It uses the `react-dropzone` library to provide a drag-and-drop interface.
274+
* It's a "use client" component because it relies on browser-side state and events.
275+
* When a file is uploaded, it sends a `POST` request with `FormData` to our `/api/ingestPdf` endpoint.
276+
* Upon a successful response, it navigates the user to the chat page.
277+
278+
![PDF Upload Page](./pdfUploader.jpg)
279+
280+
```javascript
281+
// src/components/PDFUploader.tsx
282+
283+
const uploadPdf = async (event) => {
284+
// ...
285+
const data = new FormData();
286+
data.set("file", selectedFile);
287+
288+
const response = await fetch("/api/ingestPdf", {
289+
method: "POST",
290+
body: data,
291+
});
292+
const jsonResp = await response.json();
293+
294+
router.push("/chatPage" + "?" + createQueryString("fileName", jsonResp.fileName));
295+
};
296+
```
297+
298+
### 5.2 Chat Interface
299+
300+
The chat interface is composed of several components found in `src/components/chatPage/`. The main logic for handling the conversation would typically use the `useChat` hook from the `ai` package to manage message state, user input, and the streaming response from the `/api/chat` endpoint.
301+
302+
![Chat Interface](./chatInterface.jpeg)
303+
304+
This separation of concerns allows the backend to focus on the heavy lifting of AI and data processing, while the frontend focuses on delivering a smooth user experience.
305+
306+
---
307+
308+
## Part 6: Customization and Extensions
309+
310+
### 7.1 Supporting Different Document Types
311+
You can extend the `readDocument` function in `ingestPdf/route.ts` to support other file types like `.docx` or `.txt` by using different parsing libraries.
312+
313+
### 7.2 Advanced Mastra Features
314+
Explore more of Mastra's capabilities by creating multi-agent workflows, adding more custom tools (e.g., a tool to perform web searches), or implementing more sophisticated memory strategies.
315+
316+
### 7.3 Enhanced Vector Search
317+
Improve retrieval by experimenting with hybrid search (combining vector search with traditional keyword search), filtering by metadata, or implementing more advanced chunking and embedding strategies.
318+
319+
---
320+
321+
Congratulations! You've now seen how to build a powerful RAG application with Mastra, Couchbase, and Next.js. Use this foundation to build your own custom AI-powered solutions.
703 KB
Loading

0 commit comments

Comments
 (0)