from __future__ import annotations import re from typing import Any from app.schemas.intent import IntentDefinition class ResponsePolicy: _DEFAULT_INTENT_HINTS = { "cabin_nav_to": "导航", "cabin_nav_cancel": "结束导航", "cabin_set_ac": "调空调", "cabin_ac_on": "打开空调", "cabin_ac_off": "关闭空调", "cabin_fan_up": "调大风量", "cabin_fan_down": "调小风量", "cabin_defog_front_on": "打开前挡除雾", "cabin_defog_rear_on": "打开后挡除雾", "cabin_window_open": "打开车窗", "cabin_window_close": "关闭车窗", "cabin_sunroof_open": "打开天窗", "cabin_sunroof_close": "关闭天窗", "cabin_trunk_open": "打开后备箱", "cabin_trunk_close": "关闭后备箱", "cabin_lock_doors": "锁车门", "cabin_unlock_doors": "解锁车门", "cabin_play_music": "播放音乐", "cabin_pause_music": "暂停音乐", "cabin_next_track": "下一首", "cabin_previous_track": "上一首", "cabin_volume_up": "调大音量", "cabin_volume_down": "调小音量", "cabin_volume_mute": "静音", "cabin_lights_on": "打开车灯", "cabin_lights_off": "关闭车灯", "cabin_reading_light_on": "打开阅读灯", "cabin_reading_light_off": "关闭阅读灯", "cabin_seat_heat_on": "打开座椅加热", "cabin_seat_heat_off": "关闭座椅加热", "cabin_seat_vent_on": "打开座椅通风", "cabin_seat_vent_off": "关闭座椅通风", "cabin_mirror_fold": "折叠后视镜", "cabin_mirror_unfold": "展开后视镜", "cabin_wiper_on": "打开雨刷", "cabin_wiper_off": "关闭雨刷", "cabin_screen_brightness_up": "调亮屏幕", "cabin_screen_brightness_down": "调暗屏幕", "cabin_answer_call": "接听电话", "cabin_hang_up_call": "挂断电话", "cs_query_order": "查订单", "cs_query_logistics": "查物流", "cs_cancel_order": "取消订单", "cs_transfer_human": "转人工", } def __init__( self, templates: dict[str, str] | None = None, intent_hints: dict[str, str] | None = None, ) -> None: self._templates = templates or {} self._intent_hints = {**self._DEFAULT_INTENT_HINTS, **(intent_hints or {})} def ask_for_slot(self, intent: IntentDefinition, slot_name: str, default_template: str) -> str: if slot_name == "order_id": if intent.intent_id == "cs_cancel_order": return self._template("ask_cancel_order_id", "请告诉我订单号。") return self._template("ask_order_id", "请提供订单号。") if slot_name == "destination": return self._template("ask_destination", "请告诉我要去哪里。") if slot_name == "temperature": return self._template("ask_temperature", "请告诉我要设置多少度。") if slot_name == "media_query": return self._template("ask_media_query", "想听什么风格或者具体的歌名?") return default_template.strip() or "请补充一个关键信息。" def workflow_result(self, intent: IntentDefinition, plugin_result: dict[str, Any]) -> str: if not plugin_result.get("success", True): return self._template("workflow_failed", "这次没处理成功,请稍后再试。") message = str(plugin_result.get("message") or "").strip() if not message: return self.ack(intent) if len(message) > 42: return message[:39].rstrip(",。;; ") + "..." return message def workflow_summary(self, messages: list[str]) -> str: cleaned = [item.strip() for item in messages if item and item.strip()] if not cleaned: return self._template("workflow_summary_empty", "好的,已经处理完成。") if len(cleaned) == 1: return cleaned[0] natural_clauses: list[str] = [] previous_subject: str | None = None for index, item in enumerate(cleaned[:3]): clause, subject = self._vehicle_style_clause(item, index=index, previous_subject=previous_subject) natural_clauses.append(clause) previous_subject = subject or previous_subject summary = f"好,{','.join(natural_clauses)}。" if len(cleaned) > 3: summary = summary.rstrip("。") + ",其余步骤也已完成。" if len(summary) > 70: return summary[:67].rstrip(",。;; ") + "..." return summary def ask_for_confirmation(self, intent: IntentDefinition, detail: str | None = None) -> str: if intent.intent_id == "cs_cancel_order": if detail: return f"即将取消订单,{detail}。请回复“确认”或“取消”。" return "即将取消订单。请回复“确认”或“取消”。" if detail: return f"{detail}。请回复“确认”或“取消”。" return "请确认是否继续执行。回复“确认”或“取消”。" def confirm_retry(self) -> str: return self._template("confirm_retry", "我需要一个明确确认。请回复“确认”继续,或回复“取消”终止。") def confirm_cancelled(self) -> str: return self._template("confirm_cancelled", "好的,已取消这一步。") def step_skipped(self, intent: IntentDefinition, reason: str | None = None) -> str: if intent.intent_id == "cs_cancel_order": base = "订单取消步骤未执行。" else: base = "这一步已跳过。" if reason: return f"{base}{reason}" return base def ack(self, intent: IntentDefinition | None = None) -> str: if intent is None: return self._template("ack_default", "收到,马上处理。") if intent.domain == "cabin": return self._template("ack_cabin", "好的,马上处理。") return self._template("ack_service", "收到,我来处理。") def reject(self) -> str: return self._template("reject", "这个我暂时做不了,但我可以帮你查询、控制或转人工。") def short_social(self, social_kind: str) -> str: if social_kind == "greeting": return self._template("short_social_greeting", "你好,我在。") if social_kind == "thanks": return self._template("short_social_thanks", "不客气。") if social_kind == "goodbye": return self._template("short_social_goodbye", "好的,有需要再叫我。") if social_kind == "capability": return self._template( "short_social_capability", "我可以帮你查订单、查物流、取消订单、导航、调空调、播放音乐或转人工。", ) return self._template("short_social_default", "我在。") def open_social_fallback(self) -> str: return self._template("open_social_fallback", "可以和你聊两句,你也可以继续告诉我想处理什么。") def with_pending_hint(self, text: str, pending_hint: str | None = None) -> str: base = text.strip() or self.open_social_fallback() hint = (pending_hint or "").strip() if not hint: return base return f"{base} {hint}" def pending_task_hint(self, status: str, pending_slots: list[str], current_intent: str | None = None) -> str | None: if status == "waiting_confirmation": return self._template("pending_confirmation_hint", "当前这一步还在等你确认,回复“确认”或“取消”即可。") if status == "waiting_slot" and pending_slots: if pending_slots[0] == "order_id": return self._template("pending_slot_order_id", "当前还缺订单号,你继续告诉我订单号就行。") if pending_slots[0] == "temperature": return self._template("pending_slot_temperature", "当前还缺温度,你继续告诉我要设置多少度就行。") if pending_slots[0] == "destination": return self._template("pending_slot_destination", "当前还缺目的地,你继续告诉我要去哪里就行。") if pending_slots[0] == "media_query": return self._template("pending_slot_media_query", "当前还缺歌名或风格,你直接说歌名、歌手或风格就行。") return self._template("pending_slot_default", "当前还缺一个关键信息,你继续补充就行。") if status == "running" and current_intent: return self._template("pending_running", "当前任务还在继续,你也可以直接继续下一个指令。") return None def task_stopped(self) -> str: return self._template("task_stopped", "好的,已停止当前任务。") def clarify(self, candidate_intents: list[str]) -> str: options = [ self._intent_hints.get(intent_id, intent_id) for intent_id in candidate_intents if intent_id ] deduped: list[str] = [] for item in options: if item not in deduped: deduped.append(item) if not deduped: return "我理解得还不够确定,你是想查询、控制,还是转人工?" if len(deduped) == 1: return f"请确认一下,你是想{deduped[0]}吗?" if len(deduped) == 2: return f"请确认一下,你是想{deduped[0]}还是{deduped[1]}?" return f"请确认一下,你是想{deduped[0]}、{deduped[1]},还是{deduped[2]}?" def fallback(self) -> str: return self._template("fallback", "我还没完全听懂,你可以换个简短说法,或告诉我是查询、控制还是转人工。") def _template(self, key: str, default: str) -> str: value = str(self._templates.get(key, default)).strip() return value or default def _naturalize_workflow_message(self, text: str) -> str: normalized = text.strip().rstrip("。;; ") normalized = re.sub(r"^好的[,,\s]*", "", normalized) normalized = re.sub(r"^收到[,,\s]*", "", normalized) if normalized.startswith("已将"): normalized = normalized[2:] elif normalized.startswith("已经将"): normalized = normalized[3:] elif normalized.startswith("已经"): normalized = normalized[2:] elif normalized.startswith("已"): normalized = normalized[1:] normalized = normalized.strip(",, ") if not normalized: return "已经处理好了" if normalized.endswith("了"): return normalized return f"{normalized}了" def _vehicle_style_clause( self, text: str, *, index: int, previous_subject: str | None = None, ) -> tuple[str, str | None]: normalized = self._naturalize_workflow_message(text) match = re.match(r"^(打开|关闭)(.+)了$", normalized) if match: action, subject = match.groups() subject = subject.strip() if action == "打开": if previous_subject and previous_subject == subject: return "也打开了", subject if index > 0: return f"{subject}也打开了", subject return f"{subject}已经打开了", subject if previous_subject and previous_subject == subject: return "也帮你关上了", subject if index > 0: return f"{subject}也帮你关上了", subject return f"{subject}已经关上了", subject match = re.match(r"^(锁定|解锁)(.+)了$", normalized) if match: action, subject = match.groups() subject = subject.strip() action_text = "锁好了" if action == "锁定" else "解锁了" if previous_subject and previous_subject == subject: return f"也{action_text}", subject if index > 0: return f"{subject}也{action_text}", subject return f"{subject}已经{action_text}", subject match = re.match(r"^(.+)调到\s*(.+)度了$", normalized) if match: subject, value = match.groups() subject = subject.strip() value = value.strip() if previous_subject and previous_subject == subject: return f"也调到 {value} 度了", subject if index > 0: return f"{subject}也调到 {value} 度了", subject return f"{subject}调到 {value} 度了", subject match = re.match(r"^(调大|调小)(.+)了$", normalized) if match: action, subject = match.groups() subject = subject.strip() if previous_subject and previous_subject == subject: return f"也{action}了", subject if index > 0: return f"{subject}也{action}了", subject return f"{subject}已经{action}了", subject if normalized.startswith("正在播放 "): target = normalized[len("正在播放 ") :].strip() if index > 0: return f"也开始播放 {target} 了", "播放" return f"开始播放 {target} 了", "播放" if normalized.startswith("订单 ") and normalized.endswith(" 已取消"): order_text = normalized[:-4].strip() return f"{order_text}已经取消了", "订单" if normalized.startswith("订单 ") and "当前" in normalized: return normalized, "订单" return normalized, None