Skip to content

Commit

Permalink
Fixes made before iteration #1
Browse files Browse the repository at this point in the history
  • Loading branch information
mayurvir committed Apr 7, 2024
1 parent fbcc6c5 commit 442a588
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 160 deletions.
11 changes: 4 additions & 7 deletions config/openai.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
{
"SUPPORTED_ACTIONS": [
{ "action": "search", "description": "If the user intends to perform a search for a product." },
{ "action": "select", "description": "If the user likes or selects any item, this action should be used." },
{ "action": "init", "description": "If the user wants to place an order after search and select and has shared the billing details." },
{ "action": "confirm", "description": "Confirm an order. This action gets called when users confirms an order." },
{ "action": "clear_chat", "description": "If the user wants to clear the chat." },
{ "action": "clear_all", "description": "If the user wants to clear the session." },
{ "action": "booking_collection", "description": "If the assistant had shared a plan or initinerary and user has indicated to make the bookings for that plan." }
{ "action": "search", "description": "If the user clearly indicates to perform a search for a specific product. Sample instructions : 'find a hotel', 'find an ev charger', 'find tickets'" },
{ "action": "select", "description": "If the user likes or selects any item, this action should be used. This action can only be called if a search has been called before." },
{ "action": "init", "description": "If the user wants to place an order after search and select and has shared the billing details. This action can only be called if a select has been called before." },
{ "action": "confirm", "description": "Confirm an order. This action gets called when users confirms an order. This action can only be called if an init has been called before." }
],
"SCHEMA_TRANSLATION_CONTEXT": [
{ "role": "system", "content": "Your job is to identify the endpoint, method and request body from the given schema, based on the last user input and return the extracted details in the following JSON structure : \n\n {'url':'', 'method':'', 'body':''}'"},
Expand Down
2 changes: 1 addition & 1 deletion config/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
],
"rules": [
"item.descriptor should not be used in search intent for this domain",
"search should have fulfillment for this domain."
"search should have fulfillment for this domain. fulfillment should only contain location for this domain."
]
},
"hospitality": {
Expand Down
134 changes: 72 additions & 62 deletions controllers/Bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async function process_wa_webhook(req, res) {
logger.info(`Sending formatted response to ${sender}: ${process_response.formatted}`)
if(format!='application/json'){
// res.type('text/xml').send(twiml.toString())
actionsService.send_message(sender, process_response.formatted)
await actionsService.send_message(sender, process_response.formatted)
res.send("Done!")
}
else{
Expand All @@ -68,10 +68,10 @@ async function process_wa_webhook(req, res) {
}

/**
* Function to process any text message received by the bot
* @param {*} req
* @param {*} res
*/
* Function to process any text message received by the bot
* @param {*} req
* @param {*} res
*/
async function process_text(req, res) {
let ai = new AI();

Expand All @@ -97,7 +97,7 @@ async function process_text(req, res) {
bookings: [],
active_transaction: null
}

// Update lat, long
if(req.body.Latitude && req.body.Longitude){
message+=` lat:${req.body.Latitude} long:${req.body.Longitude}`
Expand All @@ -113,7 +113,7 @@ async function process_text(req, res) {
}

try{

// Get profile
const profileResponse = await ai.get_profile_from_text(message, session.profile);
if(profileResponse.status){
Expand All @@ -124,30 +124,15 @@ async function process_text(req, res) {
}

logger.info(`\u001b[1;34m User profile : ${JSON.stringify(session.profile)}\u001b[0m`)

// get action
ai.action = await ai.get_beckn_action_from_text(message, [...session.text.slice(-1)], session.bookings);


ai.bookings = session.bookings;

// Reset actions context if action is search
if(ai.action?.action === 'search') {
session.actions = EMPTY_SESSION.actions;
session.active_transaction = ai.action.transaction_id || uuidv4();
}
// check for booking collection


if(ai.action?.action === 'clear_chat'){
session = {
...EMPTY_SESSION,
profile: session.profile
};
response.formatted = 'Session cleared! You can start a new session now.';
}
else if(ai.action?.action === 'clear_all'){
session = EMPTY_SESSION;
response.formatted = 'Session & profile cleared! You can start a new session now.';
}
else if(ai.action?.action === 'booking_collection'){
let booking_collection = await ai.check_if_booking_collection(message, [...session.text.slice(-1)]);
if(booking_collection){
logger.info(`Booking collection found!`);
response.formatted = await ai.get_ai_response_to_query('Share the list of bookings to be made? Please include only hotels and tickets to be booked. It should be a short list with just names of bookings to be made. For e.g. Here is a list of bookings you need to make: \n1. hotel at xyz \n2. Tickets for abc \nWhich one do you want to search first?', session.text);
logger.info(`AI response: ${response.formatted}`);

Expand All @@ -156,51 +141,78 @@ async function process_text(req, res) {
ai.bookings && ai.bookings.map(booking =>{
booking.transaction_id = uuidv4();
})

session.text.push({ role: 'user', content: message });
session.text.push({ role: 'assistant', content: response.formatted });
}
else if(ai.action?.action == null) {
// get ai response
response.formatted = await ai.get_ai_response_to_query(message, session.text);
logger.info(`AI response: ${response.formatted}`);

session.text.push({ role: 'user', content: message });
session.text.push({ role: 'assistant', content: response.formatted });
}
else{

session.bookings = ai.bookings;
response = await process_action(ai.action, message, session, sender, format);
ai.bookings = response.bookings;

// update actions
if(ai.action?.action === 'confirm') {

// get action
ai.action = await ai.get_beckn_action_from_text(message, session.text, session.bookings);

// Reset actions context if action is search
if(ai.action?.action === 'search') {
session.actions = EMPTY_SESSION.actions;
session.text = EMPTY_SESSION.text;
session.active_transaction = ai.action.transaction_id || uuidv4();
}
else if(response.formatted && response.raw){
session.actions.raw.push({ role: 'user', content: message });
session.actions.raw.push({ role: 'assistant', content: JSON.stringify(response.raw)});


if(ai.action?.action === 'clear_chat'){
session = {
...EMPTY_SESSION,
profile: session.profile
};
response.formatted = 'Session cleared! You can start a new session now.';
}
else if(ai.action?.action === 'clear_all'){
session = EMPTY_SESSION;
response.formatted = 'Session & profile cleared! You can start a new session now.';
}
else if(ai.action?.action == null) {

// get ai response
response.formatted = await ai.get_ai_response_to_query(message, session.text);
logger.info(`AI response: ${response.formatted}`);

session.actions.formatted.push({ role: 'user', content: message });
session.actions.formatted.push({ role: 'assistant', content: response.formatted });

session.text.push({ role: 'user', content: message });
session.text.push({ role: 'assistant', content: response.formatted });
session.text.push({ role: 'assistant', content: response.formatted });

}
else{

session.bookings = ai.bookings;
response = await process_action(ai.action, message, session, sender, format);
ai.bookings = response.bookings;

// update actions
if(ai.action?.action === 'confirm') {
session.actions = EMPTY_SESSION.actions;
session.text = EMPTY_SESSION.text;
}
else if(response.formatted && response.raw){
session.actions.raw.push({ role: 'user', content: message });
session.actions.raw.push({ role: 'assistant', content: JSON.stringify(response.raw)});

session.actions.formatted.push({ role: 'user', content: message });
session.actions.formatted.push({ role: 'assistant', content: response.formatted });

session.text.push({ role: 'user', content: message });
session.text.push({ role: 'assistant', content: response.formatted });
}
}

}

// if(session.bookings && session.bookings.length>0) session.bookings = await ai.get_bookings_status(session.bookings, session.text);
logger.info(`\u001b[1;34m Bookings status : ${JSON.stringify(ai.bookings)}\u001b[0m`)

// update session
session.bookings = ai.bookings;
await db.update_session(sender, session);

// Send response
if(format!='application/json'){
actionsService.send_message(sender, response.formatted)
await actionsService.send_message(sender, response.formatted)
res.send("Done!")
}
else (raw_yn && response.raw) ? res.send(response.raw) : res.send(response.formatted)
Expand Down Expand Up @@ -232,7 +244,7 @@ async function process_action(action, text, session, sender=null, format='applic
ai.action = action;
ai.bookings = session.bookings;

format!='application/json' && actionsService.send_message(sender, `_Please wait while we process your request through open networks..._`)
format!='application/json' && await actionsService.send_message(sender, `_Please wait while we process your request through open networks..._`)

// Get schema
const schema = await ai.get_schema_by_action(action.action);
Expand All @@ -245,9 +257,7 @@ async function process_action(action, text, session, sender=null, format='applic
if(schema && beckn_context){
let request=null;
if(ai.action.action==='search'){
let search_context = [
...session.text.slice(-1)
];
let search_context = session.text;
if(session.profile){
search_context=[
{ role: 'system', content: `User pforile: ${JSON.stringify(session.profile)}`},
Expand All @@ -268,19 +278,19 @@ async function process_action(action, text, session, sender=null, format='applic
}
}
else{
request = await ai.get_beckn_request_from_text(text, [...session.actions.raw.slice(-1)], beckn_context, schema);
request = await ai.get_beckn_request_from_text(text, session.actions.raw, beckn_context, schema, session.profile);
}

if(request.status){
// call api
const api_response = await actionsService.call_api(request.data.url, request.data.method, request.data.body, request.data.headers)
format!='application/json' && actionsService.send_message(sender, `_Your request is processed, generating a response..._`)
format!='application/json' && await actionsService.send_message(sender, `_Your request is processed, generating a response..._`)
if(!api_response.status){
logger.error(`Failed to call the API: ${api_response.error}`)
response.formatted = 'Request could not be processed. Do you want to try again?'
}
else{

response.raw = request.data.body.context.action==='search' ? await ai.compress_search_results(api_response.data) : api_response.data

// update booking status
Expand All @@ -294,7 +304,7 @@ async function process_action(action, text, session, sender=null, format='applic
logger.info(`Updated bookings: ${JSON.stringify(response.bookings)}`);
}
ai.bookings = response.bookings;

const formatted_response = await ai.format_response(
api_response.data,
[...session.actions.formatted, { role: 'user', content: text }],
Expand Down
32 changes: 0 additions & 32 deletions schemas/core_1.1.0/confirm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,23 +107,6 @@ components:
- billing
- fulfillments
properties:
id:
type: string
description: Human-readable ID of the order. This is generated at the BPP layer. The BPP can either generate order id within its system or forward the order ID created at the provider level.
status:
description: Status of the order. Allowed values can be defined by the network policy
type: string
enum:
- ACTIVE
- COMPLETE
- CANCELLED
type:
description: 'This is used to indicate the type of order being created to BPPs. Sometimes orders can be linked to previous orders, like a replacement order in a retail domain. A follow-up consultation in healthcare domain. A single order part of a subscription order. The list of order types can be standardized at the network level.'
type: string
default: DEFAULT
enum:
- DRAFT
- DEFAULT
items:
description: The items purchased / availed in this order. This should be based on what was used in the `select` call.
type: array
Expand All @@ -146,21 +129,6 @@ components:
type: string
code:
type: string
short_desc:
type: string
long_desc:
type: string
additional_desc:
type: object
properties:
url:
type: string
content_type:
type: string
enum:
- text/plain
- text/html
- application/json
FulfillmentState:
description: Describes the state of fulfillment
type: object
Expand Down
17 changes: 0 additions & 17 deletions schemas/core_1.1.0/init.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,6 @@ components:
- items
- billing
properties:
id:
type: string
description: Human-readable ID of the order. This is generated at the BPP layer. The BPP can either generate order id within its system or forward the order ID created at the provider level.
status:
description: Status of the order. Allowed values can be defined by the network policy
type: string
enum:
- ACTIVE
- COMPLETE
- CANCELLED
type:
description: 'This is used to indicate the type of order being created to BPPs. Sometimes orders can be linked to previous orders, like a replacement order in a retail domain. A follow-up consultation in healthcare domain. A single order part of a subscription order. The list of order types can be standardized at the network level.'
type: string
default: DEFAULT
enum:
- DRAFT
- DEFAULT
billing:
description: The billing details of this order
allOf:
Expand Down
31 changes: 0 additions & 31 deletions schemas/core_1.1.0/select.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,42 +103,11 @@ components:
description: Describes a legal purchase order. It contains the complete details of the legal contract created between the buyer and the seller.
type: object
properties:
id:
type: string
description: Human-readable ID of the order. This is generated at the BPP layer. The BPP can either generate order id within its system or forward the order ID created at the provider level.
status:
description: Status of the order. Allowed values can be defined by the network policy
type: string
enum:
- ACTIVE
- COMPLETE
- CANCELLED
type:
description: 'This is used to indicate the type of order being created to BPPs. Sometimes orders can be linked to previous orders, like a replacement order in a retail domain. A follow-up consultation in healthcare domain. A single order part of a subscription order. The list of order types can be standardized at the network level.'
type: string
default: DEFAULT
enum:
- DRAFT
- DEFAULT
items:
description: The items purchased / availed in this order
type: array
items:
$ref: '#/components/schemas/Item'
Billing:
description: 'Describes the billing details of an entity.<br>This has properties like name,organization,address,email,phone,time,tax_number, created_at,updated_at'
type: object
properties:
name:
description: Name of the billable entity
type: string
email:
description: Email address where the bill is sent to
type: string
format: email
phone:
description: Phone number of the billable entity
type: string
ItemQuantity:
description: Describes the count or amount of an item
type: object
Expand Down
9 changes: 7 additions & 2 deletions services/AI.js
Original file line number Diff line number Diff line change
Expand Up @@ -467,10 +467,13 @@ class AI {
async check_if_booking_collection(message, context=[]){
let response = false;
const openai_messages = [
{ role: 'system', content: `Please check the given user message and context and identify if the given message is an instruction to book a collection of products and services. For e.g. if the assistant had shared the itinerary/plan in context and user wants to make all bookings for that plan then you should return true.` },
{ role: 'system', content: `Your job is to identify if the given user input is an instruction to make multiple bookings at once.` },
{ role: 'system', content: `you must return a json object with the following structure {status: true/false}` },
{ role: 'system', content: `Status must be true if the given user message is a request to make multiple bookings and the last assistant message is an itinerary or a plan. For e.g. if the assistant had shared the itinerary/plan in context and user says 'lets make the bookings' or 'make all bookings', or 'Can you make the bookings?', status should be true` },
{ role: 'system', content: `Status should be false if its not a clear instrcution to make multiple bookings. For r.g. if the user shares details about the trip, its not a clear instrcution to make bookings.` },
{ role: 'system', content: `Status should be false if the assistant has asked to select, initiate or confirm an order.` },
{ role: 'system', content: `Context goes here...` },
...context.slice(-1),
...context,
{ role: 'user', content: message }
]

Expand Down Expand Up @@ -515,6 +518,8 @@ class AI {
logger.error(e)

}

logger.info(`Got bookings array : ${JSON.stringify(bookings)}`)
return bookings;
}

Expand Down
Loading

0 comments on commit 442a588

Please sign in to comment.