diff --git a/EvoScientist/EvoScientist.py b/EvoScientist/EvoScientist.py index ac5d43a..8afa59a 100644 --- a/EvoScientist/EvoScientist.py +++ b/EvoScientist/EvoScientist.py @@ -43,7 +43,6 @@ SKILLS_DIR = str(Path(__file__).parent / "skills") _config = None _chat_model = None -_system_prompt = None # Cache MCP tools by the effective config signature to avoid reconnecting # to MCP servers on every `/new` when config is unchanged. @@ -84,14 +83,6 @@ def _ensure_chat_model(): return _chat_model -def _ensure_system_prompt(): - """Return cached system prompt, creating it on first call.""" - global _system_prompt - if _system_prompt is None: - _system_prompt = get_system_prompt() - return _system_prompt - - # ============================================================================= # MCP caching # ============================================================================= @@ -180,7 +171,7 @@ def _build_base_kwargs(base_backend, base_middleware): backend=base_backend, subagents=subs, middleware=base_middleware, - system_prompt=_ensure_system_prompt(), + system_prompt=get_system_prompt(), skills=["/skills/"], ) @@ -229,7 +220,7 @@ def load_mcp_and_build_kwargs(base_backend, base_middleware): backend=base_backend, subagents=subs, middleware=base_middleware, - system_prompt=_ensure_system_prompt(), + system_prompt=get_system_prompt(), skills=["/skills/"], ) @@ -310,7 +301,7 @@ def __getattr__(name: str): if name == "chat_model": return _ensure_chat_model() if name == "SYSTEM_PROMPT": - return _ensure_system_prompt() + return get_system_prompt() if name == "backend": return _get_default_backend() raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/EvoScientist/ccproxy_manager.py b/EvoScientist/ccproxy_manager.py index 3907f77..4223359 100644 --- a/EvoScientist/ccproxy_manager.py +++ b/EvoScientist/ccproxy_manager.py @@ -41,6 +41,7 @@ def _ccproxy_exe() -> str | None: if found: return found import sys as _sys + candidate = os.path.join(os.path.dirname(_sys.executable), "ccproxy") if os.path.isfile(candidate) and os.access(candidate, os.X_OK): return candidate @@ -108,7 +109,8 @@ def check_ccproxy_auth(provider: str = "claude_api") -> tuple[bool, str]: # Filter out structlog warning/noise lines, keep only status lines status_lines = [ - line for line in clean.splitlines() + line + for line in clean.splitlines() if line.strip() and not _re.match(r"\d{4}-\d{2}-\d{2}", line.strip()) and "warning" not in line.lower() diff --git a/EvoScientist/config/onboard.py b/EvoScientist/config/onboard.py index 55680a6..fae8f60 100644 --- a/EvoScientist/config/onboard.py +++ b/EvoScientist/config/onboard.py @@ -776,7 +776,11 @@ def _step_anthropic_auth_mode(config: EvoScientistConfig) -> str: Choice(title="API Key (direct Anthropic access)", value="api_key"), Choice( title="Claude Code OAuth (via ccproxy — no API key needed)" - + ("" if ccproxy_available else " [requires: pip install evoscientist[oauth]]"), + + ( + "" + if ccproxy_available + else " [requires: pip install evoscientist[oauth]]" + ), value="oauth", ), ] @@ -878,7 +882,11 @@ def _step_openai_auth_mode(config: EvoScientistConfig) -> str: Choice(title="API Key (direct OpenAI access)", value="api_key"), Choice( title="Codex OAuth (via ccproxy — no API key needed)" - + ("" if ccproxy_available else " [requires: pip install evoscientist[oauth]]"), + + ( + "" + if ccproxy_available + else " [requires: pip install evoscientist[oauth]]" + ), value="oauth", ), ] @@ -2457,10 +2465,10 @@ def run_onboard(skip_validation: bool = False) -> bool: "custom-openai": "custom_openai_api_key", "custom-anthropic": "custom_anthropic_api_key", } - _skip_api_key = provider == "ollama" or ( - provider == "anthropic" and config.anthropic_auth_mode == "oauth" - ) or ( - provider == "openai" and config.openai_auth_mode == "oauth" + _skip_api_key = ( + provider == "ollama" + or (provider == "anthropic" and config.anthropic_auth_mode == "oauth") + or (provider == "openai" and config.openai_auth_mode == "oauth") ) if not _skip_api_key: new_key = _step_provider_api_key(config, provider, skip_validation) diff --git a/EvoScientist/llm/models.py b/EvoScientist/llm/models.py index b7680ef..576f782 100644 --- a/EvoScientist/llm/models.py +++ b/EvoScientist/llm/models.py @@ -306,8 +306,12 @@ def get_chat_model( kwargs["base_url"] = base_url _is_openai_proxy = "127.0.0.1" in base_url or "localhost" in base_url if _is_openai_proxy: - kwargs.setdefault("streaming", False) # ccproxy streaming format incompatible with langchain-openai - kwargs.setdefault("use_responses_api", True) # ccproxy Chat Completions does not support tool calling; Responses API does + kwargs.setdefault( + "streaming", False + ) # ccproxy streaming format incompatible with langchain-openai + kwargs.setdefault( + "use_responses_api", True + ) # ccproxy Chat Completions does not support tool calling; Responses API does api_key = os.environ.get("OPENAI_API_KEY", "") if api_key: kwargs["api_key"] = api_key diff --git a/EvoScientist/prompts.py b/EvoScientist/prompts.py index 7b5443d..54c15db 100644 --- a/EvoScientist/prompts.py +++ b/EvoScientist/prompts.py @@ -332,9 +332,17 @@ Finding one with context [1]. Another insight [2]. def get_system_prompt() -> str: - """Generate the complete system prompt. + """Generate the complete system prompt with today's date. Returns: Combined system prompt string. """ - return EXPERIMENT_WORKFLOW + "\n" + DELEGATION_STRATEGY + from datetime import datetime + + date = datetime.now().strftime("%Y-%m-%d") + return ( + f"Today's date is {date}.\n\n" + + EXPERIMENT_WORKFLOW + + "\n" + + DELEGATION_STRATEGY + ) diff --git a/tests/test_ccproxy_manager.py b/tests/test_ccproxy_manager.py index b0c5fcd..0e3e9d2 100644 --- a/tests/test_ccproxy_manager.py +++ b/tests/test_ccproxy_manager.py @@ -277,7 +277,9 @@ class TestMaybeStartCcproxy: @patch("EvoScientist.ccproxy_manager.ensure_ccproxy") @patch("EvoScientist.ccproxy_manager.check_ccproxy_auth", return_value=(True, "OK")) @patch("EvoScientist.ccproxy_manager.is_ccproxy_available", return_value=True) - def test_openai_oauth_mode_starts(self, mock_avail, mock_auth, mock_ensure, mock_env): + def test_openai_oauth_mode_starts( + self, mock_avail, mock_auth, mock_ensure, mock_env + ): proc = MagicMock() mock_ensure.return_value = proc config = MagicMock() @@ -294,7 +296,9 @@ class TestMaybeStartCcproxy: @patch("EvoScientist.ccproxy_manager.ensure_ccproxy") @patch("EvoScientist.ccproxy_manager.check_ccproxy_auth", return_value=(True, "OK")) @patch("EvoScientist.ccproxy_manager.is_ccproxy_available", return_value=True) - def test_both_oauth_starts_both(self, mock_avail, mock_auth, mock_ensure, mock_anthropic_env, mock_codex_env): + def test_both_oauth_starts_both( + self, mock_avail, mock_auth, mock_ensure, mock_anthropic_env, mock_codex_env + ): proc = MagicMock() mock_ensure.return_value = proc config = MagicMock() diff --git a/tests/test_llm.py b/tests/test_llm.py index 702b6b4..e07854a 100644 --- a/tests/test_llm.py +++ b/tests/test_llm.py @@ -472,7 +472,10 @@ class TestThirdPartyRouting: call_kwargs = mock_init.call_args[1] assert call_kwargs["model_provider"] == "openai" - assert call_kwargs["base_url"] == "https://dashscope.aliyuncs.com/compatible-mode/v1" + assert ( + call_kwargs["base_url"] + == "https://dashscope.aliyuncs.com/compatible-mode/v1" + ) assert call_kwargs["api_key"] == "ds-key-456" diff --git a/tests/test_prompts.py b/tests/test_prompts.py index 65bab92..fde171b 100644 --- a/tests/test_prompts.py +++ b/tests/test_prompts.py @@ -39,3 +39,9 @@ class TestGetSystemPrompt: def test_shell_guidelines_mention_background(self): assert "background" in EXPERIMENT_WORKFLOW.lower() + + def test_contains_todays_date(self): + from datetime import datetime + + expected = datetime.now().strftime("%Y-%m-%d") + assert expected in get_system_prompt()