Skip to content

Commit de009e0

Browse files
authored
Merge pull request #97 from NimbleAINinja/main
2 parents ad4d300 + 523a7fd commit de009e0

File tree

1 file changed

+64
-36
lines changed

1 file changed

+64
-36
lines changed

llm_apis/anthropic_client.py

+64-36
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,27 @@
33
import os
44
import base64
55
import httpx
6-
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
6+
from tenacity import retry, stop_after_attempt, wait_exponential, \
7+
retry_if_exception_type
78

89
MAX_RETRIES = 5
910

11+
1012
class AnthropicRateLimitError(Exception):
1113
"""Exception raised for rate limit errors."""
1214
def __init__(self, message, retry_after):
1315
self.message = message
1416
self.retry_after = retry_after
1517
super().__init__(self.message)
1618

19+
20+
class AnthropicOverloadError(Exception):
21+
"""Exception raised for overloaded errors."""
22+
def __init__(self, message):
23+
self.message = message
24+
super().__init__(self.message)
25+
26+
1727
class AnthropicClient:
1828
def __init__(self, verbose=False):
1929
"""Initialize the Anthropic client with the API key."""
@@ -23,16 +33,28 @@ def __init__(self, verbose=False):
2333
@retry(
2434
stop=stop_after_attempt(MAX_RETRIES),
2535
wait=wait_exponential(multiplier=1, min=4, max=10),
26-
retry=retry_if_exception_type(AnthropicRateLimitError)
36+
retry=(retry_if_exception_type(AnthropicRateLimitError) |
37+
retry_if_exception_type(AnthropicOverloadError))
2738
)
2839
def _make_api_call(self, api_args):
2940
"""Make an API call with retry mechanism."""
3041
try:
3142
return self.client.messages.create(**api_args)
3243
except httpx.HTTPStatusError as e:
3344
if e.response.status_code == 429:
34-
retry_after = int(e.response.headers.get('retry-after', 60))
35-
raise AnthropicRateLimitError(f"Rate limit exceeded. {str(e)}", retry_after)
45+
retry_after = int(e.response.headers.get(
46+
'retry-after', 60))
47+
raise AnthropicRateLimitError(
48+
f"Rate limit exceeded. {str(e)}", retry_after)
49+
elif e.response.status_code == 529:
50+
raise AnthropicOverloadError(
51+
f"Anthropic API overloaded: {str(e)}")
52+
raise
53+
except anthropic.APIStatusError as e:
54+
error_data = e.args[0]
55+
if error_data['error']['type'] == 'overloaded_error':
56+
raise AnthropicOverloadError(
57+
f"Anthropic API overloaded: {error_data['error']['message']}")
3658
raise
3759

3860
def stream_completion(self, messages, model, **kwargs):
@@ -46,46 +68,41 @@ def stream_completion(self, messages, model, **kwargs):
4668
Yields:
4769
str: Text generated by the Anthropic API.
4870
"""
49-
# Extract system message if present, otherwise set to None
50-
system_messages = [message['content'] for message in messages if message['role'] == 'system']
71+
system_messages = [msg['content'] for msg in messages
72+
if msg['role'] == 'system']
5173
system_message = system_messages[0] if system_messages else None
52-
53-
# Filter out system messages from the messages list
54-
messages = [message for message in messages if message['role'] != 'system']
5574

56-
# Prepare the arguments for the Anthropic API call
75+
messages = [msg for msg in messages
76+
if msg['role'] != 'system']
77+
5778
api_args = {
5879
"model": model,
59-
"max_tokens": kwargs.get('max_tokens', 1000), # Default to 1000 if not provided
80+
"max_tokens": kwargs.get('max_tokens', 1000),
6081
"stream": True,
6182
**kwargs
6283
}
63-
64-
# Only include the system parameter if a system message is present
84+
6585
if system_message:
6686
api_args["system"] = system_message
6787

6888
processed_messages = []
6989
for message in messages:
7090
if 'image' in message:
71-
processed_content = [
72-
{
73-
"type": "image",
74-
"source": {
75-
"type": "base64",
76-
"media_type": "image/jpeg",
77-
"data": message['image'].replace('\n', '') # Remove newlines
78-
}
91+
processed_content = [{
92+
"type": "image",
93+
"source": {
94+
"type": "base64",
95+
"media_type": "image/jpeg",
96+
"data": message['image'].replace('\n', '')
7997
}
80-
]
81-
82-
# Add original text content if present
98+
}]
99+
83100
if 'content' in message and message['content']:
84101
processed_content.append({
85102
"type": "text",
86103
"text": message['content']
87104
})
88-
105+
89106
processed_messages.append({
90107
"role": message['role'],
91108
"content": processed_content
@@ -97,7 +114,8 @@ def stream_completion(self, messages, model, **kwargs):
97114
})
98115

99116
if not processed_messages:
100-
raise ValueError(f"No messages to send to the API. Original messages: {messages}")
117+
raise ValueError(
118+
f"No messages to send. Original messages: {messages}")
101119

102120
api_args["messages"] = processed_messages
103121

@@ -110,18 +128,24 @@ def stream_completion(self, messages, model, **kwargs):
110128
if self.verbose:
111129
print(f"Rate limit error: {e.message}. Retry after {e.retry_after} seconds.")
112130
raise
131+
except AnthropicOverloadError as e:
132+
if self.verbose:
133+
print(f"Overload error: {e.message}")
134+
raise
113135
except Exception as e:
114136
if self.verbose:
115137
import traceback
116138
traceback.print_exc()
117139
print(f"An error occurred streaming completion from Anthropic API: {e}")
118-
raise RuntimeError(f"An error occurred streaming completion from Anthropic API: {e}")
140+
raise RuntimeError(
141+
f"An error occurred streaming completion from Anthropic API: {e}")
142+
119143

120144
# Test the AnthropicClient
121145
if __name__ == "__main__":
122146
client = AnthropicClient(verbose=True)
123-
124-
#test text only
147+
148+
# test text only
125149
messages = [
126150
{
127151
"role": "system",
@@ -138,18 +162,20 @@ def stream_completion(self, messages, model, **kwargs):
138162
try:
139163
for chunk in client.stream_completion(messages, model):
140164
print(chunk, end='', flush=True)
141-
print() # Add a newline at the end
165+
print()
142166
except AnthropicRateLimitError as e:
143-
print(f"\nRate limit error encountered: {e.message}. Retry after {e.retry_after} seconds.")
167+
print(f"\nRate limit error: {e.message}. Retry after {e.retry_after} seconds.")
168+
except AnthropicOverloadError as e:
169+
print(f"\nOverload error: {e.message}")
144170
except Exception as e:
145171
print(f"\nAn error occurred: {e}")
146172

147-
#test multimodal
173+
# test multimodal
148174
image_url = "https://upload.wikimedia.org/wikipedia/commons/a/a7/Camponotus_flavomarginatus_ant.jpg"
149175
image_media_type = "image/jpeg"
150176
image_data = base64.b64encode(httpx.get(image_url).content).decode("utf-8")
151-
152-
messages=[
177+
178+
messages = [
153179
{
154180
"role": "system",
155181
"content": "Respond only in rhyming couplets."
@@ -172,13 +198,15 @@ def stream_completion(self, messages, model, **kwargs):
172198
],
173199
}
174200
]
175-
201+
176202
print("\nMultimodal Response:")
177203
try:
178204
for chunk in client.stream_completion(messages, model):
179205
print(chunk, end='', flush=True)
180206
print()
181207
except AnthropicRateLimitError as e:
182-
print(f"\nRate limit error encountered: {e.message}. Retry after {e.retry_after} seconds.")
208+
print(f"\nRate limit error: {e.message}. Retry after {e.retry_after} seconds.")
209+
except AnthropicOverloadError as e:
210+
print(f"\nOverload error: {e.message}")
183211
except Exception as e:
184212
print(f"\nAn error occurred: {e}")

0 commit comments

Comments
 (0)