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
|
### 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`,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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!
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user