Skip to content

Commit 1a04998

Browse files
authored
perf: handle Anthropic usage=None on content-filtered responses (#8647)
* Initial plan * fix: handle missing anthropic usage on filtered responses --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent c4251e8 commit 1a04998

2 files changed

Lines changed: 38 additions & 2 deletions

File tree

astrbot/core/provider/sources/anthropic_source.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,12 +302,14 @@ def _prepare_payload(self, messages: list[dict]):
302302

303303
return system_prompt, new_messages
304304

305-
def _extract_usage(self, usage: Usage) -> TokenUsage:
305+
def _extract_usage(self, usage: Usage | None) -> TokenUsage:
306+
if usage is None:
307+
return TokenUsage()
306308
# https://docs.claude.com/en/docs/build-with-claude/prompt-caching#tracking-cache-performance
307309
return TokenUsage(
308310
input_other=usage.input_tokens or 0,
309311
input_cached=usage.cache_read_input_tokens or 0,
310-
output=usage.output_tokens,
312+
output=usage.output_tokens or 0,
311313
)
312314

313315
def _update_usage(self, token_usage: TokenUsage, usage: MessageDeltaUsage) -> None:

tests/test_anthropic_kimi_code_provider.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,40 @@ def _setup_provider_with_mock_client(monkeypatch) -> anthropic_source.ProviderAn
483483
return provider
484484

485485

486+
@pytest.mark.asyncio
487+
async def test_query_handles_none_usage_when_content_filtered(monkeypatch):
488+
provider = _setup_provider_with_mock_client(monkeypatch)
489+
content_filter_message = (
490+
"The request was rejected because it was considered high risk"
491+
)
492+
493+
class _FakeMessageBlock:
494+
def __init__(self, text: str):
495+
self.type = "text"
496+
self.text = text
497+
498+
class _FakeMessage:
499+
def __init__(self):
500+
self.id = "msg_content_filter"
501+
self.content = [_FakeMessageBlock(content_filter_message)]
502+
self.stop_reason = "content_filter"
503+
self.usage = None
504+
505+
async def fake_create(**kwargs):
506+
return _FakeMessage()
507+
508+
monkeypatch.setattr(anthropic_source, "Message", _FakeMessage)
509+
provider.client.messages.create = fake_create
510+
511+
llm_response = await provider.text_chat(prompt="test")
512+
513+
assert llm_response.completion_text == content_filter_message
514+
assert llm_response.usage is not None
515+
assert llm_response.usage.input_other == 0
516+
assert llm_response.usage.input_cached == 0
517+
assert llm_response.usage.output == 0
518+
519+
486520
@pytest.mark.asyncio
487521
async def test_tool_choice_auto_converts_to_dict(monkeypatch):
488522
"""tool_choice='auto' 应转换为 {'type': 'auto'}"""

0 commit comments

Comments
 (0)