From 3794ba91aa5446eb775d0aec8b2d7a08da11202b Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 2 Sep 2026 23:52:01 -0700 Subject: [PATCH] fix(anthropic): resolve the count-tokens static key the way chat does The count-tokens path probed the static key with os.getenv("ANTHROPIC_API_KEY") while chat resolves the same variable through get_secret_str, so a key held only in a secret manager was invisible to the count and the request fell through to the federation branch. The count then authenticated as the federation principal while chat used the vault key. Resolving through AnthropicModelInfo.get_api_key keeps the two surfaces on one credential. --- .../anthropic/count_tokens/token_counter.py | 3 +- .../llms/anthropic/test_count_tokens_oauth.py | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/litellm/llms/anthropic/count_tokens/token_counter.py b/litellm/llms/anthropic/count_tokens/token_counter.py index c3ad994e544..d5b8667a2e1 100644 --- a/litellm/llms/anthropic/count_tokens/token_counter.py +++ b/litellm/llms/anthropic/count_tokens/token_counter.py @@ -2,7 +2,6 @@ Anthropic Token Counter implementation using the CountTokens API. """ -import os from typing import Any, Final from litellm._logging import verbose_logger @@ -56,7 +55,7 @@ class AnthropicTokenCounter(BaseTokenCounter): deployment = deployment or {} litellm_params: Final = deployment.get("litellm_params", {}) api_base: Final = litellm_params.get("api_base") - static_key: Final = litellm_params.get("api_key") or os.getenv("ANTHROPIC_API_KEY") + static_key: Final = AnthropicModelInfo.get_api_key(litellm_params.get("api_key")) auth_token_configured: Final = AnthropicModelInfo.get_auth_token() is not None try: diff --git a/tests/test_litellm/llms/anthropic/test_count_tokens_oauth.py b/tests/test_litellm/llms/anthropic/test_count_tokens_oauth.py index d2ae29215b2..542f8f55b59 100644 --- a/tests/test_litellm/llms/anthropic/test_count_tokens_oauth.py +++ b/tests/test_litellm/llms/anthropic/test_count_tokens_oauth.py @@ -198,3 +198,57 @@ class TestCountTokensUsesWorkloadIdentity: assert result.status_code == 401 assert result.total_tokens == 0 assert "fdrl_" in (result.error_message or "") + + @pytest.mark.asyncio + async def test_a_vault_backed_static_key_never_mints(self, monkeypatch): + from litellm.llms.anthropic.count_tokens import token_counter as token_counter_module + + monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) + monkeypatch.delenv("ANTHROPIC_AUTH_TOKEN", raising=False) + vault_key = "sk-ant-api03-only-in-the-vault" + + def vault_only(secret_name, default_value=None): + return vault_key if secret_name == "ANTHROPIC_API_KEY" else None + + monkeypatch.setattr("litellm.secret_managers.main.get_secret_str", vault_only, raising=False) + + mint_calls: list[str] = [] + + async def fake_mint(_params, _api_base, model): + mint_calls.append(model) + return "sk-ant-oat01-should-not-be-minted" + + monkeypatch.setattr(token_counter_module, "aget_anthropic_wif_token", fake_mint, raising=False) + monkeypatch.setattr("litellm.llms.anthropic.wif.aget_anthropic_wif_token", fake_mint, raising=False) + + seen: dict[str, object] = {} + + async def fake_request(**kwargs): + seen.update(kwargs) + return {"input_tokens": 7} + + monkeypatch.setattr( + token_counter_module.anthropic_count_tokens_handler, + "handle_count_tokens_request", + fake_request, + raising=False, + ) + + result = await token_counter_module.AnthropicTokenCounter().count_tokens( + model_to_use="claude-sonnet-4-5", + messages=[{"role": "user", "content": "hi"}], + contents=None, + deployment={ + "litellm_params": { + "model": "anthropic/claude-sonnet-4-5", + "anthropic_federation_rule_id": "fdrl_x", + "anthropic_organization_id": "org-x", + } + }, + request_model="claude-sonnet-4-5", + ) + + assert result is not None + assert result.total_tokens == 7 + assert seen["api_key"] == vault_key + assert mint_calls == []