diff --git a/examples/web_search_pause_turn.py b/examples/web_search_pause_turn.py new file mode 100644 index 00000000..0ed18a85 --- /dev/null +++ b/examples/web_search_pause_turn.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from anthropic import Anthropic +from anthropic.types import Message, MessageParam + +client = Anthropic() + + +def assistant_turn_from_message(message: Message) -> MessageParam: + """Preserve paused server-tool blocks exactly as the SDK returned them.""" + return {"role": "assistant", "content": message.content} + + +messages: list[MessageParam] = [ + {"role": "user", "content": "Search for the latest Claude web search documentation."} +] + +while True: + message = client.messages.create( + model="claude-sonnet-4-5-20250929", + max_tokens=1024, + messages=messages, + tools=[ + { + "name": "web_search", + "type": "web_search_20250305", + } + ], + ) + + if message.stop_reason != "pause_turn": + break + + # Keep the full assistant turn intact. Splitting, filtering, or rewriting + # individual server_tool_use blocks can orphan tool state on the next request. + messages.append(assistant_turn_from_message(message)) + + +print(message.model_dump_json(indent=2)) diff --git a/tests/test_transform.py b/tests/test_transform.py index 4a874ff8..7427ba78 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,6 +8,7 @@ import pytest +from anthropic.types import ServerToolUseBlock from anthropic._types import Base64FileInput, omit, not_given from anthropic._utils import ( PropertyInfo, @@ -17,6 +18,7 @@ ) from anthropic._compat import PYDANTIC_V1 from anthropic._models import BaseModel +from anthropic.types.message_create_params import MessageCreateParamsNonStreaming _T = TypeVar("_T") @@ -104,6 +106,48 @@ async def test_union_of_typeddict(use_async: bool) -> None: } +@parametrize +@pytest.mark.asyncio +async def test_message_params_preserve_server_tool_use_blocks(use_async: bool) -> None: + server_tool_use = ServerToolUseBlock( + id="srvtoolu_123", + input={"query": "latest Claude docs"}, + name="web_search", + type="server_tool_use", + ) + + assert await transform( + { + "max_tokens": 1024, + "model": "claude-sonnet-4-5-20250929", + "messages": [ + { + "role": "assistant", + "content": [server_tool_use], + } + ], + }, + MessageCreateParamsNonStreaming, + use_async, + ) == { + "max_tokens": 1024, + "model": "claude-sonnet-4-5-20250929", + "messages": [ + { + "role": "assistant", + "content": [ + { + "id": "srvtoolu_123", + "input": {"query": "latest Claude docs"}, + "name": "web_search", + "type": "server_tool_use", + } + ], + } + ], + } + + class Foo5(TypedDict): foo: Annotated[Union[Bar4, List[Baz4]], PropertyInfo(alias="FOO")]