Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion livekit-agents/livekit/agents/beta/workflows/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def __init__(

assert isinstance(instructions, (str, Instructions)) # for type checking
self._current_address = ""
self._spell_read_back = False
self._require_confirmation = require_confirmation
self._require_explicit_ask = require_explicit_ask

Expand Down Expand Up @@ -135,9 +136,16 @@ async def _update_address_impl(
current_tools.append(confirm_tool)
await self.update_tools(current_tools)

read_back = (
f"Repeat the address field by field, spelling the street name letter by "
f"letter: {address_fields}"
if self._spell_read_back
else "Repeat the address back to the user."
)
self._spell_read_back = True
return (
f"The address has been updated to {address}\n"
f"Repeat the address field by field: {address_fields} if needed\n"
f"{read_back}\n"
f"Prompt the user for confirmation, do not call `confirm_address` directly"
)

Expand Down
11 changes: 9 additions & 2 deletions livekit-agents/livekit/agents/beta/workflows/dob.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def __init__(
self._require_explicit_ask = require_explicit_ask
self._current_dob: date | None = None
self._current_time: time | None = None
self._spell_read_back = False

super().__init__(
instructions=Instructions(
Expand Down Expand Up @@ -195,9 +196,15 @@ async def _update_dob_impl(
formatted_time = self._current_time.strftime("%I:%M %p")
response += f" at {formatted_time}"

read_back = (
f"Repeat the date back one part at a time, the month, the day, then the year: "
f"{dob.strftime('%B')}, {dob.day}, {dob.year}"
if self._spell_read_back
else "Repeat the date back to the user in a natural spoken format."
)
self._spell_read_back = True
response += (
"\nRepeat the date back to the user in a natural spoken format.\n"
"Prompt the user for confirmation, do not call `confirm_dob` directly"
f"\n{read_back}\nPrompt the user for confirmation, do not call `confirm_dob` directly"
)

return response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def __init__(

assert isinstance(instructions, (str, Instructions)) # for type checking
self._current_email = ""
self._spell_read_back = False
self._require_confirmation = require_confirmation
self._require_explicit_ask = require_explicit_ask

Expand Down Expand Up @@ -118,9 +119,15 @@ async def _update_email_impl(self, email: str, ctx: RunContext) -> str | None:
current_tools.append(confirm_tool)
await self.update_tools(current_tools)

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

Expand Down
19 changes: 9 additions & 10 deletions livekit-agents/livekit/agents/beta/workflows/name.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def __init__(
self._collect_first_name = first_name
self._collect_last_name = last_name
self._collect_middle_name = middle_name
self._verify_spelling = verify_spelling
self._spell_read_back = verify_spelling
self._require_confirmation = require_confirmation
self._require_explicit_ask = require_explicit_ask

Expand Down Expand Up @@ -268,17 +268,16 @@ async def _update_name_impl(
current_tools.append(confirm_tool)
await self.update_tools(current_tools)

if self._verify_spelling:
return (
f"The name has been updated to {full_name}\n"
f"Spell out the name letter by letter for verification: {full_name}\n"
f"Prompt the user for confirmation, do not call `confirm_name` directly"
)

read_back = (
f"Spell out the name letter by letter for verification: {full_name}"
if self._spell_read_back
else "Repeat the name back to the user."
)
self._spell_read_back = True
return (
f"The name has been updated to {full_name}\n"
f"Repeat the name back to the user and prompt for confirmation, "
f"do not call `confirm_name` directly"
f"{read_back}\n"
f"Prompt the user for confirmation, do not call `confirm_name` directly"
)

def _build_confirm_tool(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def __init__(
extra = extra_instructions if extra_instructions else ""

self._current_phone_number = ""
self._spell_read_back = False
self._require_confirmation = require_confirmation
self._require_explicit_ask = require_explicit_ask

Expand Down Expand Up @@ -152,9 +153,15 @@ async def _update_phone_number_impl(self, phone_number: str, ctx: RunContext) ->
current_tools.append(confirm_tool)
await self.update_tools(current_tools)

read_back = (
f"Read the number back digit by digit: {' '.join(cleaned)}"
if self._spell_read_back
else "Read the number back to the user in groups."
)
self._spell_read_back = True
return (
f"The phone number has been updated to {cleaned}\n"
f"Read the number back to the user in groups.\n"
f"{read_back}\n"
f"Prompt the user for confirmation, do not call `confirm_phone_number` directly"
)

Expand Down
89 changes: 89 additions & 0 deletions tests/test_workflow_readback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from __future__ import annotations

from types import SimpleNamespace
from typing import Any

import pytest

from livekit.agents import beta

pytestmark = pytest.mark.unit


def _audio_ctx() -> Any:
return SimpleNamespace(
speech_handle=SimpleNamespace(input_details=SimpleNamespace(modality="audio"))
)


@pytest.mark.asyncio
async def test_email_is_spelled_once_a_confirmation_is_refused() -> None:
task = beta.workflows.GetEmailTask()
ctx = _audio_ctx()

first = await task._update_email_impl("shayne.cole@gmail.com", ctx)
second = await task._update_email_impl("shayne.cole@gmail.com", ctx)

assert first is not None and second is not None
assert first != second
assert " ".join("shayne.cole@gmail.com") in second


@pytest.mark.asyncio
async def test_name_is_spelled_once_a_confirmation_is_refused() -> None:
task = beta.workflows.GetNameTask(first_name=True, last_name=True)
ctx = _audio_ctx()

first = await task._update_name_impl(ctx, first_name="Shayne", last_name="Cole")
second = await task._update_name_impl(ctx, first_name="Shayne", last_name="Cole")

assert first is not None and second is not None
assert first != second


@pytest.mark.asyncio
async def test_name_with_verify_spelling_is_spelled_from_the_start() -> None:
task = beta.workflows.GetNameTask(first_name=True, verify_spelling=True)
ctx = _audio_ctx()

first = await task._update_name_impl(ctx, first_name="Shayne")
second = await task._update_name_impl(ctx, first_name="Shayne")

assert first == second


@pytest.mark.asyncio
async def test_phone_is_read_digit_by_digit_once_a_confirmation_is_refused() -> None:
task = beta.workflows.GetPhoneNumberTask()
ctx = _audio_ctx()

first = await task._update_phone_number_impl("415-555-0626", ctx)
second = await task._update_phone_number_impl("415-555-0626", ctx)

assert first is not None and second is not None
assert first != second
assert " ".join("4155550626") in second


@pytest.mark.asyncio
async def test_address_is_spelled_once_a_confirmation_is_refused() -> None:
task = beta.workflows.GetAddressTask()
ctx = _audio_ctx()

first = await task._update_address_impl("1 Main St", "", "Springfield", "US", ctx)
second = await task._update_address_impl("1 Main St", "", "Springfield", "US", ctx)

assert first is not None and second is not None
assert first != second


@pytest.mark.asyncio
async def test_dob_is_read_part_by_part_once_a_confirmation_is_refused() -> None:
task = beta.workflows.GetDOBTask()
ctx = _audio_ctx()

first = await task._update_dob_impl(1990, 5, 17, ctx)
second = await task._update_dob_impl(1990, 5, 17, ctx)

assert first is not None and second is not None
assert first != second
Loading