From 3aa7bdca9966dd8b308929a507147d225464f769 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 13 Jun 2026 17:50:43 -0400 Subject: [PATCH] Fix: Return NormalizedResponse from send_openai_compatible This resolves the issue where calling 'send_openai_compatible' discarded the NormalizedResponse details, resulting in an AttributeError when accessing 'raw_response' inside the tool loop. --- src/ai_client.py | 7 ++++++- src/openai_compatible.py | 7 ++++--- tests/test_openai_compatible.py | 6 +++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/ai_client.py b/src/ai_client.py index cd9d0a24..f1146268 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -732,7 +732,12 @@ def run_with_tool_loop( def _default_send(_round_idx: int) -> NormalizedResponse: from src.openai_compatible import send_openai_compatible as _send_oc assert capabilities is not None, "capabilities required when send_func is not provided" - return _send_oc(client, request_builder(_round_idx), capabilities=capabilities) + res = _send_oc(client, request_builder(_round_idx), capabilities=capabilities) + if not res.ok: + if res.errors and res.errors[0].original: + raise res.errors[0].original + raise RuntimeError(res.errors[0].message if res.errors else "Unknown OpenAI error") + return res.data request_builder: Callable[[int], OpenAICompatibleRequest] = (request if callable(request) else (lambda _i: request)) dispatch_send: Callable[[int], NormalizedResponse] = send_func or _default_send response_text: str = "" diff --git a/src/openai_compatible.py b/src/openai_compatible.py index 7fd5be24..7368fa77 100644 --- a/src/openai_compatible.py +++ b/src/openai_compatible.py @@ -64,7 +64,7 @@ def send_openai_compatible( request: OpenAICompatibleRequest, *, capabilities: Any, -) -> Result[str]: +) -> Result[NormalizedResponse]: kwargs: dict[str, Any] = { "model": request.model, "messages": request.messages, @@ -83,9 +83,10 @@ def send_openai_compatible( response = _send_streaming(client, kwargs, request.stream_callback) else: response = _send_blocking(client, kwargs) - return Result(data=response.text) + return Result(data=response) except OpenAIError as exc: - return Result(data="", errors=[_classify_openai_compatible_error(exc, source="openai_compatible")]) + empty_resp = NormalizedResponse(text="", tool_calls=[], usage_input_tokens=0, usage_output_tokens=0, usage_cache_read_tokens=0, usage_cache_creation_tokens=0, raw_response=None) + return Result(data=empty_resp, errors=[_classify_openai_compatible_error(exc, source="openai_compatible")]) def _send_blocking(client: Any, kwargs: dict[str, Any]) -> NormalizedResponse: resp = client.chat.completions.create(**kwargs) diff --git a/tests/test_openai_compatible.py b/tests/test_openai_compatible.py index 9425dc19..ff1dcaeb 100644 --- a/tests/test_openai_compatible.py +++ b/tests/test_openai_compatible.py @@ -28,7 +28,7 @@ def test_send_non_streaming_returns_text_in_result(caps: VendorCapabilities) -> request = OpenAICompatibleRequest(messages=[{"role": "user", "content": "ping"}], model="m", max_tokens=100) result = send_openai_compatible(client, request, capabilities=caps) assert result.ok - assert result.data == "hi" + assert result.data.text == "hi" assert result.errors == [] def test_send_streaming_aggregates_chunks(caps: VendorCapabilities) -> None: @@ -43,7 +43,7 @@ def test_send_streaming_aggregates_chunks(caps: VendorCapabilities) -> None: request = OpenAICompatibleRequest(messages=[{"role": "user", "content": "ping"}], model="m", stream=True, stream_callback=received.append) result = send_openai_compatible(client, request, capabilities=caps) assert result.ok - assert result.data == "hello" + assert result.data.text == "hello" assert received == ["hel", "lo"] def test_tool_call_detection_in_blocking_response(caps: VendorCapabilities) -> None: @@ -69,7 +69,7 @@ def test_vision_multimodal_message(caps: VendorCapabilities) -> None: result = send_openai_compatible(client, request, capabilities=caps) sent_messages = client.chat.completions.create.call_args.kwargs["messages"] assert sent_messages[0]["content"] == messages[0]["content"] - assert result.data == "looks like a cat" + assert result.data.text == "looks like a cat" def test_error_classification_429_to_rate_limit(caps: VendorCapabilities) -> None: from openai import RateLimitError