import json from typing import Annotated from fastapi import Depends from langchain.chat_models import ChatOpenAI from botbuilder.core import ActivityHandler, TurnContext from botbuilder.schema import Activity, Attachment, ActivityTypes import asyncio from pydantic import ValidationError from backend.app.bots.adaptive_cards import AdaptiveCards from backend.app.bots.intent_detector import IntentDetector from backend.app.bots.slot_filler import SlotFiller from backend.app.dtos.house.house_features import HouseFeatures from backend.app.services.house_price_predictor import HousePricePredictor class Dayta(ActivityHandler): def __init__( self, intent_detector: Annotated[IntentDetector, Depends()], card_bot: Annotated[AdaptiveCards, Depends()], slot_filler: Annotated[SlotFiller, Depends()], price_predictor: Annotated[HousePricePredictor, Depends()],): self.intent_detector = intent_detector self.card_bot = card_bot self.slot_filler = slot_filler self.price_predictor = price_predictor self.chat_llm = ChatOpenAI(temperature=0.7) self.user_sessions = {} async def on_message_activity(self, turn_context: TurnContext): user_message = turn_context.activity.text user_id = turn_context.activity.from_property.id submitted_values = turn_context.activity.value known_values = self.user_sessions.get(user_id, {}) schema = HouseFeatures.model_json_schema() #required_fields = list(HouseFeatures.model_fields.keys()) required_fields = [ name for name, field in HouseFeatures.model_fields.items() if field.is_required() ] print(f"required_fields: {required_fields}") # Update known values if submitted_values is not None: known_values.update(submitted_values) else: extracted = await self.slot_filler.extract_slots(schema, user_message) known_values.update(extracted) self.user_sessions[user_id] = known_values # Detect intent only if message-based if not submitted_values: intent = await self.intent_detector.detect_intent(user_message) if intent.strip().lower() in ("unknown", ""): response = await asyncio.get_event_loop().run_in_executor( None, lambda: self.chat_llm.predict(f"The user said: '{user_message}'. Respond helpfully.") ) await turn_context.send_activity(response) return # Delegate to common logic await self._handle_collected_data(turn_context, user_id, known_values, required_fields, schema) async def _handle_collected_data( self, turn_context: TurnContext, user_id: str, known_values: dict, required_fields: list[str], full_schema: dict ): missing_fields = [f for f in required_fields if f not in known_values] print(f"Missing fields: {missing_fields}") if not missing_fields: try: features = HouseFeatures(**known_values) price = self.price_predictor.predict(features) await turn_context.send_activity(f"The estimated price of the house is ${price:.2f}") del self.user_sessions[user_id] return except ValidationError as e: await turn_context.send_activity(f"Validation failed: {e}") return # Generate adaptive card for missing fields filtered_schema = { **full_schema, "properties": { k: v for k, v in full_schema["properties"].items() if k in missing_fields }, "required": missing_fields } card_json = await self.card_bot.generate_card(filtered_schema, known_values) if isinstance(card_json, str): card_json = json.loads(card_json) print(f"card_json: {card_json}") await turn_context.send_activity( Activity( type=ActivityTypes.message, attachments=[ Attachment( content_type="application/vnd.microsoft.card.adaptive", content=card_json ) ] ) )