Module livekit.agents.beta.workflows.email_address

Classes

class GetEmailResult (email_address: str)
Expand source code
@dataclass
class GetEmailResult:
    email_address: str

GetEmailResult(email_address: 'str')

Instance variables

var email_address : str
class GetEmailTask (chat_ctx: NotGivenOr[llm.ChatContext] = NOT_GIVEN,
turn_detection: NotGivenOr[TurnDetectionMode | None] = NOT_GIVEN,
stt: NotGivenOr[stt.STT | None] = NOT_GIVEN,
vad: NotGivenOr[vad.VAD | None] = NOT_GIVEN,
llm: NotGivenOr[llm.LLM | llm.RealtimeModel | None] = NOT_GIVEN,
tts: NotGivenOr[tts.TTS | None] = NOT_GIVEN,
allow_interruptions: NotGivenOr[bool] = NOT_GIVEN)
Expand source code
class GetEmailTask(AgentTask[GetEmailResult]):
    def __init__(
        self,
        chat_ctx: NotGivenOr[llm.ChatContext] = NOT_GIVEN,
        turn_detection: NotGivenOr[TurnDetectionMode | None] = NOT_GIVEN,
        stt: NotGivenOr[stt.STT | None] = NOT_GIVEN,
        vad: NotGivenOr[vad.VAD | None] = NOT_GIVEN,
        llm: NotGivenOr[llm.LLM | llm.RealtimeModel | None] = NOT_GIVEN,
        tts: NotGivenOr[tts.TTS | None] = NOT_GIVEN,
        allow_interruptions: NotGivenOr[bool] = NOT_GIVEN,
    ) -> None:
        super().__init__(
            instructions=(
                "You are only a single step in a broader system, responsible solely for capturing an email address.\n"
                "Handle input as noisy voice transcription. Expect that users will say emails aloud with formats like:\n"
                "- 'john dot doe at gmail dot com'\n"
                "- 'susan underscore smith at yahoo dot co dot uk'\n"
                "- 'dave dash b at protonmail dot com'\n"
                "- 'jane at example' (partial—prompt for the domain)\n"
                "- 'theo t h e o at livekit dot io' (name followed by spelling)\n"
                "Normalize common spoken patterns silently:\n"
                "- Convert words like 'dot', 'underscore', 'dash', 'plus' into symbols: `.`, `_`, `-`, `+`.\n"
                "- Convert 'at' to `@`.\n"
                "- Recognize patterns where users speak their name or a word, followed by spelling: e.g., 'john j o h n'.\n"
                "- Filter out filler words or hesitations.\n"
                "- Assume some spelling if contextually obvious (e.g. 'mike b two two' → mikeb22).\n"
                "Don't mention corrections. Treat inputs as possibly imperfect but fix them silently.\n"
                "Call `update_email_address` at the first opportunity whenever you form a new hypothesis about the email. "
                "(before asking any questions or providing any answers.) \n"
                "Don't invent new email addresses, stick strictly to what the user said. \n"
                "Call `confirm_email_address` after the user confirmed the email address is correct. \n"
                "If the email is unclear or invalid, or it takes too much back-and-forth, prompt for it in parts: first the part before the '@', then the domain—only if needed. \n"
                "Ignore unrelated input and avoid going off-topic. Do not generate markdown, greetings, or unnecessary commentary. \n"
                "Always explicitly invoke a tool when applicable. Do not simulate tool usage, no real action is taken unless the tool is explicitly called."
            ),
            chat_ctx=chat_ctx,
            turn_detection=turn_detection,
            stt=stt,
            vad=vad,
            llm=llm,
            tts=tts,
            allow_interruptions=allow_interruptions,
        )

        self._current_email = ""

        # speech_handle/turn used to update the email address.
        # used to ignore the call to confirm_email_address in case the LLM is hallucinating and not asking for user confirmation
        self._email_update_speech_handle: SpeechHandle | None = None

    async def on_enter(self) -> None:
        self.session.generate_reply(
            instructions=(
                "Ask the user to provide an email address. If you already have it, ask for confirmation.\n"
                "Do not call `decline_email_capture`"
            )
        )

    @function_tool
    async def update_email_address(self, email: str, ctx: RunContext) -> str:
        """Update the email address provided by the user.

        Args:
            email: The email address provided by the user
        """
        self._email_update_speech_handle = ctx.speech_handle
        email = email.strip()

        if not re.match(EMAIL_REGEX, email):
            raise ToolError(f"Invalid email address provided: {email}")

        self._current_email = email
        separated_email = " ".join(email)

        return (
            f"The email has been updated to {email}\n"
            f"Repeat the email character by character: {separated_email} if needed\n"
            f"Prompt the user for confirmation, do not call `confirm_email_address` directly"
        )

    @function_tool
    async def confirm_email_address(self, ctx: RunContext) -> None:
        """Validates/confirms the email address provided by the user."""
        await ctx.wait_for_playout()

        if ctx.speech_handle == self._email_update_speech_handle:
            raise ToolError("error: the user must confirm the email address explicitly")

        if not self._current_email.strip():
            raise ToolError(
                "error: no email address were provided, `update_email_address` must be called before"
            )

        if not self.done():
            self.complete(GetEmailResult(email_address=self._current_email))

    @function_tool
    async def decline_email_capture(self, reason: str) -> None:
        """Handles the case when the user explicitly declines to provide an email address.

        Args:
            reason: A short explanation of why the user declined to provide the email address
        """
        if not self.done():
            self.complete(ToolError(f"couldn't get the email address: {reason}"))

