Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- common/aagents/google_agent.py +1 -0
- common/aagents/healthcare_agent.py +3 -2
- common/aagents/news_agent.py +7 -16
- common/aagents/search_agent.py +51 -0
- common/aagents/weather_agent.py +1 -4
- common/aagents/web_research_agent.py +1 -2
- common/aagents/yf_agent.py +17 -11
- common/mcp/tools/time_tools.py +1 -0
- common/mcp/tools/yf_tools.py +65 -0
- src/deep-research/app.py +60 -54
common/aagents/google_agent.py
CHANGED
|
@@ -135,5 +135,6 @@ google_agent = Agent(
|
|
| 135 |
- Respect timeouts and handle errors gracefully
|
| 136 |
""",
|
| 137 |
)
|
|
|
|
| 138 |
|
| 139 |
__all__ = ["google_agent", "google_search", "google_search_recent", "duckduckgo_search", "fetch_page_content", "current_datetime"]
|
|
|
|
| 135 |
- Respect timeouts and handle errors gracefully
|
| 136 |
""",
|
| 137 |
)
|
| 138 |
+
google_agent.description = "A Google search agent that finds accurate, up-to-date information and recent news using Google Search."
|
| 139 |
|
| 140 |
__all__ = ["google_agent", "google_search", "google_search_recent", "duckduckgo_search", "fetch_page_content", "current_datetime"]
|
common/aagents/healthcare_agent.py
CHANGED
|
@@ -6,7 +6,7 @@ from openai import AsyncOpenAI
|
|
| 6 |
|
| 7 |
# Import tools
|
| 8 |
from mcp.tools.rag_tool import rag_search, UserContext
|
| 9 |
-
from mcp.tools.search_tools import duckduckgo_search
|
| 10 |
from mcp.tools.time_tools import current_datetime
|
| 11 |
|
| 12 |
|
|
@@ -34,7 +34,7 @@ groq_model = OpenAIChatCompletionsModel(model="groq/compound", openai_client=gro
|
|
| 34 |
healthcare_agent = Agent[UserContext](
|
| 35 |
name="HealthcareRAGAgent",
|
| 36 |
model=gemini_model,
|
| 37 |
-
tools=[rag_search, duckduckgo_search],
|
| 38 |
instructions="""
|
| 39 |
You are a healthcare information retrieval agent. You retrieve information from tools and synthesize it into well-formatted markdown responses.
|
| 40 |
|
|
@@ -96,5 +96,6 @@ healthcare_agent = Agent[UserContext](
|
|
| 96 |
- Accept useless RAG results without calling web search
|
| 97 |
""",
|
| 98 |
)
|
|
|
|
| 99 |
|
| 100 |
__all__ = ["healthcare_agent"]
|
|
|
|
| 6 |
|
| 7 |
# Import tools
|
| 8 |
from mcp.tools.rag_tool import rag_search, UserContext
|
| 9 |
+
from mcp.tools.search_tools import duckduckgo_search, fetch_page_content
|
| 10 |
from mcp.tools.time_tools import current_datetime
|
| 11 |
|
| 12 |
|
|
|
|
| 34 |
healthcare_agent = Agent[UserContext](
|
| 35 |
name="HealthcareRAGAgent",
|
| 36 |
model=gemini_model,
|
| 37 |
+
tools=[rag_search, duckduckgo_search, fetch_page_content],
|
| 38 |
instructions="""
|
| 39 |
You are a healthcare information retrieval agent. You retrieve information from tools and synthesize it into well-formatted markdown responses.
|
| 40 |
|
|
|
|
| 96 |
- Accept useless RAG results without calling web search
|
| 97 |
""",
|
| 98 |
)
|
| 99 |
+
healthcare_agent.description = "A healthcare agent that combines RAG (Retrieval Augmented Generation) with web search to answer medical questions."
|
| 100 |
|
| 101 |
__all__ = ["healthcare_agent"]
|
common/aagents/news_agent.py
CHANGED
|
@@ -3,7 +3,6 @@ import os
|
|
| 3 |
from agents import Agent, OpenAIChatCompletionsModel
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
from mcp.tools.news_tools import get_top_headlines, search_news, get_news_by_category
|
| 6 |
-
from mcp.tools.search_tools import duckduckgo_search
|
| 7 |
from mcp.tools.time_tools import current_datetime
|
| 8 |
from openai import AsyncOpenAI
|
| 9 |
|
|
@@ -25,7 +24,7 @@ groq_model = OpenAIChatCompletionsModel(model="groq/compound", openai_client=gro
|
|
| 25 |
news_agent = Agent(
|
| 26 |
name="NewsAgent",
|
| 27 |
model=gemini_model,
|
| 28 |
-
tools=[current_datetime, get_top_headlines, search_news, get_news_by_category
|
| 29 |
instructions="""
|
| 30 |
You are a NewsAgent specialized in fetching and analyzing recent news articles and headlines.
|
| 31 |
Your role is to provide users with up-to-date, relevant news information from reliable sources.
|
|
@@ -46,13 +45,8 @@ news_agent = Agent(
|
|
| 46 |
- Categories: "business", "entertainment", "general", "health", "science", "sports", "technology"
|
| 47 |
- Input: { "category": "business", "country": "us", "num_results": 5 }
|
| 48 |
|
| 49 |
-
**FALLBACK TOOL (DuckDuckGo Search):**
|
| 50 |
-
4. 'duckduckgo_search': Use ONLY when NewsAPI tools fail or API key is missing
|
| 51 |
-
- Set search_type to "news" for news-specific results
|
| 52 |
-
- Input: { "query": "topic", "max_results": 5, "search_type": "news", "timelimit": "d" }
|
| 53 |
-
|
| 54 |
**TIME CONTEXT:**
|
| 55 |
-
|
| 56 |
- Input: { "format": "natural" }
|
| 57 |
|
| 58 |
## Workflow
|
|
@@ -62,14 +56,11 @@ news_agent = Agent(
|
|
| 62 |
- Topic-specific → use search_news
|
| 63 |
- Category-specific → use get_news_by_category
|
| 64 |
|
| 65 |
-
2. **
|
| 66 |
-
|
| 67 |
-
3. **Fallback if Needed**: If NewsAPI returns an error (missing API key, no results),
|
| 68 |
-
use duckduckgo_search with search_type="news"
|
| 69 |
|
| 70 |
-
|
| 71 |
|
| 72 |
-
|
| 73 |
- Headlines/titles
|
| 74 |
- Sources
|
| 75 |
- Publication dates
|
|
@@ -95,12 +86,12 @@ news_agent = Agent(
|
|
| 95 |
|
| 96 |
- Always cite sources and include publication dates
|
| 97 |
- Prioritize recent news (within last 7 days unless specified otherwise)
|
| 98 |
-
- If API key is missing, inform the user and use the fallback tool
|
| 99 |
- Never fabricate news or sources
|
| 100 |
- Present news objectively without bias
|
| 101 |
- Include URLs so users can read full articles
|
| 102 |
- Use current_datetime to ensure temporal accuracy
|
| 103 |
""",
|
| 104 |
)
|
|
|
|
| 105 |
|
| 106 |
-
__all__ = ["news_agent", "get_top_headlines", "search_news", "get_news_by_category", "
|
|
|
|
| 3 |
from agents import Agent, OpenAIChatCompletionsModel
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
from mcp.tools.news_tools import get_top_headlines, search_news, get_news_by_category
|
|
|
|
| 6 |
from mcp.tools.time_tools import current_datetime
|
| 7 |
from openai import AsyncOpenAI
|
| 8 |
|
|
|
|
| 24 |
news_agent = Agent(
|
| 25 |
name="NewsAgent",
|
| 26 |
model=gemini_model,
|
| 27 |
+
tools=[current_datetime, get_top_headlines, search_news, get_news_by_category],
|
| 28 |
instructions="""
|
| 29 |
You are a NewsAgent specialized in fetching and analyzing recent news articles and headlines.
|
| 30 |
Your role is to provide users with up-to-date, relevant news information from reliable sources.
|
|
|
|
| 45 |
- Categories: "business", "entertainment", "general", "health", "science", "sports", "technology"
|
| 46 |
- Input: { "category": "business", "country": "us", "num_results": 5 }
|
| 47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
**TIME CONTEXT:**
|
| 49 |
+
4. 'current_datetime': Use to provide current date/time context in your responses
|
| 50 |
- Input: { "format": "natural" }
|
| 51 |
|
| 52 |
## Workflow
|
|
|
|
| 56 |
- Topic-specific → use search_news
|
| 57 |
- Category-specific → use get_news_by_category
|
| 58 |
|
| 59 |
+
2. **Execute Search**: Use the appropriate NewsAPI tool.
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
+
3. **Include Time Context**: Use current_datetime to provide temporal context.
|
| 62 |
|
| 63 |
+
4. **Format Response**: Present news in a clear, organized format with:
|
| 64 |
- Headlines/titles
|
| 65 |
- Sources
|
| 66 |
- Publication dates
|
|
|
|
| 86 |
|
| 87 |
- Always cite sources and include publication dates
|
| 88 |
- Prioritize recent news (within last 7 days unless specified otherwise)
|
|
|
|
| 89 |
- Never fabricate news or sources
|
| 90 |
- Present news objectively without bias
|
| 91 |
- Include URLs so users can read full articles
|
| 92 |
- Use current_datetime to ensure temporal accuracy
|
| 93 |
""",
|
| 94 |
)
|
| 95 |
+
news_agent.description = "A news agent that fetches top headlines and searches for news articles by category or topic."
|
| 96 |
|
| 97 |
+
__all__ = ["news_agent", "get_top_headlines", "search_news", "get_news_by_category", "current_datetime"]
|
common/aagents/search_agent.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Search agent module for comprehensive web searches."""
|
| 2 |
+
import os
|
| 3 |
+
from agents import Agent, OpenAIChatCompletionsModel
|
| 4 |
+
from openai import AsyncOpenAI
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
from mcp.tools.search_tools import duckduckgo_search, fetch_page_content
|
| 7 |
+
from mcp.tools.time_tools import current_datetime
|
| 8 |
+
|
| 9 |
+
# ---------------------------------------------------------
|
| 10 |
+
# Load environment variables
|
| 11 |
+
# ---------------------------------------------------------
|
| 12 |
+
load_dotenv()
|
| 13 |
+
|
| 14 |
+
GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
|
| 15 |
+
google_api_key = os.getenv('GOOGLE_API_KEY')
|
| 16 |
+
gemini_client = AsyncOpenAI(base_url=GEMINI_BASE_URL, api_key=google_api_key)
|
| 17 |
+
gemini_model = OpenAIChatCompletionsModel(model="gemini-2.0-flash-exp", openai_client=gemini_client)
|
| 18 |
+
|
| 19 |
+
search_agent = Agent(
|
| 20 |
+
name="Web Search Agent",
|
| 21 |
+
model=gemini_model,
|
| 22 |
+
tools=[current_datetime, duckduckgo_search, fetch_page_content],
|
| 23 |
+
instructions="""
|
| 24 |
+
You are a highly efficient and specialized **Web Search Agent** 🌐. Your sole function is to retrieve and analyze information from the internet using the **duckduckgo_search** and **fetch_page_content** functions. You must act as a digital librarian and researcher, providing synthesized, cited, and up-to-date answers.
|
| 25 |
+
|
| 26 |
+
## Core Directives & Priorities
|
| 27 |
+
1. **Time Awareness First:** ALWAYS invoke **current_datetime** at the very beginning of your execution to establish the current temporal context. This is crucial for answering questions about "today", "yesterday", or recent events.
|
| 28 |
+
2. **Search Strategy:**
|
| 29 |
+
* Analyze the user's request and construct 1-3 targeted search queries.
|
| 30 |
+
* Use **duckduckgo_search** to find relevant information. Use the 'news' type for current events.
|
| 31 |
+
* **Mandatory Deep Dive:** You MUST select the **top 3** most relevant search results and use **fetch_page_content** to retrieve their full text. *Do not rely solely on the short search snippets.*
|
| 32 |
+
3. **Synthesis & Answer Construction:**
|
| 33 |
+
* Read the fetched content thoroughly.
|
| 34 |
+
* Synthesize the information into a coherent answer.
|
| 35 |
+
* **Conflict Resolution:** If sources disagree, note the discrepancy and favor the most recent or authoritative source.
|
| 36 |
+
* **Citations:** You **must** cite your sources. At the end of your response, list the *Title* and *URL* of the pages you used.
|
| 37 |
+
4. **Clarity:** Use professional, plain language. Use headings and bullet points for readability.
|
| 38 |
+
5. **Data Gaps:** If you cannot find a conclusive answer after searching and fetching, state: **"A conclusive answer could not be verified by current web search results."**
|
| 39 |
+
|
| 40 |
+
## Workflow Example
|
| 41 |
+
1. Call `current_datetime()`.
|
| 42 |
+
2. Call `duckduckgo_search(query="...")`.
|
| 43 |
+
3. Loop through top 3 results: `fetch_page_content(url=...)`.
|
| 44 |
+
4. Synthesize findings into final answer.
|
| 45 |
+
|
| 46 |
+
**Crucially, never fabricate information. Your answer must be grounded in the text you have fetched.**
|
| 47 |
+
""",
|
| 48 |
+
)
|
| 49 |
+
search_agent.description = "A web search agent that retrieves information using DuckDuckGo and fetches page content for detailed answers."
|
| 50 |
+
|
| 51 |
+
__all__ = ["search_agent"]
|
common/aagents/weather_agent.py
CHANGED
|
@@ -32,7 +32,6 @@ groq_model = OpenAIChatCompletionsModel(model="groq/compound", openai_client=gro
|
|
| 32 |
weather_agent = Agent(
|
| 33 |
name="WeatherAgent",
|
| 34 |
model=gemini_model, #"gpt-4o-mini",
|
| 35 |
-
# description="An agent that can perform web searches using DuckDuckGo.",
|
| 36 |
tools=[current_datetime, get_weather_forecast, search_weather_fallback_ddgs, search_weather_fallback_bs],
|
| 37 |
instructions="""
|
| 38 |
You are a Weather Forecast agent who forecasts weather information ONLY.
|
|
@@ -61,9 +60,7 @@ weather_agent = Agent(
|
|
| 61 |
}.
|
| 62 |
]
|
| 63 |
""",
|
| 64 |
-
# output_type=AgentOutputSchema(list[searchResult], strict_json_schema=False),
|
| 65 |
-
# output_type=list[dict], # safer than list[searchResult],
|
| 66 |
-
# output_type=list[searchResult],
|
| 67 |
)
|
|
|
|
| 68 |
|
| 69 |
__all__ = ["weather_agent", "get_weather_forecast", "search_weather_fallback_ddgs", "search_weather_fallback_bs"]
|
|
|
|
| 32 |
weather_agent = Agent(
|
| 33 |
name="WeatherAgent",
|
| 34 |
model=gemini_model, #"gpt-4o-mini",
|
|
|
|
| 35 |
tools=[current_datetime, get_weather_forecast, search_weather_fallback_ddgs, search_weather_fallback_bs],
|
| 36 |
instructions="""
|
| 37 |
You are a Weather Forecast agent who forecasts weather information ONLY.
|
|
|
|
| 60 |
}.
|
| 61 |
]
|
| 62 |
""",
|
|
|
|
|
|
|
|
|
|
| 63 |
)
|
| 64 |
+
weather_agent.description = "A weather agent that provides current and forecasted weather information for specific cities."
|
| 65 |
|
| 66 |
__all__ = ["weather_agent", "get_weather_forecast", "search_weather_fallback_ddgs", "search_weather_fallback_bs"]
|
common/aagents/web_research_agent.py
CHANGED
|
@@ -29,9 +29,7 @@ groq_client = AsyncOpenAI(base_url=GROQ_BASE_URL, api_key=groq_api_key)
|
|
| 29 |
groq_model = OpenAIChatCompletionsModel(model="groq/compound", openai_client=groq_client)
|
| 30 |
|
| 31 |
web_research_agent = Agent(
|
| 32 |
-
name="WebResearchAgent",
|
| 33 |
model="gpt-4o-mini",
|
| 34 |
-
# description="An agent that can perform web searches using DuckDuckGo.",
|
| 35 |
tools=[duckduckgo_search, fetch_page_content],
|
| 36 |
instructions="""
|
| 37 |
You are WebResearchAgent — an advanced internet research assistant with two core abilities:
|
|
@@ -79,5 +77,6 @@ IMPORTANT RULES
|
|
| 79 |
"""
|
| 80 |
,
|
| 81 |
)
|
|
|
|
| 82 |
|
| 83 |
__all__ = ["web_research_agent", "duckduckgo_search", "fetch_page_content", "searchQuery", "searchResult"]
|
|
|
|
| 29 |
groq_model = OpenAIChatCompletionsModel(model="groq/compound", openai_client=groq_client)
|
| 30 |
|
| 31 |
web_research_agent = Agent(
|
|
|
|
| 32 |
model="gpt-4o-mini",
|
|
|
|
| 33 |
tools=[duckduckgo_search, fetch_page_content],
|
| 34 |
instructions="""
|
| 35 |
You are WebResearchAgent — an advanced internet research assistant with two core abilities:
|
|
|
|
| 77 |
"""
|
| 78 |
,
|
| 79 |
)
|
| 80 |
+
web_research_agent.description = "A deep research agent that performs extensive web searches and content fetching for complex research queries."
|
| 81 |
|
| 82 |
__all__ = ["web_research_agent", "duckduckgo_search", "fetch_page_content", "searchQuery", "searchResult"]
|
common/aagents/yf_agent.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
import os
|
| 3 |
from agents import Agent, OpenAIChatCompletionsModel
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
-
from mcp.tools.yf_tools import get_summary, get_market_sentiment, get_history
|
| 6 |
from mcp.tools.time_tools import current_datetime
|
| 7 |
from openai import AsyncOpenAI
|
| 8 |
|
|
@@ -24,7 +24,7 @@ groq_model = OpenAIChatCompletionsModel(model="groq/compound", openai_client=gro
|
|
| 24 |
yf_agent = Agent(
|
| 25 |
name="YahooFinanceAgent",
|
| 26 |
model=gemini_model,
|
| 27 |
-
tools=[current_datetime, get_summary, get_market_sentiment, get_history],
|
| 28 |
instructions="""
|
| 29 |
You are a specialized **Financial Analysis Agent** 💰, expert in market research, financial data retrieval, and market analysis.
|
| 30 |
Your primary role is to provide *actionable*, *data-driven*, and *concise* financial reports based on the available tools.
|
|
@@ -35,14 +35,16 @@ yf_agent = Agent(
|
|
| 35 |
Financial data is extremely time-sensitive.
|
| 36 |
|
| 37 |
2. **Financial Data Integrity:** Use the Yahoo Finance tools for specific stock/index data:
|
| 38 |
-
- 'get_summary': Get latest summary information and intraday price data for a ticker
|
| 39 |
-
- 'get_market_sentiment': Analyze recent price changes and provide market sentiment (Bullish/Bearish/Neutral)
|
| 40 |
-
- 'get_history': Fetch historical price data for a given ticker
|
|
|
|
|
|
|
| 41 |
|
| 42 |
Be precise about the date range and data source.
|
| 43 |
|
| 44 |
-
3. **Synthesis and Analysis:** Do not just list data. You must **synthesize** financial data (prices, volume, sentiment)
|
| 45 |
-
to provide a complete analytical perspective (e.g., "Stock X is up 5% today driven by strong market momentum").
|
| 46 |
|
| 47 |
4. **Professional Clarity:** Present information in a clear, professional, and structured format.
|
| 48 |
Use numerical data and financial terminology correctly.
|
|
@@ -62,9 +64,12 @@ yf_agent = Agent(
|
|
| 62 |
|
| 63 |
Tool: get_market_sentiment
|
| 64 |
Input: { "symbol": "AAPL", "period": "1mo" }
|
| 65 |
-
|
| 66 |
-
Tool:
|
| 67 |
-
Input: { "symbol": "AAPL"
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
## Output Format Guidelines
|
| 70 |
|
|
@@ -74,5 +79,6 @@ yf_agent = Agent(
|
|
| 74 |
* Always include a disclaimer: "This analysis is for informational purposes only and is not financial advice."
|
| 75 |
""",
|
| 76 |
)
|
|
|
|
| 77 |
|
| 78 |
-
__all__ = ["yf_agent", "get_summary", "get_market_sentiment", "get_history", "current_datetime"]
|
|
|
|
| 2 |
import os
|
| 3 |
from agents import Agent, OpenAIChatCompletionsModel
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
+
from mcp.tools.yf_tools import get_summary, get_market_sentiment, get_history, get_analyst_recommendations, get_earnings_calendar
|
| 6 |
from mcp.tools.time_tools import current_datetime
|
| 7 |
from openai import AsyncOpenAI
|
| 8 |
|
|
|
|
| 24 |
yf_agent = Agent(
|
| 25 |
name="YahooFinanceAgent",
|
| 26 |
model=gemini_model,
|
| 27 |
+
tools=[current_datetime, get_summary, get_market_sentiment, get_history, get_analyst_recommendations, get_earnings_calendar],
|
| 28 |
instructions="""
|
| 29 |
You are a specialized **Financial Analysis Agent** 💰, expert in market research, financial data retrieval, and market analysis.
|
| 30 |
Your primary role is to provide *actionable*, *data-driven*, and *concise* financial reports based on the available tools.
|
|
|
|
| 35 |
Financial data is extremely time-sensitive.
|
| 36 |
|
| 37 |
2. **Financial Data Integrity:** Use the Yahoo Finance tools for specific stock/index data:
|
| 38 |
+
- 'get_summary': Get latest summary information and intraday price data for a ticker.
|
| 39 |
+
- 'get_market_sentiment': Analyze recent price changes and provide market sentiment (Bullish/Bearish/Neutral).
|
| 40 |
+
- 'get_history': Fetch historical price data for a given ticker.
|
| 41 |
+
- 'get_analyst_recommendations': Fetch latest analyst ratings (Buy/Sell/Hold) for a symbol to provide **trading recommendations**.
|
| 42 |
+
- 'get_earnings_calendar': Fetch upcoming earnings dates for a symbol.
|
| 43 |
|
| 44 |
Be precise about the date range and data source.
|
| 45 |
|
| 46 |
+
3. **Synthesis and Analysis:** Do not just list data. You must **synthesize** financial data (prices, volume, sentiment, recommendations)
|
| 47 |
+
to provide a complete analytical perspective (e.g., "Stock X is up 5% today driven by strong market momentum and a generic 'Buy' rating from analysts").
|
| 48 |
|
| 49 |
4. **Professional Clarity:** Present information in a clear, professional, and structured format.
|
| 50 |
Use numerical data and financial terminology correctly.
|
|
|
|
| 64 |
|
| 65 |
Tool: get_market_sentiment
|
| 66 |
Input: { "symbol": "AAPL", "period": "1mo" }
|
| 67 |
+
|
| 68 |
+
Tool: get_analyst_recommendations
|
| 69 |
+
Input: { "symbol": "AAPL" }
|
| 70 |
+
|
| 71 |
+
Tool: get_earnings_calendar
|
| 72 |
+
Input: { "symbol": "AAPL" }
|
| 73 |
|
| 74 |
## Output Format Guidelines
|
| 75 |
|
|
|
|
| 79 |
* Always include a disclaimer: "This analysis is for informational purposes only and is not financial advice."
|
| 80 |
""",
|
| 81 |
)
|
| 82 |
+
yf_agent.description = "A financial analysis agent that provides stock summaries, market sentiment, and historical data using Yahoo Finance."
|
| 83 |
|
| 84 |
+
__all__ = ["yf_agent", "get_summary", "get_market_sentiment", "get_history", "get_analyst_recommendations", "get_earnings_calendar", "current_datetime"]
|
common/mcp/tools/time_tools.py
CHANGED
|
@@ -18,6 +18,7 @@ def current_datetime(format: str = "natural") -> str:
|
|
| 18 |
Returns:
|
| 19 |
str: Current date and time in the specified format
|
| 20 |
"""
|
|
|
|
| 21 |
now = datetime.now()
|
| 22 |
|
| 23 |
# Natural format options
|
|
|
|
| 18 |
Returns:
|
| 19 |
str: Current date and time in the specified format
|
| 20 |
"""
|
| 21 |
+
print(f"[DEBUG] current_datetime called with format='{format}'")
|
| 22 |
now = datetime.now()
|
| 23 |
|
| 24 |
# Natural format options
|
common/mcp/tools/yf_tools.py
CHANGED
|
@@ -37,6 +37,7 @@ def get_summary(symbol: str, period: str = "1d", interval: str = "1h") -> str:
|
|
| 37 |
- Volume
|
| 38 |
- Period and interval used
|
| 39 |
"""
|
|
|
|
| 40 |
try:
|
| 41 |
ticker = yf.Ticker(symbol)
|
| 42 |
|
|
@@ -109,6 +110,7 @@ def get_market_sentiment(symbol: str, period: str = "1mo") -> str:
|
|
| 109 |
str
|
| 110 |
A human-readable sentiment string including percentage change.
|
| 111 |
"""
|
|
|
|
| 112 |
try:
|
| 113 |
ticker = yf.Ticker(symbol)
|
| 114 |
|
|
@@ -164,6 +166,7 @@ def get_history(symbol: str, period: str = "1mo") -> str:
|
|
| 164 |
str
|
| 165 |
A formatted string showing the last 5 rows of historical prices (Open, High, Low, Close, Volume).
|
| 166 |
"""
|
|
|
|
| 167 |
try:
|
| 168 |
ticker = yf.Ticker(symbol)
|
| 169 |
|
|
@@ -190,3 +193,65 @@ def get_history(symbol: str, period: str = "1mo") -> str:
|
|
| 190 |
|
| 191 |
except Exception as e:
|
| 192 |
return f"Error fetching historical data for '{symbol}': {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
- Volume
|
| 38 |
- Period and interval used
|
| 39 |
"""
|
| 40 |
+
print(f"[DEBUG] get_summary called for symbol='{symbol}', period='{period}', interval='{interval}'")
|
| 41 |
try:
|
| 42 |
ticker = yf.Ticker(symbol)
|
| 43 |
|
|
|
|
| 110 |
str
|
| 111 |
A human-readable sentiment string including percentage change.
|
| 112 |
"""
|
| 113 |
+
print(f"[DEBUG] get_market_sentiment called for symbol='{symbol}', period='{period}'")
|
| 114 |
try:
|
| 115 |
ticker = yf.Ticker(symbol)
|
| 116 |
|
|
|
|
| 166 |
str
|
| 167 |
A formatted string showing the last 5 rows of historical prices (Open, High, Low, Close, Volume).
|
| 168 |
"""
|
| 169 |
+
print(f"[DEBUG] get_history called for symbol='{symbol}', period='{period}'")
|
| 170 |
try:
|
| 171 |
ticker = yf.Ticker(symbol)
|
| 172 |
|
|
|
|
| 193 |
|
| 194 |
except Exception as e:
|
| 195 |
return f"Error fetching historical data for '{symbol}': {e}"
|
| 196 |
+
|
| 197 |
+
@function_tool
|
| 198 |
+
def get_analyst_recommendations(symbol: str) -> str:
|
| 199 |
+
"""
|
| 200 |
+
Fetch analyst recommendations for a given ticker.
|
| 201 |
+
|
| 202 |
+
Parameters:
|
| 203 |
+
-----------
|
| 204 |
+
symbol : str
|
| 205 |
+
The ticker symbol.
|
| 206 |
+
|
| 207 |
+
Returns:
|
| 208 |
+
--------
|
| 209 |
+
str
|
| 210 |
+
Formatted string string of analyst recommendations.
|
| 211 |
+
"""
|
| 212 |
+
print(f"[DEBUG] get_analyst_recommendations called for symbol='{symbol}'")
|
| 213 |
+
try:
|
| 214 |
+
ticker = yf.Ticker(symbol)
|
| 215 |
+
recs = ticker.recommendations
|
| 216 |
+
if recs is None or recs.empty:
|
| 217 |
+
return f"No analyst recommendations found for {symbol}."
|
| 218 |
+
|
| 219 |
+
# Format the last few recommendations
|
| 220 |
+
latest = recs.tail(5)
|
| 221 |
+
return f"Analyst Recommendations for {symbol}:\n{latest.to_string()}"
|
| 222 |
+
except Exception as e:
|
| 223 |
+
return f"Error fetching recommendations for '{symbol}': {e}"
|
| 224 |
+
|
| 225 |
+
@function_tool
|
| 226 |
+
def get_earnings_calendar(symbol: str) -> str:
|
| 227 |
+
"""
|
| 228 |
+
Fetch the next earnings date for a ticker.
|
| 229 |
+
|
| 230 |
+
Parameters:
|
| 231 |
+
-----------
|
| 232 |
+
symbol : str
|
| 233 |
+
The ticker symbol.
|
| 234 |
+
|
| 235 |
+
Returns:
|
| 236 |
+
--------
|
| 237 |
+
str
|
| 238 |
+
Next earnings date info.
|
| 239 |
+
"""
|
| 240 |
+
print(f"[DEBUG] get_earnings_calendar called for symbol='{symbol}'")
|
| 241 |
+
try:
|
| 242 |
+
ticker = yf.Ticker(symbol)
|
| 243 |
+
calendar = ticker.calendar
|
| 244 |
+
if calendar is None:
|
| 245 |
+
return f"No earnings calendar found for {symbol}."
|
| 246 |
+
|
| 247 |
+
# Handle dict (new yfinance) or DataFrame (old yfinance)
|
| 248 |
+
if isinstance(calendar, dict):
|
| 249 |
+
if not calendar:
|
| 250 |
+
return f"No earnings calendar found for {symbol}."
|
| 251 |
+
elif hasattr(calendar, 'empty') and calendar.empty:
|
| 252 |
+
return f"No earnings calendar found for {symbol}."
|
| 253 |
+
|
| 254 |
+
return f"Earnings Calendar for {symbol}:\n{calendar}"
|
| 255 |
+
except Exception as e:
|
| 256 |
+
return f"Error fetching earnings calendar for '{symbol}': {e}"
|
| 257 |
+
|
src/deep-research/app.py
CHANGED
|
@@ -28,70 +28,82 @@ st.set_page_config(page_title="Deep Research AI", layout="wide", page_icon="🧠
|
|
| 28 |
st.markdown("""
|
| 29 |
<style>
|
| 30 |
/* Global Defaults */
|
| 31 |
-
.stApp {
|
| 32 |
background-color: #f8f9fa;
|
| 33 |
font-family: 'Inter', sans-serif;
|
|
|
|
| 34 |
}
|
| 35 |
|
| 36 |
-
/* Remove default Streamlit top padding but add space for Fixed Header - Revert: Just remove top padding */
|
| 37 |
.block-container {
|
| 38 |
-
|
|
|
|
|
|
|
| 39 |
}
|
| 40 |
|
| 41 |
-
/*
|
| 42 |
-
header[data-testid="stHeader"] {
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
background: linear-gradient(135deg, #0f2027 0%, #203a43 50%, #2c5364 100%);
|
| 50 |
-
color:
|
| 51 |
-
|
| 52 |
-
display: flex;
|
| 53 |
-
justify-content: space-between;
|
| 54 |
-
align-items: center;
|
| 55 |
-
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
| 56 |
-
|
| 57 |
-
margin-top: -4rem; /* Pull up aggressively to cover top gap */
|
| 58 |
-
margin-left: -5rem;
|
| 59 |
-
margin-right: -5rem;
|
| 60 |
-
|
| 61 |
-
border-bottom: none;
|
| 62 |
-
border-radius: 0 0 1rem 1rem;
|
| 63 |
}
|
| 64 |
-
|
| 65 |
-
.
|
| 66 |
-
font-
|
| 67 |
-
|
| 68 |
font-weight: 700;
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
}
|
| 75 |
|
| 76 |
/* Centered Search Area */
|
| 77 |
.search-wrapper {
|
| 78 |
max-width: 800px;
|
| 79 |
-
margin:
|
| 80 |
text-align: center;
|
|
|
|
| 81 |
}
|
| 82 |
|
| 83 |
.search-headline {
|
| 84 |
-
font-size:
|
| 85 |
font-weight: 800;
|
| 86 |
color: #111;
|
| 87 |
margin-bottom: 0.5rem;
|
| 88 |
-
letter-spacing: -0.03em;
|
| 89 |
}
|
| 90 |
|
| 91 |
.search-subtext {
|
| 92 |
-
font-size:
|
| 93 |
color: #666;
|
| 94 |
-
margin-bottom:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
}
|
| 96 |
|
| 97 |
/* Input styling override */
|
|
@@ -101,11 +113,8 @@ st.markdown("""
|
|
| 101 |
padding: 1rem !important;
|
| 102 |
background: white !important;
|
| 103 |
box-shadow: 0 4px 12px rgba(0,0,0,0.03) !important;
|
| 104 |
-
font-size:
|
| 105 |
-
|
| 106 |
-
.stTextArea textarea:focus {
|
| 107 |
-
border-color: #667eea !important;
|
| 108 |
-
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.1) !important;
|
| 109 |
}
|
| 110 |
|
| 111 |
/* Custom Button */
|
|
@@ -115,8 +124,9 @@ st.markdown("""
|
|
| 115 |
border-radius: 30px !important;
|
| 116 |
padding: 0.5rem 2rem !important;
|
| 117 |
border: none !important;
|
| 118 |
-
box-shadow: 0 4px 10px rgba(0,0,0,0.2) !important;
|
| 119 |
transition: transform 0.1s ease;
|
|
|
|
|
|
|
| 120 |
}
|
| 121 |
.stButton button:hover {
|
| 122 |
transform: scale(1.02);
|
|
@@ -127,11 +137,11 @@ st.markdown("""
|
|
| 127 |
max-width: 850px;
|
| 128 |
margin: 2rem auto;
|
| 129 |
background: white;
|
| 130 |
-
padding:
|
| 131 |
-
min-height:
|
| 132 |
-
box-shadow: 0 1px 3px rgba(0,0,0,0.1)
|
| 133 |
color: #2c3e50;
|
| 134 |
-
border:
|
| 135 |
}
|
| 136 |
</style>
|
| 137 |
""", unsafe_allow_html=True)
|
|
@@ -221,13 +231,9 @@ async def run_research(query: str):
|
|
| 221 |
|
| 222 |
# Custom Header
|
| 223 |
st.markdown("""
|
| 224 |
-
<div class="
|
| 225 |
-
<div class="
|
| 226 |
-
|
| 227 |
-
</div>
|
| 228 |
-
<div>
|
| 229 |
-
<!-- Could add profile or other links here -->
|
| 230 |
-
</div>
|
| 231 |
</div>
|
| 232 |
""", unsafe_allow_html=True)
|
| 233 |
|
|
|
|
| 28 |
st.markdown("""
|
| 29 |
<style>
|
| 30 |
/* Global Defaults */
|
| 31 |
+
.stApp, [data-testid="stAppViewContainer"] {
|
| 32 |
background-color: #f8f9fa;
|
| 33 |
font-family: 'Inter', sans-serif;
|
| 34 |
+
overflow-x: hidden !important; /* Force hide horizontal scroll */
|
| 35 |
}
|
| 36 |
|
|
|
|
| 37 |
.block-container {
|
| 38 |
+
max-width: 1200px;
|
| 39 |
+
padding-top: 2rem !important;
|
| 40 |
+
padding-bottom: 2rem !important;
|
| 41 |
}
|
| 42 |
|
| 43 |
+
/* Remove default header decoration */
|
| 44 |
+
header[data-testid="stHeader"] {
|
| 45 |
+
background-color: transparent !important;
|
| 46 |
+
z-index: 100 !important;
|
| 47 |
+
}
|
| 48 |
|
| 49 |
+
div[data-testid="stDecoration"] {
|
| 50 |
+
display: none;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
/* Hero Section (Matching Chatbot Style) */
|
| 54 |
+
.hero-container {
|
| 55 |
+
position: relative;
|
| 56 |
+
width: 100vw;
|
| 57 |
+
left: 50%;
|
| 58 |
+
right: 50%;
|
| 59 |
+
margin-left: -50vw;
|
| 60 |
+
margin-right: -50vw;
|
| 61 |
+
margin-top: -6rem; /* Pull up to cover top padding */
|
| 62 |
+
padding: 4rem 1rem 2rem 1rem; /* Extra top padding for status bar area */
|
| 63 |
+
text-align: center;
|
| 64 |
+
margin-bottom: 2rem;
|
| 65 |
background: linear-gradient(135deg, #0f2027 0%, #203a43 50%, #2c5364 100%);
|
| 66 |
+
color: white;
|
| 67 |
+
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
}
|
| 69 |
+
|
| 70 |
+
.hero-title {
|
| 71 |
+
font-size: 2rem;
|
| 72 |
+
margin-bottom: 0.5rem;
|
| 73 |
font-weight: 700;
|
| 74 |
+
}
|
| 75 |
+
.hero-subtitle {
|
| 76 |
+
font-size: 1rem;
|
| 77 |
+
opacity: 0.95;
|
| 78 |
+
font-weight: 400;
|
| 79 |
}
|
| 80 |
|
| 81 |
/* Centered Search Area */
|
| 82 |
.search-wrapper {
|
| 83 |
max-width: 800px;
|
| 84 |
+
margin: 2rem auto;
|
| 85 |
text-align: center;
|
| 86 |
+
padding: 0 1rem;
|
| 87 |
}
|
| 88 |
|
| 89 |
.search-headline {
|
| 90 |
+
font-size: 2rem;
|
| 91 |
font-weight: 800;
|
| 92 |
color: #111;
|
| 93 |
margin-bottom: 0.5rem;
|
|
|
|
| 94 |
}
|
| 95 |
|
| 96 |
.search-subtext {
|
| 97 |
+
font-size: 1rem;
|
| 98 |
color: #666;
|
| 99 |
+
margin-bottom: 2rem;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
/* Mobile font sizes */
|
| 103 |
+
@media (max-width: 768px) {
|
| 104 |
+
.search-headline {
|
| 105 |
+
font-size: 1.75rem;
|
| 106 |
+
}
|
| 107 |
}
|
| 108 |
|
| 109 |
/* Input styling override */
|
|
|
|
| 113 |
padding: 1rem !important;
|
| 114 |
background: white !important;
|
| 115 |
box-shadow: 0 4px 12px rgba(0,0,0,0.03) !important;
|
| 116 |
+
font-size: 1rem !important; /* Proper reading size */
|
| 117 |
+
color: #333 !important;
|
|
|
|
|
|
|
|
|
|
| 118 |
}
|
| 119 |
|
| 120 |
/* Custom Button */
|
|
|
|
| 124 |
border-radius: 30px !important;
|
| 125 |
padding: 0.5rem 2rem !important;
|
| 126 |
border: none !important;
|
|
|
|
| 127 |
transition: transform 0.1s ease;
|
| 128 |
+
min-height: 48px; /* Large touch target */
|
| 129 |
+
white-space: nowrap !important; /* Prevent label wrapping */
|
| 130 |
}
|
| 131 |
.stButton button:hover {
|
| 132 |
transform: scale(1.02);
|
|
|
|
| 137 |
max-width: 850px;
|
| 138 |
margin: 2rem auto;
|
| 139 |
background: white;
|
| 140 |
+
padding: 2rem;
|
| 141 |
+
min-height: 600px;
|
| 142 |
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
| 143 |
color: #2c3e50;
|
| 144 |
+
border-radius: 8px;
|
| 145 |
}
|
| 146 |
</style>
|
| 147 |
""", unsafe_allow_html=True)
|
|
|
|
| 231 |
|
| 232 |
# Custom Header
|
| 233 |
st.markdown("""
|
| 234 |
+
<div class="hero-container">
|
| 235 |
+
<div class="hero-title">🧠 Deep Research</div>
|
| 236 |
+
<div class="hero-subtitle">OpenAI Agentic Research Assistant</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
</div>
|
| 238 |
""", unsafe_allow_html=True)
|
| 239 |
|