Download app/services/handoff.py from nvikou/ReplyFlow: direct link, hf CLI and curl.
- Browser
- Download file 2.99 kB
-
https://huggingface.co/spaces/nvikou/ReplyFlow/resolve/main/app/services/handoff.py
- Command line
-
hf download hf://spaces/nvikou/ReplyFlow/app/services/handoff.py
-
curl -L -o handoff.py https://huggingface.co/spaces/nvikou/ReplyFlow/resolve/main/app/services/handoff.py
2.99 kB
| """Human handoff detection and notification.""" | |
| from __future__ import annotations | |
| import logging | |
| from typing import Any | |
| import httpx | |
| from app.config import get_settings | |
| logger = logging.getLogger(__name__) | |
| SENSITIVE_HINTS = ( | |
| "complaint", | |
| "réclamation", | |
| "reclamation", | |
| "litige", | |
| "lawsuit", | |
| "lawyer", | |
| "avocat", | |
| "refund", | |
| "remboursement", | |
| "scam", | |
| "fraud", | |
| "мошен", | |
| "жалоб", | |
| "претенз", | |
| "возврат", | |
| "суд", | |
| ) | |
| HANDOFF_PHRASES = ( | |
| "human", | |
| "agent", | |
| "operator", | |
| "speak to someone", | |
| "real person", | |
| "humain", | |
| "conseiller", | |
| "parler à quelqu'un", | |
| "оператор", | |
| "человек", | |
| "живой", | |
| "специалист", | |
| ) | |
| def should_handoff( | |
| *, | |
| message: str, | |
| failure_streak: int, | |
| grounded: bool, | |
| ) -> tuple[bool, str]: | |
| """Decide whether to escalate to a human.""" | |
| lowered = message.lower() | |
| if any(p in lowered for p in HANDOFF_PHRASES): | |
| return True, "explicit_request" | |
| if any(p in lowered for p in SENSITIVE_HINTS): | |
| return True, "sensitive_topic" | |
| if not grounded and failure_streak >= 2: | |
| return True, "consecutive_failures" | |
| return False, "" | |
| async def notify_handoff( | |
| *, | |
| conversation_id: int, | |
| reason: str, | |
| history: list[dict[str, str]], | |
| lead: dict[str, Any], | |
| ) -> None: | |
| """Send handoff payload to webhook if configured.""" | |
| settings = get_settings() | |
| payload = { | |
| "conversation_id": conversation_id, | |
| "reason": reason, | |
| "lead": lead, | |
| "history": history[-20:], | |
| "app": settings.app_name, | |
| } | |
| if not settings.handoff_webhook_url: | |
| logger.info( | |
| "Handoff (no webhook): conversation=%s reason=%s", | |
| conversation_id, | |
| reason, | |
| ) | |
| return | |
| try: | |
| async with httpx.AsyncClient(timeout=10) as client: | |
| await client.post( | |
| settings.handoff_webhook_url, json=payload | |
| ) | |
| except Exception as exc: # noqa: BLE001 | |
| logger.exception("Handoff webhook failed: %s", exc) | |
| def handoff_message(language: str, reason: str) -> str: | |
| """User-facing handoff acknowledgement.""" | |
| messages = { | |
| "en": ( | |
| "I am transferring you to a human teammate. " | |
| "They will see this conversation and follow up " | |
| f"shortly. (reason: {reason.replace('_', ' ')})" | |
| ), | |
| "fr": ( | |
| "Je vous transfère à un conseiller humain. " | |
| "Il verra cet échange et vous répondra bientôt. " | |
| f"(motif : {reason.replace('_', ' ')})" | |
| ), | |
| "ru": ( | |
| "Передаю вас живому специалисту. Он увидит " | |
| "этот диалог и скоро свяжется с вами. " | |
| f"(причина: {reason.replace('_', ' ')})" | |
| ), | |
| } | |
| return messages.get(language, messages["en"]) | |