Spaces:
Running
Running
# 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 |