britbrat0's picture
Upload 2 files
330a9ce verified
# -----------------------------
# Standard Python imports
# -----------------------------
# Import standard libraries needed for the chatbot.
# - os: for operating system operations like file paths.
# - streamlit: to build the web app interface.
# - json: for structured data handling and storing results.
# These imports are essential for session management and UI rendering.
import os
import streamlit as st
import json
# -----------------------------
# Import OpenAI and SerpAPI
# -----------------------------
# Attempt to import OpenAI client to interact with GPT models.
# Attempt to import SerpAPI client to perform web searches.
# If these packages are not installed, provide informative errors.
# Ensures graceful failure and user guidance on missing dependencies.
try:
from openai import OpenAI
except ModuleNotFoundError:
print("OpenAI package not installed. Check requirements.txt.")
raise
try:
from serpapi.google_search import GoogleSearch
except ModuleNotFoundError:
print("google-search-results package not installed. Check requirements.txt.")
raise
# -----------------------------
# API Keys from Streamlit Secrets
# -----------------------------
# Retrieve API keys from Streamlit's secure secrets manager.
# The OpenAI key allows GPT model calls.
# The SerpAPI key allows performing web searches.
# If keys are missing, stop the app and prompt the user to configure them.
openai_api_key = st.secrets.get("OPENAI_API_KEY")
serpapi_key = st.secrets.get("SERPAPI_KEY")
if not openai_api_key:
st.error("OpenAI API key not found. Add it to Streamlit secrets.")
st.stop()
# -----------------------------
# Initialize OpenAI client
# -----------------------------
# Create an OpenAI client using the provided API key.
# This client will be used to send prompts and receive GPT responses.
# Centralizes GPT calls and handles authentication.
client = OpenAI(api_key=openai_api_key)
# -----------------------------
# Chat session state
# -----------------------------
# Initialize or retrieve the chat session history stored in Streamlit.
# This allows the chatbot to maintain conversation context across messages.
# If this is the first interaction, start with a friendly assistant message.
if "messages" not in st.session_state:
st.session_state["messages"] = [{"role": "assistant", "content": "Hi! Paste a URL or ask a question."}]
# Display existing messages in the chat interface
for msg in st.session_state.messages:
st.chat_message(msg["role"]).write(msg["content"])
# -----------------------------
# Helper: Web search using SerpAPI
# -----------------------------
# Defines a function to search the web for a given query using SerpAPI.
# Returns a list of top search results containing title, link, and snippet.
# Handles missing API keys and exceptions gracefully, providing informative feedback.
def search_web(query: str):
if not serpapi_key:
return []
try:
params = {"q": query, "api_key": serpapi_key, "num": 3}
search = GoogleSearch(params)
results = search.get_dict()
snippets = []
if "organic_results" in results:
for r in results["organic_results"][:3]:
snippets.append({
"title": r.get("title", "No title"),
"link": r.get("link", ""),
"snippet": r.get("snippet", "")
})
return snippets
except Exception as e:
return [{"title": "Error", "snippet": str(e), "link": ""}]
# -----------------------------
# Helper: Assess URL credibility
# -----------------------------
# Wraps the credibility scoring function from assess_credibility.py.
# Returns a dictionary with a score (0–1), star rating, and textual explanation.
# Handles exceptions and returns default score if analysis fails.
def assess_url(url: str):
try:
from assess_credibility import assess_url_credibility
result = assess_url_credibility(url)
score = result.get("score", 0.0)
stars = int(round(score * 5))
result["stars"] = "β˜…" * stars + "β˜†" * (5 - stars)
return result
except Exception as e:
return {"score": 0.0, "stars": "β˜†β˜†β˜†β˜†β˜†", "explanation": f"Error analyzing URL: {e}"}
# -----------------------------
# Main interaction with intent detection
# -----------------------------
# Handles user input and decides whether to perform:
# 1. URL credibility scoring
# 2. Skip web search for trivial greetings or casual chat
# 3. Web search + credibility scoring + GPT for questions
# Uses a hybrid approach: keyword list for trivial messages + GPT intent check for other inputs.
if prompt := st.chat_input("Ask a question or enter a URL"):
st.session_state.messages.append({"role": "user", "content": prompt})
st.chat_message("user").write(prompt)
# -----------------------------
# Step 1: Check if input is a URL
# -----------------------------
if prompt.startswith("http"):
with st.spinner("πŸ” Assessing credibility..."):
result = assess_url(prompt)
# Display score with stars and explanation
st.markdown(f"**Credibility Score: {result['stars']}**")
st.caption(result.get("explanation", ""))
st.session_state.messages.append({"role": "assistant", "content": json.dumps(result)})
else:
# -----------------------------
# Step 2: Check for trivial messages
# -----------------------------
NO_SEARCH_KEYWORDS = [
"hello", "hi", "hey", "good morning", "good afternoon",
"thanks", "thank you", "how are you"
]
prompt_clean = prompt.lower().strip()
if any(kw in prompt_clean for kw in NO_SEARCH_KEYWORDS):
msg = "Hello! How can I help you today?"
st.session_state.messages.append({"role": "assistant", "content": msg})
st.chat_message("assistant").write(msg)
else:
# -----------------------------
# Step 3: Optional GPT intent classification
# -----------------------------
try:
intent_prompt = f"""
Classify the following message:
'{prompt}'
Reply with either:
- SEARCH: if the message is a question that requires web lookup
- NO_SEARCH: if it's a greeting, thanks, or casual chat
Only reply with SEARCH or NO_SEARCH.
"""
intent_response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": intent_prompt}]
)
intent = intent_response.choices[0].message.content.strip()
except Exception:
intent = "SEARCH"
if intent == "NO_SEARCH":
msg = "Hello! How can I help you?"
st.session_state.messages.append({"role": "assistant", "content": msg})
st.chat_message("assistant").write(msg)
else:
# -----------------------------
# Step 4: Web search + credibility scoring + GPT response
# -----------------------------
with st.spinner("🌎 Searching the web..."):
web_results = search_web(prompt)
if not web_results:
st.warning("No search results found.")
st.stop()
# Assess credibility for each result (stars + explanation)
for r in web_results:
score_dict = assess_url(r["link"]) if r["link"] else {"score": 0.0, "stars": "β˜†β˜†β˜†β˜†β˜†", "explanation": "No link"}
r["credibility_score"] = score_dict.get("score", 0)
r["credibility_explanation"] = score_dict.get("explanation", "")
r["credibility_stars"] = score_dict.get("stars", "β˜†β˜†β˜†β˜†β˜†")
# Prepare context for GPT without displaying individual sources
context = "\n\n".join(
[f"Source: {r['link']}\nSnippet: {r['snippet']}\nCredibility: {r['credibility_stars']} ({r['credibility_score']:.2f}) - {r['credibility_explanation']}"
for r in web_results]
)
system_message = {
"role": "system",
"content": (
"You are a helpful assistant. Use the web results and their credibility scores to answer the user's question. "
"Highlight the credibility rating in your answer.\n\n"
f"{context}"
)
}
messages = [system_message] + st.session_state.messages
# Generate GPT response
try:
with st.spinner("πŸ€– Generating answer..."):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages
)
msg = response.choices[0].message.content
except Exception as e:
msg = f"Error with OpenAI API: {e}"
st.session_state.messages.append({"role": "assistant", "content": msg})
st.chat_message("assistant").write(msg)