Using LlamaIndex and MongoDB to Build a Job Search Assistant
Tags: tutorial, webdev, ai, cloud
Published: 2024-05-15 08:14:00

In today's job market, finding the perfect role often feels like searching for a needle in a haystack. One solution is to use AI-driven chatbots to streamline this process, offering quick responses and personalized assistance.

However, relying solely on full-text search can be limiting, as job descriptions vary widely and users may input non-standard terms. By using AI techniques like Retrieval-Augmented Generation (RAG) with LlamaIndex, you can build chatbots that not only understand the nuances of job description, but also make the job search experience more efficient and human-like.

In this tutorial, you will learn how to integrate LlamaIndex with MongoDB to create an AI-driven job search assistant that not only responds to user queries, but also continuously updates its knowledge base in real time, ensuring accurate and relevant results.

You can consult the [project repository](https://github.com/koyeb/example-llamaindex-mongodb-job-search) as work through this guide. After [provisioning the MongoDB database](#provision-a-mongo-db-database), you can deploy the job assistant application as built in this tutorial using the [Deploy to Koyeb](https://www.koyeb.com/docs/build-and-deploy/deploy-to-koyeb-button) button below:

[![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?name=llamaindex-job-search&type=git&repository=koyeb%2Fexample-llamaindex-mongodb-job-search&branch=main&builder=buildpack&env%5BOPENAI_API_KEY%5D=CHANGE_ME&env%5BMONGODB_URI%5D=CHANGE_ME&env%5BMONGODB_DATABASE%5D=jobs_db&env%5BMONGODB_VECTORS%5D=jobs_vectors&env%5BMONGODB_VECTOR_INDEX%5D=jobs_vectors_index&ports=8000%3Bhttp%3B%2F)

Once deployed, before using the application, you will need to [seed at least one initial job (directed at your Koyeb app)](#seed-the-job-database), and [set up MongoDB Atlas vector search](#set-up-mongo-db-atlas-vector-search).

**Note:** You will need to replace the values of the environment variables in the configuration with your own `OPENAI_API_KEY` and `MONGODB_URI`. Consult the tutorial here to understand the expected values. You can leave the rest of the values as-is.

## Requirements

To successfully follow this tutorial, you will need the following:

- [Node.js and npm installed](https://nodejs.org/en/download). The demo app in this tutorial uses version 18 of Node.js.
- Git Installed.
- An [OpenAI](https://platform.openai.com) account.
- A [MongoDB](https://mongodb.com) account.
- A [Koyeb](https://app.koyeb.com/) account to deploy the application.

## Demo

Before we jump into the technical stuff, let's look at a sneak peek of what you will be building in this tutorial:

{% youtube d7N8UACHRZ8 %}

## Steps

1.  [Generate an OpenAI token](#generate-an-open-ai-token)
2.  [Provision a MongoDB database](#provision-a-mongo-db-database)
3.  [Create a new Next.js application](#create-a-new-next-js-application)
4.  [Build the components of our application](#build-the-components-of-our-application)
    - [Configure shadcn/ui components](#configure-shadcn-ui-components)
    - [Create a markdown component](#create-a-markdown-component)
5.  [Define the Next.js application routes](#define-the-next-js-application-routes)
    - [Create a job indexing API endpoint](#create-a-job-indexing-api-endpoint)
    - [Seed the job database](#seed-the-job-database)
    - [Set up MongoDB Atlas vector search](#set-up-mongo-db-atlas-vector-search)
    - [Building the index route as the chatbot interface](#building-the-index-route-as-the-chatbot-interface)
    - [Create a chat API endpoint](#create-a-chat-api-endpoint)
      - [Find relevant jobs using MongoDB Atlas vector search](#find-relevant-jobs-using-mongo-db-atlas-vector-search)
      - [Create system context and instructions for OpenAI](#create-system-context-and-instructions-for-open-ai)
6.  [Deploy the Next.js application to Koyeb](#deploy-the-next-js-application-to-koyeb)
7.  [Conclusion](#conclusion)

## Generate an OpenAI token

HTTP requests to the OpenAI API require an authorization token. To generate this token, while logged into your OpenAI account, navigate to the [API Keys](https://platform.openai.com/api-keys) page and click the **Create new secret key** button after entering name for your token. Copy and securely store this token for later use.

Now, let's move on to creating your MongoDB Database.

## Provision a MongoDB database

We will be using MongoDB to store the data associated with our job search application persistently.

To get started, [sign into MongoDB](https://account.mongodb.com/account/login) and select **Database** under the **Deployment** section. Click **Build a database** to get started with creating your own database.

![Create new MongoDB database](https://www.koyeb.com/static/images/tutorials/llamaindex-mongodb-job-search/create-mongodb-database.png)

Select **M0** as the cluster type, and (optionally) give the cluster a personalized name. Click **Create Deployment** to proceed.

![Configure MongoDB database](https://www.koyeb.com/static/images/tutorials/llamaindex-mongodb-job-search/configure-mongodb-database.png)

You may be directed to create a database user. Instead of limiting connections to your local IP address, you will want to ensure that all IPs are allowed (`0.0.0.0/0`) so that your application will work as expected when deployed to Koyeb.

Next, you'll be taken to the connection screen. Click **Drivers** in the **Connect to your application** section. On the page that follows, with the **Node.js** driver selected, click the **copy icon** associated with the MongoDB connection string:

![Copy MongoDB connection string](https://www.koyeb.com/static/images/tutorials/llamaindex-mongodb-job-search/copy-mongodb-connection-string.png)

This will copy the connection string with the password for the database user replaced by `<password>`. If necessary, you can manage and regenerate the password for your user on the **Database Access** page in the **Security** section of the left-hand menu.

Save the complete connection string in a safe place for later. Now, let's move on to creating a new Next.js application to interact with your MongoDB database.

## Create a new Next.js application

Now that we have our database, let's get started by creating a new Next.js project.

In your terminal, type:

```bash
npx create-next-app@latest example-llamaindex-job-search
```

When prompted, choose:

- `Yes` when prompted to use TypeScript.
- `No` when prompted to use ESLint.
- `Yes` when prompted to use Tailwind CSS.
- `No` when prompted to use `src/` directory.
- `Yes` when prompted to use App Router.
- `No` when prompted to customize the default import alias (`@/*`).

Once that is done, move into the project directory and start the app in development mode by executing the following command:

```bash
cd example-llamaindex-job-search
npm run dev
```

The app should be running on [localhost:3000](http://localhost:3000). Stop the development server by pressing <kbd>CTRL</kbd>-<kbd>C</kbd> when you are finished.

Next, create an `.env.local` file to set up the environment variables the application needs. Replace the values with the OpenAI API key and MongoDB connection string you copied earlier. We'll also take the opportunity to set a few other MongoDB variables that our application will look for later:

```ini
OPENAI_API_KEY="<YOUR_OPENAI_API_KEY>"
MONGODB_URL="<YOUR_MONGODB_CONNECTION_STRING>"
MONGODB_DATABASE="jobs_db"
MONGODB_VECTORS="jobs_vectors"
MONGODB_VECTOR_INDEX="jobs_vectors_index"
```

Next.js will automatically load these variables when we start up the application locally.

Next, install the necessary dependencies with the following commands:

```bash
npm install llamaindex@0.3.4 mongodb@6.5.0 ai openai
npm install -D @tailwindcss/typography
```

The command above installs the packages passed to the `install` command, with the `-D` flag specifying the libraries intended for development purposes only.

The libraries installed include:

- `ai`: A library to build AI-powered streaming text and chat UIs.
- `mongodb`: A driver for Node.js to easily interact with MongoDB databases.
- `llamaindex`: A data framework for creating LLM applications.
- `openai`: A library to conveniently access OpenAI's REST API.

The development-specific libraries include:

- `@tailwindcss/typography`: A set of **prose** classes to style HTML elements (built from markdown).

Now, let's move on to creating components that will help you quickly prototype the UI and handle the complexities of creating a chatbot application with Next.js.

## Build the components of our application

### Configure shadcn/ui components

With `shadcn/ui` you will be able to use baked-in accessible `<input>` and `<button>` elements. Execute the command below to start setting up the `shadcn/ui`:

```bash
npx shadcn-ui@latest init
```

You will be asked a few questions to configure a `components.json`, answer with the following:

```bash
✔ Would you like to use TypeScript (recommended)? no / **yes**
✔ Which style would you like to use? › **Default**
✔ Which color would you like to use as base color? › **Slate**
✔ Would you like to use CSS variables for colors? no / **yes**
```

With above, you have set up a CLI that allows us to easily add React components to your Next.js application.

Before moving on, insert the following code in `tailwind.config.ts` to load the **prose** classes we installed earlier. Initializing `shadcn/ui` rewrites the `tailwind.config.ts` file, so we needed to defer this step until the file was in its current state:

```typescript
import type { Config } from "tailwindcss";

const config = {
  // Rest of the file as-is
  plugins: [
    require("tailwindcss-animate")  // [!code --]
    require("tailwindcss-animate"),  // [!code ++]
    require("@tailwindcss/typography") // [!code ++]
  ],
} satisfies Config

export default config
```

Execute the command below to get the button and input elements:

```bash
npx shadcn-ui@latest add button
npx shadcn-ui@latest add input
```

With the above, a `ui` directory should now exist inside the `components` directory containing `button.tsx` and `input.tsx` files.

### Create a markdown component

For our application, we want to render the streaming markdown compatible responses from AI in a visually appealing manner. For this, you will use `react-markdown` library. Install it with the following command:

```bash
npm install react-markdown
```

Afterwards, create a `markdown.tsx` file inside the `components` directory with the following code:

```typescript
// File: components/markdown.tsx

import clsx from "clsx";
import ReactMarkdown from "react-markdown";

interface MarkdownProps {
  index: number;
  message: string;
}

const Markdown = ({ message, index }: MarkdownProps) => {
  return (
    <ReactMarkdown
      components={{
        a({ children, href }) {
          return (
            <a href={href} target="_blank" className="underline text-black">
              {children}
            </a>
          );
        },
      }}
      className={clsx(
        "w-full mt-4 pt-4 prose break-words prose-p:leading-relaxed prose-pre:p-0",
        index !== 0 && "border-t"
      )}
    >
      {message}
    </ReactMarkdown>
  );
};

export default Markdown;
```

The code above imports the `react-markdown` library and uses it to style each HTML element.

Now, let's move to create the routes for the user-facing pages and API endpoints.

## Define the Next.js application routes

To create interactive, user-facing pages in Next.js, you will use [Next.js Client Components](https://nextjs.org/docs/app/building-your-application/rendering/client-components) to create an interactive UI that is pre-rendered on the server and run with JavaScript the browser.

To create API endpoints in Next.js, you will use [Next.js Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) to serve responses via the [web response API](https://developer.mozilla.org/en-US/docs/Web/API/Response).

The structure below is what our `app` directory will look like at the end of this section:

```bash
├── page.tsx
├── api/
├────── chat/
├───────── route.ts
├────── train/
└───────── route.ts
```

- `page.tsx` will serve as the index route, i.e. `localhost:3000`.
- `api/chat/route.ts` will serve responses for `localhost:3000/api/chat`.
- `api/train/route.ts` will serve responses for `localhost:3000/api/train`.

| **URL**      | **Matched Routes**       |
| ------------ | ------------------------ |
| `/`          | `app/page.tsx`           |
| `/api/chat`  | `app/api/chat/route.ts`  |
| `/api/train` | `app/api/train/route.ts` |

### Create a job indexing API endpoint

First, create a directory named `mongo` inside the `lib` directory with the following command:

```bash
mkdir lib/mongo
```

Then, create a file named `instance.server.ts` with the following code to setup a MongoDB client for your database:

```typescript
// File: lib/mongo/instance.server.ts
import { MongoClient } from 'mongodb'

export const databaseName = process.env.MONGODB_DATABASE as string
export const indexName = process.env.MONGODB_VECTOR_INDEX as string
export const vectorCollectionName = process.env.MONGODB_VECTORS as string

export const client = new MongoClient(process.env.MONGODB_URI as string)
```

In the code above, you have set up a MongoDB client instance using the `MongoClient` class and `MONGODB_URI` environment variable. Additionally, you have exported 3 variables representing the database, collection, and vector index names of your MongoDB instance as we configured earlier in the `.env.local` file.

Now, create a file named `store.server.ts` in the same directory with the following code to setup MongoDB Atlas Vector Search client for your MongoDB instance:

```typescript
// File: lib/mongo/store.server.ts
import { MongoDBAtlasVectorSearch } from 'llamaindex/storage/vectorStore/MongoDBAtlasVectorStore'

import {
  vectorCollectionName as collectionName,
  databaseName as dbName,
  indexName,
  client as mongodbClient,
} from '@/lib/mongo/instance.server'

export default new MongoDBAtlasVectorSearch({
  mongodbClient,
  dbName,
  indexName,
  collectionName,
})
```

In the code above, we export a `MongoDBAtlasVectorSearch` instance, configured with the MongoDB client, database name, index name, and collection name. It integrates with LlamaIndex's `MongoDBAtlasVectorStore` to facilitate vector storage and search functionalities within your Next.js application.

Using both of the above files, you can now create an endpoint that allows you to insert jobs data and index them for vector search simultaneously.

Create the `app/api/train` directory structure by typing:

```bash
mkdir -p app/api/train
```

Inside the new directory, create a new file called `route.ts` with the following content:

```typescript
import { Document, VectorStoreIndex, storageContextFromDefaults } from 'llamaindex'
import { NextRequest, NextResponse } from 'next/server'

import { client, databaseName, vectorCollectionName } from '@/lib/mongo/instance.server'
import vectorStore from '@/lib/mongo/store.server'

// File: app/api/train/route.ts

export const runtime = 'nodejs'

export async function POST(request: NextRequest) {
  try {
    const { jobs } = await request.json()
    // Get MongoDB database and collection
    const db = client.db(databaseName)
    const collection = db.collection(vectorCollectionName)
    // Insert all jobs from the request in MongoDB
    await collection.insertMany(jobs)
    // Create LlamaIndex Text Documents containing job data as Metadata
    const documents = jobs.map(
      (job: Record<any, any>) =>
        new Document({
          // Use the job description
          text: job.job_description,
          // Attach metadata as the job JD
          metadata: JSON.parse(JSON.stringify(job)),
        })
    )
    // Index all the Text Documents in MongoDB Atlas
    const storageContext = await storageContextFromDefaults({ vectorStore })
    await VectorStoreIndex.fromDocuments(documents, { storageContext })
    return NextResponse.json({ code: 1 })
  } catch (e) {
    console.log(e)
    return NextResponse.json({ code: 0 })
  }
}
```

In the code above, you create a `POST` API endpoint to accept jobs as JSON data. The data is then inserted in your MongoDB database. Afterwards, all the documents stored in the `documents` array are inserted into the MongoDB vector search index. Under the hood, the vector embeddings for each document is generated using its `text` property.

### Seed the job database

To be able to perform vector search using MongoDB Atlas Search, you need to have at least a single record in a MongoDB database. Hence, an endpoint was created first that you can use to populate your MongoDB database. Use the following command to populate your database via the API endpoint:

```bash
curl --location 'http://localhost:3000/api/train' \
--header 'Content-Type: application/json' \
--data '{
    "jobs": [
        {
            "job_title": "Customer Success Engineer",
            "job_description": "Reporting to the Senior Manager, Customer Success Engineering, the Customer Success Engineer'\''s responsibilities include resolving customer concerns, creating and improving internal tooling, and engineering solutions to help Vercel customers get the most out of the platform.",
            "job_link": "https://vercel.com/careers/customer-success-engineer-uk-5085742004"
        }
    ]
}'
```

Now, let's move on to creating your own MongoDB Atlas vector search instance.

### Set up MongoDB Atlas vector search

Navigate to your database cluster in the MongoDB dashboard. Click on the **Atlas Search** tab. Next, click **Create Search Index** to start creating the vector index.

![Create search index](https://www.koyeb.com/static/images/tutorials/llamaindex-mongodb-job-search/create-search-index.png)

Select the **JSON Editor** method under the **Atlas Vector Search** section:

![Select JSON for Atlas vector search](https://www.koyeb.com/static/images/tutorials/llamaindex-mongodb-job-search/select-json-for-atlas-vector-search.png)

On the following page, select **jobs_vectors** collection in the **jobs_db** database to index job data.

Rename the **Index Name** to `jobs_vectors_index` (remember to update the value in the `.env.local` file if you choose to use a different index name) and edit the JSON to be the following:

```json
{
  "fields": [
    {
      "type": "vector",
      "path": "embedding",
      "numDimensions": 1536,
      "similarity": "cosine"
    }
  ]
}
```

![Configure JSON for vector search](https://www.koyeb.com/static/images/tutorials/llamaindex-mongodb-job-search/configure-json-for-vector-search.png)

On the next page, click on **Create Search Index** button to finalize indexing:

![Create search index button](https://www.koyeb.com/static/images/tutorials/llamaindex-mongodb-job-search/create-search-index-button.png)

After a few seconds, you should see the status turn into **Active** after which you can start querying for relevant vectors along with their job metadata:

![Search index active](https://www.koyeb.com/static/images/tutorials/llamaindex-mongodb-job-search/search-index-active.png)

Now, let's move on to creating the chat user interface for the users to interact with.

### Building the index route as the chatbot interface

Open the `app/page.tsx` file and replace the existing code with the following:

```typescript
// File: app/page.tsx

"use client";

import { useChat } from "ai/react";
import Markdown from "@/components/markdown";
import { Input } from "@/components/ui/input";

export default function Home() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();
  return (
    <div className="mt-8 gap-y-4 px-4 md:px-0 w-full flex flex-col items-center">
      <h1 className="w-[300px] md:w-[600px] text-xl font-semibold">
        Job Assistant
      </h1>
      <form
        onSubmit={handleSubmit}
        className="border-t pt-8 mt-4 w-[300px] md:w-[600px] flex flex-col"
      >
        <Input
          id="message"
          value={input}
          type="message"
          autoComplete="off"
          onChange={handleInputChange}
          placeholder="What kind of role are you looking for?"
          className="border-black/25 hover:border-black placeholder:text-black/75 rounded"
        />
        <button
          type="submit"
          className="rounded max-w-max mt-2 px-3 py-1 border text-black hover:border-black"
        >
          Search &rarr;
        </button>
      </form>
      <div className="w-[300px] md:w-[600px] flex flex-col">
        {messages.map((i, _) => (
          <Markdown message={i.content} index={_} />
        ))}
      </div>
      <div className="mt-8 w-full">&nbsp;</div>
    </div>
  );
}
```

The code above begins by importing the `useChat` hook from `ai` package, the markdown component that you have created earlier to render each message with it, and the input element from the `shadcn/ui`. In the React component on the homepage, you will deconstruct the following with the `useChat` hook:

- The reactive `messages` array which contains the conversation between the user and AI.
- The reactive `input` value inserted by user into the input field.
- The `handleInputChange` method to make sure the `input` value is in sync with the changes.
- The `handleSubmit` method to call the API (`/api/chat`) to get a response for the user’s latest message.

Next, we'll move on to create the chat endpoint that contains references to jobs (including descriptions) related to the user query.

### Create a chat API endpoint

#### Find relevant jobs using MongoDB Atlas vector search <a name="find-relevant-jobs-using-mongo-db-atlas-vector-search"></a>

A full text search, or searching through the entire job data stored in your database, is an expensive operation, both in terms of time taken as well as the relevance to the user query. To find relevant jobs easier and faster, you can use vector search for the same. It involves querying the existing set of vectors in your MongoDB Atlas instance. Furthermore, you can filter the vectors whose embeddings carry at least 90% similarity score to the vector embeddings of the user search.

Begin by creating the `app/api/chat` directory:

```bash
mkdir app/api/chat
```

Inside, create a file called `route.ts` to handle the POST request created by `useChat` hook in our React component, with the aim to obtain a response from AI.

```typescript
// File: app/api/chat/route.ts

export const runtime = "nodejs";

import { VectorStoreIndex } from "llamaindex";
import vectorStore from "@/lib/mongo/store.server";
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
  try {
    const { messages } = await request.json();
    const userMessages = messages.filter((i: Message) => i.role === "user");
    const query = userMessages[userMessages.length - 1].content;
    const index = await VectorStoreIndex.fromVectorStore(vectorStore);
    const retriever = index.asRetriever({ similarityTopK: 10 });
    const queryEngine = index.asQueryEngine({ retriever });
    const { sourceNodes } = await queryEngine.query({ query });
    let systemKnowledge: any[] = [];
    if (sourceNodes) {
      systemKnowledge = sourceNodes
        .filter((match) => match.score && match.score > 0.9)
        .map((match) => JSON.stringify(match.node.metadata));
    }
```

In the code above, you create a `POST` API endpoint to accept the conversation between user and AI (i.e. `messages`) as JSON data. It then filters out user messages and extracts the last user message as a query. Afterwards, it creates a query engine from the index to fetch up to 10 highly relevant documents.

Next, it filters the results based on their similarity score, keeping those with a score greater than 0.9, indicating a high relevance. Finally, it creates an array with metadata of each highly relevant job to create a knowledge base for the chatbot.

#### Create system context and instructions for OpenAI <a name="create-system-context-and-instructions-for-open-ai"></a>

Next, we need to instantiate an OpenAI instance to create chat completion responses. The SDK will call the OpenAI chat completion API under the hood.

Create a file named `openai.server.ts` in the `lib` directory with the following code:

```typescript
// File: lib/openai.server.ts
import OpenAI from 'openai'

// Instantiate class to generate text completion using the OpenAI API
export default new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
})
```

At this point, you have created a knowledge base for chat with jobs data relevant to the user's query. It's now time to prompt the OpenAI chat completion model to enhance the AI response by inserting it as part of the system knowledge. You will want to get the response to the user from AI as soon as possible, so you will enable streaming using `OpenAIStream` exported by `ai` package.

To do implement of this, insert the following code in the `app/api/chat/route.ts` file:

```typescript
import { type Message, OpenAIStream, StreamingTextResponse } from 'ai'
import { VectorStoreIndex } from 'llamaindex'
import { NextRequest, NextResponse } from 'next/server'

// [!code ++]
import vectorStore from '@/lib/mongo/store.server'
import completionServer from '@/lib/openai.server'

// File: app/api/chat/route.ts

export const runtime = 'nodejs'

// [!code ++]

export async function POST(request: NextRequest) {
  try {
    const { messages } = await request.json()
    const userMessages = messages.filter((i: Message) => i.role === 'user')
    const query = userMessages[userMessages.length - 1].content
    const index = await VectorStoreIndex.fromVectorStore(vectorStore)
    const retriever = index.asRetriever({ similarityTopK: 10 })
    const queryEngine = index.asQueryEngine({ retriever })
    const { sourceNodes } = await queryEngine.query({ query })
    let systemKnowledge: any[] = []
    if (sourceNodes) {
      systemKnowledge = sourceNodes
        .filter((match) => match.score && match.score > 0.9)
        .map((match) => JSON.stringify(match.node.metadata))
    }
    const completionResponse = await completionServer.chat.completions.create({
      // [!code ++]
      stream: true, // [!code ++]
      model: 'gpt-3.5-turbo', // [!code ++]
      messages: [
        // [!code ++]
        {
          // [!code ++]
          // create a system content message to be added as
          // the open ai text completion will supply it as the context with the API
          role: 'system', // [!code ++]
          content: `Behave like a Google for job listings. You have the knowledge of only the following job that are relevant to the user right now: [${JSON.stringify(
            // [!code ++]
            systemKnowledge // [!code ++]
          )}]. Each response should be in 100% markdown compatible format, an un-ordered list, have hyperlinks in it to the job taken from the job_link attribute and brief description of what's required in the job taken from the job_description attribute. Be precise.`, // [!code ++]
        }, // [!code ++]
        // also, pass the whole conversation!
        ...messages, // [!code ++]
      ], // [!code ++]
    }) // [!code ++]
    // Convert the response into a friendly text-stream
    const stream = OpenAIStream(completionResponse) // [!code ++]
    // Respond with the stream
    return new StreamingTextResponse(stream) // [!code ++]
  } catch (e) {
    // [!code ++]
    console.log(e) // [!code ++]
    return NextResponse.json(
      // [!code ++]
      'There was an Internal Server Error. Can you try again?', // [!code ++]
      {
        // [!code ++]
        status: 500, // [!code ++]
      } // [!code ++]
    ) // [!code ++]
  } // [!code ++]
} // [!code ++]
```

The additions above prompt the Chat completion model using the `chat.completions.create` function. Each item being passed to the prompt function contains a "role" key which, in our case, can be:

- `system` representing the system knowledge.
- `user` representing the user message.
- `assistant` representing the responses from the model.

To make sure that the chatbot responds in only the expected manner, the following is used in the content field to instruct that:

- The chatbot should behave like Google for job listings.
- The chatbot should make sure that the response is in markdown format.
- The chatbot's response should have hyperlinks to the jobs.
- The chatbot goes beyond just including references to the jobs.
- The chatbot should be precise.

You have successfully created a chat endpoint that uses Retrieval Augmented Generation to provide job results closely tied to the user to the chatbot.

Now, let's deploy the application online on the Koyeb platform.

## Deploy the Next.js application to Koyeb

Koyeb is a developer-friendly serverless platform to deploy apps globally. No-ops, servers, or infrastructure management is required. It supports many different tech stacks including Rust, Golang, Python, PHP, Node.js, Ruby, and Docker.

With the app now complete, the final step is to deploy it online on Koyeb.

You will use git-driven deployment to deploy on Koyeb. To do this, you need to create a new GitHub repository using the [GitHub web interface](https://github.com/new) or the GitHub CLI with the following command:

```bash
gh repo create <YOUR_GITHUB_REPOSITORY> --private
```

Next.js automatically created a git repository with a relevant `.gitignore` file when we initialized our new project. Add the new GitHub repository as a remote by typing:

```bash
git remote add origin git@github.com:<YOUR_GITHUB_USERNAME>/<YOUR_GITHUB_REPOSITORY>.git
git branch -M main
```

Next, add all the files in your project directory to the git repository and push them to GitHub by typing:

```bash
git add .
git commit -m "Initial commit"
git push -u origin main
```

To deploy the code on the GitHub repository, visit the [Koyeb control panel](https://app.koyeb.com/), and while on the **Overview** tab, click the **Create Web Service** button to start the deployment process. On the App deployment page:

1. Select the GitHub deployment option.
2. Choose the GitHub repository for your code from the repository drop down menu. Alternatively, you can enter the [example repo for this tutorial](https://github.com/koyeb/example-llamaindex-mongodb-job-search) in the **Public GitHub repository** field: `https://github.com/koyeb/example-llamaindex-mongodb-job-search`.
3. In the **Environment variables** section, click **Bulk edit**. Paste the variables defined in your `.env.local` file and click **Save**.
4. Enter a name for the application or use the provided one.
5. Finally, initiate the deployment process by selecting the **Deploy** button.

During the deployment on Koyeb, the process identifies the `build` and `start` scripts outlined in the `package.json` file, using them to build and launch the application. You can follow the deployment progress through the displayed logs. Upon the completion of deployment and the successful execution of vital health checks, your application will be operational.

## Conclusion

In this tutorial, you created a job assistant application with Next.js using MongoDB Atlas vector search and the OpenAI chat completion API. During the process, you learned how to perform vector search with LlamaIndex on a MongoDB database to find relevant jobs for the user. You also used the OpenAI API to create markdown compatible responses containing a brief description with a link to job role(s).

Given that the application was deployed with the git deployment option, subsequent code pushes to the deployed branch will automatically initiate a new build for your application. Changes to your application will become live once the deployment is successful. In the event of a failed deployment, Koyeb retains the last operational production deployment, ensuring the uninterrupted operation of your application.