
Partager:
Liz Acosta est conseillère en développement chez Vonage. Si son parcours professionnel, d'étudiante en cinéma à spécialiste du marketing, puis ingénieur et enfin défenseur des développeurs, peut sembler peu conventionnel, il est tout à fait typique des relations avec les développeurs ! Liz aime les pizzas, les plantes, les carlins et Python.
Renvoyer un appel téléphonique via Voice Proxy avec Python et Starlette
Temps de lecture : 12 minutes
Utiliser un proxy vocal pour préserver la confidentialité de ses numéros personnels
Vous connaissez bien la situation : vous avez commandé un repas à livrer, mais le code d'accès à votre immeuble ne fonctionne pas, et le livreur doit donc vous appeler pour finaliser la commande. Ni vous ni le livreur ne souhaitez communiquer votre numéro de téléphone personnel à l'autre. C'est une question de confidentialité, mais aussi une question pratique. Les gens sont plus enclins à répondre à un numéro dont l'indicatif régional leur est familier qu'à un numéro inconnu.
Le renvoi d'appel permet d'acheminer les appels via un numéro intermédiaire, ce qui permet de préserver la confidentialité des numéros de téléphone personnels. Ce modèle est appelé proxy vocal.
Dans ce tutoriel, nous allons utiliser Starlette et Vonage pour créer un proxy vocal en Python. Starlette gère la logique serveur et Vonage s'occupe de la téléphonie. Poursuivez votre lecture pour en savoir plus sur chaque composant avant de terminer le tutoriel, ou passez directement au fichier README pour mettre le code en œuvre.
Qu'est-ce que Starlette ?
Si vous êtes développeur Python, vous connaissez sans doute certains des frameworks les plus populaires, de Flask à Django en passant par FastAPI. Chacun d’entre eux apporte une structure et des choix de conception en matière de développement HTTP, mais ils partagent tous, à la base, un modèle fondamental consistant à acheminer les requêtes vers des gestionnaires et à renvoyer des réponses. Starlette est le framework léger ASGI qui alimente FastAPI et qui peut être utilisé directement lorsque vous avez besoin d’un framework web minimaliste mais entièrement asynchrone, sans toutes les fonctionnalités supplémentaires.
Qu'est-ce que l'ASGI et comment Starlette en tire-t-elle parti ?
L'interface ASGI (Asynchronous Server Gateway Interface) est une spécification (ou convention d'appel) permettant aux serveurs web de transmettre des requêtes à des frameworks Python prenant en charge l'asynchronisme. On peut considérer l'ASGI comme un contrat définissant la manière dont les serveurs web interagissent avec les applications Python. La signature de l'interface se présente comme suit :
async def app(scope, receive, send):
pass Les paramètres de cette interface sont définis comme suit :
scopeIl s'agit d'un dictionnaire de métadonnées décrivant la requête reçue. Ce paramètre permet de déterminer le type de connexion à établir afin que la communication entre un serveur et une application Python puisse s'effectuer correctement.
receiveest une fonction permettant d'accepter et de traiter les événements provenant d'un client. Elle renvoie un dictionnaire représentant les événements qu'elle reçoit.
sendest une fonction qui renvoie des événements au client. C'est cette fonction qui est appelée pour générer et envoyer une réponse.
Voici donc à quoi ressemble un exemple d'application ASGI complète :
async def app(scope, receive, send):
# Determine the type of connection from the `scope` metadata
if scope["type"] == "http" and scope["method"] == "POST":
# You need the body data for `send()`so call `receive()`
body_event = await receive()
body = body_event["body"]
# Parse and use the body data
data = json.loads(body)
# Then send response
await send({
"type": "http.response.start",
"status": 200,
"headers": [[b"content-type", b"application/json"]],
})
await send({
"type": "http.response.body",
"body": json.dumps({"received": data}).encode(),
}) C'est ce modèle que Starlette abstrait pour vous éviter d'avoir à écrire chaque contrat à la main. Sans Starlette, votre application web devrait gérer le routage à l'aide de plusieurs niveaux d'instructions if/else :
async def app(scope, receive, send):
if scope["type"] == "http":
path = scope["path"]
method = scope["method"]
# Manual routing logic
if path == "/users" and method == "GET":
await send(
{
"type": "http.response.start",
"status": 200,
"headers": [[b"content-type", b"application/json"]],
}
)
await send(
{
"type": "http.response.body",
"body": b'[{"id": 1, "name": "Alice"}]',
}
)
elif path == "/users/1" and method == "GET":
await send(
{
"type": "http.response.start",
"status": 200,
"headers": [[b"content-type", b"application/json"]],
}
)
await send(
{
"type": "http.response.body",
"body": b'{"id": 1, "name": "Alice"}',
}
)
else:
await send(
{
"type": "http.response.start",
"status": 404,
"headers": [[b"content-type", b"text/plain"]],
}
)
await send(
{
"type": "http.response.body",
"body": b"Not Found",
}
)Starlette dissimule toutes les redondances liées aux codes standard dans une API épurée :
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
async def list_users(request):
return JSONResponse([{"id": 1, "name": "Alice"}])
async def get_user(request):
user_id = request.path_params["user_id"]
return JSONResponse({"id": user_id, "name": "Alice"})
routes = [
Route("/users", list_users, methods=["GET"]),
Route("/users/{user_id}", get_user, methods=["GET"]),
]
app = Starlette(routes=routes)Le code ci-dessus peut être décomposé en plusieurs éléments constitutifs :
L'
Routeobjet associe un chemin d’URL et une méthode HTTP à une fonction. Dans cet exemple,"/users"et lesGETméthode sont associées à lalist_usersfonction.
L'
Requestobjet que les deuxlist_usersetget_userreçoivent en paramètre contient les métadonnées relatives à la connexion entrante (la portée) ainsi que la fonction qui traite le message reçu du client (la fonction d’appel de réception).
L'
JSONResponseobjet traite le message qui sera renvoyé au client (la fonction d'envoi).
async defest la syntaxe Python permettant de définir une fonction coroutine, ce qui permet un fonctionnement asynchrone sans blocage.
Comme Starlette gère le routage et le traitement des requêtes/réponses sans intégrer de couches supplémentaires telles que l'ORM, l'authentification ou les modèles de page, il constitue la solution idéale pour un projet ciblé comme ce proxy vocal.
Vous pouvez consulter le fichier README du projet README pour découvrir un exemple d'application Starlette minimale. Pour en savoir plus sur Starlette, consultez la documentation.
Qu'est-ce qu'un proxy vocal et comment fonctionne-t-il ?
Un proxy vocal (également appelé « masquage d'appel ») est une méthode permettant d'acheminer des appels téléphoniques vers leur destinataire via un numéro de téléphone intermédiaire. Le numéro de téléphone final est masqué derrière le numéro intermédiaire, ce qui garantit la confidentialité de ce dernier. Chaque participant reçoit un numéro temporaire lui permettant de communiquer directement en temps réel sans divulguer le numéro personnel de l'une ou l'autre des parties. Une fois l'appel terminé, le numéro intermédiaire est libéré et peut être réutilisé pour un autre proxy.
Lorsqu'une personne appelle un numéro de proxy vocal, l'appel est pris en charge par une application qui le transfère ensuite vers le numéro réel du destinataire sans jamais le révéler à l'appelant. De même, la personne qui reçoit l'appel ne voit que le numéro intermédiaire et non le numéro réel de l'appelant, ce qui permet de préserver l'anonymat mutuel des numéros de téléphone.
Le masquage des appels s'avère utile dans les scénarios de commerce entre particuliers, comme celui décrit précédemment dans ce tutoriel, et peut aider à suivre et à contrôler les appels et les transactions. Avec Vonage Voice et le SDK Python de Vonage, vous pouvez mettre en œuvre le masquage d'appel grâce à une API qui dissimule toute la complexité de l'infrastructure téléphonique.
Objets de contrôle des appels et de la voix Vonage (NCCO)
L' Voice API Vonage vous permet de mettre en relation des personnes partout dans le monde et d’automatiser les interactions vocales qui enrichissent l’expérience utilisateur de votre produit. Vous pouvez gérer les appels vocaux par programmation à l’aide de deux modèles complémentaires :
L'une d'entre elles est lancée par votre application, à l'aide d'appels à l'API REST ou de SDK.
L'autre est demandé à votre application par Vonage, à l'aide de webhooks et d'objets de contrôle des appels (NCCO).
Cette double approche permet une gestion flexible des appels afin de mettre en œuvre divers flux d'appels et cas d'utilisation de la Voice programmables, allant de simples notifications à des flux sophistiqués de centres de contact, en passant par l'intégration de l'IA.
Un NCCO est une structure basée sur JSON qui définit ce qui doit se passer pendant un appel. Vous la renvoyez en réponse à la requête de webhook de Vonage lorsque l'appel est établi.
Parmi les actions courantes menées par la NCCO, on peut citer :
talk: Convertir le texte en parole et le faire écouter à l'appelantstream: Lire un fichier audiorecord: Enregistrer l'audio de l'appelconnect: Ajouter un autre participant à la conversation
Pour cette application, nous allons utiliser la connect action pour acheminer l'appel vers différents points de terminaison. Un point de terminaison peut être un autre numéro de téléphone, un WebSocket ou un point de terminaison SIP. Dans ce tutoriel, nous allons nous connecter à un numéro de téléphone afin de masquer les numéros d'origine des deux côtés de l'appel.
Consultez l' Voice API et documentation du NCCO pour en savoir plus.
Creusement de tunnels
Comme nous allons exécuter l'application Starlette en local, nous devons trouver un moyen de la rendre accessible à Vonage via une URL publique. C'est là qu'intervient le tunneling. Le tunneling permet d'exposer les serveurs locaux à l'Internet public via des URL publiques temporaires ou statiques. ngrok est une plateforme logicielle qui fournit ce service.
Pour en savoir plus sur l'utilisation de ngrok avec Vonage, consultez la documentation.
Tutoriel : Transférer un appel téléphonique via un proxy vocal avec Python, Starlette et Vonage
Dans ce tutoriel, nous allons utiliser Starlette pour définir un webhook que l' Voice API pour envoyer une requête, puis créer un proxy vocal à l’aide d’une action NCCO connect . Le déroulement est le suivant :
L'utilisateur final compose un numéro virtuel Vonage.
Vonage envoie une requête au webhook défini sur le serveur Starlette.
Le serveur renvoie une
connectaction NCCO.Vonage transfère l'appel vers le numéro de destination configuré.
Aucune des deux parties ne voit les numéros de téléphone réels : elles ne voient que le numéro virtuel.
Consultez le code complet de ce tutoriel sur GitHub et suivez le fichier fichier README pour le faire fonctionner. Nous aborderons ci-dessous les parties importantes du code.
Conditions préalables
Python 3.10 ou version ultérieure
A Installation de la CLI Vonage (facultative)
Configuration du tutoriel
Créer une application Vonage Voice
Vous devrez créer une application Vonage Voice à utiliser dans le cadre de ce tutoriel. Pour ce faire, deux possibilités s'offrent à vous :
Dans tous les cas, veillez à sélectionner et à associer un numéro compatible avec la fonction vocale à utiliser dans le cadre de ce tutoriel.
Configurez votre environnement de développement
Suivez les instructions figurant dans le fichier fichier README pour créer un environnement virtuel et installer les dépendances du projet :
python -m venv venv
source venv/bin/activate Et puis :
pip install -r requirements.txtPour en savoir plus sur les environnements virtuels, consultez notre article de blog.
Configurez vos variables d'environnement
Copiez et renommez le env.template fichier :
cp .env.template .envIndiquez ensuite dans le fichier votre numéro virtuel Vonage ainsi que le numéro de l'appareil que vous utiliserez pour tester l'application de proxy vocal. Il s'agit du numéro auquel Vonage se connectera en utilisant un numéro virtuel afin de masquer le numéro d'origine :
VONAGE_VIRTUAL_NUMBER=your-vonage-virtual-number
TEST_TO_NUMBER=your-test-number-for-voice-proxyLancer un tunnel ngrok
Pour rendre votre application accessible à Vonage, utilisez ngrok afin de créer un tunnel vers une URL publique sur le port 8000:
ngrok http 8000Pour en savoir plus sur l'utilisation de ngrok avec les applications Vonage, consultez notre article de blog.
Définissez et configurez vos webhooks avec Starlette et Vonage
L'application Starlette se trouve dans le server.py fichier. Grâce à l’abstraction de tout le code standard d’ASGI, Starlette nous permet de créer un proxy vocal en moins de 100 lignes de code, tandis que Vonage gère la complexité de la téléphonie via une API s’appuyant sur un SDK basé sur Pydantic.
Nous définissons ici une VoiceProxy classe qui étend la HTTPEndpoint . Cette classe nous permet de configurer des gestionnaires HTTP sous forme de méthodes d’une même classe, plutôt que d’écrire une fonction unique nécessitant de vérifier manuellement la méthode de requête.
class VoiceProxy(HTTPEndpoint):
"""A Starlette application that defines webhook endpoints
for Vonage to make a request to when the Vonage virtual number is called"""
async def get(self, request):
from_number = request.query_params.get("from")
to_number_endpoint = PhoneEndpoint(number=TEST_TO_NUMBER)
logger.info(
f"Received call to: {VONAGE_VIRTUAL_NUMBER} from: {from_number} -- creating voice proxy to: {TEST_TO_NUMBER}"
)
greeting = (
"Hello! Voice proxy in process. Check logs for details. "
"You should notice that the from number uses the intermediary virtual number."
)
ncco: list[NccoAction] = [
Talk(text=greeting),
Connect(endpoint=[to_number_endpoint], from_=VONAGE_VIRTUAL_NUMBER),
]
return JSONResponse(
[action.model_dump(by_alias=True, exclude_none=True) for action in ncco]
)
async def post(self, request):
event = await request.json()
uuid = event.get("uuid")
from_number = event.get("from")
to_number = event.get("to")
direction = event.get("direction")
status = event.get("status")
logger.info(
f"Voice proxy for uuid: {uuid} -- {direction} TO number: {to_number} FROM number: {from_number} -- STATUS: {status}"
)
return PlainTextResponse()La GET méthode est chargée de renvoyer l'action NCCO qui crée le masquage d'appel avec connect. Dans ce cas précis, nous avons connect configuré le système pour qu’il utilise un point de terminaison de numéro de téléphone. Il s’agit du numéro qui recevra l’appel masqué provenant du from , qui, dans ce scénario, correspond à votre numéro virtuel Vonage :
async def get(self, request):
from_number = request.query_params.get("from")
to_number_endpoint = PhoneEndpoint(number=TEST_TO_NUMBER)
logger.info(
f"Received call to: {VONAGE_VIRTUAL_NUMBER} from: {from_number} -- creating voice proxy to: {TEST_TO_NUMBER}"
)
greeting = (
"Hello! Voice proxy in process. Check logs for details. "
"You should notice that the from number uses the intermediary virtual number."
)
ncco: list[NccoAction] = [
Talk(text=greeting),
Connect(endpoint=[to_number_endpoint], from_=VONAGE_VIRTUAL_NUMBER),
]
return JSONResponse(
[action.model_dump(by_alias=True, exclude_none=True) for action in ncco]
)Cette méthode correspond au webhook auquel Vonage enverra une requête lorsqu'un appel sera passé vers votre numéro virtuel.
Les applications Vonage Voice peuvent également être configurées avec un webhook d'événement. Il s'agit d'un point de terminaison vers lequel les requêtes sont envoyées lorsque des événements Voice se produisent. Dans notre application de proxy vocal, nous utilisons la post méthode pour consigner notre workflow Voice et illustrer le masquage d'appel en action :
async def post(self, request):
event = await request.json()
uuid = event.get("uuid")
from_number = event.get("from")
to_number = event.get("to")
direction = event.get("direction")
status = event.get("status")
logger.info(
f"Voice proxy for uuid: {uuid} -- {direction} TO number: {to_number} FROM number: {from_number} -- STATUS: {status}"
)
return PlainTextResponse()Enfin, nous enregistrons nos itinéraires et les transmettons à l'application Starlette :
routes = [
Route("/", VoiceProxy),
]
app = Starlette(routes=routes)Lancez l'application à l'aide d'uvicorn :
uvicorn server:app --reloadConfigurez vos webhooks avec Vonage
Pour que Vonage puisse accéder à votre application, utilisez l'URL publique générée par ngrok. Modifiez votre application Voice dans le tableau de bord des développeurs Vonage de manière à ce que l' URL de réponse et URL d'événement pointent vers l'URL fournie par ngrok. Cela ressemblera à quelque chose comme ceci :
https://<your-ngrok-url>/Veillez à définir les méthodes de requête appropriées pour chacune d'entre elles :
GETpour l' URL de la réponsePOSTpour l' URL de l'événement
Essayez-le !
Pour tester l'application, vous aurez besoin de deux appareils capables de passer et de recevoir des appels téléphoniques.
Sur l'appareil associé au numéro de téléphone qui n'est pas configuré pour être utilisé avec l’application, appelez votre numéro virtuel Vonage.
Vous devriez entendre un message d'accueil généré par synthèse vocale, suivi d'une sonnerie, pendant que Vonage appelle le numéro de test que vous avez indiqué dans l'application.
L'appareil associé au numéro de test devrait sonner, mais au lieu que l'appel provienne de l'appareil avec lequel vous avez passé l'appel initial, le numéro affiché devrait être votre numéro virtuel. En d'autres termes, le numéro virtuel Vonage masque votre numéro de téléphone privé.
Vous pouvez consulter les journaux d'événements d'appel pour en avoir la confirmation :
INFO:server:Voice proxy for uuid: 90f693... -- inbound TO number: 12015551497 FROM number: 18055556289 -- STATUS: answered
INFO: 216.147.2.112:0 - "POST / HTTP/1.1" 200 OK
INFO:server:Voice proxy for uuid: 115e4... -- outbound TO number: 14155555833 FROM number: 12015551497 -- STATUS: ringing
INFO: 216.147.2.112:0 - "POST / HTTP/1.1" 200 OK
INFO:server:Voice proxy for uuid: 115e4... -- outbound TO number: 14155555833 FROM number: 12015551497 -- STATUS: started
INFO: 216.147.2.13:0 - "POST / HTTP/1.1" 200 OK
INFO:server:Voice proxy for uuid: 90f693... -- inbound TO number: 12016441497 FROM number: 18055556289 -- STATUS: completed
INFO: 216.147.2.101:0 - "POST / HTTP/1.1" 200 OK
INFO:server:Voice proxy for uuid: 115e4... -- outbound TO number: 14155555833 FROM number: 12015551497 -- STATUS: completed
INFO: 216.147.2.101:0 - "POST / HTTP/1.1" 200 OK
INFO:server:Voice proxy for uuid: 115e4... -- outbound TO number: 14155555833 FROM number: 12015551497 -- STATUS: cancelled Dans cet exemple :
12015551497est le numéro de masquage intermédiaire.18055556289C'est le numéro qui passe l'appel.14155555833est le numéro auquel on se connecte à l'aide du numéro de masquage.
Grâce à un serveur proxy, les appelants peuvent être mis en relation sans se dévoiler mutuellement leurs numéros de téléphone. Cela permet d'offrir un service client de type « pair à pair » tout en préservant la vie privée des clients.
En résumé
Dans ce tutoriel, vous avez développé une application de proxy vocal en Python à l'aide de Starlette et de la Voice API de Vonage. Un proxy vocal — également appelé « masquage d'appel » — achemine les appels via un numéro virtuel intermédiaire, de sorte qu'aucune des deux parties ne puisse jamais voir le numéro de téléphone réel de l'autre. Vous avez découvert comment la boîte à outils ASGI légère de Starlette simplifie la gestion des webhooks, comment les objets de contrôle d’appel (NCCO) de Vonage définissent le comportement des appels, et comment ngrok expose votre serveur local à Vonage pendant le développement. À la fin, vous disposiez d’une application fonctionnelle qui relie deux appelants via un numéro virtuel Vonage tout en préservant la confidentialité des deux numéros de téléphone — un modèle qui trouve des applications concrètes dans les plateformes de livraison, de covoiturage, de marché en ligne et de service client. Pour en savoir plus sur la manière d’utiliser Vonage afin d’intégrer la voix dans vos applications et services, consultez nos parcours d’apprentissage sur la voix ou regardez la Video ci-dessous qui explique comment automatiser les appels grâce à l’IA.
Lectures complémentaires et références
Starlette: Starlette est un framework/kit d'outils ASGI léger, idéal pour créer des services web asynchrones en Python.
Démonstration d'un proxy vocal / masquage d'appel à l'aide de Python, Starlette et Vonage Voice: Une application de démonstration expliquant comment créer un service de proxy vocal (masquage d'appel) à l'aide de la Voice API Vonage et de Starlette, un framework ASGI Python léger.
Créer des agents vocaux IA en temps réel avec Pipecat, AgentCore et Vonage: Découvrez comment créer un agent vocal IA avec Vonage, Pipecat, AWS Nova Sonic et AgentCore pour des conversations téléphoniques en temps réel.
Créer un système de conférence téléphonique avec Python: Découvrez comment connecter plusieurs participants à une même conférence téléphonique en Python à l'aide de la Voice API de Vonage et du framework Flask.
Vous avez une question ou souhaitez partager ce que vous construisez ?
S'abonner à la Bulletin d'information du développeur
Suivez-nous sur X (anciennement Twitter) pour les mises à jour
Regardez les tutoriels sur notre chaîne YouTube
Connectez-vous avec nous sur la page Vonage Developer sur LinkedIn
Aidez-nous à améliorer l'expérience des développeurs en remplissant notre formulaire de commentaires « Voice of the Developer »
Restez connecté et tenez-vous au courant des dernières nouvelles, astuces et événements concernant les développeurs.
Partager:
Liz Acosta est conseillère en développement chez Vonage. Si son parcours professionnel, d'étudiante en cinéma à spécialiste du marketing, puis ingénieur et enfin défenseur des développeurs, peut sembler peu conventionnel, il est tout à fait typique des relations avec les développeurs ! Liz aime les pizzas, les plantes, les carlins et Python.