diff --git a/src/anthropic/resources/messages.py b/src/anthropic/resources/messages.py index 8f303984..37732cac 100644 --- a/src/anthropic/resources/messages.py +++ b/src/anthropic/resources/messages.py @@ -2,6 +2,7 @@ from __future__ import annotations +import warnings from typing import List, Union, Iterable, overload from functools import partial from typing_extensions import Literal @@ -34,6 +35,15 @@ __all__ = ["Messages", "AsyncMessages"] +DEPRECATED_MODELS = { + "claude-1.3": "November 6th, 2024", + "claude-1.3-100k": "November 6th, 2024", + "claude-instant-1.1": "November 6th, 2024", + "claude-instant-1.1-100k": "November 6th, 2024", + "claude-instant-1.2": "November 6th, 2024", +} + + class Messages(SyncAPIResource): @cached_property def with_raw_response(self) -> MessagesWithRawResponse: @@ -857,6 +867,14 @@ def create( ) -> Message | Stream[RawMessageStreamEvent]: if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 + + if model in DEPRECATED_MODELS: + warnings.warn( + f"The model '{model}' is deprecated and will reach end-of-life on {DEPRECATED_MODELS[model]}.\nPlease migrate to a newer model. Visit https://docs.anthropic.com/en/docs/resources/model-deprecations for more information.", + DeprecationWarning, + stacklevel=3, + ) + return self._post( "/v1/messages", body=maybe_transform( @@ -906,6 +924,13 @@ def stream( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> MessageStreamManager: """Create a Message stream""" + if model in DEPRECATED_MODELS: + warnings.warn( + f"The model '{model}' is deprecated and will reach end-of-life on {DEPRECATED_MODELS[model]}.\nPlease migrate to a newer model. Visit https://docs.anthropic.com/en/docs/resources/model-deprecations for more information.", + DeprecationWarning, + stacklevel=3, + ) + extra_headers = { "X-Stainless-Stream-Helper": "messages", **(extra_headers or {}), @@ -1763,6 +1788,14 @@ async def create( ) -> Message | AsyncStream[RawMessageStreamEvent]: if not is_given(timeout) and self._client.timeout == DEFAULT_TIMEOUT: timeout = 600 + + if model in DEPRECATED_MODELS: + warnings.warn( + f"The model '{model}' is deprecated and will reach end-of-life on {DEPRECATED_MODELS[model]}.\nPlease migrate to a newer model. Visit https://docs.anthropic.com/en/docs/resources/model-deprecations for more information.", + DeprecationWarning, + stacklevel=3, + ) + return await self._post( "/v1/messages", body=await async_maybe_transform( @@ -1812,6 +1845,13 @@ def stream( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> AsyncMessageStreamManager: """Create a Message stream""" + if model in DEPRECATED_MODELS: + warnings.warn( + f"The model '{model}' is deprecated and will reach end-of-life on {DEPRECATED_MODELS[model]}.\nPlease migrate to a newer model. Visit https://docs.anthropic.com/en/docs/resources/model-deprecations for more information.", + DeprecationWarning, + stacklevel=3, + ) + extra_headers = { "X-Stainless-Stream-Helper": "messages", **(extra_headers or {}), diff --git a/tests/api_resources/test_messages.py b/tests/api_resources/test_messages.py index e3685408..17e1caf6 100644 --- a/tests/api_resources/test_messages.py +++ b/tests/api_resources/test_messages.py @@ -10,6 +10,7 @@ from anthropic import Anthropic, AsyncAnthropic from tests.utils import assert_matches_type from anthropic.types import Message +from anthropic.resources.messages import DEPRECATED_MODELS base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -283,6 +284,16 @@ def test_streaming_response_create_overload_2(self, client: Anthropic) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_deprecated_model_warning(self, client: Anthropic) -> None: + for deprecated_model in DEPRECATED_MODELS: + with pytest.warns(DeprecationWarning, match=f"The model '{deprecated_model}' is deprecated"): + client.messages.create( + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}], + model=deprecated_model, + ) + class TestAsyncMessages: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -552,3 +563,13 @@ async def test_streaming_response_create_overload_2(self, async_client: AsyncAnt await stream.close() assert cast(Any, response.is_closed) is True + + @parametrize + async def test_deprecated_model_warning(self, async_client: AsyncAnthropic) -> None: + for deprecated_model in DEPRECATED_MODELS: + with pytest.warns(DeprecationWarning, match=f"The model '{deprecated_model}' is deprecated"): + await async_client.messages.create( + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}], + model=deprecated_model, + ) diff --git a/tests/lib/streaming/test_messages.py b/tests/lib/streaming/test_messages.py index e719516e..d5160efa 100644 --- a/tests/lib/streaming/test_messages.py +++ b/tests/lib/streaming/test_messages.py @@ -12,6 +12,7 @@ from anthropic import Stream, Anthropic, AsyncStream, AsyncAnthropic from anthropic.lib.streaming import MessageStreamEvent from anthropic.types.message import Message +from anthropic.resources.messages import DEPRECATED_MODELS base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") api_key = "my-anthropic-api-key" @@ -128,6 +129,20 @@ def test_context_manager(self, respx_mock: MockRouter) -> None: # response should be closed even if the body isn't read assert stream.response.is_closed + @pytest.mark.respx(base_url=base_url) + def test_deprecated_model_warning_stream(self, respx_mock: MockRouter) -> None: + for deprecated_model in DEPRECATED_MODELS: + respx_mock.post("/v1/messages").mock(return_value=httpx.Response(200, content=basic_response())) + + with pytest.warns(DeprecationWarning, match=f"The model '{deprecated_model}' is deprecated"): + with sync_client.messages.stream( + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}], + model=deprecated_model, + ) as stream: + # Consume the stream to ensure the warning is triggered + stream.until_done() + class TestAsyncMessages: @pytest.mark.asyncio @@ -170,6 +185,23 @@ async def test_context_manager(self, respx_mock: MockRouter) -> None: # response should be closed even if the body isn't read assert stream.response.is_closed + @pytest.mark.asyncio + @pytest.mark.respx(base_url=base_url) + async def test_deprecated_model_warning_stream(self, respx_mock: MockRouter) -> None: + for deprecated_model in DEPRECATED_MODELS: + respx_mock.post("/v1/messages").mock( + return_value=httpx.Response(200, content=to_async_iter(basic_response())) + ) + + with pytest.warns(DeprecationWarning, match=f"The model '{deprecated_model}' is deprecated"): + async with async_client.messages.stream( + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}], + model=deprecated_model, + ) as stream: + # Consume the stream to ensure the warning is triggered + await stream.get_final_message() + @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) def test_stream_method_definition_in_sync(sync: bool) -> None: