Skip to content

Commit c4cc7b3

Browse files
committed
Split transfer into transfer_to_number and transfer_to_sip, expand messaging examples
- Replace session.transfer() with transfer_to_number() (phone numbers) and transfer_to_sip() (SIP URI via Dial XML with custom headers) - Expand send_sms.py with MMS single and multi-media examples - Rewrite whatsapp.py with all 7 message types: text, media, template, interactive buttons, list, CTA URL, and location - Add 4 new transfer tests (73 total)
1 parent 9332314 commit c4cc7b3

7 files changed

Lines changed: 296 additions & 49 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ Sign up at [cx.plivo.com/signup](https://cx.plivo.com/signup) to get your `PLIVO
8989
- [**Background audio**](examples/background_audio.py) - ambient office/typing sounds mixed with agent speech
9090
- [**Pipeline modes**](examples/pipeline_modes.py) - all five config combinations in one file
9191
- [**Metrics & observability**](examples/metrics.py) - per-turn latency breakdown, VAD and turn events
92-
- [**SMS**](examples/send_sms.py) - send an SMS message
93-
- [**WhatsApp**](examples/whatsapp.py) - WhatsApp templates and interactive messages
92+
- [**SMS & MMS**](examples/send_sms.py) - text messages and MMS with media attachments
93+
- [**WhatsApp**](examples/whatsapp.py) - text, media, templates, buttons, lists, CTA, location
9494
- [**Buy a number**](examples/buy_number.py) - search and purchase a phone number
9595
- [**Callback server**](examples/callback_server.py) - FastAPI HTTP webhook receiver for call events
9696
- [**FastAPI integration**](examples/agent_fastapi.py) - embed VoiceApp inside an existing FastAPI app

examples/full_pipeline.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ def on_tool_call(session, event: ToolCall):
256256

257257
elif event.name == "transfer_to_human":
258258
session.speak("Let me transfer you to a specialist. One moment please.")
259-
session.transfer("+18005551234")
259+
session.transfer_to_number("+18005551234")
260260

261261
elif event.name == "play_hold_music":
262262
session.play("hold_music.wav", allow_interruption=False)

examples/pipeline_modes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def on_tts_dtmf(session, event: Dtmf):
222222
session.speak("Your balance is forty two dollars and seventeen cents.")
223223
elif event.digit == "2":
224224
session.speak("Transferring you to an agent.")
225-
session.transfer("+18005551234")
225+
session.transfer_to_number("+18005551234")
226226
elif event.digit == "#":
227227
session.speak("Goodbye.")
228228
session.hangup()

examples/send_sms.py

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,75 @@
1-
"""Send an SMS message using the Plivo Agent SDK.
2-
3-
Prerequisites:
4-
pip install plivo_agent
1+
"""
2+
SMS & MMS Examples — Text and Media Messages
53
6-
Environment variables:
7-
PLIVO_AUTH_ID -- Your Plivo auth ID
8-
PLIVO_AUTH_TOKEN -- Your Plivo auth token
4+
Demonstrates sending SMS (text only) and MMS (with media attachments)
5+
using the Plivo Agent SDK.
96
10-
Run:
11-
python send_sms.py
7+
Usage:
8+
1. pip install plivo_agent
9+
2. Set PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN env vars
10+
3. python send_sms.py [sms | mms | mms-multi]
1211
"""
1312

1413
import asyncio
14+
import sys
1515

1616
from plivo_agent import AsyncClient
1717

18+
SRC = "+14155551234" # your Plivo number
19+
DST = "+14155559876" # recipient
20+
21+
22+
# --- SMS: plain text ---
1823

19-
async def main():
24+
async def send_sms():
2025
async with AsyncClient() as client:
2126
response = await client.messages.create(
22-
src="+14155551234",
23-
dst="+14155559876",
27+
src=SRC,
28+
dst=DST,
2429
text="Hello from the Plivo Agent SDK!",
2530
)
26-
print("Message UUID:", response["message_uuid"])
27-
print("API ID:", response["api_id"])
31+
print("SMS sent:", response["message_uuid"])
32+
33+
34+
# --- MMS: single image ---
35+
36+
async def send_mms():
37+
async with AsyncClient() as client:
38+
response = await client.messages.create(
39+
src=SRC,
40+
dst=DST,
41+
text="Check out this image!",
42+
type_="mms",
43+
media_urls=["https://media.plivo.com/demo/image-sample.jpg"],
44+
)
45+
print("MMS sent:", response["message_uuid"])
46+
47+
48+
# --- MMS: multiple media (image + PDF) ---
49+
50+
async def send_mms_multi():
51+
async with AsyncClient() as client:
52+
response = await client.messages.create(
53+
src=SRC,
54+
dst=DST,
55+
text="Here are your documents.",
56+
type_="mms",
57+
media_urls=[
58+
"https://media.plivo.com/demo/image-sample.jpg",
59+
"https://media.plivo.com/demo/invoice.pdf",
60+
],
61+
)
62+
print("MMS (multi) sent:", response["message_uuid"])
2863

2964

3065
if __name__ == "__main__":
31-
asyncio.run(main())
66+
examples = {
67+
"sms": send_sms,
68+
"mms": send_mms,
69+
"mms-multi": send_mms_multi,
70+
}
71+
choice = sys.argv[1] if len(sys.argv) > 1 else "sms"
72+
if choice not in examples:
73+
print(f"Usage: python send_sms.py [{' | '.join(examples)}]")
74+
sys.exit(1)
75+
asyncio.run(examples[choice]())

examples/whatsapp.py

Lines changed: 164 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,183 @@
1-
"""Send a WhatsApp template message using the Plivo Agent SDK.
2-
3-
Prerequisites:
4-
pip install plivo_agent
1+
"""
2+
WhatsApp Examples — All Message Types
53
6-
Environment variables:
7-
PLIVO_AUTH_ID -- Your Plivo auth ID
8-
PLIVO_AUTH_TOKEN -- Your Plivo auth token
4+
Demonstrates every WhatsApp message type supported by the SDK:
5+
text, media, template, interactive buttons, list, CTA URL, and location.
96
10-
Run:
11-
python whatsapp.py
7+
Usage:
8+
1. pip install plivo_agent
9+
2. Set PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN env vars
10+
3. python whatsapp.py [text | media | template | buttons | list | cta | location]
1211
"""
1312

1413
import asyncio
14+
import sys
1515

1616
from plivo_agent import AsyncClient
17-
from plivo_agent.messaging import Template
17+
from plivo_agent.messaging import InteractiveMessage, Location, Template
18+
19+
SRC = "+14155551234" # your WhatsApp Business number
20+
DST = "+14155559876" # recipient
21+
22+
23+
# --- Text message ---
24+
25+
async def send_text():
26+
async with AsyncClient() as client:
27+
response = await client.messages.create(
28+
src=SRC,
29+
dst=DST,
30+
type_="whatsapp",
31+
text="Hello from the Plivo Agent SDK!",
32+
)
33+
print("Text sent:", response["message_uuid"])
34+
1835

36+
# --- Media message (image with caption) ---
1937

20-
async def main():
38+
async def send_media():
2139
async with AsyncClient() as client:
22-
# Build a WhatsApp template with body parameters
23-
tpl = (
24-
Template("order_confirmation", language="en")
25-
.add_body_param("Alice")
26-
.add_body_param("ORD-42")
27-
.add_body_currency("$12.99", "USD", 12990)
28-
.add_body_datetime("2026-03-07T10:30:00Z")
29-
.add_button_param("url", 0, "https://example.com/track/ORD-42")
30-
.build()
40+
response = await client.messages.create(
41+
src=SRC,
42+
dst=DST,
43+
type_="whatsapp",
44+
text="Here is your receipt.",
45+
media_urls=["https://media.plivo.com/demo/image-sample.jpg"],
3146
)
47+
print("Media sent:", response["message_uuid"])
48+
3249

50+
# --- Template message (with body params, currency, datetime, button) ---
51+
52+
async def send_template():
53+
tpl = (
54+
Template("order_confirmation", language="en")
55+
.add_header_media("https://media.plivo.com/demo/banner.jpg")
56+
.add_body_param("Alice")
57+
.add_body_param("ORD-42")
58+
.add_body_currency("$12.99", "USD", 12990)
59+
.add_body_datetime("2026-03-07T10:30:00Z")
60+
.add_button_param("url", 0, "https://example.com/track/ORD-42")
61+
.build()
62+
)
63+
async with AsyncClient() as client:
3364
response = await client.messages.create(
34-
src="+14155551234",
35-
dst="+14155559876",
65+
src=SRC,
66+
dst=DST,
3667
type_="whatsapp",
3768
template=tpl,
3869
)
39-
print("Message UUID:", response["message_uuid"])
70+
print("Template sent:", response["message_uuid"])
71+
72+
73+
# --- Interactive: quick reply buttons ---
74+
75+
async def send_buttons():
76+
interactive = InteractiveMessage.button(
77+
body_text="How would you rate your experience?",
78+
buttons=[
79+
{"id": "great", "title": "Great"},
80+
{"id": "okay", "title": "Okay"},
81+
{"id": "poor", "title": "Poor"},
82+
],
83+
header={"type": "text", "text": "Feedback"},
84+
footer_text="Powered by Plivo",
85+
)
86+
async with AsyncClient() as client:
87+
response = await client.messages.create(
88+
src=SRC,
89+
dst=DST,
90+
type_="whatsapp",
91+
interactive=interactive,
92+
)
93+
print("Buttons sent:", response["message_uuid"])
94+
95+
96+
# --- Interactive: list message ---
97+
98+
async def send_list():
99+
interactive = InteractiveMessage.list(
100+
body_text="Browse our menu and pick your favorite.",
101+
button_text="View Menu",
102+
sections=[
103+
{
104+
"title": "Pizza",
105+
"rows": [
106+
{"id": "margherita", "title": "Margherita", "description": "$10"},
107+
{"id": "pepperoni", "title": "Pepperoni", "description": "$12"},
108+
],
109+
},
110+
{
111+
"title": "Sides",
112+
"rows": [
113+
{"id": "fries", "title": "Fries", "description": "$4"},
114+
{"id": "salad", "title": "Garden Salad", "description": "$6"},
115+
],
116+
},
117+
],
118+
header_text="Mario's Pizza",
119+
footer_text="Prices include tax",
120+
)
121+
async with AsyncClient() as client:
122+
response = await client.messages.create(
123+
src=SRC,
124+
dst=DST,
125+
type_="whatsapp",
126+
interactive=interactive,
127+
)
128+
print("List sent:", response["message_uuid"])
129+
130+
131+
# --- Interactive: CTA URL ---
132+
133+
async def send_cta():
134+
interactive = InteractiveMessage.cta_url(
135+
body_text="Track your order in real time.",
136+
button_title="Track Order",
137+
url="https://example.com/track/ORD-42",
138+
footer_text="Powered by Plivo",
139+
)
140+
async with AsyncClient() as client:
141+
response = await client.messages.create(
142+
src=SRC,
143+
dst=DST,
144+
type_="whatsapp",
145+
interactive=interactive,
146+
)
147+
print("CTA sent:", response["message_uuid"])
148+
149+
150+
# --- Location message ---
151+
152+
async def send_location():
153+
location = Location.build(
154+
37.7749,
155+
-122.4194,
156+
name="Plivo HQ",
157+
address="201 Spear St, San Francisco, CA 94105",
158+
)
159+
async with AsyncClient() as client:
160+
response = await client.messages.create(
161+
src=SRC,
162+
dst=DST,
163+
type_="whatsapp",
164+
location=location,
165+
)
166+
print("Location sent:", response["message_uuid"])
40167

41168

42169
if __name__ == "__main__":
43-
asyncio.run(main())
170+
examples = {
171+
"text": send_text,
172+
"media": send_media,
173+
"template": send_template,
174+
"buttons": send_buttons,
175+
"list": send_list,
176+
"cta": send_cta,
177+
"location": send_location,
178+
}
179+
choice = sys.argv[1] if len(sys.argv) > 1 else "text"
180+
if choice not in examples:
181+
print(f"Usage: python whatsapp.py [{' | '.join(examples)}]")
182+
sys.exit(1)
183+
asyncio.run(examples[choice]())

src/plivo_agent/agent/session.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,17 @@ def play(self, file_path: str, *, allow_interruption: bool = True) -> None:
136136
msg["allow_interruption"] = False
137137
self._enqueue(msg)
138138

139-
def transfer(
139+
def transfer_to_number(
140140
self,
141141
destination: str | list[str],
142142
*,
143143
dial_mode: str = "parallel",
144144
timeout: int = 30,
145145
) -> None:
146-
"""Transfer the call to one or more destinations.
146+
"""Transfer the call to one or more phone numbers.
147147
148148
Args:
149-
destination: Phone number or list of numbers.
149+
destination: Phone number or list of numbers (E.164 format).
150150
dial_mode: "parallel" (ring all at once, first to answer wins)
151151
or "sequential" (try each in order).
152152
timeout: Ring timeout per destination in seconds.
@@ -160,6 +160,33 @@ def transfer(
160160
"timeout": timeout,
161161
})
162162

163+
def transfer_to_sip(
164+
self,
165+
sip_uri: str,
166+
*,
167+
sip_headers: dict[str, str] | None = None,
168+
timeout: int = 30,
169+
) -> None:
170+
"""Transfer the call to a SIP endpoint via Plivo Dial XML.
171+
172+
Plivo uses ``<Dial><User>sip_uri</User></Dial>`` internally.
173+
174+
Args:
175+
sip_uri: SIP URI (e.g. ``"sip:agent@phone.plivo.com"``).
176+
sip_headers: Optional custom SIP headers (alphanumeric keys,
177+
max 24 chars each).
178+
timeout: Ring timeout in seconds.
179+
"""
180+
msg: dict = {
181+
"type": "agent_session.transfer",
182+
"destination": [sip_uri],
183+
"sip": True,
184+
"timeout": timeout,
185+
}
186+
if sip_headers:
187+
msg["sip_headers"] = sip_headers
188+
self._enqueue(msg)
189+
163190
def hangup(self) -> None:
164191
"""End the call."""
165192
self._enqueue({"type": "agent_session.hangup"})

0 commit comments

Comments
 (0)