Abstract base class for generic types.

On Python 3.12 and newer, generic classes implicitly inherit from Generic when they declare a parameter list after the class's name::

class Mapping[KT, VT]:
    def __getitem__(self, key: KT) -> VT:
        ...
    # Etc.

On older versions of Python, however, generic classes have to explicitly inherit from Generic.

After a class has been declared to be generic, it can then be used as follows::

def lookup_name[KT, VT](mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
    try:
        return mapping[key]
    except KeyError:
        return default

Ancestors

  • livekit.agents.voice.agent.AgentTask
  • livekit.agents.voice.agent.Agent
  • typing.Generic

Methods

async def confirm_email_address(self, ctx: RunContext) ‑> None
Expand source code
@function_tool
async def confirm_email_address(self, ctx: RunContext) -> None:
    """Validates/confirms the email address provided by the user."""
    await ctx.wait_for_playout()

    if ctx.speech_handle == self._email_update_speech_handle:
        raise ToolError("error: the user must confirm the email address explicitly")

    if not self._current_email.strip():
        raise ToolError(
            "error: no email address were provided, `update_email_address` must be called before"
        )

    if not self.done():
        self.complete(GetEmailResult(email_address=self._current_email))

Validates/confirms the email address provided by the user.

async def decline_email_capture(self, reason: str) ‑> None
Expand source code
@function_tool
async def decline_email_capture(self, reason: str) -> None:
    """Handles the case when the user explicitly declines to provide an email address.

    Args:
        reason: A short explanation of why the user declined to provide the email address
    """
    if not self.done():
        self.complete(ToolError(f"couldn't get the email address: {reason}"))

Handles the case when the user explicitly declines to provide an email address.

Args

reason
A short explanation of why the user declined to provide the email address
async def on_enter(self) ‑> None
Expand source code
async def on_enter(self) -> None:
    self.session.generate_reply(
        instructions=(
            "Ask the user to provide an email address. If you already have it, ask for confirmation.\n"
            "Do not call `decline_email_capture`"
        )
    )

Called when the task is entered

async def update_email_address(self, email: str, ctx: RunContext) ‑> str
Expand source code
@function_tool
async def update_email_address(self, email: str, ctx: RunContext) -> str:
    """Update the email address provided by the user.

    Args:
        email: The email address provided by the user
    """
    self._email_update_speech_handle = ctx.speech_handle
    email = email.strip()

    if not re.match(EMAIL_REGEX, email):
        raise ToolError(f"Invalid email address provided: {email}")

    self._current_email = email
    separated_email = " ".join(email)

    return (
        f"The email has been updated to {email}\n"
        f"Repeat the email character by character: {separated_email} if needed\n"
        f"Prompt the user for confirmation, do not call `confirm_email_address` directly"
    )

Update the email address provided by the user.

Args

email
The email address provided by the user