Skip to content

Commit 6f18a72

Browse files
committed
docs(OpenAIClient): header comments
1 parent 544c500 commit 6f18a72

1 file changed

Lines changed: 153 additions & 11 deletions

File tree

src/FreeScribe.client/utils/network/openai_client.py

Lines changed: 153 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
"""
2+
/utils/network/openai_client.py
3+
4+
This software is released under the AGPL-3.0 license
5+
Copyright (c) 2023-2024 Braedon Hendy
6+
7+
Further updates and packaging added in 2024 through the ClinicianFOCUS initiative,
8+
a collaboration with Dr. Braedon Hendy and Conestoga College Institute of Applied
9+
Learning and Technology as part of the CNERG+ applied research project,
10+
Unburdening Primary Healthcare: An Open-Source AI Clinician Partner Platform".
11+
Prof. Michael Yingbull (PI), Dr. Braedon Hendy (Partner),
12+
and Research Students - Software Developer Alex Simko, Pemba Sherpa (F24), and Naitik Patel.
13+
"""
14+
15+
116
import asyncio
217
import json
318
import httpx
@@ -10,7 +25,49 @@
1025

1126

1227
class OpenAIClient(BaseNetworkClient):
13-
"""Client for communicating with OpenAI API."""
28+
"""
29+
Client for communicating with OpenAI API endpoints.
30+
31+
This class provides an asynchronous interface for interacting with OpenAI's
32+
Chat Completion API and legacy Completion API. It supports cancellable requests,
33+
error handling, and configurable model parameters.
34+
35+
Features:
36+
- Asynchronous API calls with httpx
37+
- Request cancellation via threading and asyncio events
38+
- Automatic error handling and user-friendly error messages
39+
- Support for both Chat API (gpt-4, gpt-3.5-turbo) and Completion API
40+
- Configurable model parameters (temperature, max_tokens, etc.)
41+
- Thread-safe cancellation monitoring
42+
43+
Attributes:
44+
cancel_monitor_thread (threading.Thread): Thread for monitoring cancellation events
45+
monitoring_stop_event (threading.Event): Event to stop the monitoring thread
46+
stop_event (asyncio.Event): Async event for request cancellation
47+
threading_cancel_event (threading.Event): Threading event for cancellation signals
48+
checking_active (bool): Flag indicating if cancellation monitoring is active
49+
50+
Example:
51+
```python
52+
config = NetworkConfig(host="https://api.openai.com/v1", api_key="your-key")
53+
client = OpenAIClient(config)
54+
55+
# Synchronous call
56+
response = client.send_chat_completion_sync(
57+
text="Hello, world!",
58+
model="gpt-4",
59+
threading_cancel_event=cancel_event
60+
)
61+
62+
# Asynchronous call
63+
response = await client.send_chat_completion(
64+
text="Hello, world!",
65+
model="gpt-4",
66+
stop_event=stop_event,
67+
threading_cancel_event=cancel_event
68+
)
69+
```
70+
"""
1471

