diff --git a/app/helpers/llm_tools.py b/app/helpers/llm_tools.py index 8cfd0a1e..037dc806 100644 --- a/app/helpers/llm_tools.py +++ b/app/helpers/llm_tools.py @@ -90,9 +90,12 @@ async def new_claim( - Customer wants explicitely to create a new claim - Talking about a totally different subject """ + # Customer confirmation await self.tts_callback(customer_response) + # Launch post-call intelligence for the current call await self.post_callback(self.call) + # Store the last message and use it at first message of the new claim self.call = await _db.call_create( CallStateModel( @@ -105,8 +108,8 @@ async def new_claim( content="", persona=MessagePersonaEnum.HUMAN, ), - # Reinsert the last two messages - self.call.messages[-2], + # Reinsert the last message, using more will add the user message asking to create the new claim and the assistant can loop on it sometimes + self.call.messages[-1], ], ) ) @@ -163,6 +166,7 @@ async def new_or_updated_reminder( - Call back for a follow-up - Wait for customer to send a document """ + # Customer confirmation await self.tts_callback(customer_response) # Check if reminder already exists, if so update it @@ -250,7 +254,9 @@ async def updated_claim( - Store details about the conversation - Update the claim with a new phone number """ + # Customer confirmation await self.tts_callback(customer_response) + # Update all claim fields res = "# Updated fields" for field in updates: @@ -260,6 +266,8 @@ async def updated_claim( def _update_claim_field(self, update: UpdateClaimDict) -> str: field = update["field"] new_value = update["value"] + + # Update field old_value = self.call.claim.get(field, None) try: self.call.claim[field] = new_value @@ -340,7 +348,9 @@ async def search_document( - Know the procedure to declare a stolen luxury watch - Understand the requirements to ask for a cyber attack insurance """ + # Customer confirmation await self.tts_callback(customer_response) + # Execute in parallel tasks = await asyncio.gather( *[ @@ -348,8 +358,10 @@ async def search_document( for query in queries ] ) + # Flatten, remove duplicates, and sort by score trainings = sorted(set(training for task in tasks for training in task or [])) + # Format documents for Content Safety scan compatibility # See: https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/content-filter?tabs=warning%2Cpython-new#embedding-documents-in-your-prompt trainings_str = "\n".join( @@ -358,6 +370,7 @@ async def search_document( for training in trainings ] ) + # Format results res = "# Search results" res += f"\n{trainings_str}" @@ -412,6 +425,7 @@ async def notify_emergencies( - A neighbor is having a heart attack - Someons is stuck in a car accident """ + # Customer confirmation await self.tts_callback(customer_response) # TODO: Implement notification to emergency services for production usage logger.info( @@ -454,13 +468,18 @@ async def send_sms( - Confirm a detail like a reference number, if there is a misunderstanding - Send a confirmation, if the customer wants to have a written proof """ + # Customer confirmation await self.tts_callback(customer_response) + + # Send SMS success = await _sms.send( content=message, phone_number=self.call.initiate.phone_number, ) if not success: return "Failed to send SMS" + + # Add message to call self.call.messages.append( MessageModel( action=MessageActionEnum.SMS, @@ -505,11 +524,14 @@ async def speech_speed( """ # Clamp speed between min and max speed = max(0.75, min(speed, 1.25)) + # Update voice initial_speed = self.call.initiate.prosody_rate self.call.initiate.prosody_rate = speed + # Customer confirmation (with new speed) await self.tts_callback(customer_response) + # LLM confirmation return f"Voice speed set to {speed} (was {initial_speed})" @@ -572,7 +594,9 @@ async def speech_lang( # Update lang initial_lang = self.call.lang.short_code self.call.lang = lang + # Customer confirmation (with new language) await self.tts_callback(customer_response) + # LLM confirmation return f"Voice language set to {lang} (was {initial_lang})" diff --git a/app/models/message.py b/app/models/message.py index f31ea61f..d127dfeb 100644 --- a/app/models/message.py +++ b/app/models/message.py @@ -15,12 +15,16 @@ from openai.types.chat.chat_completion_chunk import ChoiceDeltaToolCall from pydantic import BaseModel, Field, field_validator +from app.helpers.config import CONFIG +from app.helpers.llm_utils import AbstractPlugin from app.helpers.monitoring import tracer _FUNC_NAME_SANITIZER_R = r"[^a-zA-Z0-9_-]" _MESSAGE_ACTION_R = r"(?:action=*([a-z_]*))? *(.*)" _MESSAGE_STYLE_R = r"(?:style=*([a-z_]*))? *(.*)" +_db = CONFIG.database.instance() + class StyleEnum(str, Enum): """ @@ -99,7 +103,7 @@ def to_openai(self) -> ChatCompletionMessageToolCallParam: }, ) - async def execute_function(self, plugin: object) -> None: + async def execute_function(self, plugin: AbstractPlugin) -> None: from app.helpers.logging import logger json_str = self.function_arguments @@ -137,7 +141,13 @@ async def execute_function(self, plugin: object) -> None: }, ) as span: try: - res = await getattr(plugin, name)(**args) + # Persist the call if updating it + async with _db.call_transac( + call=plugin.call, + scheduler=plugin.scheduler, + ): + res = await getattr(plugin, name)(**args) + # Format pretty logs res_log = f"{res[:20]}...{res[-20:]}" logger.info("Executing function %s (%s): %s", name, args, res_log) except TypeError as e: