From 86604c235364572a1dd91143640e31067bb1cf50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Mon, 19 Aug 2024 18:11:15 -0700 Subject: [PATCH] examples(studypal): use aiohttp instead of requests --- CHANGELOG.md | 2 + examples/README.md | 1 + examples/studypal/README.md | 9 +- .../studypal/{.env.example => env.example} | 0 examples/studypal/requirements.txt | 13 +-- examples/studypal/studypal.py | 99 +++++++++++-------- 6 files changed, 65 insertions(+), 59 deletions(-) rename examples/studypal/{.env.example => env.example} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f4417350..d30faecd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Other +- Added `studypal` example (from to the Cartesia folks!). + - Most examples now use Cartesia. - Added examples `foundational/19a-tools-anthropic.py`, diff --git a/examples/README.md b/examples/README.md index 5fbba1561..b1cd62c5e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -41,6 +41,7 @@ Next, follow the steps in the README for each demo. | [Patient intake](patient-intake) | A chatbot that can call functions in response to user input. | Deepgram, ElevenLabs, OpenAI, Daily, Daily Prebuilt UI | | [Dialin Chatbot](dialin-chatbot) | A chatbot that connects to an incoming phone call from Daily or Twilio. | Deepgram, ElevenLabs, OpenAI, Daily, Twilio | | [Twilio Chatbot](twilio-chatbot) | A chatbot that connects to an incoming phone call from Twilio. | Deepgram, ElevenLabs, OpenAI, Daily, Twilio | +| [studypal](studypal) | A chatbot to have a conversation about any article on the web | | > [!IMPORTANT] > These example projects use Daily as a WebRTC transport and can be joined using their hosted Prebuilt UI. diff --git a/examples/studypal/README.md b/examples/studypal/README.md index 3e06ce190..b8f3c48e4 100644 --- a/examples/studypal/README.md +++ b/examples/studypal/README.md @@ -1,12 +1,13 @@ # studypal + ### Have a conversation about any article on the web -studypal is a fast conversational ai built using [Daily](https://www.daily.co/) for real-time media transport and [Cartesia](https://cartesia.ai) for text-to-speech. Everything is orchestrated together (VAD -> STT -> LLM -> TTS) using [Pipecat](https://www.pipecat.ai/). +studypal is a fast conversational AI built using [Daily](https://www.daily.co/) for real-time media transport and [Cartesia](https://cartesia.ai) for text-to-speech. Everything is orchestrated together (VAD -> STT -> LLM -> TTS) using [Pipecat](https://www.pipecat.ai/). ## Setup 1. Clone the repository -2. Copy `.env.example` to a `.env` file and add API keys -3. Install the required packages: `pip install -r requirements.txt` -4. Run `python3 studypal.py` from your command line. +2. Copy `env.example` to a `.env` file and add API keys +3. Install the required packages: `pip install -r requirements.txt` +4. Run `python3 studypal.py` from your command line. 5. While the app is running, go to the `https://.daily.co/` set in `DAILY_SAMPLE_ROOM_URL` and talk to studypal! diff --git a/examples/studypal/.env.example b/examples/studypal/env.example similarity index 100% rename from examples/studypal/.env.example rename to examples/studypal/env.example diff --git a/examples/studypal/requirements.txt b/examples/studypal/requirements.txt index b9c8f42d8..210c59ebc 100644 --- a/examples/studypal/requirements.txt +++ b/examples/studypal/requirements.txt @@ -1,16 +1,5 @@ -aiohttp==3.9.5 beautifulsoup4==4.12.2 PyPDF2==3.0.1 tiktoken==0.7.0 -pipecat==0.3.0 -pipecat-ai==0.0.39 +pipecat-ai[daily,cartesia,openai,silero]==0.0.39 python-dotenv==1.0.1 -loguru==0.7.2 -requests==2.32.3 -pydantic==2.8.2 -httpx==0.27.0 -openai==1.27.0 -websockets==12.0 -daily-python==0.10.1 -torch==2.2.2 -torchaudio==2.2.2 \ No newline at end of file diff --git a/examples/studypal/studypal.py b/examples/studypal/studypal.py index 78c4a2654..67b02a9fb 100644 --- a/examples/studypal/studypal.py +++ b/examples/studypal/studypal.py @@ -2,8 +2,8 @@ import aiohttp import asyncio import os import sys -import requests import io + from bs4 import BeautifulSoup from PyPDF2 import PdfReader import tiktoken @@ -26,17 +26,15 @@ from loguru import logger from dotenv import load_dotenv load_dotenv(override=True) -from openai import OpenAI -client = OpenAI() - -# Run this script directly from your command line. -# This project was adapted from https://github.com/pipecat-ai/pipecat/blob/main/examples/foundational/07d-interruptible-cartesia.py +# Run this script directly from your command line. +# This project was adapted from +# https://github.com/pipecat-ai/pipecat/blob/main/examples/foundational/07d-interruptible-cartesia.py logger.remove(0) logger.add(sys.stderr, level="DEBUG") -# Count number of tokens used in model and truncate the content +# Count number of tokens used in model and truncate the content def truncate_content(content, model_name): encoding = tiktoken.encoding_for_model(model_name) tokens = encoding.encode(content) @@ -47,50 +45,66 @@ def truncate_content(content, model_name): return encoding.decode(truncated_tokens) return content -# Main function to extract content from url -def get_article_content(url): +# Main function to extract content from url + + +async def get_article_content(url: str, aiohttp_session: aiohttp.ClientSession): if 'arxiv.org' in url: - return get_arxiv_content(url) + return await get_arxiv_content(url, aiohttp_session) else: - return get_wikipedia_content(url) + return await get_wikipedia_content(url, aiohttp_session) -# Helper function to extract content from Wikipedia url (this is technically agnostic to URL type but will work best with Wikipedia articles) -def get_wikipedia_content(url): - response = requests.get(url) - soup = BeautifulSoup(response.content, 'html.parser') - - content = soup.find('div', {'class': 'mw-parser-output'}) - - if content: - return content.get_text() - else: - return "Failed to extract Wikipedia article content." +# Helper function to extract content from Wikipedia url (this is +# technically agnostic to URL type but will work best with Wikipedia +# articles) -# Helper function to extract content from arXiv url -def get_arxiv_content(url): + +async def get_wikipedia_content(url: str, aiohttp_session: aiohttp.ClientSession): + async with aiohttp_session.get(url) as response: + if response.status != 200: + return "Failed to download Wikipedia article." + + text = await response.text() + soup = BeautifulSoup(text, 'html.parser') + + content = soup.find('div', {'class': 'mw-parser-output'}) + + if content: + return content.get_text() + else: + return "Failed to extract Wikipedia article content." + +# Helper function to extract content from arXiv url + + +async def get_arxiv_content(url: str, aiohttp_session: aiohttp.ClientSession): if '/abs/' in url: url = url.replace('/abs/', '/pdf/') if not url.endswith('.pdf'): url += '.pdf' - response = requests.get(url) - if response.status_code == 200: - pdf_file = io.BytesIO(response.content) + async with aiohttp_session.get(url) as response: + if response.status != 200: + return "Failed to download arXiv PDF." + + content = await response.read() + pdf_file = io.BytesIO(content) pdf_reader = PdfReader(pdf_file) text = "" for page in pdf_reader.pages: text += page.extract_text() return text - else: - return "Failed to download arXiv PDF." -# This is the main function that handles STT -> LLM -> TTS +# This is the main function that handles STT -> LLM -> TTS + + async def main(): url = input("Enter the URL of the article you would like to talk about: ") - article_content = get_article_content(url) - article_content = truncate_content(article_content, model_name="gpt-4o-mini") async with aiohttp.ClientSession() as session: + article_content = await get_article_content(url, session) + article_content = truncate_content(article_content, model_name="gpt-4o-mini") + (room_url, token) = await configure(session) transport = DailyTransport( @@ -108,25 +122,22 @@ async def main(): tts = CartesiaTTSService( api_key=os.getenv("CARTESIA_API_KEY"), - voice_id="4d2fd738-3b3d-4368-957a-bb4805275bd9", # British Narration Lady: 4d2fd738-3b3d-4368-957a-bb4805275bd9 - sample_rate=44100, + voice_id=os.getenv("CARTESIA_VOICE_ID", "4d2fd738-3b3d-4368-957a-bb4805275bd9"), + # British Narration Lady: 4d2fd738-3b3d-4368-957a-bb4805275bd9 + sample_rate=44100, ) llm = OpenAILLMService( api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o-mini") - messages = [ - { - "role": "system", - "content": f"""You are an AI study partner. You have been given the following article content: + messages = [{ + "role": "system", "content": f"""You are an AI study partner. You have been given the following article content: {article_content} Your task is to help the user understand and learn from this article in 2 sentences. THESE RESPONSES SHOULD BE ONLY MAX 2 SENTENCES. THIS INSTRUCTION IS VERY IMPORTANT. RESPONSES SHOULDN'T BE LONG. -""", - }, - ] +""", }, ] tma_in = LLMUserResponseAggregator(messages) tma_out = LLMAssistantResponseAggregator(messages) @@ -146,7 +157,9 @@ Your task is to help the user understand and learn from this article in 2 senten async def on_first_participant_joined(transport, participant): transport.capture_participant_transcription(participant["id"]) messages.append( - {"role": "system", "content": "Hello! I'm ready to discuss the article with you. What would you like to learn about?"}) + { + "role": "system", + "content": "Hello! I'm ready to discuss the article with you. What would you like to learn about?"}) await task.queue_frames([LLMMessagesFrame(messages)]) runner = PipelineRunner() @@ -154,4 +167,4 @@ Your task is to help the user understand and learn from this article in 2 senten await runner.run(task) if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main())