examples(studypal): use aiohttp instead of requests

This commit is contained in:
Aleix Conchillo Flaqué
2024-08-19 18:11:15 -07:00
parent 8f31a02938
commit 86604c2353
6 changed files with 65 additions and 59 deletions

View File

@@ -98,6 +98,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Other ### Other
- Added `studypal` example (from to the Cartesia folks!).
- Most examples now use Cartesia. - Most examples now use Cartesia.
- Added examples `foundational/19a-tools-anthropic.py`, - Added examples `foundational/19a-tools-anthropic.py`,

View File

@@ -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 | | [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 | | [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 | | [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] > [!IMPORTANT]
> These example projects use Daily as a WebRTC transport and can be joined using their hosted Prebuilt UI. > These example projects use Daily as a WebRTC transport and can be joined using their hosted Prebuilt UI.

View File

@@ -1,12 +1,13 @@
# studypal # studypal
### Have a conversation about any article on the web ### 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 ## Setup
1. Clone the repository 1. Clone the repository
2. Copy `.env.example` to a `.env` file and add API keys 2. Copy `env.example` to a `.env` file and add API keys
3. Install the required packages: `pip install -r requirements.txt` 3. Install the required packages: `pip install -r requirements.txt`
4. Run `python3 studypal.py` from your command line. 4. Run `python3 studypal.py` from your command line.
5. While the app is running, go to the `https://<yourdomain>.daily.co/<room_url>` set in `DAILY_SAMPLE_ROOM_URL` and talk to studypal! 5. While the app is running, go to the `https://<yourdomain>.daily.co/<room_url>` set in `DAILY_SAMPLE_ROOM_URL` and talk to studypal!

View File

@@ -1,16 +1,5 @@
aiohttp==3.9.5
beautifulsoup4==4.12.2 beautifulsoup4==4.12.2
PyPDF2==3.0.1 PyPDF2==3.0.1
tiktoken==0.7.0 tiktoken==0.7.0
pipecat==0.3.0 pipecat-ai[daily,cartesia,openai,silero]==0.0.39
pipecat-ai==0.0.39
python-dotenv==1.0.1 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

View File

@@ -2,8 +2,8 @@ import aiohttp
import asyncio import asyncio
import os import os
import sys import sys
import requests
import io import io
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from PyPDF2 import PdfReader from PyPDF2 import PdfReader
import tiktoken import tiktoken
@@ -26,17 +26,15 @@ from loguru import logger
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv(override=True) load_dotenv(override=True)
from openai import OpenAI # Run this script directly from your command line.
client = OpenAI() # 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.remove(0)
logger.add(sys.stderr, level="DEBUG") 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): def truncate_content(content, model_name):
encoding = tiktoken.encoding_for_model(model_name) encoding = tiktoken.encoding_for_model(model_name)
tokens = encoding.encode(content) tokens = encoding.encode(content)
@@ -47,50 +45,66 @@ def truncate_content(content, model_name):
return encoding.decode(truncated_tokens) return encoding.decode(truncated_tokens)
return content return content
# Main function to extract content from url # Main function to extract content from url
def get_article_content(url):
async def get_article_content(url: str, aiohttp_session: aiohttp.ClientSession):
if 'arxiv.org' in url: if 'arxiv.org' in url:
return get_arxiv_content(url) return await get_arxiv_content(url, aiohttp_session)
else: 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) # Helper function to extract content from Wikipedia url (this is
def get_wikipedia_content(url): # technically agnostic to URL type but will work best with Wikipedia
response = requests.get(url) # articles)
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 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: if '/abs/' in url:
url = url.replace('/abs/', '/pdf/') url = url.replace('/abs/', '/pdf/')
if not url.endswith('.pdf'): if not url.endswith('.pdf'):
url += '.pdf' url += '.pdf'
response = requests.get(url) async with aiohttp_session.get(url) as response:
if response.status_code == 200: if response.status != 200:
pdf_file = io.BytesIO(response.content) return "Failed to download arXiv PDF."
content = await response.read()
pdf_file = io.BytesIO(content)
pdf_reader = PdfReader(pdf_file) pdf_reader = PdfReader(pdf_file)
text = "" text = ""
for page in pdf_reader.pages: for page in pdf_reader.pages:
text += page.extract_text() text += page.extract_text()
return 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(): async def main():
url = input("Enter the URL of the article you would like to talk about: ") 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: 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) (room_url, token) = await configure(session)
transport = DailyTransport( transport = DailyTransport(
@@ -108,25 +122,22 @@ async def main():
tts = CartesiaTTSService( tts = CartesiaTTSService(
api_key=os.getenv("CARTESIA_API_KEY"), api_key=os.getenv("CARTESIA_API_KEY"),
voice_id="4d2fd738-3b3d-4368-957a-bb4805275bd9", # British Narration Lady: 4d2fd738-3b3d-4368-957a-bb4805275bd9 voice_id=os.getenv("CARTESIA_VOICE_ID", "4d2fd738-3b3d-4368-957a-bb4805275bd9"),
sample_rate=44100, # British Narration Lady: 4d2fd738-3b3d-4368-957a-bb4805275bd9
sample_rate=44100,
) )
llm = OpenAILLMService( llm = OpenAILLMService(
api_key=os.getenv("OPENAI_API_KEY"), api_key=os.getenv("OPENAI_API_KEY"),
model="gpt-4o-mini") model="gpt-4o-mini")
messages = [ messages = [{
{ "role": "system", "content": f"""You are an AI study partner. You have been given the following article content:
"role": "system",
"content": f"""You are an AI study partner. You have been given the following article content:
{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. 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_in = LLMUserResponseAggregator(messages)
tma_out = LLMAssistantResponseAggregator(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): async def on_first_participant_joined(transport, participant):
transport.capture_participant_transcription(participant["id"]) transport.capture_participant_transcription(participant["id"])
messages.append( 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)]) await task.queue_frames([LLMMessagesFrame(messages)])
runner = PipelineRunner() 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) await runner.run(task)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())