Skip to content

Commit 7e8cd88

Browse files
MaanavDCopilot
andcommitted
fix(sdk/python): address Responses API review feedback
- Rename model and variant Responses client factories to get_responses_client to match existing get_*_client naming. - Use FoundryLocalException for Responses API transport and parsing errors instead of exporting a dedicated ResponsesAPIError. - Keep only the foundry-local-core version bump in requirements.txt and restore existing ORT dependency markers/order. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a73cf7a commit 7e8cd88

8 files changed

Lines changed: 32 additions & 46 deletions

File tree

sdk/python/requirements.txt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
pydantic>=2.0.0
22
requests>=2.32.4
33
openai>=2.24.0
4+
# Standard native binary packages from the ORT-Nightly PyPI feed.
45
foundry-local-core==1.0.0
5-
onnxruntime-gpu==1.24.4; platform_system == "Linux"
6-
onnxruntime-core==1.24.4; platform_system != "Linux"
7-
onnxruntime-genai-cuda==0.13.1; platform_system == "Linux"
8-
onnxruntime-genai-core==0.13.1; platform_system != "Linux"
6+
onnxruntime-core==1.24.4; sys_platform != "linux"
7+
onnxruntime-gpu==1.24.4; sys_platform == "linux"
8+
onnxruntime-genai-core==0.13.1; sys_platform != "linux"
9+
onnxruntime-genai-cuda==0.13.1; sys_platform == "linux"

