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
- Added `studypal` example (from to the Cartesia folks!).
- Most examples now use Cartesia.
- 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 |
| [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.

View File

@@ -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://<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
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

View File

@@ -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())
asyncio.run(main())