eve-live / greeter_handler.py
AIBRUH's picture
EVE speaks first: proactive greeting handler on session start
4a0cebd verified
Raw
History Blame Contribute Delete
4.24 kB
import threading
from abc import ABC
from typing import Dict, Optional
from loguru import logger
from pydantic import BaseModel, Field
from chat_engine.contexts.handler_context import HandlerContext
from chat_engine.data_models.chat_engine_config_data import ChatEngineConfigModel, HandlerBaseConfigModel
from chat_engine.common.handler_base import HandlerBase, HandlerBaseInfo, HandlerDataInfo, HandlerDetail
from chat_engine.data_models.chat_data.chat_data_model import ChatData
from chat_engine.data_models.chat_data_type import ChatDataType
from chat_engine.data_models.runtime_data.data_bundle import DataBundle, DataBundleDefinition, DataBundleEntry
from chat_engine.contexts.session_context import SessionContext
class GreeterConfig(HandlerBaseConfigModel, BaseModel):
greeting_prompt: str = Field(default="Introduce yourself to the visitor now, in your own words.")
delay_seconds: float = Field(default=4.0)
class GreeterContext(HandlerContext):
def __init__(self, session_id: str):
super().__init__(session_id)
self.timer = None
self.sent = False
class HandlerGreeter(HandlerBase, ABC):
"""On session start, injects one synthetic HUMAN_TEXT turn so the
avatar proactively greets the user before any mic input arrives."""
def __init__(self):
super().__init__()
self.greeting_prompt = "Introduce yourself to the visitor now, in your own words."
self.delay_seconds = 4.0
self.output_definition = None
def get_handler_info(self) -> HandlerBaseInfo:
return HandlerBaseInfo(
name="Greeter",
config_model=GreeterConfig,
)
def load(self, engine_config: ChatEngineConfigModel, handler_config: Optional[BaseModel] = None):
if isinstance(handler_config, GreeterConfig):
self.greeting_prompt = handler_config.greeting_prompt
self.delay_seconds = handler_config.delay_seconds
logger.info(f"Greeter loaded: delay={self.delay_seconds}s prompt={self.greeting_prompt!r}")
def get_handler_detail(self, session_context: SessionContext,
context: HandlerContext) -> HandlerDetail:
definition = DataBundleDefinition()
definition.add_entry(DataBundleEntry.create_audio_entry("avatar_audio", 1, 24000))
self.output_definition = definition
# AVATAR_TEXT input is declared only so the engine wires this handler
# into the graph; its data is ignored.
inputs = {
ChatDataType.AVATAR_TEXT: HandlerDataInfo(
type=ChatDataType.AVATAR_TEXT,
)
}
outputs = {
ChatDataType.HUMAN_TEXT: HandlerDataInfo(
type=ChatDataType.HUMAN_TEXT,
definition=definition,
)
}
return HandlerDetail(inputs=inputs, outputs=outputs)
def create_context(self, session_context, handler_config=None):
return GreeterContext(session_context.session_info.session_id)
def start_context(self, session_context, handler_context):
context = handler_context
if not isinstance(context, GreeterContext):
return
def _send_greeting():
if context.sent:
return
context.sent = True
try:
output = DataBundle(self.output_definition)
output.set_main_data(self.greeting_prompt)
context.submit_data(output, finish_stream=True)
logger.info(f"Greeter: injected greeting turn for session {context.session_id}")
except Exception as e:
logger.error(f"Greeter failed to inject greeting: {e}")
context.timer = threading.Timer(self.delay_seconds, _send_greeting)
context.timer.daemon = True
context.timer.start()
def handle(self, context: HandlerContext, inputs: ChatData,
output_definitions: Dict[ChatDataType, HandlerDataInfo]):
# Passive: greeting is emitted from start_context, inputs ignored.
return
def destroy_context(self, context: HandlerContext):
if isinstance(context, GreeterContext) and context.timer is not None:
context.timer.cancel()