From fc0f404d26ddb8f7cb589c2f84c2c3ef96885f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Wed, 26 Feb 2025 17:23:58 -0800 Subject: [PATCH] examples: add new 36-user-email-gathering.py --- CHANGELOG.md | 4 + .../foundational/36-user-email-gathering.py | 141 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 examples/foundational/36-user-email-gathering.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3006ed5ca..64f342dc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,6 +166,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Other +- Added a new example `examples/foundational/36-user-email-gathering.py` to show + how to gather user emails. The example uses's Cartesia's `` + tags and Rime `spell()` function to spell out the emails for confirmation. + - Update the `34-audio-recording.py` example to include an STT processor. - Added foundational example `35-voice-switching.py` showing how to use the new diff --git a/examples/foundational/36-user-email-gathering.py b/examples/foundational/36-user-email-gathering.py new file mode 100644 index 000000000..0e76826e3 --- /dev/null +++ b/examples/foundational/36-user-email-gathering.py @@ -0,0 +1,141 @@ +# +# Copyright (c) 2024–2025, Daily +# +# SPDX-License-Identifier: BSD 2-Clause License +# + +import asyncio +import os +import sys + +import aiohttp +from dotenv import load_dotenv +from loguru import logger +from openai.types.chat import ChatCompletionToolParam +from runner import configure + +from pipecat.audio.vad.silero import SileroVADAnalyzer +from pipecat.pipeline.pipeline import Pipeline +from pipecat.pipeline.runner import PipelineRunner +from pipecat.pipeline.task import PipelineParams, PipelineTask +from pipecat.services.cartesia import CartesiaTTSService +from pipecat.services.openai import OpenAILLMContext, OpenAILLMService +from pipecat.services.rime import RimeHttpTTSService +from pipecat.transports.services.daily import DailyParams, DailyTransport + +load_dotenv(override=True) + +logger.remove(0) +logger.add(sys.stderr, level="DEBUG") + + +async def store_user_emails(function_name, tool_call_id, args, llm, context, result_callback): + print(f"User emails: {args}") + + +async def main(): + async with aiohttp.ClientSession() as session: + (room_url, token) = await configure(session) + + transport = DailyTransport( + room_url, + token, + "Respond bot", + DailyParams( + audio_out_enabled=True, + transcription_enabled=True, + vad_enabled=True, + vad_analyzer=SileroVADAnalyzer(), + ), + ) + + # Cartesia offers a `` tags that we can use to ask the user + # to confirm the emails. + # (see https://docs.cartesia.ai/build-with-sonic/formatting-text-for-sonic/spelling-out-input-text) + tts = CartesiaTTSService( + api_key=os.getenv("CARTESIA_API_KEY"), + voice_id="71a7ad14-091c-4e8e-a314-022ece01c121", # British Reading Lady + aiohttp_session=session, + ) + + # Rime offers a function `spell()` that we can use to ask the user + # to confirm the emails. + # (see https://docs.rime.ai/api-reference/spell) + # tts = RimeHttpTTSService( + # api_key=os.getenv("RIME_API_KEY", ""), + # voice_id="eva", + # aiohttp_session=session, + # ) + + llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o") + # You can aslo register a function_name of None to get all functions + # sent to the same callback with an additional function_name parameter. + llm.register_function("store_user_emails", store_user_emails) + + tools = [ + ChatCompletionToolParam( + type="function", + function={ + "name": "store_user_emails", + "description": "Store user emails when confirmed", + "parameters": { + "type": "object", + "properties": { + "emails": { + "type": "array", + "description": "The list of user emails", + "items": {"type": "string"}, + }, + }, + "required": ["emails"], + }, + }, + ) + ] + messages = [ + { + "role": "system", + # Cartesia + "content": "You need to gather a valid email or emails from the user. Your output will be converted to audio so don't include special characters in your answers. If the user provides one or more email addresses confirm them with the user. Enclose all emails with tags, for example a@a.com.", + # Rime spell() + # "content": "You need to gather a valid email or emails from the user. Your output will be converted to audio so don't include special characters in your answers. If the user provides one or more email addresses confirm them with the user. Enclose all emails with spell(), for example spell(a@a.com).", + }, + ] + + context = OpenAILLMContext(messages, tools) + context_aggregator = llm.create_context_aggregator(context) + + pipeline = Pipeline( + [ + transport.input(), + context_aggregator.user(), + llm, + tts, + transport.output(), + context_aggregator.assistant(), + ] + ) + + task = PipelineTask( + pipeline, + params=PipelineParams( + allow_interruptions=True, + enable_metrics=True, + enable_usage_metrics=True, + report_only_initial_ttfb=True, + ), + ) + + @transport.event_handler("on_first_participant_joined") + async def on_first_participant_joined(transport, participant): + await transport.capture_participant_transcription(participant["id"]) + # Kick off the conversation. + await task.queue_frames([context_aggregator.user().get_context_frame()]) + + runner = PipelineRunner() + + await runner.run(task) + + +if __name__ == "__main__": + asyncio.run(main())