diff --git a/python/configs/agent_cards/news_agent.json b/python/configs/agent_cards/news_agent.json new file mode 100644 index 000000000..2f70c77d0 --- /dev/null +++ b/python/configs/agent_cards/news_agent.json @@ -0,0 +1,55 @@ +{ + "name": "NewsAgent", + "display_name": "News Agent", + "url": "http://localhost:10005/", + "description": "Professional news agent specialized in gathering, analyzing, and presenting current events, breaking news, and financial market information from reliable sources with real-time web search capabilities.", + "capabilities": { + "streaming": true + }, + "skills": [ + { + "id": "web_search", + "name": "Real-time news search", + "description": "Search for current news articles and information across the web using advanced search capabilities.", + "examples": [ + "What's the latest news about artificial intelligence?", + "Search for recent developments in renewable energy", + "Find news about the latest tech IPOs" + ], + "tags": ["web search", "real-time", "current events", "research"] + }, + { + "id": "get_breaking_news", + "name": "Breaking news monitoring", + "description": "Monitor and retrieve urgent breaking news updates and alerts from multiple sources.", + "examples": [ + "Any breaking news right now?", + "What are the latest urgent news updates?", + "Show me breaking news from the past hour" + ], + "tags": ["breaking news", "urgent", "alerts", "monitoring"] + }, + { + "id": "get_financial_news", + "name": "Financial market news", + "description": "Provide comprehensive financial market news, business updates, and economic developments.", + "examples": [ + "What's happening in the stock market today?", + "Latest financial news and market updates", + "Show me news about major economic indicators" + ], + "tags": ["finance", "markets", "business", "stocks", "economics"] + } + ], + "enabled": true, + "metadata": { + "version": "1.0.0", + "author": "ValueCell Team", + "tags": [ + "news", + "current events", + "financial markets", + "real-time search" + ] + } +} \ No newline at end of file diff --git a/python/configs/agents/news_agent.yaml b/python/configs/agents/news_agent.yaml new file mode 100644 index 000000000..626d1cdfa --- /dev/null +++ b/python/configs/agents/news_agent.yaml @@ -0,0 +1,109 @@ +# ============================================ +# News Agent Configuration +# ============================================ +# The News Agent specializes in gathering, analyzing, and presenting news information. +# It provides real-time news search, categorized news retrieval, and comprehensive analysis. +# ============================================ +# Configuration Priority (highest to lowest): +# 1. Environment Variables (highest priority) +# 2. .env file +# 3. YAML files (lowest priority, system defaults) +# ============================================ + +name: "News Agent" +enabled: true + +# Model Configuration +# News Agent needs a capable model for news analysis and summarization +models: + # Primary model for news analysis, summarization, and query processing + primary: + model_id: "anthropic/claude-haiku-4.5" + provider: "openrouter" # Must explicitly specify provider (not null) + + # Provider-specific model mappings for fallback + # Used when primary provider fails and fallback is attempted + provider_models: + siliconflow: "Qwen/Qwen3-235B-A22B-Thinking-2507" # Strong reasoning for news analysis + google: "gemini-2.5-flash" # Fast and efficient for news processing + + parameters: + # temperature: 0.3 # Lower temperature for more factual news reporting + # max_tokens: 4096 # Sufficient for comprehensive news analysis + + # Web search model for real-time information gathering + # Uses specialized models optimized for web search and real-time data + search: + model_id: "perplexity/sonar" + provider: "openrouter" + + # Provider-specific search model mappings + provider_models: + google: "gemini-2.5-flash" # With search grounding enabled + + parameters: + # max_tokens: 2048 # Optimized for search result processing + +# Environment Variable Overrides +# Format: ENV_VAR_NAME -> config.path +# These allow runtime configuration through environment variables +env_overrides: + # Primary model overrides + NEWS_AGENT_MODEL_ID: "models.primary.model_id" + NEWS_AGENT_PROVIDER: "models.primary.provider" + NEWS_AGENT_TEMPERATURE: "models.primary.parameters.temperature" + NEWS_AGENT_MAX_TOKENS: "models.primary.parameters.max_tokens" + + # Search model overrides + NEWS_SEARCH_MODEL_ID: "models.search.model_id" + NEWS_SEARCH_PROVIDER: "models.search.provider" + + # Web search provider configuration + WEB_SEARCH_PROVIDER: "capabilities.web_search.provider" + +# News Agent Capabilities +capabilities: + # Web search configuration + web_search: + enabled: true + provider: "perplexity" # Default: perplexity, alternative: google + max_results: 10 + + # Breaking news monitoring + breaking_news: + enabled: true # Enable breaking news monitoring + refresh_interval: 300 # seconds + + # Financial news specialization + financial_news: + enabled: true + supported_markets: ["US", "Global"] + include_sectors: true + include_individual_stocks: true + + + + # Multi-language support + languages: + enabled: true + supported: ["en", "zh", "ja", "ko", "es", "fr", "de"] + default: "en" + + # News analysis features + analysis: + sentiment_analysis: true + topic_extraction: true + entity_recognition: true + summarization: true + fact_checking: false # Requires additional verification tools + +# Tool Configuration +tools: + web_search: + enabled: true + + get_breaking_news: + enabled: true + + get_financial_news: + enabled: true \ No newline at end of file diff --git a/python/scripts/launch.py b/python/scripts/launch.py index 464c784a4..0ae1c3b3f 100644 --- a/python/scripts/launch.py +++ b/python/scripts/launch.py @@ -31,15 +31,13 @@ TRADING_AGENTS_NAME = "TradingAgents" RESEARCH_AGENT_NAME = "ResearchAgent" AUTO_TRADING_AGENT_NAME = "AutoTradingAgent" +NEWS_AGENT_NAME = "NewsAgent" # AGENTS = list(MAP_NAME_ANALYST.keys()) + [ # TRADING_AGENTS_NAME, # RESEARCH_AGENT_NAME, # AUTO_TRADING_AGENT_NAME, # ] -AGENTS = [ - RESEARCH_AGENT_NAME, - AUTO_TRADING_AGENT_NAME, -] +AGENTS = [RESEARCH_AGENT_NAME, AUTO_TRADING_AGENT_NAME, NEWS_AGENT_NAME] PROJECT_DIR = Path(__file__).resolve().parent.parent.parent PYTHON_DIR = PROJECT_DIR / "python" @@ -66,6 +64,9 @@ MAP_NAME_COMMAND[AUTO_TRADING_AGENT_NAME] = ( f"uv run --env-file {ENV_PATH_STR} -m valuecell.agents.auto_trading_agent" ) +MAP_NAME_COMMAND[NEWS_AGENT_NAME] = ( + f"uv run --env-file {ENV_PATH_STR} -m valuecell.agents.news_agent" +) BACKEND_COMMAND = ( f"cd {PYTHON_DIR_STR} && uv run --env-file {ENV_PATH_STR} -m valuecell.server.main" ) diff --git a/python/valuecell/agents/news_agent/__init__.py b/python/valuecell/agents/news_agent/__init__.py new file mode 100644 index 000000000..bb0036a6c --- /dev/null +++ b/python/valuecell/agents/news_agent/__init__.py @@ -0,0 +1,5 @@ +"""News Agent for fetching and analyzing news content.""" + +from .core import NewsAgent + +__all__ = ["NewsAgent"] diff --git a/python/valuecell/agents/news_agent/__main__.py b/python/valuecell/agents/news_agent/__main__.py new file mode 100644 index 000000000..beeb0d24d --- /dev/null +++ b/python/valuecell/agents/news_agent/__main__.py @@ -0,0 +1,8 @@ +import asyncio + +from valuecell.agents.news_agent import NewsAgent +from valuecell.core import create_wrapped_agent + +if __name__ == "__main__": + agent = create_wrapped_agent(NewsAgent) + asyncio.run(agent.serve()) diff --git a/python/valuecell/agents/news_agent/core.py b/python/valuecell/agents/news_agent/core.py new file mode 100644 index 000000000..8bd6f992c --- /dev/null +++ b/python/valuecell/agents/news_agent/core.py @@ -0,0 +1,133 @@ +"""News Agent Core Implementation.""" + +from typing import Any, AsyncGenerator, Dict, Optional + +from agno.agent import Agent +from loguru import logger + +from valuecell.adapters.models import create_model_for_agent +from valuecell.config.manager import get_config_manager +from valuecell.core.agent.responses import streaming +from valuecell.core.types import BaseAgent, StreamResponse + +from .prompts import NEWS_AGENT_INSTRUCTIONS +from .tools import get_breaking_news, get_financial_news, web_search + + +class NewsAgent(BaseAgent): + """News Agent for fetching and analyzing news.""" + + def __init__(self, **kwargs): + """Initialize the News Agent.""" + super().__init__(**kwargs) + # Load agent configuration + self.config_manager = get_config_manager() + self.agent_config = self.config_manager.get_agent_config("news_agent") + + # Load tools based on configuration + available_tools = [] + + available_tools.extend([web_search, get_breaking_news, get_financial_news]) + + # Use create_model_for_agent to load agent-specific configuration + self.knowledge_news_agent = Agent( + model=create_model_for_agent("news_agent"), + tools=available_tools, + instructions=NEWS_AGENT_INSTRUCTIONS, + ) + + logger.info("NewsAgent initialized with news tools") + + async def stream( + self, + query: str, + conversation_id: str, + task_id: str, + dependencies: Optional[Dict] = None, + ) -> AsyncGenerator[StreamResponse, None]: + """Stream news responses.""" + logger.info( + f"Processing news query: {query[:100]}{'...' if len(query) > 100 else ''}" + ) + + try: + response_stream = self.knowledge_news_agent.arun( + query, + stream=True, + stream_intermediate_steps=True, + session_id=conversation_id, + ) + async for event in response_stream: + if event.event == "RunContent": + yield streaming.message_chunk(event.content) + elif event.event == "ToolCallStarted": + yield streaming.tool_call_started( + event.tool.tool_call_id, event.tool.tool_name + ) + elif event.event == "ToolCallCompleted": + yield streaming.tool_call_completed( + event.tool.result, event.tool.tool_call_id, event.tool.tool_name + ) + + yield streaming.done() + logger.info("News query processed successfully") + + except Exception as e: + logger.error(f"Error processing news query: {str(e)}") + logger.exception("Full error details:") + yield {"type": "error", "content": f"Error processing news query: {str(e)}"} + + async def run(self, query: str, **kwargs) -> str: + """Run news agent and return response.""" + logger.info( + f"Running news agent with query: {query[:100]}{'...' if len(query) > 100 else ''}" + ) + + try: + logger.debug("Starting news agent processing") + + # Get the complete response from the knowledge news agent + response = await self.knowledge_news_agent.arun(query) + + logger.info("News agent query completed successfully") + logger.debug(f"Response length: {len(str(response.content))} characters") + + return response.content + + except Exception as e: + logger.error(f"Error in NewsAgent run: {e}") + logger.exception("Full error details:") + return f"Error processing news query: {str(e)}" + + def get_capabilities(self) -> Dict[str, Any]: + """Get agent capabilities.""" + logger.debug("Retrieving news agent capabilities") + + capabilities = { + "name": "News Agent", + "description": "Professional news agent for fetching and analyzing news", + "tools": [ + { + "name": "web_search", + "description": "Search for general news and information", + }, + { + "name": "get_breaking_news", + "description": "Get latest breaking news", + }, + { + "name": "get_financial_news", + "description": "Get financial and market news", + }, + ], + "supported_queries": [ + "Latest news", + "Breaking news", + "Financial news", + "Market updates", + "Topic-specific news search", + ], + } + + logger.debug("Capabilities retrieved successfully") + return capabilities diff --git a/python/valuecell/agents/news_agent/prompts.py b/python/valuecell/agents/news_agent/prompts.py new file mode 100644 index 000000000..8e5d02a96 --- /dev/null +++ b/python/valuecell/agents/news_agent/prompts.py @@ -0,0 +1,96 @@ +"""Prompts for the News Agent.""" + +NEWS_AGENT_INSTRUCTIONS = """ +You are a professional News Agent specialized in gathering, analyzing, and presenting news information. Your primary role is to help users stay informed about current events, breaking news, and specific topics of interest. + +## Core Capabilities + +1. **News Retrieval**: Fetch the latest news from various sources and categories +2. **Topic Analysis**: Provide in-depth analysis of specific news topics +3. **Breaking News**: Monitor and report urgent news updates +4. **Financial News**: Specialized coverage of market and financial news +5. **Technology News**: Focus on tech industry developments and innovations +6. **Custom Search**: Search for news on specific topics with date ranges + +## Tool Usage Guidelines +- Use `get_breaking_news()` for urgent updates +- Use `get_financial_news()` for market and business news +- Use `web_search()` for comprehensive information gathering + +## Date Awareness + +Always be aware of the current date and time context. When discussing news: +- Clearly indicate when events occurred +- Distinguish between breaking news and historical events +- Provide temporal context for better understanding + +## Response Planning + +Before responding to any query: +1. Identify the type of news request (general, breaking, financial, tech, specific topic) +2. Determine the appropriate tools to use +3. Consider the time sensitivity of the information +4. Plan how to structure the response for maximum clarity + +## Output Format + +Structure your responses as follows: + +### News Summary +- Provide a clear, concise headline +- Include key details (who, what, when, where, why) +- Mention the source and credibility when possible + +### Analysis (when appropriate) +- Explain the significance of the news +- Provide context and background information +- Discuss potential implications or consequences + +### Stock News Special Format + +For stock news queries, organize information according to the following structure: +1. **Macroeconomic Level**: Macroeconomic policies, market environment changes affecting the stock +2. **Industry Level**: Development trends, policy changes, and competitive landscape of the industry +3. **Individual Stock Level**: Specific business dynamics, financial reports, and major events of the company + +### Additional Information +- Related news or developments +- Links to further reading (when available) +- Recommendations for follow-up topics + +## Tone and Style + +- **Professional**: Maintain journalistic integrity and objectivity +- **Clear**: Use accessible language while being informative +- **Timely**: Emphasize the currency and relevance of information +- **Balanced**: Present multiple perspectives when covering controversial topics +- **Factual**: Stick to verified information and clearly distinguish between facts and opinions + +## Constraints + +- Always verify information through multiple sources when possible +- Clearly indicate when information is preliminary or unconfirmed +- Avoid spreading misinformation or unverified claims +- Respect privacy and ethical journalism standards +- Be transparent about limitations in real-time information access + +## Follow-up Strategy + +After providing news information: +- Ask if the user wants more details on specific aspects +- Suggest related topics that might be of interest +- Offer to set up monitoring for ongoing stories +- Provide options for different types of news coverage + +## Examples + +### Example 1: Breaking News +User: "Any breaking news right now?" +Response: Use `get_breaking_news()` to get urgent updates, then present them with appropriate urgency indicators and context. + +### Example 2: Financial News +User: "What's happening in the stock market?" +Response: Use `get_financial_news()` to get market updates, then provide analysis of market movements, key factors, and implications. + +Remember: Your goal is to keep users well-informed with accurate, timely, and relevant news information while maintaining the highest standards of journalistic integrity. +""" diff --git a/python/valuecell/agents/news_agent/tools.py b/python/valuecell/agents/news_agent/tools.py new file mode 100644 index 000000000..038dcd84d --- /dev/null +++ b/python/valuecell/agents/news_agent/tools.py @@ -0,0 +1,115 @@ +"""News-related tools for the News Agent.""" + +import os +from datetime import datetime +from typing import Optional + +from agno.agent import Agent +from loguru import logger + +from valuecell.adapters.models import create_model + + +async def web_search(query: str) -> str: + """Search web for the given query and return a summary of the top results. + + This function uses the centralized configuration system to create model instances. + It supports multiple search providers: + - Google (Gemini with search enabled) - when WEB_SEARCH_PROVIDER=google and GOOGLE_API_KEY is set + - Perplexity (via OpenRouter) - default fallback + + Args: + query: The search query string. + + Returns: + A summary of the top search results. + """ + # Check which provider to use based on environment configuration + if os.getenv("WEB_SEARCH_PROVIDER", "google").lower() == "google" and os.getenv( + "GOOGLE_API_KEY" + ): + return await _web_search_google(query) + + # Use Perplexity Sonar via OpenRouter for web search + # Perplexity models are optimized for web search and real-time information + model = create_model( + provider="openrouter", + model_id="perplexity/sonar", + max_tokens=None, + ) + response = await Agent(model=model).arun(query) + return response.content + + +async def _web_search_google(query: str) -> str: + """Search Google for the given query and return a summary of the top results. + + Uses Google Gemini with search grounding enabled for real-time web information. + + Args: + query: The search query string. + + Returns: + A summary of the top search results. + """ + # Use Google Gemini with search enabled + # The search=True parameter enables Google Search grounding for real-time information + model = create_model( + provider="google", + model_id="gemini-2.5-flash", + search=True, # Enable Google Search grounding + ) + response = await Agent(model=model).arun(query) + return response.content + + +async def get_breaking_news() -> str: + """Get breaking news and urgent updates. + + Returns: + Formatted string containing breaking news + """ + try: + search_query = "breaking news urgent updates today" + logger.info("Fetching breaking news") + + news_content = await web_search(search_query) + return news_content + + except Exception as e: + logger.error(f"Error fetching breaking news: {e}") + return f"Error fetching breaking news: {str(e)}" + + +async def get_financial_news( + ticker: Optional[str] = None, sector: Optional[str] = None +) -> str: + """Get financial and market news. + + Args: + ticker: Stock ticker symbol for company-specific news + sector: Industry sector for sector-specific news + + Returns: + Formatted string containing financial news + """ + try: + search_query = "financial market news" + + if ticker: + search_query = f"{ticker} stock news financial market" + elif sector: + search_query = f"{sector} sector financial news market" + + # Add time constraint for recent news + today = datetime.now().strftime("%Y-%m-%d") + search_query += f" {today}" + + logger.info(f"Searching for financial news with query: {search_query}") + + news_content = await web_search(search_query) + return news_content + + except Exception as e: + logger.error(f"Error fetching financial news: {e}") + return f"Error fetching financial news: {str(e)}"