spikard 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spikard-0.0.1/PKG-INFO +412 -0
- spikard-0.0.1/README.md +367 -0
- spikard-0.0.1/pyproject.toml +167 -0
- spikard-0.0.1/setup.cfg +4 -0
- spikard-0.0.1/spikard/__init__.py +39 -0
- spikard-0.0.1/spikard/_ref.py +14 -0
- spikard-0.0.1/spikard/base.py +588 -0
- spikard-0.0.1/spikard/exceptions.py +159 -0
- spikard-0.0.1/spikard/openai.py +468 -0
- spikard-0.0.1/spikard.egg-info/PKG-INFO +412 -0
- spikard-0.0.1/spikard.egg-info/SOURCES.txt +12 -0
- spikard-0.0.1/spikard.egg-info/dependency_links.txt +1 -0
- spikard-0.0.1/spikard.egg-info/requires.txt +26 -0
- spikard-0.0.1/spikard.egg-info/top_level.txt +1 -0
spikard-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spikard
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Universal LLM client
|
|
5
|
+
Author-email: Na'aman Hirschfeld <nhirschfeld@gmail.com>
|
|
6
|
+
Project-URL: Repository, https://github.com/Goldziher/spikard
|
|
7
|
+
Keywords: ai,client,llm
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Information Technology
|
|
11
|
+
Classifier: Natural Language :: English
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: anyio>=4.4.0
|
|
27
|
+
Requires-Dist: jsonschema>=4.22.0
|
|
28
|
+
Requires-Dist: msgspec>=0.18.6
|
|
29
|
+
Requires-Dist: typing-extensions>=4.12.2
|
|
30
|
+
Provides-Extra: anthropic
|
|
31
|
+
Requires-Dist: anthropic>=0.49.0; extra == "anthropic"
|
|
32
|
+
Provides-Extra: anthropic-vertex
|
|
33
|
+
Requires-Dist: anthropic[vertex]>=0.49.0; extra == "anthropic-vertex"
|
|
34
|
+
Provides-Extra: aws-bedrock
|
|
35
|
+
Requires-Dist: aioboto3>=14.1.0; extra == "aws-bedrock"
|
|
36
|
+
Provides-Extra: google-genai
|
|
37
|
+
Requires-Dist: google-genai>=1.8.0; extra == "google-genai"
|
|
38
|
+
Provides-Extra: google-vertex
|
|
39
|
+
Requires-Dist: google-cloud-aiplatform>=1.86.0; extra == "google-vertex"
|
|
40
|
+
Provides-Extra: groq
|
|
41
|
+
Requires-Dist: groq>=0.20.0; extra == "groq"
|
|
42
|
+
Provides-Extra: openai
|
|
43
|
+
Requires-Dist: openai>=1.69.0; extra == "openai"
|
|
44
|
+
Requires-Dist: tiktoken>=0.9.0; extra == "openai"
|
|
45
|
+
|
|
46
|
+
# Spikard
|
|
47
|
+
|
|
48
|
+
Spikard is a universal LLM client.
|
|
49
|
+
|
|
50
|
+
**What does this mean?** Each LLM provider has its own API. While many providers follow the OpenAI API format, others do not.
|
|
51
|
+
Spikard provides a simple universal interface allowing you to use any LLM provider with the same code.
|
|
52
|
+
|
|
53
|
+
**Why use Spikard?** You might have already encountered the need to use multiple LLM providers, or to switch between them.
|
|
54
|
+
In the end, there is quite a bit of redundant boilerplate involved. Spikard offers a permissively licensed (MIT), high quality and lightweight abstraction layer.
|
|
55
|
+
|
|
56
|
+
**Why not use my favorite framework <insert name>?** The point of this library is to be a building block, not a framework.
|
|
57
|
+
If your use case is for a framework, use a framework. If, on the other hand, you want a lightweight building block with minimal dependencies and excellent Python, this library might be for you.
|
|
58
|
+
|
|
59
|
+
**What the hell is a "Spikard?"** Great that you ask! Spikards are powerful magical items that look like spiked rings, each spike connecting a magic source in one of the shadows.
|
|
60
|
+
For further reading, grab a copy of the Amber cycle of books by Roger Zelazny.
|
|
61
|
+
|
|
62
|
+
## Design Philosophy
|
|
63
|
+
|
|
64
|
+
The design philosophy is straightforward. There is an abstract LLM client class. This class offers a uniform interface for LLM clients, and it includes validation logic that is shared. It is then extended by provider-specific classes that implement the actual API calls.
|
|
65
|
+
|
|
66
|
+
- We are not creating specialized clients for the different providers. Rather, we use `optional-dependencies` to add the provider-specific client packages, which allows us to have a lean and lightweight package.
|
|
67
|
+
- We will try to always support the latest version of a client API library on a best effort basis.
|
|
68
|
+
- We rely on strict, extensive typing with overloads to ensure the best possible experience for users and strict static analysis.
|
|
69
|
+
- You can also implement your own LLM clients using the abstract LLM client class. Again, the point of this library is to be a building block.
|
|
70
|
+
|
|
71
|
+
This library is open to contributions- if you see a provider that is missing, please open an issue or submit a pull request.
|
|
72
|
+
|
|
73
|
+
## Installation
|
|
74
|
+
|
|
75
|
+
Install the library with the providers you want to use, for example:
|
|
76
|
+
|
|
77
|
+
```shell
|
|
78
|
+
pip install spikard[openai]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Or multiple providers:
|
|
82
|
+
|
|
83
|
+
```shell
|
|
84
|
+
pip install spikard[openai,anthropic]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Architecture
|
|
88
|
+
|
|
89
|
+
Spikard follows a layered architecture with a consistent interface across all providers:
|
|
90
|
+
|
|
91
|
+
1. **Base Layer**: `LLMClient` abstract base class in `base.py` defines the standard interface for all providers.
|
|
92
|
+
1. **Provider Layer**: Provider-specific implementations extend the base class (e.g., `OpenAIClient`, `AzureOpenAIClient`).
|
|
93
|
+
1. **Configuration Layer**: Each provider has its own configuration class (e.g., `OpenAIClientConfig`).
|
|
94
|
+
1. **Response Layer**: All providers return responses in a standardized `LLMResponse` format.
|
|
95
|
+
|
|
96
|
+
This design allows for consistent usage patterns regardless of the underlying LLM provider while maintaining provider-specific configuration options.
|
|
97
|
+
|
|
98
|
+
## Usage
|
|
99
|
+
|
|
100
|
+
### Client Instantiation
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from spikard.openai import OpenAIClient, OpenAIClientConfig
|
|
104
|
+
|
|
105
|
+
# all client expect a 'client_config' value, which is a specific subclass of 'LMClientConfig'
|
|
106
|
+
client = OpenAIClient(client_config=OpenAIClientConfig(api_key="sk_...."))
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Generating Content
|
|
110
|
+
|
|
111
|
+
All clients expose a single method called `generate_completion`. With some complex typing in place, this method correctly handles three scenarios:
|
|
112
|
+
|
|
113
|
+
- A text completion request (non-streaming) that returns a text content
|
|
114
|
+
- A text completion request (streaming) that returns an async iterator of text chunks
|
|
115
|
+
- A chat completion request that performs a tool call and returns structured output
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from typing import TypedDict
|
|
119
|
+
|
|
120
|
+
from spikard.openai import OpenAIClient, OpenAIClientConfig, OpenAICompletionConfig, ToolDefinition
|
|
121
|
+
|
|
122
|
+
client = OpenAIClient(client_config=OpenAIClientConfig(api_key="sk_...."))
|
|
123
|
+
|
|
124
|
+
# generate a text completion
|
|
125
|
+
async def generate_completion() -> None:
|
|
126
|
+
response = await client.generate_completion(
|
|
127
|
+
messages=["Tell me about machine learning"],
|
|
128
|
+
system_prompt="You are a helpful AI assistant",
|
|
129
|
+
config=OpenAICompletionConfig(
|
|
130
|
+
model="gpt-4o",
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# response is an LLMResponse[str] value
|
|
135
|
+
print(response.content) # The response text
|
|
136
|
+
print(response.tokens) # Token count used
|
|
137
|
+
print(response.duration) # Generation duration
|
|
138
|
+
|
|
139
|
+
# stream a text completion
|
|
140
|
+
async def stream_completion() -> None:
|
|
141
|
+
async for response in await client.generate_completion(
|
|
142
|
+
messages=["Tell me about machine learning"],
|
|
143
|
+
system_prompt="You are a helpful AI assistant",
|
|
144
|
+
config=OpenAICompletionConfig(
|
|
145
|
+
model="gpt-4o",
|
|
146
|
+
),
|
|
147
|
+
stream=True, # Enable streaming mode
|
|
148
|
+
):
|
|
149
|
+
print(response.content) # The response text chunk
|
|
150
|
+
print(response.tokens) # Token count for this chunk
|
|
151
|
+
print(response.duration) # Generation duration, measured from the last response
|
|
152
|
+
|
|
153
|
+
# call a tool and generate structured output
|
|
154
|
+
async def call_tool() -> None:
|
|
155
|
+
# For tool calling we need to define a return type. This can be any type that can be represented as JSON, but
|
|
156
|
+
# it cannot be a union type. We are using msgspec for deserialization, and it does not support union types - although
|
|
157
|
+
# you can override this behavior via subclassing.
|
|
158
|
+
|
|
159
|
+
# A type can be for example a subclass of msgspec.Struct, a pydantic.BaseModel, a dataclass, a TypedDict,
|
|
160
|
+
# or a primitive such as dict[str, Any] or list[SomeType] etc.
|
|
161
|
+
|
|
162
|
+
from msgspec import Struct
|
|
163
|
+
|
|
164
|
+
class MyResponse(Struct):
|
|
165
|
+
name: str
|
|
166
|
+
age: int
|
|
167
|
+
hobbies: list[str]
|
|
168
|
+
|
|
169
|
+
# Since we are using a msgspec struct, we do not need to define the tool's JSON schema because we can infer it
|
|
170
|
+
response = await client.generate_completion(
|
|
171
|
+
messages=["Return a JSON object with name, age and hobbies"],
|
|
172
|
+
system_prompt="You are a helpful AI assistant",
|
|
173
|
+
config=OpenAICompletionConfig(
|
|
174
|
+
model="gpt-4o",
|
|
175
|
+
),
|
|
176
|
+
response_type=MyResponse,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
assert isinstance(response.content, MyResponse) # The response is a MyResponse object that is structurally valid
|
|
180
|
+
print(response.tokens) # Token count used
|
|
181
|
+
print(response.duration) # Generation duration
|
|
182
|
+
|
|
183
|
+
async def cool_tool_with_tool_definition() -> None:
|
|
184
|
+
# Sometimes we either want to manually create a JSON schema for some reason, or use a type that cannot (currently) be
|
|
185
|
+
# automatically inferred into a JSON schema. For example, let's say we are using a TypedDict to represent a simple JSON structure:
|
|
186
|
+
|
|
187
|
+
class MyResponse(TypedDict):
|
|
188
|
+
name: str
|
|
189
|
+
age: int
|
|
190
|
+
hobbies: list[str]
|
|
191
|
+
|
|
192
|
+
# In this case we need to define the tool definition manually:
|
|
193
|
+
tool_definition = ToolDefinition(
|
|
194
|
+
name="person_data", # Optional name for the tool
|
|
195
|
+
response_type=MyResponse,
|
|
196
|
+
description="Get information about a person", # Optional description
|
|
197
|
+
schema={
|
|
198
|
+
"type": "object",
|
|
199
|
+
"required": ["name", "age", "hobbies"],
|
|
200
|
+
"properties": {
|
|
201
|
+
"name": {"type": "string"},
|
|
202
|
+
"age": {"type": "integer"},
|
|
203
|
+
"hobbies": {
|
|
204
|
+
"type": "array",
|
|
205
|
+
"items": {"type": "string"},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Now we can use the tool definition in the generate_completion call
|
|
212
|
+
response = await client.generate_completion(
|
|
213
|
+
messages=["Return a JSON object with name, age and hobbies"],
|
|
214
|
+
system_prompt="You are a helpful AI assistant",
|
|
215
|
+
config=OpenAICompletionConfig(
|
|
216
|
+
model="gpt-4o",
|
|
217
|
+
),
|
|
218
|
+
tool_definition=tool_definition,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
assert isinstance(response.content, MyResponse) # The response is a MyResponse dict that is structurally valid
|
|
222
|
+
print(response.tokens) # Token count used
|
|
223
|
+
print(response.duration) # Generation duration
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Callbacks
|
|
227
|
+
|
|
228
|
+
Callbacks can be used to validate and transform LLM responses. They are a powerful way to add custom logic to process responses before they are returned to your application.
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from spikard import Callback, LLMResponse
|
|
232
|
+
from spikard.openai import OpenAIClient, OpenAIClientConfig, OpenAICompletionConfig
|
|
233
|
+
|
|
234
|
+
client = OpenAIClient(client_config=OpenAIClientConfig(api_key="sk_...."))
|
|
235
|
+
|
|
236
|
+
# A simple callback function that transforms the response content
|
|
237
|
+
def uppercase_callback(response: LLMResponse[str]) -> LLMResponse[str]:
|
|
238
|
+
"""Convert the response text to uppercase."""
|
|
239
|
+
response.content = response.content.upper()
|
|
240
|
+
return response
|
|
241
|
+
|
|
242
|
+
# Using the callback with a text completion
|
|
243
|
+
async def generate_with_callback() -> None:
|
|
244
|
+
response = await client.generate_completion(
|
|
245
|
+
messages=["Tell me a short joke"],
|
|
246
|
+
system_prompt="You are a helpful AI assistant",
|
|
247
|
+
config=OpenAICompletionConfig(model="gpt-4o"),
|
|
248
|
+
callback=uppercase_callback, # Apply the callback to process the response
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
print(response.content) # The response text will be in uppercase
|
|
252
|
+
|
|
253
|
+
# Callbacks can also be asynchronous
|
|
254
|
+
async def validation_callback(response: LLMResponse[str]) -> LLMResponse[str]:
|
|
255
|
+
"""Validate that the response contains specific text."""
|
|
256
|
+
if "error" in response.content.lower():
|
|
257
|
+
raise ValueError("Response contains error message")
|
|
258
|
+
return response
|
|
259
|
+
|
|
260
|
+
# Callbacks work with structured responses too
|
|
261
|
+
from msgspec import Struct
|
|
262
|
+
|
|
263
|
+
class WeatherInfo(Struct):
|
|
264
|
+
temperature: float
|
|
265
|
+
conditions: str
|
|
266
|
+
location: str
|
|
267
|
+
|
|
268
|
+
def temperature_converter(response: LLMResponse[WeatherInfo]) -> LLMResponse[WeatherInfo]:
|
|
269
|
+
"""Convert temperature from Celsius to Fahrenheit."""
|
|
270
|
+
# Only modify the value if it looks like Celsius
|
|
271
|
+
if response.content.temperature < 50:
|
|
272
|
+
response.content.temperature = (response.content.temperature * 9 / 5) + 32
|
|
273
|
+
return response
|
|
274
|
+
|
|
275
|
+
async def get_weather() -> None:
|
|
276
|
+
response = await client.generate_completion(
|
|
277
|
+
messages=["What's the weather in New York?"],
|
|
278
|
+
config=OpenAICompletionConfig(model="gpt-4o"),
|
|
279
|
+
response_type=WeatherInfo,
|
|
280
|
+
callback=temperature_converter, # Process the structured response
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
print(f"Temperature: {response.content.temperature}°F")
|
|
284
|
+
print(f"Conditions: {response.content.conditions}")
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## JSON Schema Validation
|
|
288
|
+
|
|
289
|
+
Spikard can enforce JSON schema validation for structured responses to ensure they conform to your expected format:
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from msgspec import Struct
|
|
293
|
+
from spikard.openai import OpenAIClient, OpenAIClientConfig, OpenAICompletionConfig
|
|
294
|
+
|
|
295
|
+
class Person(Struct):
|
|
296
|
+
name: str
|
|
297
|
+
age: int
|
|
298
|
+
email: str # We expect a valid email
|
|
299
|
+
|
|
300
|
+
client = OpenAIClient(client_config=OpenAIClientConfig(api_key="sk_...."))
|
|
301
|
+
|
|
302
|
+
async def get_person_info() -> None:
|
|
303
|
+
# Enable schema validation with enforce_schema_validation=True
|
|
304
|
+
response = await client.generate_completion(
|
|
305
|
+
messages=["Generate information about a fictional person"],
|
|
306
|
+
config=OpenAICompletionConfig(model="gpt-4o"),
|
|
307
|
+
response_type=Person,
|
|
308
|
+
enforce_schema_validation=True, # Will validate against Person schema
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# If the LLM returns invalid data (e.g., missing or wrong type fields),
|
|
312
|
+
# a ResponseValidationError will be raised
|
|
313
|
+
print(response.content.name)
|
|
314
|
+
print(response.content.age)
|
|
315
|
+
print(response.content.email)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Custom msgspec Decoders
|
|
319
|
+
|
|
320
|
+
You can customize how Spikard deserializes responses by providing custom decoder mappings. This is useful for handling custom types or adding special deserialization logic:
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
from msgspec import Struct
|
|
324
|
+
from spikard.openai import OpenAIClient, OpenAIClientConfig, OpenAICompletionConfig
|
|
325
|
+
|
|
326
|
+
# Custom type that needs special handling
|
|
327
|
+
class GeoCoordinate:
|
|
328
|
+
def __init__(self, latitude: float, longitude: float):
|
|
329
|
+
self.latitude = latitude
|
|
330
|
+
self.longitude = longitude
|
|
331
|
+
|
|
332
|
+
def __str__(self) -> str:
|
|
333
|
+
return f"{self.latitude}, {self.longitude}"
|
|
334
|
+
|
|
335
|
+
class Location(Struct):
|
|
336
|
+
name: str
|
|
337
|
+
coordinates: GeoCoordinate
|
|
338
|
+
population: int
|
|
339
|
+
|
|
340
|
+
# Custom decoder function to convert from dict to custom GeoCoordinate type
|
|
341
|
+
def decode_geo_coordinate(value: dict) -> GeoCoordinate:
|
|
342
|
+
"""Convert dictionary representation to GeoCoordinate object."""
|
|
343
|
+
# LLM might return coordinates in different formats, so handle various cases
|
|
344
|
+
if isinstance(value, dict):
|
|
345
|
+
if "latitude" in value and "longitude" in value:
|
|
346
|
+
return GeoCoordinate(value["latitude"], value["longitude"])
|
|
347
|
+
elif "lat" in value and "lng" in value:
|
|
348
|
+
return GeoCoordinate(value["lat"], value["lng"])
|
|
349
|
+
# Default fallback
|
|
350
|
+
return GeoCoordinate(0.0, 0.0)
|
|
351
|
+
|
|
352
|
+
# Create client with custom decoder for our GeoCoordinate type
|
|
353
|
+
client = OpenAIClient(
|
|
354
|
+
client_config=OpenAIClientConfig(api_key="sk_...."),
|
|
355
|
+
decoder_mapping={
|
|
356
|
+
GeoCoordinate: decode_geo_coordinate,
|
|
357
|
+
},
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
async def get_location_info() -> None:
|
|
361
|
+
response = await client.generate_completion(
|
|
362
|
+
messages=["Provide information about San Francisco with coordinates"],
|
|
363
|
+
config=OpenAICompletionConfig(model="gpt-4o"),
|
|
364
|
+
response_type=Location,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# The dictionary response for coordinates will be automatically converted
|
|
368
|
+
# to a GeoCoordinate object through our custom decoder
|
|
369
|
+
print(f"City: {response.content.name}")
|
|
370
|
+
print(f"Coordinates: {response.content.coordinates}") # Uses our custom __str__ method
|
|
371
|
+
print(f"Population: {response.content.population}")
|
|
372
|
+
|
|
373
|
+
# We can access the custom object's properties directly
|
|
374
|
+
print(f"Latitude: {response.content.coordinates.latitude}")
|
|
375
|
+
print(f"Longitude: {response.content.coordinates.longitude}")
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The decoder mapping allows you to handle complex types that aren't directly supported by msgspec's default deserialization process. When the LLM returns JSON data, Spikard will use your custom decoder functions to convert appropriate parts of the response to your custom object types.
|
|
379
|
+
|
|
380
|
+
## Automatic Retries
|
|
381
|
+
|
|
382
|
+
Spikard provides built-in support for retrying failed API calls with configurable retry behavior:
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
from spikard import RetryConfig
|
|
386
|
+
from spikard.openai import OpenAIClient, OpenAIClientConfig, OpenAICompletionConfig
|
|
387
|
+
|
|
388
|
+
# Create a custom retry configuration
|
|
389
|
+
retry_config = RetryConfig(
|
|
390
|
+
max_retries=5, # Maximum number of retry attempts
|
|
391
|
+
initial_interval=2.0, # Initial wait time between retries in seconds
|
|
392
|
+
exponential=True, # Use exponential backoff (True) or fixed intervals (False)
|
|
393
|
+
exponent=2.0, # Base for exponential backoff calculation
|
|
394
|
+
max_interval=120.0, # Maximum wait time between retries
|
|
395
|
+
jitter=True, # Add random jitter to retry intervals to prevent thundering herd
|
|
396
|
+
jitter_factor=0.2, # Jitter range as a fraction of the base interval
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
client = OpenAIClient(client_config=OpenAIClientConfig(api_key="sk_...."))
|
|
400
|
+
|
|
401
|
+
async def generate_with_retry() -> None:
|
|
402
|
+
# Pass the retry_config to control retry behavior
|
|
403
|
+
response = await client.generate_completion(
|
|
404
|
+
messages=["Tell me about machine learning"],
|
|
405
|
+
config=OpenAICompletionConfig(model="gpt-4o"),
|
|
406
|
+
retry_config=retry_config, # Apply custom retry behavior
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
print(response.content)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
The retry mechanism automatically handles transient errors like rate limits, network issues, and temporary service unavailability. If the retry attempts are exhausted, a `RetryError` is raised containing the history of errors that occurred.
|