Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
c230a65
outline of scheduler and recorder examples
tinalenguyen Feb 20, 2025
da6de48
updated outline
tinalenguyen Feb 21, 2025
3e0f387
update userdata
tinalenguyen Feb 22, 2025
e15d96e
ruff, adjust userdata
tinalenguyen Feb 22, 2025
c2ac0bd
integrating cal api, wip
tinalenguyen Feb 24, 2025
c90e1b0
Merge branch 'livekit:main' into main
tinalenguyen Feb 24, 2025
7c4dff3
refactored api request sending
tinalenguyen Feb 24, 2025
fcd14e8
Update dentist_scheduler.py
tinalenguyen Feb 28, 2025
16ce8d2
added setup file for cal api
tinalenguyen Mar 4, 2025
9509c46
added schedule config, inquiry functions, updates to instructions
tinalenguyen Mar 5, 2025
6da5207
ruff
tinalenguyen Mar 5, 2025
f98db1a
updated to voice agent, schedule appt works
tinalenguyen Mar 5, 2025
e39f805
reschedule and cancel functionality fixed
tinalenguyen Mar 6, 2025
bf77019
polishing scheduler, supabase wip
tinalenguyen Mar 6, 2025
cd44ace
unavailability check wip
tinalenguyen Mar 6, 2025
7dca592
Merge branch 'livekit:main' into main
tinalenguyen Mar 6, 2025
4e7c027
supabase integration
tinalenguyen Mar 7, 2025
bb06401
Merge branch 'main' of https://github.com/tinalenguyen/agents
tinalenguyen Mar 7, 2025
f8d2ac2
drafting readme'
tinalenguyen Mar 7, 2025
852ef7e
Update README.md
tinalenguyen Mar 7, 2025
80ff056
organized task folder
tinalenguyen Mar 8, 2025
da644eb
Update __init__.py
tinalenguyen Mar 8, 2025
c9b1afd
better imports, added task bank
tinalenguyen Mar 9, 2025
a94ba41
ruff + readme in tasks folder
tinalenguyen Mar 9, 2025
0848525
api setup schedule check + voices
tinalenguyen Mar 10, 2025
9ae3355
testing instructions
tinalenguyen Mar 11, 2025
d325f0c
availability checking for reschedule
tinalenguyen Mar 11, 2025
a0fb62d
wip on docs and persistor+recorder'
tinalenguyen Mar 11, 2025
3c76989
persistor user transcripts
tinalenguyen Mar 12, 2025
2037e00
Update README.md
tinalenguyen Mar 12, 2025
2c67597
rescheduling nonexistent event fix + instructions update
tinalenguyen Mar 13, 2025
588871c
persistor event fix
tinalenguyen Mar 13, 2025
08954f6
update per renames
tinalenguyen Mar 17, 2025
cf40be1
fixes
tinalenguyen Mar 18, 2025
4e0ce3b
docs and cleanup
tinalenguyen Mar 18, 2025
912c01e
added speech_created and conversation_item_added events
tinalenguyen Mar 26, 2025
64c2d82
Merge branch 'livekit:main' into main
tinalenguyen Mar 31, 2025
17d60eb
Merge branch 'livekit:main' into main
tinalenguyen Mar 31, 2025
43bde0b
recorder and ruff
tinalenguyen Mar 31, 2025
b7c065a
Merge branch 'livekit:main' into main
tinalenguyen Apr 2, 2025
4c1219b
fixes and function calls
tinalenguyen Apr 2, 2025
fa06c40
rename and fixes
tinalenguyen Apr 2, 2025
8271b8a
ruff
tinalenguyen Apr 2, 2025
c2c30e2
realtime persistor
tinalenguyen Apr 9, 2025
bb92b78
update conversation event and recorder
tinalenguyen Apr 14, 2025
e914bd4
Merge branch 'livekit:main' into main
tinalenguyen Apr 14, 2025
58773ac
updated events
tinalenguyen Apr 14, 2025
addc852
Merge branch 'main' of https://github.com/tinalenguyen/agents
tinalenguyen Apr 14, 2025
3e2ae93
add interrupt log
tinalenguyen Apr 15, 2025
8e41b37
add runcontext to methods
tinalenguyen Apr 16, 2025
53ffbc7
Update scheduler_task.py
tinalenguyen Apr 16, 2025
80ece40
Merge branch 'livekit:main' into main
tinalenguyen Apr 16, 2025
bfcdfb1
global date function
tinalenguyen Apr 17, 2025
358466b
update global function
tinalenguyen Apr 17, 2025
cbb1564
datefix
tinalenguyen Apr 17, 2025
c9c05cf
fix args
tinalenguyen Apr 17, 2025
ac9f85b
remove print
tinalenguyen Apr 17, 2025
07bfa79
Merge branch 'livekit:main' into main
tinalenguyen May 8, 2025
ef00e6e
switched for rtc audioresampler
tinalenguyen May 8, 2025
ba72fc6
Merge branch 'main' of https://github.com/tinalenguyen/agents
tinalenguyen May 8, 2025
269fabc
fixes
tinalenguyen May 16, 2025
17c7da2
quality change and stt wrapper fix
tinalenguyen May 22, 2025
8a60b08
Merge branch 'livekit:main' into main
tinalenguyen May 28, 2025
b870bee
Update conversation_recorder.py
tinalenguyen May 28, 2025
0226e50
mixed streams, will handle interruptions next
tinalenguyen May 29, 2025
8f2bbd7
handle interruptions
tinalenguyen Jun 3, 2025
948b4c3
update aclose
tinalenguyen Jun 3, 2025
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
90 changes: 90 additions & 0 deletions examples/full_examples/dentist-scheduler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# :tooth: Building a Dentist Scheduler Assistant
Build an AI-powered dentist assistant that manages appointments, takes messages, and answers inquiries

