Outbound w/ Scheduling
In this example, we'll build an outbound voice agent for a dental office. The agent calls patients to help them schedule appointments.
Define the Agent
guava.Agent is our starting point for building Guava agents. We'll create one for this example.
import guava
agent = guava.Agent(
organization="Bright Smile Dental",
purpose="Call patients to help them schedule a dental appointment.",
)Set up DatetimeFilter
DatetimeFilter is a built-in helper that accepts a natural-language availability query (e.g. "Tuesdays work best") and returns a short list of matching slots from your source data. In a production system you would swap this for a call to your own scheduling backend.
from guava.helpers.openai import DatetimeFilter
from guava.examples.example_data import MOCK_APPOINTMENTS
datetime_filter = DatetimeFilter(source_list=MOCK_APPOINTMENTS)Reach the right person
on_call_start fires at the beginning of every outbound call. We read the patient's name from the call variables and invoke reach_person, which instructs the agent to confirm it is speaking with the intended recipient before proceeding. Later in this example, we'll see how to set the patient_name variable.
@agent.on_call_start
def on_call_start(call: guava.Call):
call.reach_person(
contact_full_name=call.get_variable("patient_name"),
)Under the hood reach_person() is just a call to set_task(). You can replace reach_person() with your own Task if you need custom behavior here.
Handle the reach-person outcome
on_reach_person fires once the agent has determined whether the intended person is available. If they are, we set a task to collect an appointment time. If not, we hang up gracefully.
@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str) -> None:
if outcome == "unavailable":
call.hangup("Apologize for your mistake and hang up the call.")
elif outcome == "available":
call.set_task(
"schedule_appointment",
checklist=[
"Tell them that it's been a while since their regular cleaning with Dr. Teeth.",
guava.Field(
key="appointment_time",
field_type="calendar_slot",
description="Find a time that works for the caller",
searchable=True,
),
"Tell them their appointment has been confirmed and answer any questions before ending the call.",
],
)Register an on_search_query handler
The appointment_time field has a special attribute searchable=True set. This turns the field into a "Search Field". Instead of providing a fixed list of choices, we will register an on_search_query handler.
The agent will invoke this handler to generate possible candidates for filling the field - at each invocation the agent provides a natural-language search query for us to match against.
In this example, we can simply forward that query to DatetimeFilter and return the result.
@agent.on_search_query("appointment_time")
def search_appointments(call: guava.Call, query: str):
return datetime_filter.filter(query, max_results=3)The agent may call this handler multiple times if the patient rejects the initial options or refines their availability.
Handle task completion
on_task_complete fires once every item in the checklist is resolved. This is where you'd write the confirmed slot back to your database, trigger a confirmation SMS, or perform any other post-booking actions.
@agent.on_task_complete("schedule_appointment")
def on_appointment_scheduled(call: guava.Call):
call.hangup("Thank them for their time and hang up the call.")Place the outbound call
Use call_phone to initiate the call. Here is where you set initial values for variables - they'll be available inside your handlers via get_variable().
agent.call_phone(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=args.phone,
variables={"patient_name": args.name},
)If you intend to dial multiple participants, use Campaigns instead of individual outbound calls. Campaigns offer settings for automatic retries, call windows, multiple origin phone numbers, and concurrency control.
Complete example
import logging
import os
import argparse
import guava
from guava import logging_utils, Agent
from guava.examples.example_data import MOCK_APPOINTMENTS
from guava.helpers.openai import DatetimeFilter
logger = logging.getLogger("guava.examples.scheduling_outbound")
agent = Agent(
organization="Bright Smile Dental",
purpose="Call patients to help them schedule a dental appointment.",
)
datetime_filter = DatetimeFilter(source_list=MOCK_APPOINTMENTS)
@agent.on_call_start
def on_call_start(call: guava.Call):
call.reach_person(
contact_full_name=call.get_variable("patient_name"),
)
@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str) -> None:
if outcome == "unavailable":
call.hangup("Apologize for your mistake and hang up the call.")
elif outcome == "available":
call.set_task(
"schedule_appointment",
checklist=[
"Tell them that it's been a while since their regular cleaning with Dr. Teeth.",
guava.Field(
key="appointment_time",
field_type="calendar_slot",
description="Find a time that works for the caller",
searchable=True,
),
"Tell them their appointment has been confirmed and answer any questions before ending the call.",
],
)
@agent.on_search_query("appointment_time")
def search_appointments(call: guava.Call, query: str):
return datetime_filter.filter(query, max_results=3)
@agent.on_task_complete("schedule_appointment")
def on_appointment_scheduled(call: guava.Call):
call.hangup("Thank them for their time and hang up the call.")
if __name__ == "__main__":
logging_utils.configure_logging()
parser = argparse.ArgumentParser()
parser.add_argument("phone", type=str, help="Phone number to call.")
parser.add_argument("name", nargs="?", help="Name of the patient", default="Benjamin Buttons")
args = parser.parse_args()
agent.call_phone(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=args.phone,
variables={"patient_name": args.name},
)Questions? hi@goguava.ai
import guava
agent = guava.Agent(
organization="Bright Smile Dental",
purpose="Call patients to help them schedule a dental appointment.",
)from guava.helpers.openai import DatetimeFilter
from guava.examples.example_data import MOCK_APPOINTMENTS
datetime_filter = DatetimeFilter(source_list=MOCK_APPOINTMENTS)@agent.on_call_start
def on_call_start(call: guava.Call):
call.reach_person(
contact_full_name=call.get_variable("patient_name"),
)@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str) -> None:
if outcome == "unavailable":
call.hangup("Apologize for your mistake and hang up the call.")
elif outcome == "available":
call.set_task(
"schedule_appointment",
checklist=[
"Tell them that it's been a while since their regular cleaning with Dr. Teeth.",
guava.Field(
key="appointment_time",
field_type="calendar_slot",
description="Find a time that works for the caller",
searchable=True,
),
"Tell them their appointment has been confirmed and answer any questions before ending the call.",
],
)@agent.on_search_query("appointment_time")
def search_appointments(call: guava.Call, query: str):
return datetime_filter.filter(query, max_results=3)@agent.on_task_complete("schedule_appointment")
def on_appointment_scheduled(call: guava.Call):
call.hangup("Thank them for their time and hang up the call.")agent.call_phone(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=args.phone,
variables={"patient_name": args.name},
)import logging
import os
import argparse
import guava
from guava import logging_utils, Agent
from guava.examples.example_data import MOCK_APPOINTMENTS
from guava.helpers.openai import DatetimeFilter
logger = logging.getLogger("guava.examples.scheduling_outbound")
agent = Agent(
organization="Bright Smile Dental",
purpose="Call patients to help them schedule a dental appointment.",
)
datetime_filter = DatetimeFilter(source_list=MOCK_APPOINTMENTS)
@agent.on_call_start
def on_call_start(call: guava.Call):
call.reach_person(
contact_full_name=call.get_variable("patient_name"),
)
@agent.on_reach_person
def on_reach_person(call: guava.Call, outcome: str) -> None:
if outcome == "unavailable":
call.hangup("Apologize for your mistake and hang up the call.")
elif outcome == "available":
call.set_task(
"schedule_appointment",
checklist=[
"Tell them that it's been a while since their regular cleaning with Dr. Teeth.",
guava.Field(
key="appointment_time",
field_type="calendar_slot",
description="Find a time that works for the caller",
searchable=True,
),
"Tell them their appointment has been confirmed and answer any questions before ending the call.",
],
)
@agent.on_search_query("appointment_time")
def search_appointments(call: guava.Call, query: str):
return datetime_filter.filter(query, max_results=3)
@agent.on_task_complete("schedule_appointment")
def on_appointment_scheduled(call: guava.Call):
call.hangup("Thank them for their time and hang up the call.")
if __name__ == "__main__":
logging_utils.configure_logging()
parser = argparse.ArgumentParser()
parser.add_argument("phone", type=str, help="Phone number to call.")
parser.add_argument("name", nargs="?", help="Name of the patient", default="Benjamin Buttons")
args = parser.parse_args()
agent.call_phone(
from_number=os.environ["GUAVA_AGENT_NUMBER"],
to_number=args.phone,
variables={"patient_name": args.name},
)