Creating an AI app from scratch can be a very challenging task. Whether you want to build a simple chatbot or an advanced intelligent virtual assistant, it can take weeks to develop the desired app successfully. But that’s where Julep comes to rescue us.
Julep is a platform that helps to build stateful and functional LLM-powered applications. With Julep, you can build a fully functional AI app with just a few lines of code.
Platforms like OpenAI's GPT-3, Microsoft's Azure Bot Service, and Google's Dialogflow can build AI applications. However, Julep stands out due to its advantages like statefulness to track conversation history and context, easy integration with multiple LLMs, and a user-friendly interface for managing users, agents, and sessions.
In this blog, we will create Movio, an AI-powered Movie Companion App that provides recommendations and information about any movie the user asks for. We will walk through each step and understand how you can use Julep in your projects.
Let’s get started!
Prerequisites
Make sure you have Nodejs installed in your device. Download and Install Node.js from their official website
Creating React App
To create a React App, run this command in the terminal:
npm create-vite@latest
You can check out the Vite Docs to create a React app.
Create the basic structure in the App.jsx
file. Add an <input>
tag allowing the users to enter the query:
<div className="container">
<h1>Hi, I'm Movio</h1>
<h4>Your Ultimate Movie Companion</h4>
<div id="conversation">
{conversation.map((item, index) => (
<p key={index} className={item.role}>
{item.message}
</p>
))}
</div>
<input
type="text"
id="queryInput"
placeholder="Ask me anything about Movies..."
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button onClick={sendQuery}>Submit</button>
</div>
To handle and capture the user’s query, we have defined a functionality:
const [query, setQuery] = useState("");
const [conversation, setConversation] = useState([]);
We have used useState()
hook to define query
and conversation
variables along with their state updating functions.
const sendQuery = async () => {
if (!query) return;
// Append user message to conversation
setConversation((prev) => [...prev, { role: "user", message: query }]);
setQuery("");
try {
const response = await axios.post("http://localhost:3000/chat", {
query,
});
const agentResponse = response.data.response;
// Append agent response to conversation
setConversation((prev) => [
...prev,
{ role: "agent", message: agentResponse },
]);
} catch (error) {
console.error("Error fetching response:", error);
}
};
The function sendQuery()
first checks if the query is empty. If it isn't, it updates the conversation
state by appending a new object with role
as "user" and message
as the query. Then, it resets the query
to an empty string using setQuery("")
.
Inside the try block, axios
posts the user's query to the endpoint. The server's response is stored in agentResponse
, which is extracted from the response
data.
Next, setConversation
is used again to add the agent's response to the conversation
state, with role as "agent"
and message as agentResponse
.
Finally, any errors during the axios request are caught in the catch block and logged to the console.
Here’s the full App.jsx
code:
import React, { useState } from "react";
import axios from "axios";
function App() {
const [query, setQuery] = useState("");
const [conversation, setConversation] = useState([]);
const sendQuery = async () => {
if (!query) return;
// Append user message to conversation
setConversation((prev) => [...prev, { role: "user", message: query }]);
setQuery("");
try {
const response = await axios.post("http://localhost:3000/chat", {
query,
});
const agentResponse = response.data.response;
// Append agent response to conversation
setConversation((prev) => [
...prev,
{ role: "agent", message: agentResponse },
]);
} catch (error) {
console.error("Error fetching response:", error);
}
};
return (
<div className="container">
<h1>Hi, I'm Movio</h1>
<h4>Your Ultimate Movie Companion</h4>
<div id="conversation">
{conversation.map((item, index) => (
<p key={index} className={item.role}>
{item.message}
</p>
))}
</div>
<input
type="text"
id="queryInput"
placeholder="Ask me anything about Movies..."
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button onClick={sendQuery}>Submit</button>
</div>
);
}
export default App;
We have also added some basic styling in the <style>
tag for the user interface.
Installing Libraries
For the Movie Companion app, we will install some necessary libraries. These libraries are:
express - To create and manage your web server
julep SDK - To interact with a specific service or API provided by Julep
body-parser - To parse incoming request bodies, making it easier to handle data sent by clients
cors - To enable cross-origin requests, allowing your server to handle requests from different domains
dotenv - To retrieve the value stored in the
.env
fileaxios - a promise-based HTTP Client for node.js and the browser
Run this command to install libraries:
npm install express @julep/sdk cors body-parser dotenv axios
Integrating Julep
To integrate Julep, we need an API key.
Go to platform.julep.ai and Sign in with Google account credentials. Copy the YOUR API TOKEN present on the top-right corner.
This API Token will serve as your API key.
Create a .env
file in your project directory and paste the following code:
JULEP_API_KEY = “api_key”
Replace the api_key
with the copied API Token.
Create a file in your project directory and name it server.js
. Whole Julep code will come in this file.
Firstly, we will import the required libraries.
import express from "express";
import julep from "@julep/sdk";
import bodyParser from "body-parser";
import cors from "cors";
import { fileURLToPath } from "url"; // Import the fileURLToPath function
import path from "path";
import dotenv from "dotenv";
Create a new client
using Julep SDK‘s Client
class. This client interacts with the Julep API and initializes the managers for agents, users, sessions, documents, memories, and tools.
const apiKey = process.env.JULEP_API_KEY;
const client = new julep.Client({ apiKey });
Now, we will create an Express app instance to serve as the backend server. Use the bodyparser.json()
to configure the app to parse incoming JSON requests automatically, and cors()
to enable Cross-Origin Resource Sharing (CORS) allowing requests from multiple origins.
const app = express();
app.use(bodyParser.json());
app.use(cors());
Set up an asynchronous route handler for POST requests to the Express app's /chat
endpoint.
app.post("/chat", async (req, res) => {
Inside the try block, Create a query
variable that stores the query entered by the user.
try {
const query = req.body.query;
Now, let’s create users, agents, and sessions to perform the interaction with Julep API.
Creating User
User object represents an entity, either a real person or a system, that interacts with the application. Every AI application developed using Julep supports multiple users, each capable of interacting with the Agent. Each of these users is distinct, meaning they have their own unique identities and assigned roles.
User is an optional entity, and an application can function properly without defining one. However, it's advisable to create a user profile for each individual or system interacting with the Agent for better organization and tracking. Specifically, adding some basic details about the user can help the application better understand their behavior. This enables the application to provide personalized results tailored to the user's preferences and needs.
When it comes to creating a user, Julep offers a users.create()
method which we can be used on the client
to create a user. Creating a user demands 4 attributes:
Name - Name of the user
About - Small description of the user
Documents - Essential documents formatted as text tailored to the user's needs (Optional)
Metadata - Additional data beyond the ID that pertains to the user within the application (Optional)
Here’s an example:
const user = await client.users.create({
name: "Sam",
about: "Machine Learning Developer and AI Enthusiast",
docs:[{"title": "AI Efficiency Report", "content": "...", "metadata": {"page": 1}}], // Optional
metadata:{"db_uuid": "1234"}, // Optional
});
Now, let’s create a user for our Movie Companion app:
const user = await client.users.create({
name: "Ayush",
about: "A developer",
});
Here, we have created a user with name Ayush
and A developer
as description.
Creating Agents
Agent is the intelligent interface serving between the user and the application, handling all the interactions and enhancing the user experience. Agents are programmed to process the queries user has asked for and provide tailored results or suggestions.
Agent contains all the configurations and settings of LLM Models you want to use in your AI application. This enables the applications to carry out specific tasks and cater to the individual preferences of users.
These agents can be as simple as a chatbot and can range up to highly complex AI-driven intelligent assistants capable of understanding natural language and performing intricate tasks.
Similarly, like users, Julep includes the agents.create()
method to create an agent. Creating an agent requires a collection of attributes:
Name - Name of the agent
About - Small description of the agent (Optional)
Instructions - List of instructions for the agent to follow (Optional)
Tools - List of functions for agent to execute tasks (Optional)
Model Name - LLM Model that agent will use (Optional)
Settings - Configurations on LLM model (Optional)
Documents - Important documents in text format to be used by agent to improve the persona (Optional)
Metadata - Additional information apart from ID to identify the user or agent (Optional)
Here’s an example:
const agent = client.agents.create(
(name = "Cody"),
(about =
"Cody is an AI powered code reviewer. It can review code, provide feedback, suggest improvements, and answer questions about code."),
(instructions = [
"On every new issue, Review the issue made in the code. Summarize the issue made in the code and add a comment",
"Scrutinize the changes very deeply for potential bugs, errors, security vulnerabilities. Assume the worst case scenario and explain your reasoning for the same.",
]),
(tools = [
{
type: "function",
function: {
name: "github_comment",
description:
"Posts a comment made on a GitHub Pull Request after every new commit. The tool will return a boolean value to indicate if the comment was successfully posted or not.",
parameters: {
type: "object",
properties: {
comment: {
type: "string",
description:
"The comment to be posted on the issue. It should be a summary of the changes made in the PR and the feedback on the same.",
},
pr_number: {
type: "number",
description:
"The issue number on which the comment is to be posted.",
},
},
required: ["comment", "pr_number"],
},
},
},
]),
(model = "gpt-4"),
(default_settings = {
temperature: 0.7,
top_p: 1,
min_p: 0.01,
presence_penalty: 0,
frequency_penalty: 0,
length_penalty: 1.0,
}),
(docs = [{ title: "API Reference", content: "...", metadata: { page: 1 } }]),
(metadata = { db_uuid: "1234" })
);
Now, let’s create the agent for our Movio app:
const agent = await client.agents.create({
name: "Movie suggesting assistant",
model: "gpt-4-turbo",
});
As you can see, we have used the gpt-4-turbo
LLM model in this agent, but Julep supports multiple LLM models that you can use to create AI applications. Check out the documentation to know more.
Creating Sessions
Session is an entity where the users interact with the agent. It’s the period of interaction between the user and the agent. It serves as a framework for the entire interaction that happens, including back-and-forth messaging queries and any other relevant details.
Sessions store a record of all the messages exchanged between the user and agent. This record helps the AI understand the ongoing conversation better and provide more personalized answers.
To create a session, we can use the sessions.create() method. Let’s take a look at the attributes it requires:
Agent ID - ID of the created agent
User ID - ID of the created user (Optional)
Situation - A prompt to describe the background of the interaction
Metadata - Additional information apart from IDs to identify the session (attribute)
Situation attribute plays a vital role in the session as it provides a context for the interaction or the conversation. The situation helps the agent better understand and compute the user’s query and give more tailored replies.
Here’s an example:
// Assuming 'client' is an object with a 'sessions' property containing a 'create' method
let session = client.sessions.create({
agent_id: agent.id,
user_id: user.id,
situation: `
You are James a Software Developer, public speaker & renowned educator.
You are an educator who is qualified to train students, developers & entrepreneurs.
About you:
...
Important guidelines:
...
`,
metadata: { db_uuid: "1234" },
});
Let’s create a session for our Movio app:
const session = await client.sessions.create({
agentId: agent.id,
userId: user.id,
situation:
"You are Movio. You tell the people about movies they ask for, and recommend movies to the users",
});
Here, the agentID
and userId
are the IDs of the agent and user we created earlier, and the situation
is the small context provided for the interaction.
Getting Response Message
After creating the user, agent, and session, we need to handle the interaction. We will use the sessions.chat()
method to handle the chat interaction and get the response message.
This method demands two attributes to function - session.id and an object having messages array.
const chatParams = {
messages: [
{
role: "user",
name: "Ayush",
content: query,
},
],
};
const chatResponse = await client.sessions.chat(session.id, chatParams);
const responseMessage = chatResponse.response[0][0].content;
res.json({ response: responseMessage });
Here, chatParams
object contains the messages array, which includes an object with three properties:
role: The role of the message sender, "user".
name: The user's name, "Ayush".
content: The user's query, stored in the variable query.
Then, the sessions.chat()
method is called on client
with session.id
and chatParams
as arguments. The resultant object is stored in chatResponse.
The value of the content
property is extracted from the chatResponse
and stored in responseMessage
.
Handling Error
To handle the errors, we will use the catch
block to capture the error and display it
catch (error) {
res.status(500).json({ error: error.message });
}
Start the server
To start the server on localhost, we use the listen()
method on app
specifying the port number.
app.listen(3000, () => {
console.log("Server is running on port 3000");
});
This will host the server on localhost:3000
and print the defined string in the console window.
Congratulations! Your AI app is successfully created.
Run the App
The project is completed, we will run it and try it.
To run the app, first, we will run the server.js
file to initiate the Julep API and then the html file for the user interface.
Run this command to start the server:
node server.js
To run the React App, run this command:
npm run dev
This will run your project on the localhost. Here is the demo of the project:
Your app is running successfully.
Project link - https://github.com/ayush2390/julep-movio
Try out Movio - https://codesandbox.io/p/github/ayush2390/julep-movio/main?import=true
Excited to see what more Julep offers? The journey starts with a single click. Visit the repository and give it a star: https://github.com/julep-ai/julep
Check out the tutorial for a deeper understanding of Julep.
Have any questions or feedback? Join the Julep Discord Community