Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +49 -39
src/streamlit_app.py
CHANGED
|
@@ -28,6 +28,7 @@ st.markdown("""
|
|
| 28 |
border-radius: 5px;
|
| 29 |
height: 3em;
|
| 30 |
background-color: #f0f2f6;
|
|
|
|
| 31 |
}
|
| 32 |
.reportview-container {
|
| 33 |
background: #ffffff;
|
|
@@ -35,22 +36,34 @@ st.markdown("""
|
|
| 35 |
</style>
|
| 36 |
""", unsafe_allow_html=True)
|
| 37 |
|
| 38 |
-
# Ensure keys exist
|
| 39 |
if "OPENAI_API_KEY" not in os.environ:
|
| 40 |
st.error("โ OPENAI_API_KEY missing. Please check Space Settings.")
|
| 41 |
st.stop()
|
| 42 |
|
| 43 |
-
# --- 2. DATA MODELS ---
|
| 44 |
class AgentResponse(BaseModel):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
answer: str
|
| 46 |
sources: List[str]
|
| 47 |
context_used: List[str]
|
| 48 |
|
| 49 |
class TickerExtraction(BaseModel):
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
class RoutePrediction(BaseModel):
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
# --- 3. CACHED INITIALIZATION ---
|
| 56 |
@st.cache_resource(show_spinner=False)
|
|
@@ -86,6 +99,9 @@ def initialize_resources():
|
|
| 86 |
|
| 87 |
return nasdaq_df, index
|
| 88 |
|
|
|
|
|
|
|
|
|
|
| 89 |
# --- 4. HELPER FUNCTIONS ---
|
| 90 |
def get_symbol_from_csv(query_str: str, df) -> Optional[str]:
|
| 91 |
if df.empty: return None
|
|
@@ -206,31 +222,15 @@ def run_agent(user_query: str, index, df) -> AgentResponse:
|
|
| 206 |
# --- 7. UI LOGIC ---
|
| 207 |
with st.sidebar:
|
| 208 |
st.image("https://img.icons8.com/color/96/000000/bullish.png", width=80)
|
| 209 |
-
st.title("System Status")
|
| 210 |
|
| 211 |
-
|
| 212 |
-
nasdaq_df, pinecone_index = initialize_resources()
|
| 213 |
-
|
| 214 |
-
if not nasdaq_df.empty:
|
| 215 |
-
st.success(f"โ
Market Data: {len(nasdaq_df):,} Tickers")
|
| 216 |
-
else:
|
| 217 |
-
st.warning("โ ๏ธ Market Data: Offline")
|
| 218 |
-
|
| 219 |
-
if pinecone_index:
|
| 220 |
-
st.success("โ
Knowledge Base: Online")
|
| 221 |
-
else:
|
| 222 |
-
st.error("โ Knowledge Base: Offline")
|
| 223 |
-
|
| 224 |
-
st.markdown("---")
|
| 225 |
-
st.markdown("### ๐ง Capabilities")
|
| 226 |
|
| 227 |
st.info("**Deep Dive (10-K Reports)**")
|
| 228 |
-
st.markdown("
|
| 229 |
-
st.
|
| 230 |
|
| 231 |
-
st.
|
| 232 |
-
st.markdown("-
|
| 233 |
-
st.caption("*Ask about Price, PE Ratio, Volume*")
|
| 234 |
|
| 235 |
st.markdown("---")
|
| 236 |
if st.button("๐งน Clear Conversation"):
|
|
@@ -240,19 +240,30 @@ with st.sidebar:
|
|
| 240 |
# Main Hero Section
|
| 241 |
st.title("๐๏ธ Wall St. AI Analyst")
|
| 242 |
st.markdown("""
|
| 243 |
-
|
|
|
|
| 244 |
""")
|
| 245 |
|
| 246 |
-
#
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
# Chat State
|
| 258 |
if "messages" not in st.session_state:
|
|
@@ -272,7 +283,6 @@ for message in st.session_state.messages:
|
|
| 272 |
|
| 273 |
# Handle Input (Button or Text)
|
| 274 |
if user_input := st.chat_input("Ask a financial question...") or prompt:
|
| 275 |
-
# If button was clicked, override text input
|
| 276 |
final_query = prompt if prompt else user_input
|
| 277 |
|
| 278 |
st.session_state.messages.append({"role": "user", "content": final_query})
|
|
@@ -280,7 +290,7 @@ if user_input := st.chat_input("Ask a financial question...") or prompt:
|
|
| 280 |
st.markdown(final_query)
|
| 281 |
|
| 282 |
with st.chat_message("assistant"):
|
| 283 |
-
#
|
| 284 |
with st.status("๐ง Analyzing 10-Ks and Market Data...", expanded=True) as status:
|
| 285 |
try:
|
| 286 |
response = run_agent(final_query, pinecone_index, nasdaq_df)
|
|
@@ -290,7 +300,7 @@ if user_input := st.chat_input("Ask a financial question...") or prompt:
|
|
| 290 |
status.update(label="โ Error", state="error")
|
| 291 |
st.stop()
|
| 292 |
|
| 293 |
-
#
|
| 294 |
st.markdown(response.answer)
|
| 295 |
|
| 296 |
# Sources (Collapsible)
|
|
|
|
| 28 |
border-radius: 5px;
|
| 29 |
height: 3em;
|
| 30 |
background-color: #f0f2f6;
|
| 31 |
+
border: 1px solid #d1d5db;
|
| 32 |
}
|
| 33 |
.reportview-container {
|
| 34 |
background: #ffffff;
|
|
|
|
| 36 |
</style>
|
| 37 |
""", unsafe_allow_html=True)
|
| 38 |
|
|
|
|
| 39 |
if "OPENAI_API_KEY" not in os.environ:
|
| 40 |
st.error("โ OPENAI_API_KEY missing. Please check Space Settings.")
|
| 41 |
st.stop()
|
| 42 |
|
| 43 |
+
# --- 2. DATA MODELS (WITH REQUIRED DOCSTRINGS) ---
|
| 44 |
class AgentResponse(BaseModel):
|
| 45 |
+
"""
|
| 46 |
+
Structured output for the financial agent.
|
| 47 |
+
Contains the synthesized natural language answer, the list of cited sources,
|
| 48 |
+
and the raw context chunks used to formulate the answer.
|
| 49 |
+
"""
|
| 50 |
answer: str
|
| 51 |
sources: List[str]
|
| 52 |
context_used: List[str]
|
| 53 |
|
| 54 |
class TickerExtraction(BaseModel):
|
| 55 |
+
"""
|
| 56 |
+
Extracts a list of stock tickers or company names mentioned in the user's query.
|
| 57 |
+
Used to identify which companies the user wants to research.
|
| 58 |
+
"""
|
| 59 |
+
symbols: List[str] = Field(description="List of stock tickers or company names.")
|
| 60 |
|
| 61 |
class RoutePrediction(BaseModel):
|
| 62 |
+
"""
|
| 63 |
+
Determines which tools to use based on the user's query.
|
| 64 |
+
Can select multiple tools if the query requires both financial RAG and market data.
|
| 65 |
+
"""
|
| 66 |
+
tools: List[Literal["financial_rag", "market_data", "general_chat"]] = Field(description="List of selected tools.")
|
| 67 |
|
| 68 |
# --- 3. CACHED INITIALIZATION ---
|
| 69 |
@st.cache_resource(show_spinner=False)
|
|
|
|
| 99 |
|
| 100 |
return nasdaq_df, index
|
| 101 |
|
| 102 |
+
# Silently load resources
|
| 103 |
+
nasdaq_df, pinecone_index = initialize_resources()
|
| 104 |
+
|
| 105 |
# --- 4. HELPER FUNCTIONS ---
|
| 106 |
def get_symbol_from_csv(query_str: str, df) -> Optional[str]:
|
| 107 |
if df.empty: return None
|
|
|
|
| 222 |
# --- 7. UI LOGIC ---
|
| 223 |
with st.sidebar:
|
| 224 |
st.image("https://img.icons8.com/color/96/000000/bullish.png", width=80)
|
|
|
|
| 225 |
|
| 226 |
+
st.markdown("### ๐ง Agent Capabilities")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
| 228 |
st.info("**Deep Dive (10-K Reports)**")
|
| 229 |
+
st.markdown("I have ingested the full SEC 10-K filings for the following companies:")
|
| 230 |
+
st.markdown("- ๐ **Apple (AAPL)**\n- ๐ **Tesla (TSLA)**\n- ๐ฎ **Nvidia (NVDA)**")
|
| 231 |
|
| 232 |
+
st.success("**Live Market Data**")
|
| 233 |
+
st.markdown("I can fetch real-time trading metrics for **all companies listed on the NASDAQ**.")
|
|
|
|
| 234 |
|
| 235 |
st.markdown("---")
|
| 236 |
if st.button("๐งน Clear Conversation"):
|
|
|
|
| 240 |
# Main Hero Section
|
| 241 |
st.title("๐๏ธ Wall St. AI Analyst")
|
| 242 |
st.markdown("""
|
| 243 |
+
Welcome! This hybrid AI agent bridges the gap between **Real-Time Market Data** and **Deep 10-K Analysis**.
|
| 244 |
+
It utilizes a dynamic routing engine to fetch real-time quantitative metrics via `yfinance` and qualitative insights from a Pinecone Vector Database.
|
| 245 |
""")
|
| 246 |
|
| 247 |
+
# Sample Questions Section
|
| 248 |
+
with st.expander("๐ก View Sample Questions", expanded=True):
|
| 249 |
+
st.markdown("""
|
| 250 |
+
**Try asking about Qualitative 10-K Data:**
|
| 251 |
+
* *"What are the primary supply chain risks mentioned in Apple's latest 10-K?"*
|
| 252 |
+
* *"Who is the CEO of Nvidia and what is their strategy?"*
|
| 253 |
+
|
| 254 |
+
**Try asking for Real-Time Quantitative Data:**
|
| 255 |
+
* *"What is the current PE ratio and market cap of Tesla?"*
|
| 256 |
+
* *"Fetch the trading volume and 52-week high for Microsoft."*
|
| 257 |
+
|
| 258 |
+
**Try a Hybrid Search (Live Data + RAG):**
|
| 259 |
+
* *"Compare the competitive threats facing Tesla with its current stock price."*
|
| 260 |
+
""")
|
| 261 |
+
|
| 262 |
+
# Single Automated Action Button
|
| 263 |
+
if st.button("๐ Auto-Run a Complex Query: Compare Apple & Tesla Risks"):
|
| 264 |
+
prompt = "Compare the supply chain risks of Apple and Tesla."
|
| 265 |
+
else:
|
| 266 |
+
prompt = None
|
| 267 |
|
| 268 |
# Chat State
|
| 269 |
if "messages" not in st.session_state:
|
|
|
|
| 283 |
|
| 284 |
# Handle Input (Button or Text)
|
| 285 |
if user_input := st.chat_input("Ask a financial question...") or prompt:
|
|
|
|
| 286 |
final_query = prompt if prompt else user_input
|
| 287 |
|
| 288 |
st.session_state.messages.append({"role": "user", "content": final_query})
|
|
|
|
| 290 |
st.markdown(final_query)
|
| 291 |
|
| 292 |
with st.chat_message("assistant"):
|
| 293 |
+
# The spinner happens here
|
| 294 |
with st.status("๐ง Analyzing 10-Ks and Market Data...", expanded=True) as status:
|
| 295 |
try:
|
| 296 |
response = run_agent(final_query, pinecone_index, nasdaq_df)
|
|
|
|
| 300 |
status.update(label="โ Error", state="error")
|
| 301 |
st.stop()
|
| 302 |
|
| 303 |
+
# The answer prints outside the status block so it is immediately visible!
|
| 304 |
st.markdown(response.answer)
|
| 305 |
|
| 306 |
# Sources (Collapsible)
|