fix: do not load the anthropic package on import

`serena.analytics` imported `anthropic.types` at module level for two type
annotations, so every `import serena.cli` / `import serena.agent` paid for
loading the whole anthropic package (2-4 s on the reporter's Windows machine)
even with the default CHAR_COUNT token estimator. That delay can push MCP
startup past a client's initial tool-discovery window.

The import now lives behind `TYPE_CHECKING`; `AnthropicTokenCount` already
imported `anthropic` lazily in `__init__`, and the request body is a plain
dict (what `MessageParam` produces at runtime anyway).

Tests: importing serena.cli / serena.agent / serena.analytics in a fresh
interpreter leaves `anthropic` out of `sys.modules`; the Anthropic estimator
still sends the expected count_tokens request.

Fixes #2012
This commit is contained in:
L4XB authored and Dr. Dominik Jain committed 2026-09-16 02:22:00 +02:00
1 parent 922cac9da4
commit 16322651bb
3 files changed
+38 -2

No files matched your search

+2
View File
@@ -13,6 +13,8 @@ Status of the `main` branch. Changes prior to the next official version change w
* General:
- Fix: MCP `initialize` now reports Serena's version instead of the installed mcp SDK version (#1889)
- Fix: importing Serena no longer loads the `anthropic` package unless the Anthropic token counter is
actually used; the unconditional import added seconds to CLI/MCP startup on some machines (#2012)
- Fix: Parallel agents auto-registering projects could overwrite each other's changes to the global
project list in `serena_config.yml`
- Fix: `TextUtils.insert_text_at_position` returned a wrong position when the inserted text merged
+7 -2
View File
@@ -9,10 +9,15 @@ from collections import defaultdict
from copy import copy
from dataclasses import asdict, dataclass
from enum import Enum
from typing import TYPE_CHECKING
from anthropic.types import MessageParam, MessageTokensCount
from dotenv import load_dotenv
if TYPE_CHECKING:
# Imported for annotations only: loading the anthropic package costs seconds on some
# machines (see #2012) and is only needed when the Anthropic token counter is used.
from anthropic.types import MessageTokensCount
log = logging.getLogger(__name__)
@@ -64,7 +69,7 @@ class AnthropicTokenCount(TokenCountEstimator):
def _send_count_tokens_request(self, text: str) -> MessageTokensCount:
return self._anthropic_client.messages.count_tokens(
model=self._model_name,
messages=[MessageParam(role="user", content=text)],
messages=[{"role": "user", "content": text}],
)
def estimate_token_count(self, text: str) -> int:
+29
View File
@@ -0,0 +1,29 @@
"""The anthropic package must only be loaded when the Anthropic token counter is used (#2012)."""
import subprocess
import sys
from unittest.mock import MagicMock
import pytest
from serena.analytics import AnthropicTokenCount
@pytest.mark.parametrize("module", ["serena.cli", "serena.agent", "serena.analytics"])
def test_importing_serena_does_not_load_anthropic(module: str) -> None:
code = f"import sys, {module}; print('anthropic' in sys.modules)"
result = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, check=True, timeout=120)
assert result.stdout.strip() == "False", f"importing {module} loaded the anthropic package"
def test_anthropic_token_count_sends_a_plain_user_message() -> None:
estimator = AnthropicTokenCount.__new__(AnthropicTokenCount)
estimator._model_name = "claude-sonnet-4-20250514"
estimator._anthropic_client = MagicMock()
estimator._anthropic_client.messages.count_tokens.return_value = MagicMock(input_tokens=7)
assert estimator.estimate_token_count("hello") == 7
estimator._anthropic_client.messages.count_tokens.assert_called_once_with(
model="claude-sonnet-4-20250514",
messages=[{"role": "user", "content": "hello"}],
)