Skip to content

Commit 8bee30c

Browse files
authored
Merge pull request #26 from Zipstack/fix/error_handling
Fix TypeError in whisper method when API returns JSON string
2 parents f19a569 + ae92396 commit 8bee30c

File tree

3 files changed

+91
-6
lines changed

3 files changed

+91
-6
lines changed

src/unstract/llmwhisperer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "2.4.1"
1+
__version__ = "2.4.2"
22

33
from .client_v2 import LLMWhispererClientV2 # noqa: F401
44

src/unstract/llmwhisperer/client_v2.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -351,13 +351,23 @@ def generate() -> Generator[bytes, None, None]:
351351
s = requests.Session()
352352
response = s.send(prepared, timeout=wait_timeout, stream=should_stream)
353353
response.encoding = encoding
354-
if response.status_code != 200 and response.status_code != 202:
355-
message = json.loads(response.text)
354+
if response.status_code not in (200, 202):
355+
try:
356+
message = json.loads(response.text)
357+
if not isinstance(message, dict):
358+
message = {"message": str(message)}
359+
except (json.JSONDecodeError, ValueError):
360+
message = {"message": response.text}
356361
message["status_code"] = response.status_code
357362
message["extraction"] = {}
358363
raise LLMWhispererClientException(message)
359364
if response.status_code == 202:
360-
message = json.loads(response.text)
365+
try:
366+
message = json.loads(response.text)
367+
if not isinstance(message, dict):
368+
message = {"message": str(message)}
369+
except (json.JSONDecodeError, ValueError):
370+
message = {"message": response.text}
361371
message["status_code"] = response.status_code
362372
message["extraction"] = {}
363373
if not wait_for_completion:
@@ -455,7 +465,9 @@ def whisper_status(self, whisper_hash: str) -> Any:
455465
# Truncate response text if too long to avoid log pollution
456466
response_preview = response.text[:500] + "..." if len(response.text) > 500 else response.text
457467
self.logger.error(f"API error - JSON decode failed: {e}; Response preview: {response_preview!r}")
458-
raise LLMWhispererClientException(f"API error: non-JSON response - {response_preview}", response.status_code) from e
468+
raise LLMWhispererClientException(
469+
f"API error: non-JSON response - {response_preview}", response.status_code
470+
) from e
459471
raise LLMWhispererClientException(err, response.status_code)
460472
message = json.loads(response.text)
461473
message["status_code"] = response.status_code

tests/unit/client_v2_test.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from typing import Any
22
from unittest.mock import MagicMock
33

4-
from unstract.llmwhisperer.client_v2 import LLMWhispererClientV2
4+
import pytest
5+
from unstract.llmwhisperer.client_v2 import LLMWhispererClientException, LLMWhispererClientV2
56

67
WEBHOOK_URL = "http://test-webhook.com/callback"
78
AUTH_TOKEN = "dummy-auth-token"
@@ -34,3 +35,75 @@ def test_get_webhook_details(mocker: Any, client_v2: LLMWhispererClientV2) -> No
3435

3536
assert response["status"] == "success"
3637
assert response["webhook_details"]["url"] == WEBHOOK_URL
38+
39+
40+
def test_whisper_json_string_response_error(mocker: Any, client_v2: LLMWhispererClientV2) -> None:
41+
"""Test whisper method handles JSON string responses correctly for error
42+
cases."""
43+
mock_send = mocker.patch("requests.Session.send")
44+
mock_response = MagicMock()
45+
mock_response.status_code = 400
46+
mock_response.text = '"Error message as JSON string"'
47+
mock_response.encoding = "utf-8"
48+
mock_send.return_value = mock_response
49+
50+
with pytest.raises(LLMWhispererClientException) as exc_info:
51+
client_v2.whisper(url="https://example.com/test.pdf")
52+
53+
error = exc_info.value.args[0]
54+
assert error["message"] == "Error message as JSON string"
55+
assert error["status_code"] == 400
56+
assert error["extraction"] == {}
57+
58+
59+
def test_whisper_json_string_response_202(mocker: Any, client_v2: LLMWhispererClientV2) -> None:
60+
"""Test whisper method handles JSON string responses correctly for 202
61+
status."""
62+
mock_send = mocker.patch("requests.Session.send")
63+
mock_response = MagicMock()
64+
mock_response.status_code = 202
65+
mock_response.text = '"Processing in progress"'
66+
mock_response.encoding = "utf-8"
67+
mock_send.return_value = mock_response
68+
69+
response = client_v2.whisper(url="https://example.com/test.pdf", wait_for_completion=False)
70+
71+
assert response["message"] == "Processing in progress"
72+
assert response["status_code"] == 202
73+
assert response["extraction"] == {}
74+
75+
76+
def test_whisper_invalid_json_response_error(mocker: Any, client_v2: LLMWhispererClientV2) -> None:
77+
"""Test whisper method handles invalid JSON responses correctly for error
78+
cases."""
79+
mock_send = mocker.patch("requests.Session.send")
80+
mock_response = MagicMock()
81+
mock_response.status_code = 500
82+
mock_response.text = "Invalid JSON response"
83+
mock_response.encoding = "utf-8"
84+
mock_send.return_value = mock_response
85+
86+
with pytest.raises(LLMWhispererClientException) as exc_info:
87+
client_v2.whisper(url="https://example.com/test.pdf")
88+
89+
error = exc_info.value.args[0]
90+
assert error["message"] == "Invalid JSON response"
91+
assert error["status_code"] == 500
92+
assert error["extraction"] == {}
93+
94+
95+
def test_whisper_invalid_json_response_202(mocker: Any, client_v2: LLMWhispererClientV2) -> None:
96+
"""Test whisper method handles invalid JSON responses correctly for 202
97+
status."""
98+
mock_send = mocker.patch("requests.Session.send")
99+
mock_response = MagicMock()
100+
mock_response.status_code = 202
101+
mock_response.text = "Invalid JSON response"
102+
mock_response.encoding = "utf-8"
103+
mock_send.return_value = mock_response
104+
105+
response = client_v2.whisper(url="https://example.com/test.pdf", wait_for_completion=False)
106+
107+
assert response["message"] == "Invalid JSON response"
108+
assert response["status_code"] == 202
109+
assert response["extraction"] == {}

0 commit comments

Comments
 (0)