streamlit inclusion
This commit is contained in:
parent
dc76c43d79
commit
d16e2b757d
1 changed files with 255 additions and 146 deletions
|
|
@ -21,10 +21,129 @@ import asyncio
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import time
|
import time
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
def init_session_state():
|
||||||
|
"""Initialize session state variables for storing API keys and configurations."""
|
||||||
|
defaults = {
|
||||||
|
"initialized": False,
|
||||||
|
"qdrant_url": "",
|
||||||
|
"qdrant_api_key": "",
|
||||||
|
"firecrawl_api_key": "",
|
||||||
|
"openai_api_key": "",
|
||||||
|
"doc_url": "",
|
||||||
|
"setup_complete": False,
|
||||||
|
"client": None,
|
||||||
|
"embedding_model": None,
|
||||||
|
"processor_agent": None,
|
||||||
|
"tts_agent": None,
|
||||||
|
"selected_voice": "coral" # Default voice
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value in defaults.items():
|
||||||
|
if key not in st.session_state:
|
||||||
|
st.session_state[key] = value
|
||||||
|
|
||||||
|
def sidebar_config():
|
||||||
|
"""Render and handle the configuration sidebar."""
|
||||||
|
with st.sidebar:
|
||||||
|
st.title("🔑 Configuration")
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# API Keys and URLs
|
||||||
|
st.session_state.qdrant_url = st.text_input(
|
||||||
|
"Qdrant URL",
|
||||||
|
value=st.session_state.qdrant_url,
|
||||||
|
type="password"
|
||||||
|
)
|
||||||
|
st.session_state.qdrant_api_key = st.text_input(
|
||||||
|
"Qdrant API Key",
|
||||||
|
value=st.session_state.qdrant_api_key,
|
||||||
|
type="password"
|
||||||
|
)
|
||||||
|
st.session_state.firecrawl_api_key = st.text_input(
|
||||||
|
"Firecrawl API Key",
|
||||||
|
value=st.session_state.firecrawl_api_key,
|
||||||
|
type="password"
|
||||||
|
)
|
||||||
|
st.session_state.openai_api_key = st.text_input(
|
||||||
|
"OpenAI API Key",
|
||||||
|
value=st.session_state.openai_api_key,
|
||||||
|
type="password"
|
||||||
|
)
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
st.session_state.doc_url = st.text_input(
|
||||||
|
"Documentation URL",
|
||||||
|
value=st.session_state.doc_url,
|
||||||
|
placeholder="https://docs.example.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Voice selection
|
||||||
|
st.markdown("---")
|
||||||
|
st.markdown("### 🎤 Voice Settings")
|
||||||
|
voices = ["alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"]
|
||||||
|
st.session_state.selected_voice = st.selectbox(
|
||||||
|
"Select Voice",
|
||||||
|
options=voices,
|
||||||
|
index=voices.index(st.session_state.selected_voice),
|
||||||
|
help="Choose the voice for the audio response"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Setup button
|
||||||
|
if st.button("Initialize System", type="primary"):
|
||||||
|
if all([
|
||||||
|
st.session_state.qdrant_url,
|
||||||
|
st.session_state.qdrant_api_key,
|
||||||
|
st.session_state.firecrawl_api_key,
|
||||||
|
st.session_state.openai_api_key,
|
||||||
|
st.session_state.doc_url
|
||||||
|
]):
|
||||||
|
progress_placeholder = st.empty()
|
||||||
|
with progress_placeholder.container():
|
||||||
|
try:
|
||||||
|
# Setup Qdrant
|
||||||
|
st.markdown("🔄 Setting up Qdrant connection...")
|
||||||
|
client, embedding_model = setup_qdrant_collection(
|
||||||
|
st.session_state.qdrant_url,
|
||||||
|
st.session_state.qdrant_api_key
|
||||||
|
)
|
||||||
|
st.session_state.client = client
|
||||||
|
st.session_state.embedding_model = embedding_model
|
||||||
|
st.markdown("✅ Qdrant setup complete!")
|
||||||
|
|
||||||
|
# Crawl documentation
|
||||||
|
st.markdown("🔄 Crawling documentation pages...")
|
||||||
|
pages = crawl_documentation(
|
||||||
|
st.session_state.firecrawl_api_key,
|
||||||
|
st.session_state.doc_url
|
||||||
|
)
|
||||||
|
st.markdown(f"✅ Crawled {len(pages)} documentation pages!")
|
||||||
|
|
||||||
|
# Store embeddings
|
||||||
|
store_embeddings(
|
||||||
|
client,
|
||||||
|
embedding_model,
|
||||||
|
pages,
|
||||||
|
"docs_embeddings"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Setup agents
|
||||||
|
processor_agent, tts_agent = setup_agents(
|
||||||
|
st.session_state.openai_api_key
|
||||||
|
)
|
||||||
|
st.session_state.processor_agent = processor_agent
|
||||||
|
st.session_state.tts_agent = tts_agent
|
||||||
|
|
||||||
|
st.session_state.setup_complete = True
|
||||||
|
st.success("✅ System initialized successfully!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Error during setup: {str(e)}")
|
||||||
|
else:
|
||||||
|
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 ---")
|
print("\n--- Step 1: Setting up Qdrant Collection ---")
|
||||||
|
|
@ -194,167 +313,157 @@ async def process_query(
|
||||||
collection_name: str,
|
collection_name: str,
|
||||||
openai_api_key: str
|
openai_api_key: str
|
||||||
):
|
):
|
||||||
print("\n--- Step 5: Processing Query ---")
|
|
||||||
try:
|
try:
|
||||||
# Generate query embedding
|
# Generate query embedding
|
||||||
print("Generating query embedding...")
|
|
||||||
query_embedding = list(embedding_model.embed([query]))[0]
|
query_embedding = list(embedding_model.embed([query]))[0]
|
||||||
print(f"✓ Generated query embedding with shape: {len(query_embedding)}")
|
|
||||||
print(f"Vector sample (first 5 elements): {query_embedding[:5]}")
|
|
||||||
|
|
||||||
# Try to get collection info first
|
# Search in Qdrant
|
||||||
print("\nVerifying collection status...")
|
search_response = client.query_points(
|
||||||
try:
|
collection_name=collection_name,
|
||||||
collection_info = client.get_collection(collection_name)
|
query=query_embedding.tolist(),
|
||||||
print(f"Collection exists with {collection_info.points_count} points")
|
limit=3,
|
||||||
except Exception as e:
|
with_payload=True
|
||||||
print(f"Warning: Could not get collection info: {str(e)}")
|
)
|
||||||
|
|
||||||
# Attempt search with query parameter (confirmed working)
|
search_results = search_response.points if hasattr(search_response, 'points') else []
|
||||||
print("\nAttempting vector search...")
|
|
||||||
try:
|
if not search_results:
|
||||||
print("Querying with 'query' parameter...")
|
raise Exception("No relevant documents found in the vector database")
|
||||||
search_response = client.query_points(
|
|
||||||
collection_name=collection_name,
|
# Build context from search results
|
||||||
query=query_embedding.tolist(),
|
context = "Based on the following documentation:\n\n"
|
||||||
limit=3,
|
for result in search_results:
|
||||||
with_payload=True
|
payload = result.payload
|
||||||
)
|
if not payload:
|
||||||
print("✓ Query successful")
|
continue
|
||||||
|
url = payload.get('url', 'Unknown URL')
|
||||||
# Debug search response
|
content = payload.get('content', '')
|
||||||
print("\nSearch Response Debug:")
|
context += f"From {url}:\n{content}\n\n"
|
||||||
print(f"Response type: {type(search_response)}")
|
|
||||||
|
context += f"\nUser Question: {query}\n\n"
|
||||||
# Get points from the response
|
context += "Please provide a clear, concise answer that can be easily spoken out loud."
|
||||||
if hasattr(search_response, 'points'):
|
|
||||||
search_results = search_response.points
|
# Process response with agents
|
||||||
else:
|
processor_result = await Runner.run(processor_agent, context)
|
||||||
search_results = []
|
processor_response = processor_result.final_output
|
||||||
|
|
||||||
|
tts_result = await Runner.run(tts_agent, processor_response)
|
||||||
|
tts_response = tts_result.final_output
|
||||||
|
|
||||||
|
# Generate audio
|
||||||
|
async_openai = AsyncOpenAI(api_key=openai_api_key)
|
||||||
|
audio_response = await async_openai.audio.speech.create(
|
||||||
|
model="gpt-4o-mini-tts",
|
||||||
|
voice=st.session_state.selected_voice,
|
||||||
|
input=processor_response,
|
||||||
|
instructions=tts_response,
|
||||||
|
response_format="mp3"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save audio to a temporary file
|
||||||
|
temp_dir = tempfile.gettempdir()
|
||||||
|
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:
|
||||||
|
f.write(audio_response.content)
|
||||||
|
|
||||||
print(f"\n✓ Found {len(search_results)} relevant documents")
|
return {
|
||||||
|
"status": "success",
|
||||||
if not search_results:
|
"text_response": processor_response,
|
||||||
raise Exception("No relevant documents found in the vector database")
|
"tts_instructions": tts_response,
|
||||||
|
"audio_path": audio_path,
|
||||||
# Build context from search results
|
"sources": [r.payload.get("url", "Unknown URL") for r in search_results if r.payload],
|
||||||
context = "Based on the following documentation:\n\n"
|
"query_details": {
|
||||||
for result in search_results:
|
"vector_size": len(query_embedding),
|
||||||
payload = result.payload
|
"results_found": len(search_results),
|
||||||
if not payload:
|
"collection_name": collection_name
|
||||||
print(f"Warning: Result missing payload")
|
|
||||||
continue
|
|
||||||
|
|
||||||
url = payload.get('url', 'Unknown URL')
|
|
||||||
content = payload.get('content', '')
|
|
||||||
score = getattr(result, 'score', 'N/A')
|
|
||||||
|
|
||||||
print(f"\nDocument from {url}")
|
|
||||||
print(f"Relevance score: {score}")
|
|
||||||
context += f"From {url}:\n{content}\n\n"
|
|
||||||
|
|
||||||
context += f"\nUser Question: {query}\n\n"
|
|
||||||
context += "Please provide a clear, concise answer that can be easily spoken out loud."
|
|
||||||
|
|
||||||
# Process response with agents
|
|
||||||
print("\nProcessing with Documentation Agent...")
|
|
||||||
processor_result = await Runner.run(processor_agent, context)
|
|
||||||
processor_response = processor_result.final_output
|
|
||||||
print("✓ Generated text response")
|
|
||||||
|
|
||||||
print("\nProcessing with TTS Agent...")
|
|
||||||
tts_result = await Runner.run(tts_agent, processor_response)
|
|
||||||
tts_response = tts_result.final_output
|
|
||||||
print("✓ Generated TTS instructions")
|
|
||||||
|
|
||||||
# Generate and play audio
|
|
||||||
print("\nGenerating audio response...")
|
|
||||||
async_openai = AsyncOpenAI(api_key=openai_api_key)
|
|
||||||
async with async_openai.audio.speech.with_streaming_response.create(
|
|
||||||
model="tts-1",
|
|
||||||
voice="alloy",
|
|
||||||
input=processor_response,
|
|
||||||
instructions=tts_response,
|
|
||||||
response_format="pcm"
|
|
||||||
) as response:
|
|
||||||
print("✓ Streaming audio response")
|
|
||||||
await LocalAudioPlayer().play(response)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"status": "success",
|
|
||||||
"text_response": processor_response,
|
|
||||||
"tts_instructions": tts_response,
|
|
||||||
"sources": [r.payload.get("url", "Unknown URL") for r in search_results if r.payload],
|
|
||||||
"query_details": {
|
|
||||||
"vector_size": len(query_embedding),
|
|
||||||
"results_found": len(search_results),
|
|
||||||
"collection_name": collection_name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
except Exception as e:
|
|
||||||
print(f"Error during vector search: {str(e)}")
|
|
||||||
print("Full error details:")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
raise
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"\nError processing query: {str(e)}")
|
print(f"\nError processing query: {str(e)}")
|
||||||
print("Full error details:")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"error": str(e),
|
"error": str(e),
|
||||||
"error_details": traceback.format_exc(),
|
|
||||||
"query": query
|
"query": query
|
||||||
}
|
}
|
||||||
|
|
||||||
async def main():
|
def run_streamlit():
|
||||||
try:
|
"""Main Streamlit application."""
|
||||||
env_vars = get_env_vars()
|
st.set_page_config(
|
||||||
print("✓ Loaded environment variables")
|
page_title="AI Voice Documentation Agent Team",
|
||||||
|
page_icon="🎙️",
|
||||||
client, embedding_model = setup_qdrant_collection(
|
layout="wide"
|
||||||
env_vars["QDRANT_URL"],
|
)
|
||||||
env_vars["QDRANT_API_KEY"]
|
|
||||||
)
|
init_session_state()
|
||||||
|
sidebar_config()
|
||||||
pages = crawl_documentation(
|
|
||||||
env_vars["FIRECRAWL_API_KEY"],
|
# Main content area
|
||||||
"https://docs.agentmail.to/api-reference",
|
st.title("🎙️ AI Voice Documentation Agent Team")
|
||||||
"crawled_docs"
|
st.markdown("""
|
||||||
)
|
Get OpenAI SDK voice-powered answers to your documentation questions! Simply:
|
||||||
|
1. Configure your API keys in the sidebar
|
||||||
store_embeddings(client, embedding_model, pages, "docs_embeddings")
|
2. Enter the documentation URL you want to learn about or have questions about
|
||||||
|
3. Ask your question below and get both text and voice responses
|
||||||
processor_agent, tts_agent = setup_agents(env_vars["OPENAI_API_KEY"])
|
""")
|
||||||
|
|
||||||
query = "What are the required parameters for List Threads API of Agent Mail?"
|
# Query input and processing
|
||||||
result = await process_query(
|
query = st.text_input(
|
||||||
query,
|
"What would you like to know about the documentation?",
|
||||||
client,
|
placeholder="e.g., How do I authenticate API requests?",
|
||||||
embedding_model,
|
disabled=not st.session_state.setup_complete
|
||||||
processor_agent,
|
)
|
||||||
tts_agent,
|
|
||||||
"docs_embeddings",
|
if query and st.session_state.setup_complete:
|
||||||
env_vars["OPENAI_API_KEY"]
|
with st.status("Processing your query...", expanded=True) as status:
|
||||||
)
|
try:
|
||||||
|
st.markdown("🔄 Searching documentation and generating response...")
|
||||||
print("\n--- Final Results ---")
|
result = asyncio.run(process_query(
|
||||||
print(json.dumps(result, indent=2))
|
query,
|
||||||
|
st.session_state.client,
|
||||||
except ValueError as e:
|
st.session_state.embedding_model,
|
||||||
print(f"\nConfiguration Error: {str(e)}")
|
st.session_state.processor_agent,
|
||||||
print("\nPlease ensure your .env file contains all required variables:")
|
st.session_state.tts_agent,
|
||||||
print("FIRECRAWL_API_KEY=your_key")
|
"docs_embeddings",
|
||||||
print("QDRANT_URL=your_qdrant_url")
|
st.session_state.openai_api_key
|
||||||
print("QDRANT_API_KEY=your_qdrant_key")
|
))
|
||||||
print("OPENAI_API_KEY=your_openai_key")
|
|
||||||
except Exception as e:
|
if result["status"] == "success":
|
||||||
print(f"\nError: {str(e)}")
|
status.update(label="✅ Query processed!", state="complete")
|
||||||
|
|
||||||
|
st.markdown("### Response:")
|
||||||
|
st.write(result["text_response"])
|
||||||
|
|
||||||
|
if "audio_path" in result:
|
||||||
|
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)
|
||||||
|
|
||||||
|
# For download button, we still need to read the bytes
|
||||||
|
with open(result["audio_path"], "rb") as audio_file:
|
||||||
|
audio_bytes = audio_file.read()
|
||||||
|
st.download_button(
|
||||||
|
label="📥 Download Audio Response",
|
||||||
|
data=audio_bytes,
|
||||||
|
file_name=f"voice_response_{st.session_state.selected_voice}.mp3",
|
||||||
|
mime="audio/mp3"
|
||||||
|
)
|
||||||
|
|
||||||
|
st.markdown("### Sources:")
|
||||||
|
for source in result["sources"]:
|
||||||
|
st.markdown(f"- {source}")
|
||||||
|
else:
|
||||||
|
status.update(label="❌ Error processing query", state="error")
|
||||||
|
st.error(f"Error: {result.get('error', 'Unknown error occurred')}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
status.update(label="❌ Error processing query", state="error")
|
||||||
|
st.error(f"Error processing query: {str(e)}")
|
||||||
|
|
||||||
|
elif not st.session_state.setup_complete:
|
||||||
|
st.info("👈 Please configure the system using the sidebar first!")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(main())
|
run_streamlit()
|
||||||
Loading…
Reference in a new issue