1572
def __init__(self, config: NetworkConfig):
1673
super().__init__(config)
@@ -168,6 +225,13 @@ async def send_completion(
168225
await self._close_client()
169226

170227
def _apply_options(self, payload: Dict[str, Any], options: Dict[str, Any]) -> None:
228+
"""
229+
Apply model configuration options to the API request payload.
230+
231+
Args:
232+
payload (Dict[str, Any]): The request payload to modify
233+
options (Dict[str, Any]): Dictionary of options to apply (temperature, max_tokens, etc.)
234+
"""
171235
# Set default values
172236
payload.update({
173237
"temperature": 0.7,
@@ -201,7 +265,18 @@ def _build_payload(
201265
system_message: Optional[str] = None,
202266
**options
203267
) -> Dict[str, Any]:
204-
"""Build the request payload for OpenAI Chat API."""
268+
"""
269+
Build the request payload for OpenAI Chat API.
270+
271+
Args:
272+
text (str): The user message text to send
273+
model (str): The model name to use (e.g., 'gpt-4', 'gpt-3.5-turbo')
274+
system_message (Optional[str]): Optional system message to set context
275+
**options: Additional model options
276+
277+
Returns:
278+
Dict[str, Any]: The formatted request payload for the Chat API
279+
"""
205280
messages = []
206281

207282
# Add system message if provided
@@ -222,7 +297,17 @@ def _build_payload(
222297
return payload
223298

224299
def _build_completion_payload(self, prompt: str, model: str, **options) -> Dict[str, Any]:
225-
"""Build the request payload for OpenAI Completion API."""
300+
"""
301+
Build the request payload for OpenAI Completion API.
302+
303+
Args:
304+
prompt (str): The prompt text to send to the API
305+
model (str): The model name to use
306+
**options: Additional model options
307+
308+
Returns:
309+
Dict[str, Any]: The formatted request payload for the Completion API
310+
"""
226311
payload = {
227312
"model": model.strip(),
228313
"prompt": prompt
@@ -233,7 +318,19 @@ def _build_completion_payload(self, prompt: str, model: str, **options) -> Dict[
233318
return payload
234319

235320
async def _send(self, endpoint: str, payload: Dict[str, Any]) -> Dict[str, Any]:
236-
"""Unified method to send HTTP requests to OpenAI API endpoints."""
321+
"""
322+
Unified method to send HTTP requests to OpenAI API endpoints.
323+
324+
Args:
325+
endpoint (str): The API endpoint path (e.g., 'chat/completions')
326+
payload (Dict[str, Any]): The request payload to send
327+
328+
Returns:
329+
Dict[str, Any]: The JSON response from the API
330+
331+
Raises:
332+
httpx.HTTPStatusError: If the API returns an error status code
333+
"""
237334
url = f"{self.config.host}/{endpoint}"
238335
headers = {
239336
"Authorization": f"Bearer {self.config.api_key}",
@@ -244,23 +341,47 @@ async def _send(self, endpoint: str, payload: Dict[str, Any]) -> Dict[str, Any]:
244341
return response.json()
245342

246343
def _parse_response_data(self, response_data: Dict[str, Any]) -> str:
247-
"""Parse the response data from OpenAI Chat API."""
344+
"""
345+
Parse the response data from OpenAI Chat API.
346+
347+
Args:
348+
response_data (Dict[str, Any]): The raw response data from the API
349+
350+
Returns:
351+
str: The extracted message content or error message if parsing fails
352+
"""
248353
try:
249354
return response_data['choices'][0]['message']['content']
250355
except (KeyError, IndexError) as e:
251356
logger.error(f"Error parsing OpenAI response structure: {e}")
252357
return 'Error: Invalid response format from OpenAI API'
253358

254359
def _parse_completion_response_data(self, response_data: Dict[str, Any]) -> str:
255-
"""Parse the response data from OpenAI Completion API."""
360+
"""
361+
Parse the response data from OpenAI Completion API.
362+
363+
Args:
364+
response_data (Dict[str, Any]): The raw response data from the API
365+
366+
Returns:
367+
str: The extracted completion text or error message if parsing fails
368+
"""
256369
try:
257370
return response_data['choices'][0]['text'].strip()
258371
except (KeyError, IndexError) as e:
259372
logger.error(f"Error parsing OpenAI completion response structure: {e}")
260373
return 'Error: Invalid response format from OpenAI Completion API'
261374

262375
def _handle_error(self, error: Exception) -> str:
263-
"""Handle and categorize different types of errors specific to OpenAI."""
376+
"""
377+
Handle and categorize different types of errors specific to OpenAI.
378+
379+
Args:
380+
error (Exception): The exception that occurred during the API request
381+
382+
Returns:
383+
str: A user-friendly error message describing the issue
384+
"""
264385
if isinstance(error, httpx.ReadError):
265386
return "Network connection lost to OpenAI API. Please check your internet connection."
266387
elif isinstance(error, httpx.ConnectError):
@@ -283,7 +404,12 @@ def _handle_error(self, error: Exception) -> str:
283404
return f"OpenAI API request failed: {str(error)}"
284405

285406
async def cancel_request(self):
286-
"""Cancel an ongoing API request by setting the stop event and closing the client."""
407+
"""
408+
Cancel an ongoing API request by setting the stop event and closing the client.
409+
410+
This method gracefully closes the HTTP client connection to terminate
411+
any pending requests.
412+
"""
287413
try:
288414
logger.info("Cancelling OpenAI API request...")
289415
await self._close_client()
@@ -292,7 +418,12 @@ async def cancel_request(self):
292418
logger.exception(f"Error during OpenAI API cancellation: {e}")
293419

294420
def start_cancel_monitoring(self):
295-
"""Start the cancellation monitoring loop in a separate thread."""
421+
"""
422+
Start the cancellation monitoring loop in a separate thread.
423+
424+
Begins monitoring the threading cancel event to detect cancellation
425+
requests and handle them appropriately by closing the client connection.
426+
"""
296427
if self.threading_cancel_event:
297428
self.checking_active = True
298429
self.monitoring_stop_event.clear()
@@ -304,7 +435,13 @@ def start_cancel_monitoring(self):
304435
self.cancel_monitor_thread.start()
305436

306437
def _monitor_cancellation(self):
307-
"""Monitor cancellation event in a separate thread."""
438+
"""
439+
Monitor cancellation event in a separate thread.
440+
441+
Continuously checks for cancellation signals and closes the client
442+
connection when cancellation is detected. Runs in a daemon thread
443+
with periodic sleep intervals.
444+
"""
308445
while self.checking_active and not self.monitoring_stop_event.is_set():
309446
try:
310447
if self.threading_cancel_event is None:
@@ -339,7 +476,12 @@ def close_client():
339476
break
340477

341478
def stop_cancel_monitoring(self):
342-
"""Stop the cancellation monitoring."""
479+
"""
480+
Stop the cancellation monitoring.
481+
482+
Terminates the cancellation monitoring thread and cleans up
483+
associated events and resources.
484+
"""
343485
logger.info("Stopping OpenAI API cancellation monitoring.")
344486
self.checking_active = False
345487
self.monitoring_stop_event.set()

0 commit comments

Comments
 (0)