from typing import Optional, List, Tuple, Dict
from .envelope import Event
from abc import ABC, abstractmethod
from openfloor import Parameters, DialogEvent, TextFeature, To, Sender, Manifest, Conversation, Envelope, Event, InviteEvent, UtteranceEvent, ContextEvent, UninviteEvent, DeclineInviteEvent, ByeEvent, GetManifestsEvent, PublishManifestsEvent, RequestFloorEvent, GrantFloorEvent, RevokeFloorEvent
[docs]
class OpenFloorEvents(Event):
"""Base class for Open Floor agents that defines event handlers"""
__events__ = (
'on_envelope',
'on_utterance',
'on_context',
'on_invite',
'on_uninvite',
'on_decline_invite',
'on_bye',
'on_get_manifests',
'on_publish_manifests',
'on_request_floor',
'on_grant_floor',
'on_revoke_floor',
"on_yield_floor"
)
[docs]
class OpenFloorAgent(OpenFloorEvents):
_manifest : Optional[Manifest] = None
def __init__(self, manifest: Manifest):
super().__init__()
self._manifest = manifest
@property
def speakerUri(self) -> str:
"""A convenient shorthand to get the speakerUri from the manifest"""
return self._manifest.identification.speakerUri
@property
def serviceUrl(self) -> str:
"""A convenient shorthand to get the serviceUrl from the manifest"""
return self._manifest.identification.serviceUrl
[docs]
class BotAgent(OpenFloorAgent):
"""
BotAgent is a simple bot agent. It can be used as a class in its own right or subclassed to create more complex agents.
The class provides default event handlers that meet the Open Floor specification
requirements for a bot agent.
The default on_envelope handler processes events in a specific order, checking for invite events first if the bot is not already in
a conversation.
Event handlers can be customized by subclassing and overriding the default handlers.
For a minimal implementation, all that is required is the following
- Implement a handler for invite events to send a greeting
- Implement a handler for bye events to send a farewell
- Implement a handler for utterance events to handle conversation
"""
_current_context : List[ContextEvent] = []
_active_conversation : Optional[Conversation] = None
_has_floor : bool = False
def __init__(self, manifest: Manifest):
super().__init__(manifest)
self._active_conversation = None
self._has_floor = False
self._current_context = []
self.__attach_handlers__()
def __attach_handlers__(self):
# Register all handlers with the event system
self.on_envelope += self.bot_on_envelope
self.on_invite += self.bot_on_invite
self.on_utterance += self.bot_on_utterance
self.on_context += self.bot_on_context
self.on_uninvite += self.bot_on_uninvite
self.on_grant_floor += self.bot_on_grant_floor
self.on_revoke_floor += self.bot_on_revoke_floor
self.on_get_manifests += self.bot_on_get_manifests
print(f"registered handlers: {len(self)}")
"""
The following events are not handled because according the spec they can be ignored by a simple bot agent.
self.on_bye
self.on_decline_invite
self.on_yield_floor
self.on_publish_manifests
self.on_request_floor
"""
# Map event types to their handler functions
self._event_type_to_handler = {
"invite": self.on_invite,
"utterance": self.on_utterance,
"context": self.on_context,
"uninvite": self.on_uninvite,
"declineInvite": self.on_decline_invite,
"bye": self.on_bye,
"getManifests": self.on_get_manifests,
"publishManifests": self.on_publish_manifests,
"requestFloor": self.on_request_floor,
"grantFloor": self.on_grant_floor,
"revokeFloor": self.on_revoke_floor,
"yieldFloor": self.on_yield_floor
}
[docs]
def process_envelope(self, in_envelope: Envelope) -> Envelope:
#Create an empty response envelope. use the conversation from the input envelope and set the sender from the manifest.
out_envelope=Envelope(
conversation=in_envelope.conversation,
sender=Sender(
speakerUri=self.speakerUri,
serviceUrl=self.serviceUrl
)
)
self.on_envelope(in_envelope, out_envelope)
return out_envelope
[docs]
def bot_on_envelope(self, in_envelope: Envelope, out_envelope: Envelope) -> Envelope:
print("Entering bot_on_envelope")
#clear the current context
self._current_context = []
#If we are already in a different conversation then raise an exception. This is a situation that simple agents should not allow.
print(f"self._active_conversation: {self._active_conversation}")
print(f"in_envelope.conversation.id: {in_envelope.conversation.id}")
if (self._active_conversation is not None and self._active_conversation.id != in_envelope.conversation.id):
raise Exception("Bot is already in a different conversation. Cannot accept invite to a different conversation.")
"""Remove any events that are not intended for this agent"""
events_with_metadata = self.add_metadata(in_envelope.events)
my_events_with_metadata = [[event,metadata] for event, metadata in events_with_metadata if metadata["addressed_to_me"]]
#Process the remaining events in order
for event,metadata in my_events_with_metadata:
print(f"Processing event: {event.to_json()}")
print(f"Processing event with type: {event.eventType}")
handler = self._event_type_to_handler.get(event.eventType)
if handler is None:
raise ValueError(f"Unknown event type: {event.eventType}")
handler(event, in_envelope, out_envelope)
print(f"out_envelope: {out_envelope.to_json()}")
return out_envelope
[docs]
def bot_on_invite(self, event: InviteEvent, in_envelope: Envelope, out_envelope: Envelope) -> None:
print("Entering bot_on_invite")
#Accept the invitation
self._active_conversation = Conversation(id=in_envelope.conversation.id)
#automatically treat this as if the inviting agent had also granted the floor. (This is default behavior according to the spec)
self.bot_on_grant_floor(
GrantFloorEvent(
to=To(speakerUri=self._manifest.identification.speakerUri),
reason="Automatic floor grant as a result of the invitation"
),
in_envelope,
out_envelope
)
[docs]
def bot_on_grant_floor(self, event: GrantFloorEvent, in_envelope: Envelope, out_envelope: Envelope) -> None:
print("Entering bot_on_grant_floor")
self._has_floor = True
[docs]
def bot_on_revoke_floor(self, event: RevokeFloorEvent, in_envelope: Envelope, out_envelope: Envelope) -> None:
print("Entering bot_on_revoke_floor")
self._has_floor = False
[docs]
def bot_on_utterance(self, event: UtteranceEvent, in_envelope: Envelope, out_envelope: Envelope) -> None:
print("Entering bot_on_utterance")
utterance=DialogEvent(
speakerUri=self._manifest.identification.speakerUri,
features={"text":TextFeature(values=["Sorry! I'm a simple bot that has not been programmed to do anything yet."])},
)
out_envelope.events.append(UtteranceEvent(dialogEvent=utterance))
[docs]
def bot_on_context(self, event: ContextEvent, in_envelope: Envelope, out_envelope: Envelope) -> None:
print("Entering bot_on_context")
self._current_context.append(event)
[docs]
def bot_on_uninvite(self, event: UninviteEvent, in_envelope: Envelope, out_envelope: Envelope) -> None:
print("Entering bot_on_uninvite")
self._active_conversation = None
[docs]
def bot_on_get_manifests(self, event: GetManifestsEvent, in_envelope: Envelope, out_envelope: Envelope) -> None:
print("Entering bot_on_get_manifests")
out_envelope.events.append(
PublishManifestsEvent(
Parameters=Parameters(
manifests={"servicingManifests" : [self._manifest], "discoveryManifests" : []}
)
)
)