Files
patchy631--ai-engineering-hub/database-memory-agent/app.py
T
2026-07-13 12:37:47 +08:00

261 lines
8.5 KiB
Python

import streamlit as st
import base64
import uuid
import sys
from io import StringIO
from config import vector_collection
from ingest_data import ingest_data
from planning import generate_response, tool_selector
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Set up page configuration
st.set_page_config(page_title="Database Memory Agent", layout="wide")
# Initialize session state variables
if "session_id" not in st.session_state:
st.session_state.session_id = str(uuid.uuid4())[:8]
if "messages" not in st.session_state:
st.session_state.messages = []
if "vector_index_ready" not in st.session_state:
st.session_state.vector_index_ready = False
if "data_ingested" not in st.session_state:
st.session_state.data_ingested = False
session_id = st.session_state.session_id
def reset_chat():
"""Reset chat history."""
st.session_state.messages = []
st.session_state.session_id = str(uuid.uuid4())[:8]
def display_pdf(file):
"""Display PDF preview in sidebar."""
st.markdown("### PDF Preview")
base64_pdf = base64.b64encode(file.read()).decode("utf-8")
pdf_display = f"""<iframe src="data:application/pdf;base64,{base64_pdf}" width="400" height="100%" type="application/pdf"
style="height:100vh; width:100%"
>
</iframe>"""
st.markdown(pdf_display, unsafe_allow_html=True)
def check_vector_index():
"""Check if vector index exists and is ready."""
if st.session_state.vector_index_ready:
return True
try:
existing_indexes = list(vector_collection.list_search_indexes("vector_index"))
if existing_indexes and existing_indexes[0].get("queryable"):
st.session_state.vector_index_ready = True
return True
except Exception as e:
st.error(f"Error checking vector index: {e}")
return False
return False
def process_pdf_upload(uploaded_file):
"""Process uploaded PDF and ingest into MongoDB."""
with st.spinner("🔄 Processing..."):
try:
old_stdout = sys.stdout
sys.stdout = StringIO()
try:
ingest_data()
finally:
sys.stdout = old_stdout
if check_vector_index():
st.session_state.data_ingested = True
st.success("✅ Document processed and ready for queries!")
return True
return False
except Exception as e:
st.error(f"Error processing PDF: {str(e)}")
return False
def ingest_sample_data():
"""Ingest sample MongoDB earnings report."""
try:
with st.spinner("🔄 Processing..."):
# Suppress print output from ingest_data()
old_stdout = sys.stdout
sys.stdout = StringIO()
try:
ingest_data()
finally:
sys.stdout = old_stdout
# ingest_data() already creates the index, just check if it's ready
if check_vector_index():
st.session_state.data_ingested = True
st.success("✅ Sample data ingested and ready for queries!")
return True
return False
except Exception as e:
st.error(f"Error ingesting sample data: {str(e)}")
return False
# Sidebar for configuration and document upload
with st.sidebar:
st.header("🔧 Configuration")
st.markdown("**Session ID:**")
st.code(session_id)
if st.button("🔄 New Session"):
reset_chat()
st.rerun()
st.markdown("---")
# Document upload section
st.header("📄 Upload Document")
st.markdown("Upload a PDF document or use sample data")
col1, col2 = st.columns(2)
with col1:
if st.button("📊 Use Sample Data", use_container_width=True):
ingest_sample_data()
with col2:
if st.button("🗑️ Clear Data", use_container_width=True):
st.session_state.data_ingested = False
st.session_state.vector_index_ready = False
st.info("Data cleared. Upload a new document to continue.")
uploaded_file = st.file_uploader("Or upload your PDF file", type="pdf")
if uploaded_file:
if process_pdf_upload(uploaded_file):
display_pdf(uploaded_file)
st.markdown("---")
# System status
st.header("📊 System Status")
if st.session_state.data_ingested:
st.success("🟢 Data Ready")
else:
st.info("🔵 No Data Loaded")
if st.session_state.vector_index_ready:
st.success("🟢 Vector Index Ready")
else:
st.warning("🟡 Vector Index Not Ready")
# Main chat interface
col1, col2 = st.columns([6, 1])
with col1:
st.markdown('''
<h1 style='color: #2E86AB; margin-bottom: 10px; font-size: 2.5em;'>
Database Memory Agent
</h1>
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 20px;">
<span style='color: #A23B72; font-size: 16px;'>Powered by</span>
<div style="display: flex; align-items: center; gap: 20px;">
<a href="https://www.mongodb.com/" style="display: inline-block; vertical-align: middle;">
<img src="https://webimages.mongodb.com/_com_assets/cms/mongodb_logo1-76twgcu2dm.png"
alt="MongoDB" style="height: 40px;">
</a>
<a href="https://www.voyageai.com/" style="display: inline-block; vertical-align: middle;">
<img src="https://www.voyageai.com/favicon.ico"
alt="Voyage AI" style="height: 32px;">
</a>
</div>
</div>
''', unsafe_allow_html=True)
with col2:
if st.button("Clear Chat ↺", on_click=reset_chat):
st.rerun()
# System info
if st.session_state.data_ingested and st.session_state.vector_index_ready:
st.success("🟢 System Ready - You can ask questions about your document!")
elif st.session_state.data_ingested:
st.warning("🟡 Data loaded but vector index is not ready. Please wait...")
else:
st.info("🔵 Upload a PDF document or use sample data to get started")
# Display chat messages from history
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Accept user input
if prompt := st.chat_input("Ask a question about your document..."):
if not st.session_state.data_ingested or not st.session_state.vector_index_ready:
st.error("⚠️ Please upload a document or use sample data first.")
st.stop()
# Add user message to chat history
st.session_state.messages.append({
"role": "user",
"content": prompt
})
# Display user message
with st.chat_message("user"):
st.markdown(prompt)
# Generate response
with st.chat_message("assistant"):
message_placeholder = st.empty()
try:
with st.spinner("🔄 Processing..."):
# Get tool info for display (simple check)
session_history = [{"role": msg["role"], "content": msg["content"]} for msg in st.session_state.messages[-5:]]
tool, _ = tool_selector(prompt, session_history if session_history else None)
# Generate response
response = generate_response(session_id, prompt)
message_placeholder.markdown(response)
# Show simple tool indicator
if tool == "vector_search_tool":
st.info("📚 Using document search")
elif tool == "calculator_tool":
st.info("🔢 Using calculator")
metadata = {"tool": tool}
except Exception as e:
st.error(f"❌ Error processing your question: {str(e)}")
response = "I apologize, but I encountered an error while processing your question. Please try again."
message_placeholder.markdown(response)
metadata = {}
# Add assistant response to chat history
st.session_state.messages.append({
"role": "assistant",
"content": response,
"metadata": metadata
})
# Footer
st.markdown("---")
st.markdown(
"<p style='text-align: center; color: #666; font-size: 12px;'>"
"Database Memory Agent • Built with Streamlit, MongoDB Atlas Vector Search, and Voyage AI"
"</p>",
unsafe_allow_html=True
)