handler.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. # -*- coding: utf-8 -*-
  2. """
  3. Intent Handler Data Classes.
  4. Datenklassen für die Kommunikation zwischen NLP und Intent-Handlern.
  5. """
  6. from dataclasses import dataclass, field
  7. from typing import Any, TYPE_CHECKING
  8. if TYPE_CHECKING:
  9. from trixy_core.conversation.session import ConversationSession
  10. @dataclass
  11. class IntentReceivedData:
  12. """
  13. Daten die an einen Intent-Handler übergeben werden.
  14. Enthält alle Informationen die der Handler benötigt,
  15. um den Intent zu verarbeiten.
  16. Attributes:
  17. intent: Name des erkannten Intents
  18. confidence: Konfidenz der Erkennung (0.0 - 1.0)
  19. slots: Extrahierte Slot-Werte
  20. original_text: Ursprünglicher Text (STT-Ergebnis)
  21. satellite_id: ID des Satellites
  22. room_id: Raum-ID
  23. session_id: Konversations-Session-ID
  24. session: Optionale Konversations-Session
  25. response_text: Vom NLP generierte Antwort (falls vorhanden)
  26. wakeword_type: Typ des Wakewords ("custom" oder "system_command")
  27. is_authenticated: Ist der Benutzer als Admin authentifiziert?
  28. metadata: Zusätzliche Metadaten
  29. """
  30. intent: str
  31. confidence: float = 0.0
  32. slots: dict[str, Any] = field(default_factory=dict)
  33. original_text: str = ""
  34. satellite_id: str = ""
  35. room_id: str = ""
  36. session_id: str = ""
  37. session: "ConversationSession | None" = None
  38. response_text: str = ""
  39. wakeword_type: str = "custom" # "custom" oder "system_command"
  40. is_authenticated: bool = False # Admin-Authentifizierung
  41. metadata: dict[str, Any] = field(default_factory=dict)
  42. def get_slot(self, name: str, default: Any = None) -> Any:
  43. """
  44. Gibt einen Slot-Wert zurueck.
  45. Bei ResolvedEntity wird der Rohwert (.raw) zurueckgegeben
  46. fuer Abwaertskompatibilitaet. Fuer den vollen ResolvedEntity
  47. get_resolved_slot() verwenden.
  48. Args:
  49. name: Slot-Name
  50. default: Standardwert
  51. Returns:
  52. Slot-Wert (String) oder default
  53. """
  54. val = self.slots.get(name, default)
  55. # ResolvedEntity → Rohwert fuer Abwaertskompatibilitaet
  56. if val is not None and hasattr(val, "raw") and hasattr(val, "entity_type"):
  57. return val.raw
  58. return val
  59. def get_resolved_slot(self, name: str) -> Any:
  60. """
  61. Gibt den vollen ResolvedEntity fuer einen Slot zurueck.
  62. Falls der Slot kein ResolvedEntity ist, wird None zurueckgegeben.
  63. Args:
  64. name: Slot-Name
  65. Returns:
  66. ResolvedEntity oder None
  67. """
  68. val = self.slots.get(name)
  69. if val is not None and hasattr(val, "raw") and hasattr(val, "entity_type"):
  70. return val
  71. return None
  72. def has_slot(self, name: str) -> bool:
  73. """
  74. Prüft ob ein Slot vorhanden ist.
  75. Args:
  76. name: Slot-Name
  77. Returns:
  78. True wenn vorhanden und nicht leer
  79. """
  80. return name in self.slots and self.slots[name] is not None
  81. def get_required_slots(self, *names: str) -> tuple[bool, list[str]]:
  82. """
  83. Prüft ob alle erforderlichen Slots vorhanden sind.
  84. Args:
  85. *names: Erforderliche Slot-Namen
  86. Returns:
  87. (all_present, missing_slots)
  88. """
  89. missing = [n for n in names if not self.has_slot(n)]
  90. return len(missing) == 0, missing
  91. @dataclass
  92. class IntentResult:
  93. """
  94. Ergebnis eines Intent-Handlers.
  95. Der Handler gibt dieses Objekt zurück, um das Ergebnis
  96. der Intent-Verarbeitung zu kommunizieren.
  97. Attributes:
  98. success: War die Verarbeitung erfolgreich?
  99. response_text: Antworttext für TTS
  100. follow_up_intent: Optionaler Folge-Intent
  101. data: Zusätzliche Ergebnisdaten
  102. error: Fehlermeldung falls nicht erfolgreich
  103. suppress_tts: TTS-Ausgabe unterdrücken
  104. """
  105. success: bool = True
  106. response_text: str = ""
  107. follow_up_intent: str = ""
  108. follow_up_valid_responses: list[str] = field(default_factory=list)
  109. follow_up_retry_text: str = ""
  110. data: dict[str, Any] = field(default_factory=dict)
  111. error: str = ""
  112. suppress_tts: bool = False
  113. @classmethod
  114. def success_with_response(cls, response_text: str, **data: Any) -> "IntentResult":
  115. """
  116. Erstellt ein erfolgreiches Ergebnis mit Antwort.
  117. Args:
  118. response_text: Antworttext
  119. **data: Zusätzliche Daten
  120. Returns:
  121. IntentResult
  122. """
  123. return cls(success=True, response_text=response_text, data=data)
  124. @classmethod
  125. def failure(cls, error: str, response_text: str = "") -> "IntentResult":
  126. """
  127. Erstellt ein Fehler-Ergebnis.
  128. Args:
  129. error: Fehlerbeschreibung
  130. response_text: Optionaler Fehlertext für TTS
  131. Returns:
  132. IntentResult
  133. """
  134. return cls(success=False, error=error, response_text=response_text)
  135. @classmethod
  136. def silent_success(cls, **data: Any) -> "IntentResult":
  137. """
  138. Erstellt ein erfolgreiches Ergebnis ohne TTS-Ausgabe.
  139. Args:
  140. **data: Zusätzliche Daten
  141. Returns:
  142. IntentResult
  143. """
  144. return cls(success=True, suppress_tts=True, data=data)
  145. @classmethod
  146. def follow_up(
  147. cls,
  148. response_text: str,
  149. follow_up_intent: str,
  150. valid_responses: list[str] | None = None,
  151. retry_text: str = "",
  152. **data: Any,
  153. ) -> "IntentResult":
  154. """
  155. Erstellt ein Ergebnis mit Rueckfrage.
  156. Args:
  157. response_text: Rueckfrage-Text (TTS)
  158. follow_up_intent: Intent der als naechstes erwartet wird
  159. valid_responses: Optionale Liste gueltiger Antworten.
  160. Wenn gesetzt, werden nur diese akzeptiert.
  161. retry_text: Text bei ungueltiger Antwort
  162. (z.B. "Bitte waehle aus: ...")
  163. **data: Zusaetzliche Daten (z.B. bisherige Bestellung)
  164. Beispiel:
  165. ```python
  166. return IntentResult.follow_up(
  167. "Welchen Belag moechtest du?",
  168. follow_up_intent="pizza_select_topping",
  169. valid_responses=["Salami", "Hawaii", "Margherita"],
  170. retry_text="Bitte waehle einen Belag: Salami, Hawaii oder Margherita",
  171. )
  172. ```
  173. """
  174. return cls(
  175. success=True,
  176. response_text=response_text,
  177. follow_up_intent=follow_up_intent,
  178. follow_up_valid_responses=valid_responses or [],
  179. follow_up_retry_text=retry_text,
  180. data=data,
  181. )
  182. def has_response(self) -> bool:
  183. """Prüft ob eine Antwort vorhanden ist."""
  184. return bool(self.response_text) and not self.suppress_tts
  185. def needs_follow_up(self) -> bool:
  186. """Prüft ob ein Folge-Intent benötigt wird."""
  187. return bool(self.follow_up_intent)
  188. def has_valid_responses(self) -> bool:
  189. """Prueft ob gueltige Antworten definiert sind."""
  190. return bool(self.follow_up_valid_responses)