116 lines
4.3 KiB
Python

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
)
]
)
)