Files
567-labs--instructor/tests/test_incomplete_output_exception.py
wehub-resource-sync 97e91a83f3
Ruff / Ruff (push) Has been cancelled
Test / Core Tests (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.10) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.11) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.12) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.13) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.9) (push) Has been cancelled
Test / Full Coverage (Python 3.11) (push) Has been cancelled
Test / Core Provider Tests (OpenAI) (push) Has been cancelled
Test / Core Provider Tests (Anthropic) (push) Has been cancelled
Test / Core Provider Tests (Google) (push) Has been cancelled
Test / Core Provider Tests (Other) (push) Has been cancelled
Test / Anthropic Tests (push) Has been cancelled
Test / Gemini Tests (push) Has been cancelled
Test / Google GenAI Tests (push) Has been cancelled
Test / Vertex AI Tests (push) Has been cancelled
Test / OpenAI Tests (push) Has been cancelled
Test / Writer Tests (push) Has been cancelled
Test / Auto Client Tests (push) Has been cancelled
ty / type-check (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:38 +08:00

118 lines
4.0 KiB
Python

"""
Tests that IncompleteOutputException propagates directly from retry_sync/retry_async
without being wrapped in InstructorRetryException.
Regression tests for issue #2273.
"""
from unittest.mock import AsyncMock, Mock
import pytest
from pydantic import BaseModel
import instructor
from instructor.core.exceptions import IncompleteOutputException
from instructor.mode import Mode
class User(BaseModel):
name: str
age: int
def _make_truncated_response() -> Mock:
"""Return a mock response that simulates a MAX_TOKENS truncation."""
mock_response = Mock()
mock_response.choices = [Mock()]
mock_response.choices[0].message = Mock()
mock_response.choices[0].message.content = '{"name": "Alice"' # truncated JSON
mock_response.choices[0].finish_reason = "length"
mock_response.usage = None
return mock_response
def _raise_incomplete(*_args, **_kwargs):
raise IncompleteOutputException(last_completion=None)
def test_incomplete_output_exception_is_catchable_sync():
"""IncompleteOutputException must be catchable directly, not wrapped."""
mock_client = Mock()
mock_client.chat = Mock()
mock_client.chat.completions = Mock()
mock_client.chat.completions.create = Mock(side_effect=_raise_incomplete)
client = instructor.patch(mock_client, mode=Mode.TOOLS)
with pytest.raises(IncompleteOutputException):
client.chat.completions.create( # ty: ignore[no-matching-overload] - runtime-patched API
model="gpt-4o-mini",
response_model=User,
messages=[{"role": "user", "content": "test"}],
max_retries=0,
)
def test_incomplete_output_not_wrapped_in_instructor_retry_exception_sync():
"""IncompleteOutputException must NOT be wrapped in InstructorRetryException."""
mock_client = Mock()
mock_client.chat = Mock()
mock_client.chat.completions = Mock()
mock_client.chat.completions.create = Mock(side_effect=_raise_incomplete)
client = instructor.patch(mock_client, mode=Mode.TOOLS)
with pytest.raises(IncompleteOutputException):
client.chat.completions.create( # ty: ignore[no-matching-overload] - runtime-patched API
model="gpt-4o-mini",
response_model=User,
messages=[{"role": "user", "content": "test"}],
max_retries=3,
)
def test_incomplete_output_exception_with_max_retries_zero():
"""IncompleteOutputException is raised immediately when max_retries=0."""
call_count = {"n": 0}
def _raise_incomplete_and_count(*_args, **_kwargs):
call_count["n"] += 1
raise IncompleteOutputException(last_completion=None)
mock_client = Mock()
mock_client.chat = Mock()
mock_client.chat.completions = Mock()
mock_client.chat.completions.create = _raise_incomplete_and_count
client = instructor.patch(mock_client, mode=Mode.TOOLS)
with pytest.raises(IncompleteOutputException):
client.chat.completions.create( # ty: ignore[no-matching-overload] - runtime-patched API
model="gpt-4o-mini",
response_model=User,
messages=[{"role": "user", "content": "test"}],
max_retries=0,
)
# Should only have been called once (no retries for IncompleteOutputException)
assert call_count["n"] == 1
@pytest.mark.asyncio
async def test_incomplete_output_exception_is_catchable_async():
"""Async path: IncompleteOutputException must be directly catchable."""
mock_client = Mock()
mock_client.chat = Mock()
mock_client.chat.completions = Mock()
mock_client.chat.completions.create = AsyncMock(side_effect=_raise_incomplete)
client = instructor.patch(mock_client, mode=Mode.TOOLS)
with pytest.raises(IncompleteOutputException):
await client.chat.completions.create( # ty: ignore[no-matching-overload] - runtime-patched API
model="gpt-4o-mini",
response_model=User,
messages=[{"role": "user", "content": "test"}],
max_retries=0,
)