examples(studypal): use aiohttp instead of requests
This commit is contained in:
@@ -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`,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user