Skip to content

Commit 1e8e75a

Browse files
committed
fix(anthropic): handle empty response.content safely to prevent IndexError
Closes #178
1 parent caf0bff commit 1e8e75a

2 files changed

Lines changed: 64 additions & 2 deletions

File tree

‎rlm/clients/anthropic.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ def completion(self, prompt: str | list[dict[str, Any]], model: str | None = Non
4444

4545
response = self.client.messages.create(**kwargs)
4646
self._track_cost(response, model)
47-
return response.content[0].text
47+
texts = [b.text for b in response.content if getattr(b, "type", "") == "text"]
48+
return texts[0] if texts else ""
4849

4950
async def acompletion(
5051
self, prompt: str | list[dict[str, Any]], model: str | None = None
@@ -61,7 +62,8 @@ async def acompletion(
6162

6263
response = await self.async_client.messages.create(**kwargs)
6364
self._track_cost(response, model)
64-
return response.content[0].text
65+
texts = [b.text for b in response.content if getattr(b, "type", "") == "text"]
66+
return texts[0] if texts else ""
6567

6668
def _prepare_messages(
6769
self, prompt: str | list[dict[str, Any]]

‎tests/clients/test_anthropic.py‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Tests for the Anthropic client."""
2+
3+
import os
4+
from unittest.mock import MagicMock, patch
5+
6+
import pytest
7+
8+
from rlm.clients.anthropic import AnthropicClient
9+
from rlm.core.types import ModelUsageSummary, UsageSummary
10+
11+
12+
class TestAnthropicClientUnit:
13+
"""Unit tests that don't require API calls."""
14+
15+
def test_init_with_api_key(self):
16+
"""Test client initialization with explicit API key."""
17+
with patch("rlm.clients.anthropic.anthropic.Anthropic"):
18+
with patch("rlm.clients.anthropic.anthropic.AsyncAnthropic"):
19+
client = AnthropicClient(api_key="test-key", model_name="claude-3-5-sonnet")
20+
assert client.model_name == "claude-3-5-sonnet"
21+
22+
def test_completion_empty_content(self):
23+
"""Test completion returns empty string when response.content is empty."""
24+
mock_response = MagicMock()
25+
mock_response.content = []
26+
mock_response.usage.input_tokens = 10
27+
mock_response.usage.output_tokens = 0
28+
29+
with patch("rlm.clients.anthropic.anthropic.Anthropic") as mock_client_class:
30+
mock_client = MagicMock()
31+
mock_client.messages.create.return_value = mock_response
32+
mock_client_class.return_value = mock_client
33+
34+
with patch("rlm.clients.anthropic.anthropic.AsyncAnthropic"):
35+
client = AnthropicClient(api_key="test-key", model_name="claude-3-5-sonnet")
36+
result = client.completion("Hello")
37+
38+
assert result == ""
39+
40+
def test_completion_with_text_content(self):
41+
"""Test completion returns text when response.content has text blocks."""
42+
mock_block = MagicMock()
43+
mock_block.type = "text"
44+
mock_block.text = "Hello from Claude!"
45+
46+
mock_response = MagicMock()
47+
mock_response.content = [mock_block]
48+
mock_response.usage.input_tokens = 10
49+
mock_response.usage.output_tokens = 5
50+
51+
with patch("rlm.clients.anthropic.anthropic.Anthropic") as mock_client_class:
52+
mock_client = MagicMock()
53+
mock_client.messages.create.return_value = mock_response
54+
mock_client_class.return_value = mock_client
55+
56+
with patch("rlm.clients.anthropic.anthropic.AsyncAnthropic"):
57+
client = AnthropicClient(api_key="test-key", model_name="claude-3-5-sonnet")
58+
result = client.completion("Hello")
59+
60+
assert result == "Hello from Claude!"

0 commit comments

Comments
 (0)