Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ LLMWHISPERER_BASE_URL=https://llmwhisperer-api.unstract.com/v1
LLMWHISPERER_BASE_URL_V2=https://llmwhisperer-api.us-central.unstract.com/api/v2
LLMWHISPERER_LOG_LEVEL=DEBUG
LLMWHISPERER_API_KEY=
WEBHOOK_TEST_URL=https://httpbin.org/post
2 changes: 1 addition & 1 deletion src/unstract/llmwhisperer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "2.4.0"
__version__ = "2.4.1"

from .client_v2 import LLMWhispererClientV2 # noqa: F401

Expand Down
14 changes: 11 additions & 3 deletions src/unstract/llmwhisperer/client_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,9 +446,17 @@ def whisper_status(self, whisper_hash: str) -> Any:
s = requests.Session()
response = s.send(prepared, timeout=self.api_timeout)
if response.status_code != 200:
err = json.loads(response.text)
err["status_code"] = response.status_code
raise LLMWhispererClientException(err)
if not (response.text or "").strip():
self.logger.error(f"API error - empty response body, status code: {response.status_code}")
raise LLMWhispererClientException("API error: empty response body", response.status_code)
try:
err = json.loads(response.text)
except json.JSONDecodeError as e:
# Truncate response text if too long to avoid log pollution
response_preview = response.text[:500] + "..." if len(response.text) > 500 else response.text
self.logger.error(f"API error - JSON decode failed: {e}; Response preview: {response_preview!r}")
raise LLMWhispererClientException(f"API error: non-JSON response - {response_preview}", response.status_code) from e
raise LLMWhispererClientException(err, response.status_code)
message = json.loads(response.text)
message["status_code"] = response.status_code
return message
Expand Down
22 changes: 14 additions & 8 deletions tests/integration/client_v2_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@

logger = logging.getLogger(__name__)

# Test tolerance constants for better maintainability
COORDINATE_TOLERANCE = 2
PERCENTAGE_TOLERANCE = 0.05
PAGE_HEIGHT_TOLERANCE = 5
OCR_SIMILARITY_THRESHOLD = 0.90


def test_get_usage_info(client_v2: LLMWhispererClientV2) -> None:
usage_info = client_v2.get_usage_info()
Expand Down Expand Up @@ -103,12 +109,12 @@ def test_highlight(client_v2: LLMWhispererClientV2, data_dir: str, input_file: s

# Assert line 2 data
line2 = highlight_data["2"]
assert line2["base_y"] == 155
assert line2["base_y_percent"] == pytest.approx(4.8927) # Using approx for float comparison
assert line2["height"] == 51
assert line2["height_percent"] == pytest.approx(1.6098) # Using approx for float comparison
assert line2["base_y"] == pytest.approx(155, abs=COORDINATE_TOLERANCE)
assert line2["base_y_percent"] == pytest.approx(4.8927, abs=PERCENTAGE_TOLERANCE)
assert line2["height"] == pytest.approx(51, abs=COORDINATE_TOLERANCE)
assert line2["height_percent"] == pytest.approx(1.6098, abs=PERCENTAGE_TOLERANCE)
assert line2["page"] == 0
assert line2["page_height"] == 3168
assert line2["page_height"] == pytest.approx(3168, abs=PAGE_HEIGHT_TOLERANCE)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -170,7 +176,7 @@ def test_whisper_v2_url_in_post(
"url,token,webhook_name",
[
(
"https://webhook.site/0990fff9-ce95-4d11-95e1-be9ad38c40d6", # need to find a clean solution
os.getenv("WEBHOOK_TEST_URL", "https://httpbin.org/post"), # configurable via env var, defaults to httpbin.org
"",
"client_v2_test",
),
Expand Down Expand Up @@ -237,13 +243,13 @@ def assert_extracted_text(file_path: str, whisper_result: dict, mode: str, outpu
assert whisper_result["status_code"] == 200

# For OCR based processing
threshold = 0.94
threshold = OCR_SIMILARITY_THRESHOLD

# For text based processing
if mode == "native_text" and output_mode == "text":
threshold = 0.99
elif mode == "low_cost":
threshold = 0.90
threshold = OCR_SIMILARITY_THRESHOLD
extracted_text = whisper_result["extraction"]["result_text"]
similarity = SequenceMatcher(None, extracted_text, exp).ratio()

Expand Down