| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- # -*- coding: utf-8 -*-
- """
- Intent Handler Data Classes.
- Datenklassen für die Kommunikation zwischen NLP und Intent-Handlern.
- """
- from dataclasses import dataclass, field
- from typing import Any, TYPE_CHECKING
- if TYPE_CHECKING:
- from trixy_core.conversation.session import ConversationSession
- @dataclass
- class IntentReceivedData:
- """
- Daten die an einen Intent-Handler übergeben werden.
- Enthält alle Informationen die der Handler benötigt,
- um den Intent zu verarbeiten.
- Attributes:
- intent: Name des erkannten Intents
- confidence: Konfidenz der Erkennung (0.0 - 1.0)
- slots: Extrahierte Slot-Werte
- original_text: Ursprünglicher Text (STT-Ergebnis)
- satellite_id: ID des Satellites
- room_id: Raum-ID
- session_id: Konversations-Session-ID
- session: Optionale Konversations-Session
- response_text: Vom NLP generierte Antwort (falls vorhanden)
- wakeword_type: Typ des Wakewords ("custom" oder "system_command")
- is_authenticated: Ist der Benutzer als Admin authentifiziert?
- metadata: Zusätzliche Metadaten
- """
- intent: str
- confidence: float = 0.0
- slots: dict[str, Any] = field(default_factory=dict)
- original_text: str = ""
- satellite_id: str = ""
- room_id: str = ""
- session_id: str = ""
- session: "ConversationSession | None" = None
- response_text: str = ""
- wakeword_type: str = "custom" # "custom" oder "system_command"
- is_authenticated: bool = False # Admin-Authentifizierung
- metadata: dict[str, Any] = field(default_factory=dict)
- def get_slot(self, name: str, default: Any = None) -> Any:
- """
- Gibt einen Slot-Wert zurueck.
- Bei ResolvedEntity wird der Rohwert (.raw) zurueckgegeben
- fuer Abwaertskompatibilitaet. Fuer den vollen ResolvedEntity
- get_resolved_slot() verwenden.
- Args:
- name: Slot-Name
- default: Standardwert
- Returns:
- Slot-Wert (String) oder default
- """
- val = self.slots.get(name, default)
- # ResolvedEntity → Rohwert fuer Abwaertskompatibilitaet
- if val is not None and hasattr(val, "raw") and hasattr(val, "entity_type"):
- return val.raw
- return val
- def get_resolved_slot(self, name: str) -> Any:
- """
- Gibt den vollen ResolvedEntity fuer einen Slot zurueck.
- Falls der Slot kein ResolvedEntity ist, wird None zurueckgegeben.
- Args:
- name: Slot-Name
- Returns:
- ResolvedEntity oder None
- """
- val = self.slots.get(name)
- if val is not None and hasattr(val, "raw") and hasattr(val, "entity_type"):
- return val
- return None
- def has_slot(self, name: str) -> bool:
- """
- Prüft ob ein Slot vorhanden ist.
- Args:
- name: Slot-Name
- Returns:
- True wenn vorhanden und nicht leer
- """
- return name in self.slots and self.slots[name] is not None
- def get_required_slots(self, *names: str) -> tuple[bool, list[str]]:
- """
- Prüft ob alle erforderlichen Slots vorhanden sind.
- Args:
- *names: Erforderliche Slot-Namen
- Returns:
- (all_present, missing_slots)
- """
- missing = [n for n in names if not self.has_slot(n)]
- return len(missing) == 0, missing
- @dataclass
- class IntentResult:
- """
- Ergebnis eines Intent-Handlers.
- Der Handler gibt dieses Objekt zurück, um das Ergebnis
- der Intent-Verarbeitung zu kommunizieren.
- Attributes:
- success: War die Verarbeitung erfolgreich?
- response_text: Antworttext für TTS
- follow_up_intent: Optionaler Folge-Intent
- data: Zusätzliche Ergebnisdaten
- error: Fehlermeldung falls nicht erfolgreich
- suppress_tts: TTS-Ausgabe unterdrücken
- """
- success: bool = True
- response_text: str = ""
- follow_up_intent: str = ""
- follow_up_valid_responses: list[str] = field(default_factory=list)
- follow_up_retry_text: str = ""
- data: dict[str, Any] = field(default_factory=dict)
- error: str = ""
- suppress_tts: bool = False
- @classmethod
- def success_with_response(cls, response_text: str, **data: Any) -> "IntentResult":
- """
- Erstellt ein erfolgreiches Ergebnis mit Antwort.
- Args:
- response_text: Antworttext
- **data: Zusätzliche Daten
- Returns:
- IntentResult
- """
- return cls(success=True, response_text=response_text, data=data)
- @classmethod
- def failure(cls, error: str, response_text: str = "") -> "IntentResult":
- """
- Erstellt ein Fehler-Ergebnis.
- Args:
- error: Fehlerbeschreibung
- response_text: Optionaler Fehlertext für TTS
- Returns:
- IntentResult
- """
- return cls(success=False, error=error, response_text=response_text)
- @classmethod
- def silent_success(cls, **data: Any) -> "IntentResult":
- """
- Erstellt ein erfolgreiches Ergebnis ohne TTS-Ausgabe.
- Args:
- **data: Zusätzliche Daten
- Returns:
- IntentResult
- """
- return cls(success=True, suppress_tts=True, data=data)
- @classmethod
- def follow_up(
- cls,
- response_text: str,
- follow_up_intent: str,
- valid_responses: list[str] | None = None,
- retry_text: str = "",
- **data: Any,
- ) -> "IntentResult":
- """
- Erstellt ein Ergebnis mit Rueckfrage.
- Args:
- response_text: Rueckfrage-Text (TTS)
- follow_up_intent: Intent der als naechstes erwartet wird
- valid_responses: Optionale Liste gueltiger Antworten.
- Wenn gesetzt, werden nur diese akzeptiert.
- retry_text: Text bei ungueltiger Antwort
- (z.B. "Bitte waehle aus: ...")
- **data: Zusaetzliche Daten (z.B. bisherige Bestellung)
- Beispiel:
- ```python
- return IntentResult.follow_up(
- "Welchen Belag moechtest du?",
- follow_up_intent="pizza_select_topping",
- valid_responses=["Salami", "Hawaii", "Margherita"],
- retry_text="Bitte waehle einen Belag: Salami, Hawaii oder Margherita",
- )
- ```
- """
- return cls(
- success=True,
- response_text=response_text,
- follow_up_intent=follow_up_intent,
- follow_up_valid_responses=valid_responses or [],
- follow_up_retry_text=retry_text,
- data=data,
- )
- def has_response(self) -> bool:
- """Prüft ob eine Antwort vorhanden ist."""
- return bool(self.response_text) and not self.suppress_tts
- def needs_follow_up(self) -> bool:
- """Prüft ob ein Folge-Intent benötigt wird."""
- return bool(self.follow_up_intent)
- def has_valid_responses(self) -> bool:
- """Prueft ob gueltige Antworten definiert sind."""
- return bool(self.follow_up_valid_responses)
|