This example integrates the Cal.com and Supabase APIs with LiveKit and exhibits the functionality of a multi-agent framework. To curate a complete assistant, this recipe includes a Recepionist, Scheduler, and Messenger.

# Prerequisites
For this example, you need to create an VoiceAgent using the [Voice Agent quickstart](https://docs.livekit.io/agents/quickstarts/voice-agent/).

### Setting up the Cal API
You need to [create an account](https://app.cal.com/signup) and enroll in a plan. The `.env.local` file should have your API key and Cal.com username:
```
CAL_API_KEY="<cal_api_key>"
CAL_API_USERNAME="<cal_api_username>"
```

We'll set up the details for this example for you in-house upon running the agent, this includes the schedule and necessary event types.

### Setting up Supabase
1. [Create an account](https://supabase.com/dashboard/sign-up) and a new project for this example.
2. After configuring your RLS settings (SELECT and INSERT policies for this example), create a table in the public schema.

Your table should have these text columns: `name`, `message`, and `phone_number`. You can optionally add a datetime column that automatically records the time the message was received.

Here is what your table should look like:
| date | name | message | phone_number |
| ---- | ---- | ------- | ------------ |


3. Add your project API key and URL into the `.env.local` file:
```
SUPABASE_API_KEY="<supabase_api_key>"
SUPABASE_URL="<supabase_project_url>"
```

# Storing data across agents
Agents can access shared data through `VoiceAgent.userdata`. You can store information about the user, tasks, and API information.

In this example, we create `UserInfo` to store user information:
```
@dataclass
class UserInfo:
name: str | None = None
email: str | None = None
phone: str | None = None
message: str | None = None
```

We also use `Agents` as an agents bank for smooth transfers:
```
@dataclass
class Agents:
@property
def receptionist(self) -> Agent:
return Receptionist()

@property
def messenger(self) -> Agent:
return Messenger()

def scheduler(self, service: str) -> Agent:
return Scheduler(service=service)

```


This example stores event IDs from setting up the Cal API, an instance of `UserInfo`, and an instance of `Agents()`. Modify `VoiceAgent`'s arguments to include `userdata`:
```
userdata = {"event_ids": event_ids, "userinfo": UserInfo(), "agents": Agents()}
agent = VoiceAgent(
task=Receptionist(),
userdata=userdata,
stt=deepgram.STT(),
llm=openai.LLM(),
tts=cartesia.TTS(),
vad=silero.VAD.load(),
)
```

# Running the example
After setting up the environment, run `python dental_agent.py dev` to meet the Receptionist.

Try out:
- Scheduling a new appointment
- Asking about the office hours and location
- Leaving a message for the office
- Transferring between agents




148 changes: 148 additions & 0 deletions examples/full_examples/dentist-scheduler/api_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import os

import aiohttp
from dotenv import load_dotenv

load_dotenv()

HEADERS = {
"cal-api-version": "2024-06-14",
"Authorization": "Bearer " + os.getenv("CAL_API_KEY"),
}

SESSION_LENGTH = 60


async def get_event_id(slug: str) -> str | None:
"""Searches for an event type. Returns the event ID if found, None if not

Args:
slug (str): The unique identifier of the event type
"""
payload = {"username": os.getenv("CAL_API_USERNAME"), "eventSlug": slug}
async with aiohttp.ClientSession() as session:
async with session.get(
"https://api.cal.com/v2/event-types", params=payload, headers=HEADERS
) as response:
response = await response.json()
if response["status"] == "success" and response["data"]:
return response["data"][0]["id"]
if response["status"] == "error":
raise Exception("Error retrieving event type")
else:
return None


async def search_schedule(name: str) -> str | None:
"""Checks if needed schedule already exists, returns schedule ID"""
async with aiohttp.ClientSession() as session:
async with session.get(
"https://api.cal.com/v2/schedules/default", headers=HEADERS
) as response:
response = await response.json()
if (
response["status"] == "success"
and response["data"]
and response["data"]["name"] == name
):
return response["data"]["id"]
else:
return None


async def create_schedule() -> str:
"""Sets schedule for example, returns schedule ID"""
payload = {
"name": "LiveKit Dental Office Hours",
"timeZone": "America/Los_Angeles",
"isDefault": True,
"availability": [
{
"days": [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
],
"startTime": "10:00",
"endTime": "12:00",
},
{
"days": [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
],
"startTime": "13:00",
"endTime": "16:00",
},
],
}
async with aiohttp.ClientSession() as session:
async with session.post(
"https://api.cal.com/v2/schedules", json=payload, headers=HEADERS
) as response:
response = await response.json()
if response["status"] == "success" and response["data"]:
return response["data"]["id"]
if response["status"] == "error":
raise Exception(f"Error creating schedule: {response}")


async def create_event_type(*, title: str, slug: str, schedule_id: str) -> str:
"""Creates specified event type and returns the event ID

Args:
title (str): The title of the event type
slug (str): The unique identifier of the event type, typically with dashes instead of spaces
"""
payload = {
"lengthInMinutes": SESSION_LENGTH,
"title": title,
"slug": slug,
"scheduleId": schedule_id,
}
async with aiohttp.ClientSession() as session:
async with session.post(
"https://api.cal.com/v2/event-types", json=payload, headers=HEADERS
) as response:
response = await response.json()
if response["status"] == "success":
return response["data"]["id"]
else:
raise Exception(f"{response['error']['code']}")


async def setup_event_types() -> dict:
"""Ensures that the schedule and event types are set up correctly in Cal.com for this example.
Returns a dictionary with event slugs and their respective IDs
"""

schedule_id = await search_schedule("LiveKit Dental Office Hours")
if not schedule_id:
schedule_id = await create_schedule()

event_ids = {}

checkup_event_id = await get_event_id("routine-checkup")
if not checkup_event_id:
checkup_event_id = await create_event_type(
title="Routine Checkup", slug="routine-checkup", schedule_id=schedule_id
)

event_ids["routine-checkup"] = checkup_event_id

extraction_event_id = await get_event_id("tooth-extraction")
if not extraction_event_id:
extraction_event_id = await create_event_type(
title="Tooth Extraction", slug="tooth-extraction", schedule_id=schedule_id
)

event_ids["tooth-extraction"] = extraction_event_id

return event_ids
62 changes: 62 additions & 0 deletions examples/full_examples/dentist-scheduler/dental_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import logging
from dataclasses import dataclass

from api_setup import setup_event_types
from dotenv import load_dotenv
from pydantic import BaseModel
from tasks import Messenger, Receptionist, Scheduler

from livekit.agents import (
JobContext,
WorkerOptions,
cli,
)
from livekit.agents.voice import Agent, AgentSession
from livekit.plugins import cartesia, deepgram, openai, silero


class UserInfo(BaseModel):
name: str | None = None
email: str | None = None
phone: str | None = None
message: str | None = None


@dataclass
class Agents:
@property
def receptionist(self) -> Agent:
return Receptionist()

@property
def messenger(self) -> Agent:
return Messenger()

def scheduler(self, service: str) -> Agent:
return Scheduler(service=service)


load_dotenv()

logger = logging.getLogger("dental-scheduler")
logger.setLevel(logging.INFO)


async def entrypoint(ctx: JobContext):
event_ids = await setup_event_types()
userdata = {"event_ids": event_ids, "userinfo": UserInfo(), "agents": Agents()}

session = AgentSession(
userdata=userdata,
stt=deepgram.STT(),
llm=openai.LLM(),
tts=cartesia.TTS(),
vad=silero.VAD.load(),
)

await ctx.connect()
await session.start(agent=userdata["agents"].receptionist, room=ctx.room)


if __name__ == "__main__":
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
Loading
Loading