diff --git a/python/configs/agent_cards/sec_agent.json b/python/configs/agent_cards/sec_agent.json deleted file mode 100644 index 760a5dcd5..000000000 --- a/python/configs/agent_cards/sec_agent.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "SECAgent", - "display_name": "SEC Agent", - "url": "http://localhost:10003/", - "description": "SecAgent can analyze SEC filings like 10-Q, 10-K, 13-F and analyze stock holdings of institutional investment managers. It can chat about stock performance, financial metrics, and market trends or track specific stocks and provide updates.", - "skills": [ - { - "id": "analyze_13f_filings", - "name": "Analyze 13F Filings", - "description": "Analyze 13F filings to extract stock holdings and provide insights on institutional investment managers' portfolios.", - "examples": [ - "What are the top holdings of Berkshire Hathaway in the latest 13F filing?", - "How has the portfolio of Vanguard Group changed over the last four quarters?", - "Can you provide insights on the stock performance of Apple and Microsoft based on recent 13F filings?" - ], - "tags": [ - "13F", - "stock holdings", - "institutional investors" - ] - }, - - { - "id": "financial_data_queries", - "name": "Financial data queries", - "description": "Analyze financial extract stock holdings and provide insights on institutional investment managers' portfolios.", - "examples": [ - "What are the 10K fillings of Berkshire Hathaway indicate?" - ], - "tags": [ - "stock holdings", - "institutional investors" - ] - }, - - { - "id": "track_company_info", - "name": "Track company sec info update", - "description": "Track and analyze company's information changes .", - "examples": [ - "Please check Berkshire Hathaway’s filings and notify me with an analysis whenever there’s an update." - ], - "tags": [ - "track_company" - ] - } - ], - "enabled": true, - "metadata": { - "version": "1.0.0", - "author": "ValueCell Team", - "tags": [ - "sec", - "13f", - "fund-analysis" - ] - } -} \ No newline at end of file diff --git a/python/scripts/launch.py b/python/scripts/launch.py index 572baca6b..9eaa4055c 100644 --- a/python/scripts/launch.py +++ b/python/scripts/launch.py @@ -9,8 +9,6 @@ from pathlib import Path from typing import Dict -import questionary - # Mapping from agent name to analyst key (for ai-hedge-fund agents) MAP_NAME_ANALYST: Dict[str, str] = { "AswathDamodaranAgent": "aswath_damodaran", @@ -30,9 +28,9 @@ "ValuationAnalystAgent": "valuation_analyst", "WarrenBuffettAgent": "warren_buffett", } -SEC_AGENT_NAME = "SECAgent" TRADING_AGENTS_NAME = "TradingAgents" -AGENTS = list(MAP_NAME_ANALYST.keys()) + [SEC_AGENT_NAME, TRADING_AGENTS_NAME] +RESEARCH_AGENT_NAME = "ResearchAgent" +AGENTS = list(MAP_NAME_ANALYST.keys()) + [TRADING_AGENTS_NAME, RESEARCH_AGENT_NAME] PROJECT_DIR = Path(__file__).resolve().parent.parent.parent PYTHON_DIR = PROJECT_DIR / "python" @@ -50,12 +48,12 @@ MAP_NAME_COMMAND[name] = ( f"cd {PYTHON_DIR_STR}/third_party/ai-hedge-fund && uv run --env-file {ENV_PATH_STR} -m adapter --analyst {analyst}" ) -MAP_NAME_COMMAND[SEC_AGENT_NAME] = ( - f"uv run --env-file {ENV_PATH_STR} -m valuecell.agents.sec_agent" -) MAP_NAME_COMMAND[TRADING_AGENTS_NAME] = ( f"cd {PYTHON_DIR_STR}/third_party/TradingAgents && uv run --env-file {ENV_PATH_STR} -m adapter" ) +MAP_NAME_COMMAND[RESEARCH_AGENT_NAME] = ( + f"uv run --env-file {ENV_PATH_STR} -m valuecell.agents.research_agent" +) BACKEND_COMMAND = ( f"cd {PYTHON_DIR_STR} && uv run --env-file {ENV_PATH_STR} -m valuecell.server.main" ) @@ -77,10 +75,11 @@ def main(): log_dir = f"{PROJECT_DIR_STR}/logs/{timestamp}" # Use questionary multi-select to allow choosing multiple agents - selected_agents = questionary.checkbox( - "Choose agents to launch (use space to select, enter to confirm):", - choices=AGENTS, - ).ask() + # selected_agents = questionary.checkbox( + # "Choose agents to launch (use space to select, enter to confirm):", + # choices=AGENTS, + # ).ask() + selected_agents = AGENTS if not selected_agents: print("No agents selected.") diff --git a/python/valuecell/agents/research_agent/core.py b/python/valuecell/agents/research_agent/core.py index c1d1a6d0d..7dfca2bf6 100644 --- a/python/valuecell/agents/research_agent/core.py +++ b/python/valuecell/agents/research_agent/core.py @@ -1,7 +1,7 @@ import os -from typing import AsyncGenerator, Dict, Iterator, Optional +from typing import AsyncGenerator, Dict, Optional -from agno.agent import Agent, RunOutputEvent +from agno.agent import Agent from agno.db.in_memory import InMemoryDb from agno.models.google import Gemini from agno.models.openrouter import OpenRouter @@ -17,12 +17,13 @@ fetch_event_sec_filings, fetch_periodic_sec_filings, ) +from valuecell.agents.utils.context import build_ctx_from_dep from valuecell.core.agent.responses import streaming from valuecell.core.types import BaseAgent, StreamResponse from valuecell.utils.env import agent_debug_mode_enabled -def _get_model_based_on_env() -> str: +def _get_model_based_on_env(): model_id = os.getenv("RESEARCH_AGENT_MODEL_ID") if os.getenv("GOOGLE_API_KEY"): return Gemini(id=model_id or "gemini-2.5-flash") @@ -58,11 +59,13 @@ async def stream( task_id: str, dependencies: Optional[Dict] = None, ) -> AsyncGenerator[StreamResponse, None]: - response_stream: Iterator[RunOutputEvent] = self.knowledge_research_agent.arun( + response_stream = self.knowledge_research_agent.arun( query, stream=True, stream_intermediate_steps=True, session_id=conversation_id, + add_dependencies_to_context=True, + dependencies=build_ctx_from_dep(dependencies), ) async for event in response_stream: if event.event == "RunContent": diff --git a/python/valuecell/agents/research_agent/prompts.py b/python/valuecell/agents/research_agent/prompts.py index 1c9e3e21b..56b16698a 100644 --- a/python/valuecell/agents/research_agent/prompts.py +++ b/python/valuecell/agents/research_agent/prompts.py @@ -3,7 +3,7 @@ You are a financial research assistant. Your primary objective is to satisfy the user's information request about a company's financials, filings, or performance with accurate, sourceable, and actionable answers. -- + - fetch_periodic_sec_filings(ticker_or_cik, forms, year?, quarter?, limit?): Use this for scheduled reports like 10-K/10-Q when you need primary-source facts (revenue, net income, MD&A text). Prefer batching by year to reduce calls. Note: year/quarter filters apply to filing_date (edgar behavior), not period_of_report. If year is omitted, the tool returns the latest filings using `limit` (default 10). If quarter is provided, year must also be provided. - fetch_event_sec_filings(ticker_or_cik, forms, start_date?, end_date?, limit?): Use this for event-driven filings like 8-K and ownership forms (3/4/5). Use date ranges and limits to control scope. - Knowledge base search: Use the agent's internal knowledge index to find summaries, historical context, analyst commentary, and previously ingested documents. diff --git a/python/valuecell/agents/utils/__init__.py b/python/valuecell/agents/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/valuecell/agents/utils/context.py b/python/valuecell/agents/utils/context.py new file mode 100644 index 000000000..e0411f0f2 --- /dev/null +++ b/python/valuecell/agents/utils/context.py @@ -0,0 +1,49 @@ +from typing import Any, Dict, Optional + +from valuecell.core.constants import LANGUAGE, TIMEZONE + + +def build_ctx_from_dep( + dep: Optional[Dict[str, Any]], +) -> Dict[str, str] | None: + if not dep: + return None + + context = {} + lang_ctx = _build_lang_ctx_from_dep(dep) + if lang_ctx: + context["compose_answer_hint"] = lang_ctx + + return context + + +def _build_lang_ctx_from_dep( + dependencies: Optional[Dict[str, Any]], +) -> str | None: + if not dependencies: + return None + + user_lang = dependencies.get(LANGUAGE) + user_tz = dependencies.get(TIMEZONE) + + parts = [] + parts.append( + "When composing your answer, consider the user's language and timezone:" + ) + if user_lang: + parts.append(f"- Preferred language: {user_lang}") + else: + parts.append( + "- Preferred language: not set. Infer the user's language from their query and respond in that language." + ) + + if user_tz: + parts.append( + f"- Timezone: {user_tz} (use this to interpret or present times/dates)" + ) + else: + parts.append( + "- Timezone: not set. Do NOT ask the user for their timezone unless absolutely necessary. Instead, infer timezone from context (locale, timestamps, phrasing) when possible; if you cannot reasonably infer it, default to UTC when presenting absolute times." + ) + + return "\n".join(parts) diff --git a/python/valuecell/core/agent/client.py b/python/valuecell/core/agent/client.py index d333834eb..6bb386c34 100644 --- a/python/valuecell/core/agent/client.py +++ b/python/valuecell/core/agent/client.py @@ -65,7 +65,7 @@ async def _setup_client(self): except Exception as e: raise RuntimeError( "Failed to resolve agent card. Maybe the agent URL is incorrect or the agent is unreachable." - " Agents could be launched via `scripts/launch_agent.py`." + " Check the agent logs for more details." ) from e self._client = client_factory.create(self.agent_card) diff --git a/python/valuecell/core/agent/tests/test_client.py b/python/valuecell/core/agent/tests/test_client.py index 8ed907789..045029ca7 100644 --- a/python/valuecell/core/agent/tests/test_client.py +++ b/python/valuecell/core/agent/tests/test_client.py @@ -282,7 +282,7 @@ async def test_ensure_initialized_card_resolution_failure(self): error_message = str(exc_info.value) assert "Failed to resolve agent card" in error_message - assert "scripts/launch_agent.py" in error_message + assert "Check the agent logs" in error_message assert "Connection timeout" in str( exc_info.value.__cause__ ) # Original exception should be chained