Skip to content

Commit c619b43

Browse files
badri-singhalswaroopvarma1
authored andcommitted
Cancellation reason support
1 parent c551519 commit c619b43

File tree

6 files changed

+98
-23
lines changed

6 files changed

+98
-23
lines changed

app/agents/voice/breeze_buddy/managers/calls.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,19 +143,23 @@ async def _retry_call(
143143
if outcome == LeadCallOutcome.NO_ANSWER or outcome == LeadCallOutcome.BUSY:
144144
reporting_webhook_url = lead.payload.get("reporting_webhook_url")
145145
if reporting_webhook_url:
146+
call_duration = None
147+
if lead.call_initiated_time:
148+
call_initiated_time_utc = lead.call_initiated_time.astimezone(
149+
timezone.utc
150+
)
151+
call_duration = (
152+
datetime.now(timezone.utc) - call_initiated_time_utc
153+
).total_seconds()
154+
146155
summary_data = {
147156
"callSid": lead.call_id,
148157
"outcome": outcome.value,
158+
"attemptCount": lead.attempt_count + 1,
159+
"callDuration": call_duration,
149160
"orderId": lead.payload.get("order_id"),
150161
}
151-
if lead.merchant_id != RequestedBy.BREEZE:
152-
call_duration = None
153-
if lead.call_initiated_time:
154-
call_duration = (
155-
datetime.now(timezone.utc) - lead.call_initiated_time
156-
).total_seconds()
157-
summary_data["attemptCount"] = lead.attempt_count + 1
158-
summary_data["callDuration"] = call_duration
162+
159163
try:
160164
async with create_aiohttp_session() as session:
161165
payload = json.dumps(summary_data).replace(" ", "")
@@ -337,6 +341,7 @@ async def handle_call_completion(
337341
transcription: dict,
338342
call_end_time: datetime,
339343
updated_address: str | None = None,
344+
cancellation_reason: str | None = None,
340345
):
341346
"""
342347
Handles call completion events.
@@ -362,6 +367,8 @@ async def handle_call_completion(
362367
meta_data = {"transcription": transcription}
363368
if updated_address:
364369
meta_data["updated_address"] = updated_address
370+
if cancellation_reason:
371+
meta_data["cancellation_reason"] = cancellation_reason
365372

366373
await update_lead_call_completion_details(
367374
id=lead.id,

app/agents/voice/breeze_buddy/workflows/order_confirmation/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from app.schemas import LeadCallOutcome
22

3-
43
# Mapping dictionary for outcome strings to LeadCallOutcome enum values
54
OUTCOME_TO_ENUM = {
65
"confirmed": LeadCallOutcome.CONFIRM,

app/agents/voice/breeze_buddy/workflows/order_confirmation/websocket_bot.py

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
auto_trace,
3636
setup_tracing,
3737
)
38-
from app.agents.voice.breeze_buddy.workflows.order_confirmation.types import OrderData
3938
from app.agents.voice.breeze_buddy.stt import get_stt_service
39+
from app.agents.voice.breeze_buddy.workflows.order_confirmation.types import OrderData
4040
from app.agents.voice.breeze_buddy.workflows.order_confirmation.utils import (
4141
OUTCOME_TO_ENUM,
4242
indian_number_to_speech,
@@ -59,7 +59,7 @@
5959
)
6060
from app.core.logger import logger
6161
from app.core.security.sha import calculate_hmac_sha256
62-
from app.database.accessor import get_lead_by_call_id
62+
from app.database.accessor import get_lead_by_call_id, update_lead_call_initiated_time
6363
from app.schemas import CallProvider, LeadCallOutcome, RequestedBy
6464

6565
load_dotenv(override=True)
@@ -88,6 +88,7 @@ def __init__(
8888
self.shop_name = None
8989
self.address = None
9090
self.updated_address = None
91+
self.cancellation_reason = None
9192
self.updated_fields = {} # Track only updated fields for webhook
9293
self.serializer = serializer
9394
self.hangup_function = hangup_function
@@ -99,6 +100,7 @@ def __init__(
99100
async def run(self):
100101
logger.info("Starting WebSocket bot")
101102
await self.ws.accept()
103+
call_initiated_time = datetime.now(timezone.utc)
102104

103105
try:
104106
start_data = self.ws.iter_text()
@@ -155,6 +157,7 @@ async def run(self):
155157
stream_sid = call_data.get("stream_sid")
156158
self.call_sid = call_data.get("start").get("call_sid")
157159

160+
await update_lead_call_initiated_time(self.call_sid, call_initiated_time)
158161
lead = await get_lead_by_call_id(self.call_sid)
159162
if not lead:
160163
logger.error(f"Could not find lead for call_sid: {self.call_sid}")
@@ -267,7 +270,6 @@ async def run(self):
267270
self.system_prompt = self._get_system_prompt(
268271
self.shop_name,
269272
customer_name,
270-
self.order_id,
271273
self.order_summary,
272274
price_words,
273275
self.address,
@@ -380,7 +382,6 @@ def _get_system_prompt(
380382
self,
381383
shop_name,
382384
customer_name,
383-
order_id,
384385
order_summary,
385386
total_price_words,
386387
address,
@@ -450,19 +451,22 @@ async def _finalize_call(self):
450451

451452
call_duration = None
452453
if self.lead and self.lead.call_initiated_time:
454+
call_initiated_time_utc = self.lead.call_initiated_time.astimezone(
455+
timezone.utc
456+
)
453457
call_duration = (
454-
datetime.now(timezone.utc) - self.lead.call_initiated_time
458+
datetime.now(timezone.utc) - call_initiated_time_utc
455459
).total_seconds()
456460

457461
summary_data = {
458462
"callSid": self.call_sid,
463+
"cancellationReason": self.cancellation_reason,
459464
"outcome": call_outcome,
460465
"updatedAddress": self.updated_address,
466+
"attemptCount": self.lead.attempt_count + 1,
467+
"callDuration": call_duration,
461468
"orderId": self.order_id,
462469
}
463-
if self.lead.merchant_id != RequestedBy.BREEZE:
464-
summary_data["attemptCount"] = self.lead.attempt_count + 1
465-
summary_data["callDuration"] = call_duration
466470
logger.info(f"Call summary data: {summary_data}")
467471

468472
if self.reporting_webhook_url and call_outcome != LeadCallOutcome.BUSY:
@@ -502,6 +506,7 @@ async def _finalize_call(self):
502506
},
503507
call_end_time=datetime.now(),
504508
updated_address=self.updated_address,
509+
cancellation_reason=self.cancellation_reason,
505510
)
506511
logger.info(
507512
f"Updated database for call_id: {self.call_sid} with outcome: {call_outcome}"
@@ -533,9 +538,14 @@ def _get_flow_config(self):
533538
),
534539
FlowsFunctionSchema(
535540
name="cancel_order",
536-
description="Call this function to cancel the user's order.",
541+
description="Call this function to cancel the user's order. If the user gives a reason for cancellation, pass it.",
537542
handler=self._deny_order_handler,
538-
properties={},
543+
properties={
544+
"reason": {
545+
"type": "string",
546+
"description": "The reason for cancelling the order.",
547+
}
548+
},
539549
required=[],
540550
),
541551
]
@@ -558,9 +568,14 @@ def _get_flow_config(self):
558568
),
559569
FlowsFunctionSchema(
560570
name="cancel_order",
561-
description="Call this function to cancel the user's order.",
571+
description="Call this function to cancel the user's order. If the user gives a reason for cancellation, pass it.",
562572
handler=self._deny_order_handler,
563-
properties={},
573+
properties={
574+
"reason": {
575+
"type": "string",
576+
"description": "The reason for cancelling the order.",
577+
}
578+
},
564579
required=[],
565580
),
566581
FlowsFunctionSchema(
@@ -766,10 +781,19 @@ async def _confirm_order_with_question_handler(self):
766781
"order_confirmation_with_question_and_end"
767782
)
768783

784+
def _get_cancellation_reason(self, reason: str | dict) -> str:
785+
"""Extracts the cancellation reason from the LLM, which can be a string or a dict."""
786+
if isinstance(reason, dict):
787+
return reason.get("reason", "User requested for cancellation")
788+
return reason
789+
769790
@auto_trace("cancel_order")
770-
async def _deny_order_handler(self):
771-
logger.info("Order denied. Transitioning to cancellation node.")
791+
async def _deny_order_handler(self, reason: str = "user asked to cancel"):
792+
logger.info(
793+
f"Order denied with reason: {reason}. Transitioning to cancellation node."
794+
)
772795
self.outcome = "cancelled"
796+
self.cancellation_reason = self._get_cancellation_reason(reason)
773797
return {}, self._create_node_from_config("order_cancellation_and_end")
774798

775799
@auto_trace("user_busy")

app/database/accessor/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
get_leads_by_status_and_time_before,
1818
update_lead_call_completion_details,
1919
update_lead_call_details,
20+
update_lead_call_initiated_time,
2021
update_lead_call_recording_url,
2122
)
2223
from .breeze_buddy.outbound_number import (
@@ -45,6 +46,7 @@
4546
"update_lead_call_details",
4647
"get_lead_by_call_id",
4748
"update_lead_call_completion_details",
49+
"update_lead_call_initiated_time",
4850
"update_lead_call_recording_url",
4951
"get_all_lead_call_trackers",
5052
"get_lead_call_trackers_count",

app/database/accessor/breeze_buddy/lead_call_tracker.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
insert_lead_call_tracker_query,
2020
update_lead_call_completion_details_query,
2121
update_lead_call_details_query,
22+
update_lead_call_initiated_time_query,
2223
update_lead_call_recording_url_query,
2324
)
2425
from app.schemas import (
@@ -157,6 +158,32 @@ async def get_lead_by_call_id(call_id: str) -> Optional[LeadCallTracker]:
157158
return None
158159

159160

161+
async def update_lead_call_initiated_time(
162+
call_id: str, call_initiated_time: datetime
163+
) -> Optional[LeadCallTracker]:
164+
"""
165+
Update lead call initiated time.
166+
"""
167+
logger.info(f"Updating lead with call ID {call_id} with call initiated time")
168+
169+
try:
170+
query_text, values = update_lead_call_initiated_time_query(
171+
call_id, call_initiated_time
172+
)
173+
result = await run_parameterized_query(query_text, values)
174+
if result and get_row_count(result) > 0:
175+
decoded_result = decode_lead_call_tracker(result[0])
176+
logger.info(f"Lead updated successfully: {decoded_result}")
177+
return decoded_result
178+
179+
logger.error("Failed to update lead")
180+
return None
181+
182+
except Exception as e:
183+
logger.error(f"Error updating lead: {e}")
184+
return None
185+
186+
160187
async def update_lead_call_recording_url(
161188
call_id: str, recording_url: str
162189
) -> Optional[LeadCallTracker]:

app/database/queries/breeze_buddy/lead_call_tracker.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,22 @@ def update_lead_call_recording_url_query(
134134
return text, values
135135

136136

137+
def update_lead_call_initiated_time_query(
138+
call_id: str, call_initiated_time: datetime
139+
) -> Tuple[str, List[Any]]:
140+
"""
141+
Generate query to update lead call initiated time.
142+
"""
143+
text = f"""
144+
UPDATE "{LEAD_CALL_TRACKER_TABLE}"
145+
SET "call_initiated_time" = $1, "updated_at" = NOW()
146+
WHERE "call_id" = $2
147+
RETURNING *;
148+
"""
149+
values = [call_initiated_time, call_id]
150+
return text, values
151+
152+
137153
def update_lead_call_completion_details_query(
138154
id: str,
139155
status: LeadCallStatus,

0 commit comments

Comments
 (0)