Final commit - working fine

This commit is contained in:
Madhu 2025-03-25 04:19:32 +05:30
parent 6f0dc1fb35
commit 0bec4eef97

View file

@ -1,5 +1,4 @@
from typing import List, Dict, Optional from typing import List, Dict, Optional
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
import os import os
from firecrawl import FirecrawlApp from firecrawl import FirecrawlApp
@ -7,26 +6,19 @@ from qdrant_client import QdrantClient
from qdrant_client.http import models from qdrant_client.http import models
from qdrant_client.http.models import Distance, VectorParams from qdrant_client.http.models import Distance, VectorParams
from fastembed import TextEmbedding from fastembed import TextEmbedding
from agents import Agent, ModelSettings, function_tool, Runner from agents import Agent, Runner
from openai import OpenAI, AsyncOpenAI from openai import AsyncOpenAI
from openai.helpers import LocalAudioPlayer
import textwrap
import tempfile import tempfile
import uuid import uuid
import numpy as np
from typing import Callable
from urllib.parse import urlparse
from dotenv import load_dotenv
import asyncio
import json
from datetime import datetime from datetime import datetime
import time import time
import streamlit as st import streamlit as st
from dotenv import load_dotenv
import asyncio
load_dotenv() load_dotenv()
def init_session_state(): def init_session_state():
"""Initialize session state variables for storing API keys and configurations."""
defaults = { defaults = {
"initialized": False, "initialized": False,
"qdrant_url": "", "qdrant_url": "",
@ -39,7 +31,7 @@ def init_session_state():
"embedding_model": None, "embedding_model": None,
"processor_agent": None, "processor_agent": None,
"tts_agent": None, "tts_agent": None,
"selected_voice": "coral" # Default voice "selected_voice": "coral"
} }
for key, value in defaults.items(): for key, value in defaults.items():
@ -47,12 +39,10 @@ def init_session_state():
st.session_state[key] = value st.session_state[key] = value
def sidebar_config(): def sidebar_config():
"""Render and handle the configuration sidebar."""
with st.sidebar: with st.sidebar:
st.title("🔑 Configuration") st.title("🔑 Configuration")
st.markdown("---") st.markdown("---")
# API Keys and URLs
st.session_state.qdrant_url = st.text_input( st.session_state.qdrant_url = st.text_input(
"Qdrant URL", "Qdrant URL",
value=st.session_state.qdrant_url, value=st.session_state.qdrant_url,
@ -81,7 +71,6 @@ def sidebar_config():
placeholder="https://docs.example.com" placeholder="https://docs.example.com"
) )
# Voice selection
st.markdown("---") st.markdown("---")
st.markdown("### 🎤 Voice Settings") st.markdown("### 🎤 Voice Settings")
voices = ["alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"] voices = ["alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"]
@ -92,7 +81,6 @@ def sidebar_config():
help="Choose the voice for the audio response" help="Choose the voice for the audio response"
) )
# Setup button
if st.button("Initialize System", type="primary"): if st.button("Initialize System", type="primary"):
if all([ if all([
st.session_state.qdrant_url, st.session_state.qdrant_url,
@ -104,7 +92,6 @@ def sidebar_config():
progress_placeholder = st.empty() progress_placeholder = st.empty()
with progress_placeholder.container(): with progress_placeholder.container():
try: try:
# Setup Qdrant
st.markdown("🔄 Setting up Qdrant connection...") st.markdown("🔄 Setting up Qdrant connection...")
client, embedding_model = setup_qdrant_collection( client, embedding_model = setup_qdrant_collection(
st.session_state.qdrant_url, st.session_state.qdrant_url,
@ -114,7 +101,6 @@ def sidebar_config():
st.session_state.embedding_model = embedding_model st.session_state.embedding_model = embedding_model
st.markdown("✅ Qdrant setup complete!") st.markdown("✅ Qdrant setup complete!")
# Crawl documentation
st.markdown("🔄 Crawling documentation pages...") st.markdown("🔄 Crawling documentation pages...")
pages = crawl_documentation( pages = crawl_documentation(
st.session_state.firecrawl_api_key, st.session_state.firecrawl_api_key,
@ -122,7 +108,6 @@ def sidebar_config():
) )
st.markdown(f"✅ Crawled {len(pages)} documentation pages!") st.markdown(f"✅ Crawled {len(pages)} documentation pages!")
# Store embeddings
store_embeddings( store_embeddings(
client, client,
embedding_model, embedding_model,
@ -130,7 +115,6 @@ def sidebar_config():
"docs_embeddings" "docs_embeddings"
) )
# Setup agents
processor_agent, tts_agent = setup_agents( processor_agent, tts_agent = setup_agents(
st.session_state.openai_api_key st.session_state.openai_api_key
) )
@ -146,163 +130,117 @@ def sidebar_config():
st.error("Please fill in all the required fields!") st.error("Please fill in all the required fields!")
def setup_qdrant_collection(qdrant_url: str, qdrant_api_key: str, collection_name: str = "docs_embeddings"): def setup_qdrant_collection(qdrant_url: str, qdrant_api_key: str, collection_name: str = "docs_embeddings"):
print("\n--- Step 1: Setting up Qdrant Collection ---") client = QdrantClient(url=qdrant_url, api_key=qdrant_api_key)
embedding_model = TextEmbedding()
test_embedding = list(embedding_model.embed(["test"]))[0]
embedding_dim = len(test_embedding)
try: try:
client = QdrantClient(url=qdrant_url, api_key=qdrant_api_key)
print("✓ Connected to Qdrant")
embedding_model = TextEmbedding()
test_embedding = list(embedding_model.embed(["test"]))[0]
embedding_dim = len(test_embedding)
print(f"✓ Embedding model ready (dimension: {embedding_dim})")
client.create_collection( client.create_collection(
collection_name=collection_name, collection_name=collection_name,
vectors_config=VectorParams(size=embedding_dim, distance=Distance.COSINE) vectors_config=VectorParams(size=embedding_dim, distance=Distance.COSINE)
) )
print(f"✓ Created collection: {collection_name}")
return client, embedding_model
except Exception as e: except Exception as e:
if "already exists" in str(e): if "already exists" not in str(e):
print(f"✓ Collection {collection_name} already exists") raise e
return client, embedding_model
raise e return client, embedding_model
def crawl_documentation(firecrawl_api_key: str, url: str, output_dir: Optional[str] = None): def crawl_documentation(firecrawl_api_key: str, url: str, output_dir: Optional[str] = None):
print("\n--- Step 2: Crawling Documentation ---") firecrawl = FirecrawlApp(api_key=firecrawl_api_key)
try: pages = []
firecrawl = FirecrawlApp(api_key=firecrawl_api_key)
print(f"✓ Initialized Firecrawl")
if output_dir:
os.makedirs(output_dir, exist_ok=True)
print(f"✓ Created output directory: {output_dir}")
print(f"Starting crawl of {url}...")
pages = []
response = firecrawl.crawl_url(
url,
params={
'limit': 5,
'scrapeOptions': {
'formats': ['markdown', 'html']
}
}
)
while True:
if response.get('status') == 'scraping':
print(f"Progress: {response.get('completed', 0)}/{response.get('total', 0)} pages")
print(f"Credits used: {response.get('creditsUsed', 0)}")
for page in response.get('data', []):
content = page.get('markdown') or page.get('html', '')
metadata = page.get('metadata', {})
source_url = metadata.get('sourceURL', '')
if output_dir and content:
filename = f"{uuid.uuid4()}.md"
filepath = os.path.join(output_dir, filename)
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
pages.append({
"content": content,
"url": source_url,
"metadata": {
"title": metadata.get('title', ''),
"description": metadata.get('description', ''),
"language": metadata.get('language', 'en'),
"crawl_date": datetime.now().isoformat()
}
})
print(f"✓ Processed page: {metadata.get('title', 'Untitled')}")
next_url = response.get('next')
if not next_url:
break
response = firecrawl.get(next_url)
time.sleep(1)
print(f"✓ Crawled {len(pages)} pages")
return pages
except Exception as e: if output_dir:
print(f"Error crawling documentation: {str(e)}") os.makedirs(output_dir, exist_ok=True)
raise e
response = firecrawl.crawl_url(
url,
params={
'limit': 5,
'scrapeOptions': {
'formats': ['markdown', 'html']
}
}
)
while True:
for page in response.get('data', []):
content = page.get('markdown') or page.get('html', '')
metadata = page.get('metadata', {})
source_url = metadata.get('sourceURL', '')
if output_dir and content:
filename = f"{uuid.uuid4()}.md"
filepath = os.path.join(output_dir, filename)
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
pages.append({
"content": content,
"url": source_url,
"metadata": {
"title": metadata.get('title', ''),
"description": metadata.get('description', ''),
"language": metadata.get('language', 'en'),
"crawl_date": datetime.now().isoformat()
}
})
next_url = response.get('next')
if not next_url:
break
response = firecrawl.get(next_url)
time.sleep(1)
return pages
def store_embeddings(client: QdrantClient, embedding_model: TextEmbedding, pages: List[Dict], collection_name: str): def store_embeddings(client: QdrantClient, embedding_model: TextEmbedding, pages: List[Dict], collection_name: str):
print("\n--- Step 3: Generating and Storing Embeddings ---") for page in pages:
try: embedding = list(embedding_model.embed([page["content"]]))[0]
for page in pages: client.upsert(
embedding = list(embedding_model.embed([page["content"]]))[0] collection_name=collection_name,
points=[
client.upsert( models.PointStruct(
collection_name=collection_name, id=str(uuid.uuid4()),
points=[ vector=embedding.tolist(),
models.PointStruct( payload={
id=str(uuid.uuid4()), "content": page["content"],
vector=embedding.tolist(), "url": page["url"],
payload={ **page["metadata"]
"content": page["content"], }
"url": page["url"], )
**page["metadata"] ]
} )
)
]
)
print(f"✓ Stored embedding for: {page['metadata']['title'] or page['url']}")
print(f"✓ Stored {len(pages)} embeddings in Qdrant")
except Exception as e:
print(f"Error storing embeddings: {str(e)}")
raise e
def setup_agents(openai_api_key: str): def setup_agents(openai_api_key: str):
print("\n--- Step 4: Setting up OpenAI Agents ---") os.environ["OPENAI_API_KEY"] = openai_api_key
try:
# Set OpenAI API key in environment
os.environ["OPENAI_API_KEY"] = openai_api_key
print("✓ Set OpenAI API key in environment")
processor_agent = Agent(
name="Documentation Processor",
instructions="""You are a helpful documentation assistant. Your task is to:
1. Analyze the provided documentation content
2. Answer the user's question clearly and concisely
3. Include relevant examples when available
4. Cite the source URLs when referencing specific content
5. Keep responses natural and conversational
6. Format your response in a way that's easy to speak out loud""",
model="gpt-4o"
)
print("✓ Set up Documentation Processor Agent")
tts_agent = Agent(
name="Text-to-Speech Agent",
instructions="""You are a text-to-speech agent. Your task is to:
1. Convert the processed documentation response into natural speech
2. Maintain proper pacing and emphasis
3. Handle technical terms clearly
4. Keep the tone professional but friendly
5. Use appropriate pauses for better comprehension
6. Ensure the speech is clear and well-articulated""",
model="gpt-4o-mini-tts"
)
print("✓ Set up TTS Agent")
return processor_agent, tts_agent
except Exception as e: processor_agent = Agent(
print(f"Error setting up agents: {str(e)}") name="Documentation Processor",
raise e instructions="""You are a helpful documentation assistant. Your task is to:
1. Analyze the provided documentation content
2. Answer the user's question clearly and concisely
3. Include relevant examples when available
4. Cite the source URLs when referencing specific content
5. Keep responses natural and conversational
6. Format your response in a way that's easy to speak out loud""",
model="gpt-4o"
)
tts_agent = Agent(
name="Text-to-Speech Agent",
instructions="""You are a text-to-speech agent. Your task is to:
1. Convert the processed documentation response into natural speech
2. Maintain proper pacing and emphasis
3. Handle technical terms clearly
4. Keep the tone professional but friendly
5. Use appropriate pauses for better comprehension
6. Ensure the speech is clear and well-articulated""",
model="gpt-4o-mini-tts"
)
return processor_agent, tts_agent
async def process_query( async def process_query(
query: str, query: str,
@ -314,10 +252,7 @@ async def process_query(
openai_api_key: str openai_api_key: str
): ):
try: try:
# Generate query embedding
query_embedding = list(embedding_model.embed([query]))[0] query_embedding = list(embedding_model.embed([query]))[0]
# Search in Qdrant
search_response = client.query_points( search_response = client.query_points(
collection_name=collection_name, collection_name=collection_name,
query=query_embedding.tolist(), query=query_embedding.tolist(),
@ -330,7 +265,6 @@ async def process_query(
if not search_results: if not search_results:
raise Exception("No relevant documents found in the vector database") raise Exception("No relevant documents found in the vector database")
# Build context from search results
context = "Based on the following documentation:\n\n" context = "Based on the following documentation:\n\n"
for result in search_results: for result in search_results:
payload = result.payload payload = result.payload
@ -343,14 +277,12 @@ async def process_query(
context += f"\nUser Question: {query}\n\n" context += f"\nUser Question: {query}\n\n"
context += "Please provide a clear, concise answer that can be easily spoken out loud." context += "Please provide a clear, concise answer that can be easily spoken out loud."
# Process response with agents
processor_result = await Runner.run(processor_agent, context) processor_result = await Runner.run(processor_agent, context)
processor_response = processor_result.final_output processor_response = processor_result.final_output
tts_result = await Runner.run(tts_agent, processor_response) tts_result = await Runner.run(tts_agent, processor_response)
tts_response = tts_result.final_output tts_response = tts_result.final_output
# Generate audio
async_openai = AsyncOpenAI(api_key=openai_api_key) async_openai = AsyncOpenAI(api_key=openai_api_key)
audio_response = await async_openai.audio.speech.create( audio_response = await async_openai.audio.speech.create(
model="gpt-4o-mini-tts", model="gpt-4o-mini-tts",
@ -360,11 +292,9 @@ async def process_query(
response_format="mp3" response_format="mp3"
) )
# Save audio to a temporary file
temp_dir = tempfile.gettempdir() temp_dir = tempfile.gettempdir()
audio_path = os.path.join(temp_dir, f"response_{uuid.uuid4()}.mp3") audio_path = os.path.join(temp_dir, f"response_{uuid.uuid4()}.mp3")
# Write the audio content to the file
with open(audio_path, "wb") as f: with open(audio_path, "wb") as f:
f.write(audio_response.content) f.write(audio_response.content)
@ -382,7 +312,6 @@ async def process_query(
} }
except Exception as e: except Exception as e:
print(f"\nError processing query: {str(e)}")
return { return {
"status": "error", "status": "error",
"error": str(e), "error": str(e),
@ -390,7 +319,6 @@ async def process_query(
} }
def run_streamlit(): def run_streamlit():
"""Main Streamlit application."""
st.set_page_config( st.set_page_config(
page_title="AI Voice Documentation Agent Team", page_title="AI Voice Documentation Agent Team",
page_icon="🎙️", page_icon="🎙️",
@ -400,7 +328,6 @@ def run_streamlit():
init_session_state() init_session_state()
sidebar_config() sidebar_config()
# Main content area
st.title("🎙️ AI Voice Documentation Agent Team") st.title("🎙️ AI Voice Documentation Agent Team")
st.markdown(""" st.markdown("""
Get OpenAI SDK voice-powered answers to your documentation questions! Simply: Get OpenAI SDK voice-powered answers to your documentation questions! Simply:
@ -409,7 +336,6 @@ def run_streamlit():
3. Ask your question below and get both text and voice responses 3. Ask your question below and get both text and voice responses
""") """)
# Query input and processing
query = st.text_input( query = st.text_input(
"What would you like to know about the documentation?", "What would you like to know about the documentation?",
placeholder="e.g., How do I authenticate API requests?", placeholder="e.g., How do I authenticate API requests?",
@ -438,10 +364,8 @@ def run_streamlit():
if "audio_path" in result: if "audio_path" in result:
st.markdown(f"### 🔊 Audio Response (Voice: {st.session_state.selected_voice})") st.markdown(f"### 🔊 Audio Response (Voice: {st.session_state.selected_voice})")
# Pass the file path directly to st.audio
st.audio(result["audio_path"], format="audio/mp3", start_time=0) st.audio(result["audio_path"], format="audio/mp3", start_time=0)
# For download button, we still need to read the bytes
with open(result["audio_path"], "rb") as audio_file: with open(result["audio_path"], "rb") as audio_file:
audio_bytes = audio_file.read() audio_bytes = audio_file.read()
st.download_button( st.download_button(