mishrabp commited on
Commit
f89e481
·
verified ·
1 Parent(s): d087573

Upload folder using huggingface_hub

Browse files
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use official Python slim image
2
+ FROM python:3.11-slim
3
+
4
+ # Set environment variables
5
+ ENV PYTHONUNBUFFERED=1 \
6
+ PIP_NO_CACHE_DIR=1 \
7
+ DEBIAN_FRONTEND=noninteractive
8
+
9
+ # Set working directory
10
+ WORKDIR /app
11
+
12
+ # Install system dependencies
13
+ RUN apt-get update && apt-get install -y \
14
+ git \
15
+ build-essential \
16
+ curl \
17
+ && rm -rf /var/lib/apt/lists/*
18
+
19
+ # Copy requirements file
20
+ COPY requirements.txt .
21
+
22
+ # Install Python dependencies
23
+ RUN pip install --upgrade pip
24
+ RUN pip install -r requirements.txt
25
+
26
+ # Copy the rest of the app
27
+ COPY . .
28
+
29
+ # Expose port for Streamlit
30
+ EXPOSE 7860
31
+
32
+ # Command to run the Streamlit app
33
+ CMD ["streamlit", "run", "ui/app.py", "--server.port=7860", "--server.address=0.0.0.0", "--server.headless=true"]
README.md CHANGED
@@ -1,10 +1,18 @@
1
- ---
2
- title: Chatbot App
3
- emoji: 🦀
4
- colorFrom: gray
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
1
+ # Market Research AI Chatbot
2
+
3
+ This is a Streamlit-based AI chatbot for market research. It supports:
4
+
5
+ - Predefined prompts in the sidebar (with truncated display and tooltip for full text)
6
+ - Chat interface with latest messages at the top
7
+ - Enter key submission for messages
8
+ - AI responses using `MarketResearchAgent`
9
+
10
+ ## 🚀 How to Run Locally
11
+
12
+ 1. Clone the repo:
13
+
14
+ ```bash
15
+ git clone <repo_url>
16
+ cd <repo_folder>
17
+ python run.py
18
+ ```
SETUP.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### Setting up .venv
2
+ ```bash
3
+ conda create --prefix /home/azureuser/ws/agenticai/projects/chatbot/.venv python=3.11 -y
4
+
5
+ conda activate /home/azureuser/ws/agenticai/projects/chatbot/.venv
6
+
7
+ conda deactivate
8
+
9
+ uv pip install -r requirements.txt
10
+ ```
11
+
12
+ ### Run Unit Tests
13
+ ```bash
14
+ pytest -v tests/test_data_agent.py
15
+
16
+ python -m pytest -v
17
+
18
+ ```
appagents/__init__.py ADDED
File without changes
appagents/research_agent.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from tools.google_tools import GoogleTools
2
+ from tools.news_tools import NewsTools
3
+ from tools.yahoo_tools import FinanceTools
4
+ from agents import Agent
5
+
6
+ class MarketResearchAgent:
7
+ """
8
+ Encapsulates the AI agent definition for market research.
9
+ """
10
+
11
+ @staticmethod
12
+ def create():
13
+ """
14
+ Returns a configured Agent instance ready for use.
15
+ """
16
+ tools = [
17
+ GoogleTools.search,
18
+ FinanceTools.get_summary,
19
+ FinanceTools.get_history,
20
+ NewsTools.top_headlines,
21
+ NewsTools.search_news,
22
+ ]
23
+
24
+ instructions = """
25
+ You are a research assistant. Your primary goal is to give accurate, concise, and well-sourced answers.
26
+
27
+ If the question involves current events, factual data, or recent updates, always call the provided tools (like Google or News) to ensure information is current.
28
+
29
+ Prefer factual precision over opinion. Summarize retrieved results into clear, user-friendly responses.
30
+ """
31
+
32
+ agent = Agent(
33
+ name="AI Assistant",
34
+ tools=tools,
35
+ instructions=instructions,
36
+ model="gpt-4o-mini"
37
+ )
38
+ return agent
core/__init__.py ADDED
File without changes
core/logger.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import datetime
3
+
4
+ def log_call(func):
5
+ """
6
+ A decorator that logs when a function is called and when it finishes.
7
+ """
8
+ @functools.wraps(func)
9
+ def wrapper(*args, **kwargs):
10
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
11
+ arg_list = ", ".join(
12
+ [repr(a) for a in args] + [f"{k}={v!r}" for k, v in kwargs.items()]
13
+ )
14
+ print(f"[{timestamp}] 🚀 Calling: {func.__name__}({arg_list})")
15
+ try:
16
+ result = func(*args, **kwargs)
17
+ print(f"[{timestamp}] ✅ Finished: {func.__name__}")
18
+ return result
19
+ except Exception as e:
20
+ print(f"[{timestamp}] ❌ Error in {func.__name__}: {e}")
21
+ raise
22
+ return wrapper
prompts/company_analysis.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Analyze the recent performance of Apple Inc. (AAPL).
2
+ Include stock price trends, recent news, and any important announcements.
prompts/crypto_update.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Give a summary of the current cryptocurrency market.
2
+ Include top performers, losers, and overall market sentiment.
prompts/economic_news.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Provide an update on major economic indicators released today.
2
+ Include interest rates, unemployment data, and other relevant statistics.
prompts/market_overview.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Provide a concise overview of the current stock market trends and sentiment today.
2
+ Include any notable market-moving news or events.
prompts/market_sentiment.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ What is the current market sentiment based on news and financial reports?
2
+ Provide a brief analysis of investor confidence and market outlook.
prompts/tech_news.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Summarize the latest technology news from the past 24 hours.
2
+ Highlight any major product launches, acquisitions, or trends.
requirements.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ openai==1.85.0
2
+ # via
3
+ # agents (pyproject.toml)
4
+ # autogen-ext
5
+ # langchain-openai
6
+ # openai-agents
7
+ # semantic-kernel
8
+ openai-agents==0.0.17
9
+ # via agents (pyproject.toml)
10
+ python-dotenv>=1.0.1
11
+ requests>=2.31.0
12
+ # via
13
+ # agents (pyproject.toml)
14
+ # autogen-ext
15
+ # langchain-openai
16
+ # openai-agents
17
+ # semantic-kernel
18
+ yfinance>=0.2.27
19
+ # via tools/news_tools.py, tools/yahoo_tools.py
20
+ streamlit
run.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+
4
+ # Run the Streamlit app with automatic reload on file changes
5
+ subprocess.run([
6
+ "streamlit",
7
+ "run",
8
+ os.path.join("ui", "app.py"),
9
+ "--server.runOnSave", "true"
10
+ ])
tools/__init__.py ADDED
File without changes
tools/google_tools.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ import yfinance as yf
4
+ from dotenv import load_dotenv
5
+ from agents import function_tool
6
+ from core.logger import log_call
7
+
8
+ # Load environment variables once
9
+ load_dotenv()
10
+
11
+
12
+ # ============================================================
13
+ # 🔹 GOOGLE SEARCH TOOLSET (Serper.dev API)
14
+ # ============================================================
15
+ class GoogleTools:
16
+ """Provides tools for web search using Serper.dev (Google Search API)."""
17
+
18
+ @staticmethod
19
+ @function_tool
20
+ @log_call
21
+ def search(query: str, num_results: int = 3) -> str:
22
+ """
23
+ Perform a general Google search using the Serper.dev API.
24
+
25
+ Args:
26
+ query (str): The search query string.
27
+ num_results (int): Number of results to return.
28
+
29
+ Returns:
30
+ str: Formatted search results.
31
+ """
32
+ try:
33
+ api_key = os.getenv("SERPER_API_KEY")
34
+ if not api_key:
35
+ return "Missing SERPER_API_KEY in environment variables."
36
+
37
+ url = "https://google.serper.dev/search"
38
+ headers = {"X-API-KEY": api_key, "Content-Type": "application/json"}
39
+ payload = {"q": query, "num": num_results}
40
+
41
+ response = requests.post(url, headers=headers, json=payload)
42
+ response.raise_for_status()
43
+ data = response.json()
44
+
45
+ if "organic" not in data:
46
+ return "No results found."
47
+
48
+ formatted_results = [
49
+ f"Title: {item.get('title')}\n"
50
+ f"Link: {item.get('link')}\n"
51
+ f"Snippet: {item.get('snippet', '')}\n"
52
+ for item in data["organic"][:num_results]
53
+ ]
54
+ return "\n".join(formatted_results)
55
+
56
+ except Exception as e:
57
+ return f"Error performing Google search: {e}"
58
+
59
+
60
+ # # ============================================================
61
+ # # 🔹 OPENAI & OTHER MODEL APIs (optional future grouping)
62
+ # # ============================================================
63
+ # class ModelTools:
64
+ # """Provides access to LLM APIs like OpenAI, Gemini, or Groq."""
65
+
66
+ # @staticmethod
67
+ # @function_tool
68
+ # def query_openai(prompt: str, model: str = "gpt-4o-mini") -> str:
69
+ # """Query OpenAI model."""
70
+ # try:
71
+ # client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
72
+ # response = client.chat.completions.create(
73
+ # model=model,
74
+ # messages=[{"role": "user", "content": prompt}],
75
+ # )
76
+ # return response.choices[0].message.content
77
+ # except Exception as e:
78
+ # return f"Error querying OpenAI API: {e}"
tools/news_tools.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ import yfinance as yf
4
+ from dotenv import load_dotenv
5
+ from agents import function_tool
6
+ from core.logger import log_call
7
+
8
+ # Load environment variables once
9
+ load_dotenv()
10
+
11
+
12
+ # ============================================================
13
+ # 🔹 NEWS TOOLSET (NewsAPI.org)
14
+ # ============================================================
15
+ class NewsTools:
16
+ """Provides tools to fetch top headlines and topic-based news."""
17
+
18
+ @staticmethod
19
+ @function_tool
20
+ @log_call
21
+ def top_headlines(country: str = "us", num_results: int = 5) -> str:
22
+ """Fetch top headlines for a given country."""
23
+ return NewsTools._fetch_news(query="", country=country, num_results=num_results)
24
+
25
+ @staticmethod
26
+ @function_tool
27
+ @log_call
28
+ def search_news(query: str, num_results: int = 5) -> str:
29
+ """Search for recent news articles about a specific topic."""
30
+ return NewsTools._fetch_news(query=query, country="", num_results=num_results)
31
+
32
+ @staticmethod
33
+ @log_call
34
+ def _fetch_news(query: str, country: str, num_results: int) -> str:
35
+ """Internal helper for NewsAPI.org."""
36
+ try:
37
+ api_key = os.getenv("NEWS_API_KEY")
38
+ if not api_key:
39
+ return "Missing NEWS_API_KEY in environment variables."
40
+
41
+ if query:
42
+ url = "https://newsapi.org/v2/everything"
43
+ params = {
44
+ "q": query,
45
+ "pageSize": num_results,
46
+ "apiKey": api_key,
47
+ "sortBy": "publishedAt",
48
+ "language": "en"
49
+ }
50
+ else:
51
+ url = "https://newsapi.org/v2/top-headlines"
52
+ params = {
53
+ "country": country or "us",
54
+ "pageSize": num_results,
55
+ "apiKey": api_key
56
+ }
57
+
58
+ response = requests.get(url, params=params)
59
+ response.raise_for_status()
60
+ data = response.json()
61
+
62
+ if not data.get("articles"):
63
+ return f"No news found for '{query or country}'."
64
+
65
+ formatted = [
66
+ f"📰 {a.get('title')}\n"
67
+ f" Source: {a.get('source', {}).get('name')}\n"
68
+ f" URL: {a.get('url')}\n"
69
+ for a in data["articles"][:num_results]
70
+ ]
71
+ return "\n".join(formatted)
72
+
73
+ except requests.exceptions.RequestException as e:
74
+ return f"Network error while calling News API: {e}"
75
+ except Exception as e:
76
+ return f"Unexpected error fetching news: {e}"
tools/yahoo_tools.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ import yfinance as yf
4
+ from dotenv import load_dotenv
5
+ from agents import function_tool
6
+ from core.logger import log_call
7
+
8
+ # Load environment variables once
9
+ load_dotenv()
10
+
11
+
12
+ # ============================================================
13
+ # 🔹 YAHOO FINANCE TOOLSET
14
+ # ============================================================
15
+ class FinanceTools:
16
+ """Provides tools for fetching stock, crypto, or ETF data from Yahoo Finance."""
17
+
18
+ @staticmethod
19
+ @function_tool
20
+ @log_call
21
+ def get_summary(symbol: str, period: str = "1d", interval: str = "1h") -> str:
22
+ """Fetch summary and price data for a ticker."""
23
+ try:
24
+ ticker = yf.Ticker(symbol)
25
+ data = ticker.history(period=period, interval=interval)
26
+
27
+ if data.empty:
28
+ return f"No data found for symbol '{symbol}'."
29
+
30
+ latest = data.iloc[-1]
31
+ current_price = round(latest["Close"], 2)
32
+ open_price = round(latest["Open"], 2)
33
+ change = round(current_price - open_price, 2)
34
+ pct_change = round((change / open_price) * 100, 2)
35
+
36
+ info = ticker.info
37
+ long_name = info.get("longName", symbol)
38
+ currency = info.get("currency", "USD")
39
+
40
+ formatted = [
41
+ f"📈 {long_name} ({symbol})",
42
+ f"Current Price: {current_price} {currency}",
43
+ f"Change: {change} ({pct_change}%)",
44
+ f"Open: {open_price} | High: {round(latest['High'], 2)} | Low: {round(latest['Low'], 2)}",
45
+ f"Volume: {int(latest['Volume'])}",
46
+ f"Period: {period} | Interval: {interval}",
47
+ ]
48
+ return "\n".join(formatted)
49
+
50
+ except Exception as e:
51
+ return f"Error fetching data for '{symbol}': {e}"
52
+
53
+ @staticmethod
54
+ @function_tool
55
+ @log_call
56
+ def get_history(symbol: str, period: str = "1mo") -> str:
57
+ """Fetch historical data for a given ticker."""
58
+ try:
59
+ ticker = yf.Ticker(symbol)
60
+ data = ticker.history(period=period)
61
+ if data.empty:
62
+ return f"No historical data found for '{symbol}'."
63
+ return f"Historical data for {symbol} ({period}):\n{data.tail(5).to_string()}"
64
+ except Exception as e:
65
+ return f"Error fetching historical data: {e}"
ui/app.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+ import glob
4
+ import asyncio
5
+ import sys
6
+
7
+ # Add project root to sys.path
8
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
9
+
10
+ from appagents.research_agent import MarketResearchAgent
11
+ from agents import Runner, trace
12
+
13
+ # -----------------------------
14
+ # Load predefined prompts
15
+ # -----------------------------
16
+ def load_prompts(folder="prompts"):
17
+ prompts = []
18
+ for file_path in glob.glob(os.path.join(folder, "*.txt")):
19
+ with open(file_path, "r") as f:
20
+ content = f.read().strip()
21
+ if content:
22
+ prompts.append(content)
23
+ return prompts
24
+
25
+ prompts = load_prompts()
26
+
27
+ # -----------------------------
28
+ # Streamlit page config
29
+ # -----------------------------
30
+ st.set_page_config(page_title="Market Research AI", layout="wide")
31
+
32
+ # -----------------------------
33
+ # Session state
34
+ # -----------------------------
35
+ if "chat_history" not in st.session_state:
36
+ st.session_state.chat_history = []
37
+
38
+ if "input_value" not in st.session_state:
39
+ st.session_state.input_value = ""
40
+
41
+ # -----------------------------
42
+ # Function to fetch AI response
43
+ # -----------------------------
44
+ async def get_ai_response(prompt):
45
+ agent = MarketResearchAgent.create()
46
+ with trace("Chatting with AI"):
47
+ result = await Runner.run(agent, prompt)
48
+ return result.final_output
49
+
50
+ # -----------------------------
51
+ # Sidebar prompts (2-line truncation with tooltip)
52
+ # -----------------------------
53
+ st.sidebar.title("Predefined Prompts")
54
+
55
+ for idx, prompt_text in enumerate(prompts):
56
+ # Truncate to 2 lines (~80 characters per line)
57
+ truncated = prompt_text
58
+ if len(prompt_text) > 160:
59
+ truncated = prompt_text[:157] + "..."
60
+ # Use a container for each button
61
+ with st.sidebar.container():
62
+ # Show the truncated prompt as a button
63
+ if st.button(truncated, key=f"prompt_{idx}"):
64
+ # Add user message
65
+ st.session_state.chat_history.insert(0, {"role": "user", "message": prompt_text})
66
+ # Fetch AI response
67
+ response = asyncio.run(get_ai_response(prompt_text))
68
+ st.session_state.chat_history.insert(0, {"role": "assistant", "message": response})
69
+ # Show tooltip below (hover shows full prompt)
70
+ st.markdown(f"<span title='{prompt_text}' style='font-size:10px;color:gray;'>Hover to see full prompt</span>", unsafe_allow_html=True)
71
+
72
+
73
+ # -----------------------------
74
+ # Main chat area
75
+ # -----------------------------
76
+ st.title("Market Research AI Chat")
77
+
78
+ # -----------------------------
79
+ # Chat input with Enter key submit (using st.form)
80
+ # -----------------------------
81
+ with st.form(key="chat_form", clear_on_submit=True):
82
+ user_input = st.text_input(
83
+ "Type your message here:",
84
+ value=st.session_state.input_value,
85
+ placeholder="Write a message...",
86
+ key="chat_input"
87
+ )
88
+ send_button = st.form_submit_button("Send")
89
+
90
+ if send_button and user_input.strip():
91
+ message = user_input.strip()
92
+ # Add user message
93
+ st.session_state.chat_history.insert(0, {"role": "user", "message": message})
94
+ # Get AI response
95
+ response = asyncio.run(get_ai_response(message))
96
+ st.session_state.chat_history.insert(0, {"role": "assistant", "message": response})
97
+ st.session_state.input_value = "" # input will be cleared automatically by form
98
+
99
+ # -----------------------------
100
+ # Display chat history
101
+ # -----------------------------
102
+ if st.session_state.chat_history:
103
+ chat_style = """
104
+ <style>
105
+ .chat-container {
106
+ display: flex;
107
+ flex-direction: column; /* latest messages on top */
108
+ border: 1px solid #ccc;
109
+ padding: 10px;
110
+ border-radius: 8px;
111
+ background-color: #fafafa;
112
+ }
113
+ </style>
114
+ """
115
+ st.markdown(chat_style, unsafe_allow_html=True)
116
+
117
+ chat_html = '<div class="chat-container">'
118
+ for chat in st.session_state.chat_history:
119
+ if chat["role"] == "user":
120
+ chat_html += (
121
+ f"<div style='display:flex; align-items:center; justify-content:flex-end; margin:5px;'>"
122
+ f"<div style='background-color: #daf1fc; padding:10px; border-radius:10px; max-width:70%;'>{chat['message']}</div>"
123
+ f"<span style='font-size:28px; margin-left:8px;'>👤</span>"
124
+ f"</div>"
125
+ )
126
+ else:
127
+ chat_html += (
128
+ f"<div style='display:flex; align-items:center; justify-content:flex-start; margin:5px;'>"
129
+ f"<span style='font-size:28px; margin-right:8px;'>🤖</span>"
130
+ f"<div style='background-color: #f1f0f0; padding:10px; border-radius:10px; max-width:70%;'>{chat['message']}</div>"
131
+ f"</div>"
132
+ )
133
+ chat_html += '</div>'
134
+
135
+ st.markdown(chat_html, unsafe_allow_html=True)