career_chatbots / core /router.py
liuyuelintop's picture
Upload folder using huggingface_hub
8e7f687 verified
# Message router extracted from app.py
import json
import re
from config.prompts import (
ROUTER_SCHEMA, ROUTER_SYSTEM_PROMPT, WHY_HIRE_REGEX,
canonical_why_hire_pitch, CONTACT_COLLECTION_PROMPT
)
from config.settings import USE_CANONICAL_WHY_HIRE
class MessageRouter:
"""Handles message classification and routing logic"""
def __init__(self, openai_client):
self.openai = openai_client
def classify(self, message: str) -> dict:
"""Classify user message using AI with regex fallback for email detection"""
messages = [{"role": "system", "content": ROUTER_SYSTEM_PROMPT}]
# Optionally prepend few-shots for stability:
# messages = [{"role": "system", "content": system}, *fewshots]
messages.append({"role": "user", "content": message})
resp = self.openai.chat.completions.create(
model="gemini-2.5-flash",
messages=messages,
response_format={
"type": "json_schema",
"json_schema": {"name": "router", "schema": ROUTER_SCHEMA}
},
temperature=0.0,
top_p=1.0,
max_tokens=200
)
try:
parsed = json.loads(resp.choices[0].message.content)
# Minimal defensive checks
if not isinstance(parsed, dict) or "intent" not in parsed:
raise ValueError("schema mismatch")
# Hybrid approach: If AI missed email, catch with regex
if parsed["intent"] != "contact_exchange":
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
if re.search(email_pattern, message):
parsed["intent"] = "contact_exchange"
parsed["requires_contact"] = False
parsed["matched_phrases"].append("email_detected_by_regex")
return parsed
except Exception:
# Safe, schema-conformant fallback
return {
"intent": "career",
"why_hire": False,
"requires_contact": False,
"confidence": 0.0,
"matched_phrases": []
}
def should_use_canonical_why_hire(self, message: str, why_hire_flag: bool, mode: str) -> bool:
"""Check if canonical pitch should be used"""
if mode != "career":
return False
if WHY_HIRE_REGEX.search(message):
return True
if why_hire_flag:
return True
return False
def get_response_for_route(self, message: str, route: dict, mode: str) -> str | None:
"""Get immediate response based on routing, or None to continue to chat"""
intent = route.get("intent", "career")
why_hire_flag = bool(route.get("why_hire"))
requires_contact_flag = bool(route.get("requires_contact"))
# Handle boundary cases
if intent == "other":
from config.settings import BOUNDARY_REPLY
return BOUNDARY_REPLY
# Handle contact collection for interested users
if requires_contact_flag:
return CONTACT_COLLECTION_PROMPT
# Handle canonical "why hire" pitch
if USE_CANONICAL_WHY_HIRE and self.should_use_canonical_why_hire(message, why_hire_flag, mode):
return canonical_why_hire_pitch()
# Continue to regular chat
return None