sdk/python/src/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from .configuration import Configuration
99
from .foundry_local_manager import FoundryLocalManager
10-
from .openai.responses_client import ResponsesAPIError, ResponsesClient, ResponsesClientSettings
10+
from .openai.responses_client import ResponsesClient, ResponsesClientSettings
1111
from .openai.responses_types import (
1212
ContentPart,
1313
DeleteResponseResult,
@@ -72,7 +72,6 @@
7272
"ResponseObject",
7373
"ResponseOutputItem",
7474
"ResponseUsage",
75-
"ResponsesAPIError",
7675
"ResponsesClient",
7776
"ResponsesClientSettings",
7877
"StreamingEvent",

sdk/python/src/detail/model.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,6 @@ def get_embedding_client(self) -> EmbeddingClient:
148148
"""Get an embedding client for the currently selected variant."""
149149
return self._selected_variant.get_embedding_client()
150150

151-
def create_responses_client(self, base_url: str) -> "ResponsesClient":
152-
"""Create a Responses API client for the currently selected variant."""
153-
return self._selected_variant.create_responses_client(base_url)
151+
def get_responses_client(self, base_url: str) -> "ResponsesClient":
152+
"""Get a Responses API client for the currently selected variant."""
153+
return self._selected_variant.get_responses_client(base_url)

sdk/python/src/detail/model_variant.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ def get_embedding_client(self) -> EmbeddingClient:
177177
"""Create an OpenAI-compatible ``EmbeddingClient`` for this variant."""
178178
return EmbeddingClient(self.id, self._core_interop)
179179

180-
def create_responses_client(self, base_url: str) -> ResponsesClient:
181-
"""Create a Responses API client for this variant.
180+
def get_responses_client(self, base_url: str) -> ResponsesClient:
181+
"""Create an OpenAI-compatible ``ResponsesClient`` for this variant.
182182
183183
:param base_url: Base URL of the running Foundry Local web service
184184
(e.g. ``manager.urls[0]``).

sdk/python/src/imodel.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ def get_embedding_client(self) -> 'EmbeddingClient':
138138
pass
139139

140140
@abstractmethod
141-
def create_responses_client(self, base_url: str) -> 'ResponsesClient':
141+
def get_responses_client(self, base_url: str) -> 'ResponsesClient':
142142
"""
143-
Create an OpenAI Responses API client bound to the running web service.
143+
Get an OpenAI Responses API client bound to the running web service.
144144
145145
Unlike the other clients, the Responses API is HTTP-only and requires
146146
the Foundry Local web service to be started. Pass the base URL

sdk/python/src/openai/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
LiveAudioTranscriptionResponse,
1515
TranscriptionContentPart,
1616
)
17-
from .responses_client import ResponsesClient, ResponsesClientSettings, ResponsesAPIError
17+
from .responses_client import ResponsesClient, ResponsesClientSettings
1818

1919
__all__ = [
2020
"AudioClient",
@@ -25,8 +25,7 @@
2525
"LiveAudioTranscriptionOptions",
2626
"LiveAudioTranscriptionResponse",
2727
"LiveAudioTranscriptionSession",
28-
"ResponsesAPIError",
2928
"ResponsesClient",
3029
"ResponsesClientSettings",
3130
"TranscriptionContentPart",
32-
]
31+
]

sdk/python/src/openai/responses_client.py

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
import requests
3838

39+
from ..exception import FoundryLocalException
3940
from .responses_types import (
4041
DeleteResponseResult,
4142
InputItemsListResponse,
@@ -105,20 +106,11 @@ def _serialize(self) -> Dict[str, Any]:
105106
return {k: v for k, v in raw.items() if v is not None}
106107

107108

108-
class ResponsesAPIError(Exception):
109-
"""Raised for HTTP/transport errors against the Responses API."""
110-
111-
def __init__(self, message: str, status_code: Optional[int] = None, body: Optional[str] = None):
112-
super().__init__(message)
113-
self.status_code = status_code
114-
self.body = body
115-
116-
117109
class ResponsesClient:
118110
"""Client for the OpenAI Responses API served by Foundry Local.
119111
120112
Construct via ``manager.create_responses_client(model_id)`` or
121-
``model.create_responses_client(base_url)``.
113+
``model.get_responses_client(base_url)``.
122114
"""
123115

124116
def __init__(self, base_url: str, model_id: Optional[str] = None):
@@ -294,7 +286,7 @@ def _request_json(self, method: str, path: str, body: Optional[Dict[str, Any]] =
294286
timeout=timeout,
295287
)
296288
except requests.RequestException as e:
297-
raise ResponsesAPIError(f"Network error calling {method} {path}: {e}") from e
289+
raise FoundryLocalException(f"Network error calling {method} {path}: {e}") from e
298290

299291
return self._handle_json_response(resp, method, path)
300292

@@ -305,15 +297,13 @@ def _post_json(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
305297
def _handle_json_response(resp: requests.Response, method: str, path: str) -> Dict[str, Any]:
306298
text = resp.text
307299
if not resp.ok:
308-
raise ResponsesAPIError(
309-
f"Responses API error ({resp.status_code}) for {method} {path}: {text[:500]}",
310-
status_code=resp.status_code,
311-
body=text,
300+
raise FoundryLocalException(
301+
f"Responses API error ({resp.status_code}) for {method} {path}: {text[:500]}"
312302
)
313303
try:
314304
return json.loads(text) if text else {}
315305
except json.JSONDecodeError as e:
316-
raise ResponsesAPIError(
306+
raise FoundryLocalException(
317307
f"Failed to parse response JSON from {method} {path}: {text[:200]}"
318308
) from e
319309

@@ -332,15 +322,13 @@ def _post_stream(
332322
timeout=(connect_timeout, None),
333323
)
334324
except requests.RequestException as e:
335-
raise ResponsesAPIError(f"Network error calling POST {path}: {e}") from e
325+
raise FoundryLocalException(f"Network error calling POST {path}: {e}") from e
336326

337327
if not resp.ok:
338328
body_text = resp.text
339329
resp.close()
340-
raise ResponsesAPIError(
341-
f"Responses API error ({resp.status_code}) for POST {path}: {body_text[:500]}",
342-
status_code=resp.status_code,
343-
body=body_text,
330+
raise FoundryLocalException(
331+
f"Responses API error ({resp.status_code}) for POST {path}: {body_text[:500]}"
344332
)
345333

346334
return _iter_sse_events(resp)
@@ -409,7 +397,7 @@ def _parse_sse_block(block: str) -> Any:
409397
try:
410398
parsed = json.loads(data)
411399
except json.JSONDecodeError as e:
412-
raise ResponsesAPIError(f"Failed to parse streaming event JSON: {e}") from e
400+
raise FoundryLocalException(f"Failed to parse streaming event JSON: {e}") from e
413401
if not isinstance(parsed, dict):
414402
return None
415403
return parse_streaming_event(parsed)
@@ -418,5 +406,4 @@ def _parse_sse_block(block: str) -> Any:
418406
__all__ = [
419407
"ResponsesClient",
420408
"ResponsesClientSettings",
421-
"ResponsesAPIError",
422409
]

sdk/python/test/openai/test_responses_client.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818

1919
import pytest
2020

21+
from foundry_local_sdk.exception import FoundryLocalException
2122
from foundry_local_sdk.openai.responses_client import (
22-
ResponsesAPIError,
2323
ResponsesClient,
2424
ResponsesClientSettings,
2525
_parse_sse_block,
@@ -291,7 +291,7 @@ def test_multi_line_data(self):
291291

292292
def test_invalid_json_raises(self):
293293
block = 'data: {not valid json'
294-
with pytest.raises(ResponsesAPIError):
294+
with pytest.raises(FoundryLocalException):
295295
_parse_sse_block(block)
296296

297297
def test_empty_block_returns_none(self):
@@ -527,15 +527,15 @@ def test_delete_parses_result(self):
527527
assert result.deleted is True
528528
assert result.id == "resp_1"
529529

530-
def test_http_error_raises_responses_api_error(self):
530+
def test_http_error_raises_foundry_local_exception(self):
531531
resp = MagicMock()
532532
resp.ok = False
533533
resp.status_code = 400
534534
resp.text = '{"error":{"message":"bad"}}'
535535
with patch("foundry_local_sdk.openai.responses_client.requests.request", return_value=resp):
536-
with pytest.raises(ResponsesAPIError) as excinfo:
536+
with pytest.raises(FoundryLocalException) as excinfo:
537537
self.client.create("hi")
538-
assert excinfo.value.status_code == 400
538+
assert "400" in str(excinfo.value)
539539
assert "bad" in str(excinfo.value)
540540

541541
def test_create_streaming_yields_events(self):
@@ -566,9 +566,9 @@ def test_streaming_http_error(self):
566566
resp.text = "boom"
567567
resp.close = MagicMock()
568568
with patch("foundry_local_sdk.openai.responses_client.requests.post", return_value=resp):
569-
with pytest.raises(ResponsesAPIError) as excinfo:
569+
with pytest.raises(FoundryLocalException) as excinfo:
570570
list(self.client.create_streaming("hi"))
571-
assert excinfo.value.status_code == 500
571+
assert "500" in str(excinfo.value)
572572

573573
def test_settings_merge_precedence(self):
574574
self.client.settings.temperature = 0.1

0 commit comments

Comments
 (0)