Skip to content

Commit

Permalink
Added sessions to Bot controller, added business logic for process_in…
Browse files Browse the repository at this point in the history
…structions.
  • Loading branch information
mayurvir committed Mar 28, 2024
1 parent 45f1104 commit 31fa3fc
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 20 deletions.
30 changes: 24 additions & 6 deletions controllers/Bot.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
import ActionsService from '../services/Actions.js'
import DBService from '../services/DBService.js'
import logger from '../utils/logger.js'
import twilio from 'twilio'

const { MessagingResponse } = twilio.twiml
const actionsService = new ActionsService()
const db = new DBService();

async function process_wa_webhook(req, res) {
try {
const messageBody = req.body.Body
const message = req.body.Body
const sender = req.body.From
const twiml = new MessagingResponse()
const twiml = new MessagingResponse();

logger.info(`Received message from ${sender}: ${messageBody}`)
// get or create session
const session_response = await db.get_session(sender);
let session = session_response.data;
if(!session_response.status){
session = {
sessionId: sender,
data : []
}
}

const responseMessage =
await actionsService.process_instruction(messageBody)

twiml.message(responseMessage)
logger.info(`Received message from ${sender}: ${message}`)

const process_response = await actionsService.process_instruction(message, session.data)

if(process_response.status && process_response.message){
session.data.push({ role: 'user', content: message }); // add user message to session
session.data.push({ role: 'assistant', content: process_response.message }); // add system response to session
await db.update_session(sender, session);
}

twiml.message(process_response.message)

res.type('text/xml').send(twiml.toString())
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"test": "NODE_ENV=test mocha tests --recursive --timeout 900000 -r dotenv/config --exit",
"test:unit": "NODE_ENV=test mocha tests/unit --recursive --timeout 0 -r dotenv/config --exit",
"test:apis": "NODE_ENV=test mocha tests/apis --recursive --timeout 0 -r dotenv/config --exit",
"start": "nodemon server.js",
"start": "nodemon --env-file=.env server.js",
"dev": "NODE_ENV=dev && npm start",
"prod": "NODE_ENV=prod && npm start",
"prettify": "prettier --write .",
Expand Down
5 changes: 5 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import express from 'express'
import bodyParser from 'body-parser'
import logger from './utils/logger.js'
import messageController from './controllers/Bot.js'
import DBService from './services/DBService.js'

const app = express()

Expand All @@ -17,6 +18,10 @@ app.use(bodyParser.json())
// app.post('/act', actions.act)
app.post('/webhook', messageController.process_wa_webhook)

// Reset all sessions
const db = new DBService();
await db.clear_all_sessions();

// Start the Express server
app.listen(process.env.SERVER_PORT, () => {
logger.info(`Server is running on port ${process.env.SERVER_PORT}`)
Expand Down
4 changes: 2 additions & 2 deletions services/AI.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class AI {
return response;
}

async get_message_from_beckn_response(json_response) {
async get_text_from_json(json_response) {
const openai_messages = [
{
role: 'system',
Expand All @@ -187,7 +187,7 @@ class AI {
try {
const completion = await openai.chat.completions.create({
messages: openai_messages,
model: process.env.OPENAI_MODEL_ID,
model: 'gpt-4-0125-preview', // using a higher token size model
temperature: 0,
response_format: { type: 'json_object' },
})
Expand Down
40 changes: 35 additions & 5 deletions services/Actions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import twilio from 'twilio'
import logger from '../utils/logger.js'
import axios from 'axios'
import AI from './AI.js'

const accountSid = process.env.TWILIO_ACCOUNT_SID
const authToken = process.env.TWILIO_AUTH_TOKEN
Expand All @@ -11,6 +12,7 @@ const client = twilio(accountSid, authToken)
class Actions {

constructor() {
this.ai = new AI()
this.context = [];
}

Expand Down Expand Up @@ -45,15 +47,43 @@ class Actions {
return responseObject
}

async process_instruction(messageBody) {
async process_instruction(message, context=[]) {
let response = {
status: false,
message: 'Failed to process the instruction',
}
try {
return `You said "${messageBody}"`

// Get becnk request from text message
const beckn_request = await this.ai.get_beckn_request_from_text(message, context);
if(!beckn_request.status){
response.message = beckn_request.message;
}
else{
// Call the API
const call_api_response = await this.call_api(beckn_request.data.url, beckn_request.data.method, beckn_request.data.body, beckn_request.data.headers)
if(!call_api_response.status){
response.message = `Failed to call the API: ${call_api_response.error}`
response.data = call_api_response.data
}
else{

// Format the response
const get_text_from_json_response = await this.ai.get_text_from_json(call_api_response.data)
response = {
status: true,
message: get_text_from_json_response.message
}
}
}
} catch (error) {
logger.error(`Error processing instruction: ${error.message}`)
throw new Error(`Failed to process the instruction: ${error.message}`)
logger.error(`Error processing instruction: ${error.message}`)
response.message = `Failed to process the instruction: ${error.message}`
}

return response;
}

async send_message(recipient, message) {
try {
await client.messages.create({
Expand Down
122 changes: 122 additions & 0 deletions services/DBService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import logger from '../utils/logger.js'
import redis from 'redis'

class DBService {

constructor() {
let redisUrl = process.env.REDIS_URL || 'redis://localhost:6379'
this.redisClient = redis.createClient({ url: redisUrl })

this.redisClient.on('error', (err) => {
logger.error('Redis Client Error', err)
})

this.redisClient.connect()
}

/**
* Get session using Redis
* @param {*} sessionId
* @returns
*/
async get_session(sessionId) {
let response = {
status: false,
}
try {
let sessionData = await this.redisClient.get(sessionId)
if (sessionData === null) {
response.status = false
response.message = 'Session does not exist!'
} else {
response.status = true
response.message = 'Session retrieved successfully!'
response.data = JSON.parse(sessionData)
}
} catch (err) {
logger.error(err)
response.error = err
}

logger.info(response)
return response
}

/**
* Deletes a session using Redis
* @param {*} sessionId
* @returns
*/
async delete_session(sessionId) {
let response = {
status: false,
}
try {
let deleteResponse = await this.redisClient.del(sessionId)
if (deleteResponse === 0) {
response.status = false
response.message = 'Session does not exist!'
} else {
response.status = true
response.message = 'Session deleted successfully!'
}
} catch (err) {
logger.error(err)
response.error = err
}

logger.info(response)
return response
}

/**
* Function to clear all sessions
* @returns
*/
async clear_all_sessions(){
let response = {
status: false,
}
try {
let deleteResponse = await this.redisClient.flushAll()
if (deleteResponse === 0) {
response.status = false
response.message = 'Sessions do not exist!'
} else {
response.status = true
response.message = 'Session flushed successfully!'
}
} catch (err) {
logger.error(err)
response.error = err
}

logger.info(response)
return response
}

/**
* Updates a session
* @param {*} sessionId
* @param {*} sessionData
* @returns
*/
async update_session(sessionId, sessionData) {
let response = {
status: false,
}
try {
await this.redisClient.set(sessionId, JSON.stringify(sessionData))
response.status = true
response.message = 'Session updated successfully!'
} catch (err) {
logger.error(err)
response.error = err
}

logger.info(response)
return response
}
}

export default DBService;
2 changes: 1 addition & 1 deletion tests/unit/services/actions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('Test cases for services/actions.js', () => {
it('should process the instruction message', async () => {
const messageBody = 'test message';
const result = await actionsService.process_instruction(messageBody);
expect(result).to.equal('You said "test message"');
expect(result.message).to.equal('You said "test message"');
});
})

Expand Down
10 changes: 5 additions & 5 deletions tests/unit/services/ai.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,14 @@ describe('Test cases for services/ai/get_beckn_request_from_text()', () => {
});


describe('Test cases for services/ai/get_message_from_beckn_response()', () => {
it('Should test get_message_from_beckn_response() and throw response with success false for empty object', async () => {
const response = await ai.get_message_from_beckn_response({})
describe('Test cases for services/ai/get_text_from_json()', () => {
it('Should test get_text_from_json() and throw response with success false for empty object', async () => {
const response = await ai.get_text_from_json({})
expect(response.success).to.equal(false)
expect(response.message).to.equal('Empty JSON')
})
it('Should test get_message_from_beckn_response() return some message with success true', async () => {
const response = await ai.get_message_from_beckn_response(on_init)
it('Should test get_text_from_json() return some message with success true', async () => {
const response = await ai.get_text_from_json(on_init)
expect(response.success).to.equal(true)
})
})

0 comments on commit 31fa3fc

Please sign in to comment.