anthropic 0.66.0__py3-none-any.whl → 0.68.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. anthropic/__init__.py +3 -0
  2. anthropic/_base_client.py +3 -3
  3. anthropic/_compat.py +48 -48
  4. anthropic/_models.py +54 -45
  5. anthropic/_utils/__init__.py +8 -2
  6. anthropic/_utils/_compat.py +45 -0
  7. anthropic/_utils/_datetime_parse.py +136 -0
  8. anthropic/_utils/_transform.py +5 -1
  9. anthropic/_utils/_typing.py +1 -1
  10. anthropic/_utils/_utils.py +0 -1
  11. anthropic/_version.py +1 -1
  12. anthropic/lib/tools/__init__.py +20 -0
  13. anthropic/lib/tools/_beta_functions.py +289 -0
  14. anthropic/lib/tools/_beta_runner.py +405 -0
  15. anthropic/resources/beta/messages/messages.py +370 -1
  16. anthropic/types/beta/__init__.py +14 -0
  17. anthropic/types/beta/beta_base64_pdf_source.py +15 -0
  18. anthropic/types/beta/beta_citation_config.py +9 -0
  19. anthropic/types/beta/beta_content_block.py +2 -0
  20. anthropic/types/beta/beta_content_block_param.py +4 -0
  21. anthropic/types/beta/beta_document_block.py +26 -0
  22. anthropic/types/beta/beta_plain_text_source.py +15 -0
  23. anthropic/types/beta/beta_raw_content_block_start_event.py +2 -0
  24. anthropic/types/beta/beta_request_document_block_param.py +1 -1
  25. anthropic/types/beta/beta_server_tool_usage.py +3 -0
  26. anthropic/types/beta/beta_server_tool_use_block.py +1 -1
  27. anthropic/types/beta/beta_server_tool_use_block_param.py +3 -1
  28. anthropic/types/beta/beta_tool_union_param.py +2 -0
  29. anthropic/types/beta/beta_web_fetch_block.py +21 -0
  30. anthropic/types/beta/beta_web_fetch_block_param.py +22 -0
  31. anthropic/types/beta/beta_web_fetch_tool_20250910_param.py +46 -0
  32. anthropic/types/beta/beta_web_fetch_tool_result_block.py +20 -0
  33. anthropic/types/beta/beta_web_fetch_tool_result_block_param.py +25 -0
  34. anthropic/types/beta/beta_web_fetch_tool_result_error_block.py +14 -0
  35. anthropic/types/beta/beta_web_fetch_tool_result_error_block_param.py +15 -0
  36. anthropic/types/beta/beta_web_fetch_tool_result_error_code.py +16 -0
  37. anthropic/types/beta/message_count_tokens_params.py +2 -0
  38. anthropic/types/document_block_param.py +1 -1
  39. {anthropic-0.66.0.dist-info → anthropic-0.68.0.dist-info}/METADATA +51 -1
  40. {anthropic-0.66.0.dist-info → anthropic-0.68.0.dist-info}/RECORD +42 -25
  41. {anthropic-0.66.0.dist-info → anthropic-0.68.0.dist-info}/WHEEL +0 -0
  42. {anthropic-0.66.0.dist-info → anthropic-0.68.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,405 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from abc import ABC, abstractmethod
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ Any,
8
+ List,
9
+ Union,
10
+ Generic,
11
+ TypeVar,
12
+ Callable,
13
+ Iterable,
14
+ Iterator,
15
+ Coroutine,
16
+ AsyncIterator,
17
+ )
18
+ from typing_extensions import TypedDict, override
19
+
20
+ import httpx
21
+
22
+ from ..._types import Body, Query, Headers, NotGiven
23
+ from ..._utils import consume_sync_iterator, consume_async_iterator
24
+ from ...types.beta import BetaMessage, BetaContentBlock, BetaMessageParam
25
+ from ._beta_functions import BetaFunctionTool, BetaAsyncFunctionTool
26
+ from ..streaming._beta_messages import BetaMessageStream, BetaAsyncMessageStream
27
+ from ...types.beta.message_create_params import MessageCreateParamsBase
28
+ from ...types.beta.beta_tool_result_block_param import BetaToolResultBlockParam
29
+
30
+ if TYPE_CHECKING:
31
+ from ..._client import Anthropic, AsyncAnthropic
32
+
33
+
34
+ AnyFunctionToolT = TypeVar("AnyFunctionToolT", bound=Union[BetaFunctionTool[Any], BetaAsyncFunctionTool[Any]])
35
+ RunnerItemT = TypeVar("RunnerItemT")
36
+
37
+ log = logging.getLogger(__name__)
38
+
39
+
40
+ class RequestOptions(TypedDict, total=False):
41
+ extra_headers: Headers | None
42
+ extra_query: Query | None
43
+ extra_body: Body | None
44
+ timeout: float | httpx.Timeout | None | NotGiven
45
+
46
+
47
+ class BaseToolRunner(Generic[AnyFunctionToolT]):
48
+ def __init__(
49
+ self,
50
+ *,
51
+ params: MessageCreateParamsBase,
52
+ options: RequestOptions,
53
+ tools: Iterable[AnyFunctionToolT],
54
+ max_iterations: int | None = None,
55
+ ) -> None:
56
+ self._tools_by_name = {tool.name: tool for tool in tools}
57
+ self._params: MessageCreateParamsBase = {
58
+ **params,
59
+ "messages": [message for message in params["messages"]],
60
+ }
61
+ self._options = options
62
+ self._messages_modified = False
63
+ self._cached_tool_call_response: BetaMessageParam | None = None
64
+ self._max_iterations = max_iterations
65
+ self._iteration_count = 0
66
+
67
+ def set_messages_params(
68
+ self, params: MessageCreateParamsBase | Callable[[MessageCreateParamsBase], MessageCreateParamsBase]
69
+ ) -> None:
70
+ """
71
+ Update the parameters for the next API call. This invalidates any cached tool responses.
72
+
73
+ Args:
74
+ params (MessageCreateParamsBase | Callable): Either new parameters or a function to mutate existing parameters
75
+ """
76
+ if callable(params):
77
+ params = params(self._params)
78
+ self._params = params
79
+
80
+ def append_messages(self, *messages: BetaMessageParam | BetaMessage) -> None:
81
+ """Add one or more messages to the conversation history.
82
+
83
+ This invalidates the cached tool response, i.e. if tools were already called, then they will
84
+ be called again on the next loop iteration.
85
+ """
86
+ message_params: List[BetaMessageParam] = [
87
+ {"role": message.role, "content": message.content} if isinstance(message, BetaMessage) else message
88
+ for message in messages
89
+ ]
90
+ self._messages_modified = True
91
+ self.set_messages_params(lambda params: {**params, "messages": [*self._params["messages"], *message_params]})
92
+ self._cached_tool_call_response = None
93
+
94
+ def _should_stop(self) -> bool:
95
+ if self._max_iterations is not None and self._iteration_count >= self._max_iterations:
96
+ return True
97
+ return False
98
+
99
+
100
+ class BaseSyncToolRunner(BaseToolRunner[BetaFunctionTool[Any]], Generic[RunnerItemT], ABC):
101
+ def __init__(
102
+ self,
103
+ *,
104
+ params: MessageCreateParamsBase,
105
+ options: RequestOptions,
106
+ tools: Iterable[BetaFunctionTool[Any]],
107
+ client: Anthropic,
108
+ max_iterations: int | None = None,
109
+ ) -> None:
110
+ super().__init__(params=params, options=options, tools=tools, max_iterations=max_iterations)
111
+ self._client = client
112
+ self._iterator = self.__run__()
113
+ self._last_message: Callable[[], BetaMessage] | BetaMessage | None = None
114
+
115
+ def __next__(self) -> RunnerItemT:
116
+ return self._iterator.__next__()
117
+
118
+ def __iter__(self) -> Iterator[RunnerItemT]:
119
+ for item in self._iterator:
120
+ yield item
121
+
122
+ @abstractmethod
123
+ def __run__(self) -> Iterator[RunnerItemT]:
124
+ raise NotImplementedError()
125
+
126
+ def until_done(self) -> BetaMessage:
127
+ """
128
+ Consumes the tool runner stream and returns the last message if it has not been consumed yet.
129
+ If it has, it simply returns the last message.
130
+ """
131
+ consume_sync_iterator(self)
132
+ last_message = self._get_last_message()
133
+ assert last_message is not None
134
+ return last_message
135
+
136
+ def generate_tool_call_response(self) -> BetaMessageParam | None:
137
+ """Generate a MessageParam by calling tool functions with any tool use blocks from the last message.
138
+
139
+ Note the tool call response is cached, repeated calls to this method will return the same response.
140
+
141
+ None can be returned if no tool call was applicable.
142
+ """
143
+ if self._cached_tool_call_response is not None:
144
+ log.debug("Returning cached tool call response.")
145
+ return self._cached_tool_call_response
146
+ response = self._generate_tool_call_response()
147
+ self._cached_tool_call_response = response
148
+ return response
149
+
150
+
151
+ def _generate_tool_call_response(self) -> BetaMessageParam | None:
152
+ content = self._get_last_assistant_message_content()
153
+ if not content:
154
+ return None
155
+
156
+ tool_use_blocks = [block for block in content if block.type == "tool_use"]
157
+ if not tool_use_blocks:
158
+ return None
159
+
160
+ results: list[BetaToolResultBlockParam] = []
161
+
162
+ for tool_use in tool_use_blocks:
163
+ tool = self._tools_by_name.get(tool_use.name)
164
+ if tool is None:
165
+ results.append(
166
+ {
167
+ "type": "tool_result",
168
+ "tool_use_id": tool_use.id,
169
+ "content": f"Error: Tool '{tool_use.name}' not found",
170
+ "is_error": True,
171
+ }
172
+ )
173
+ continue
174
+
175
+ try:
176
+ result = tool.call(tool_use.input)
177
+ results.append({"type": "tool_result", "tool_use_id": tool_use.id, "content": result})
178
+ except Exception as exc:
179
+ log.exception(f"Error occurred while calling tool: {tool.name}", exc_info=exc)
180
+ results.append(
181
+ {
182
+ "type": "tool_result",
183
+ "tool_use_id": tool_use.id,
184
+ "content": repr(exc),
185
+ "is_error": True,
186
+ }
187
+ )
188
+
189
+ return {"role": "user", "content": results}
190
+
191
+ def _get_last_message(self) -> BetaMessage | None:
192
+ if callable(self._last_message):
193
+ return self._last_message()
194
+ return self._last_message
195
+
196
+ def _get_last_assistant_message_content(self) -> list[BetaContentBlock] | None:
197
+ last_message = self._get_last_message()
198
+ if last_message is None or last_message.role != "assistant" or not last_message.content:
199
+ return None
200
+
201
+ return last_message.content
202
+
203
+
204
+ class BetaToolRunner(BaseSyncToolRunner[BetaMessage]):
205
+ @override
206
+ def __run__(self) -> Iterator[BetaMessage]:
207
+ self._last_message = message = self._client.beta.messages.create(**self._params, **self._options)
208
+ yield message
209
+ self._iteration_count += 1
210
+
211
+ while not self._should_stop():
212
+ response = self.generate_tool_call_response()
213
+ if response is None:
214
+ log.debug("Tool call was not requested, exiting from tool runner loop.")
215
+ return
216
+
217
+ if not self._messages_modified:
218
+ self.append_messages(message, response)
219
+
220
+ self._iteration_count += 1
221
+ self._messages_modified = False
222
+ self._cached_tool_call_response = None
223
+ self._last_message = message = self._client.beta.messages.create(**self._params, **self._options)
224
+ yield message
225
+
226
+
227
+ class BetaStreamingToolRunner(BaseSyncToolRunner[BetaMessageStream]):
228
+ @override
229
+ def __run__(self) -> Iterator[BetaMessageStream]:
230
+ with self._client.beta.messages.stream(**self._params, **self._options) as stream:
231
+ self._last_message = stream.get_final_message
232
+ yield stream
233
+ message = stream.get_final_message()
234
+ self._iteration_count += 1
235
+
236
+ while not self._should_stop():
237
+ response = self.generate_tool_call_response()
238
+ if response is None:
239
+ log.debug("Tool call was not requested, exiting from tool runner loop.")
240
+ return
241
+
242
+ if not self._messages_modified:
243
+ self.append_messages(message, response)
244
+ self._iteration_count += 1
245
+ self._messages_modified = False
246
+
247
+ with self._client.beta.messages.stream(**self._params, **self._options) as stream:
248
+ self._cached_tool_call_response = None
249
+ self._last_message = stream.get_final_message
250
+ yield stream
251
+ message = stream.get_final_message()
252
+
253
+
254
+ class BaseAsyncToolRunner(BaseToolRunner[BetaAsyncFunctionTool[Any]], Generic[RunnerItemT], ABC):
255
+ def __init__(
256
+ self,
257
+ *,
258
+ params: MessageCreateParamsBase,
259
+ options: RequestOptions,
260
+ tools: Iterable[BetaAsyncFunctionTool[Any]],
261
+ client: AsyncAnthropic,
262
+ max_iterations: int | None = None,
263
+ ) -> None:
264
+ super().__init__(params=params, options=options, tools=tools, max_iterations=max_iterations)
265
+ self._client = client
266
+ self._iterator = self.__run__()
267
+ self._last_message: Callable[[], Coroutine[None, None, BetaMessage]] | BetaMessage | None = None
268
+
269
+ async def __anext__(self) -> RunnerItemT:
270
+ return await self._iterator.__anext__()
271
+
272
+ async def __aiter__(self) -> AsyncIterator[RunnerItemT]:
273
+ async for item in self._iterator:
274
+ yield item
275
+
276
+ @abstractmethod
277
+ async def __run__(self) -> AsyncIterator[RunnerItemT]:
278
+ raise NotImplementedError()
279
+ yield # type: ignore[unreachable]
280
+
281
+ async def until_done(self) -> BetaMessage:
282
+ """
283
+ Consumes the tool runner stream and returns the last message if it has not been consumed yet.
284
+ If it has, it simply returns the last message.
285
+ """
286
+ await consume_async_iterator(self)
287
+ last_message = await self._get_last_message()
288
+ assert last_message is not None
289
+ return last_message
290
+
291
+ async def generate_tool_call_response(self) -> BetaMessageParam | None:
292
+ """Generate a MessageParam by calling tool functions with any tool use blocks from the last message.
293
+
294
+ Note the tool call response is cached, repeated calls to this method will return the same response.
295
+
296
+ None can be returned if no tool call was applicable.
297
+ """
298
+ if self._cached_tool_call_response is not None:
299
+ log.debug("Returning cached tool call response.")
300
+ return self._cached_tool_call_response
301
+
302
+ response = await self._generate_tool_call_response()
303
+ self._cached_tool_call_response = response
304
+ return response
305
+
306
+ async def _get_last_message(self) -> BetaMessage | None:
307
+ if callable(self._last_message):
308
+ return await self._last_message()
309
+ return self._last_message
310
+
311
+ async def _get_last_assistant_message_content(self) -> list[BetaContentBlock] | None:
312
+ last_message = await self._get_last_message()
313
+ if last_message is None or last_message.role != "assistant" or not last_message.content:
314
+ return None
315
+
316
+ return last_message.content
317
+
318
+ async def _generate_tool_call_response(self) -> BetaMessageParam | None:
319
+ content = await self._get_last_assistant_message_content()
320
+ if not content:
321
+ return None
322
+
323
+ tool_use_blocks = [block for block in content if block.type == "tool_use"]
324
+ if not tool_use_blocks:
325
+ return None
326
+
327
+ results: list[BetaToolResultBlockParam] = []
328
+
329
+ for tool_use in tool_use_blocks:
330
+ tool = self._tools_by_name.get(tool_use.name)
331
+ if tool is None:
332
+ results.append(
333
+ {
334
+ "type": "tool_result",
335
+ "tool_use_id": tool_use.id,
336
+ "content": f"Error: Tool '{tool_use.name}' not found",
337
+ "is_error": True,
338
+ }
339
+ )
340
+ continue
341
+
342
+ try:
343
+ result = await tool.call(tool_use.input)
344
+ results.append({"type": "tool_result", "tool_use_id": tool_use.id, "content": result})
345
+ except Exception as exc:
346
+ log.exception(f"Error occurred while calling tool: {tool.name}", exc_info=exc)
347
+ results.append(
348
+ {
349
+ "type": "tool_result",
350
+ "tool_use_id": tool_use.id,
351
+ "content": repr(exc),
352
+ "is_error": True,
353
+ }
354
+ )
355
+
356
+ return {"role": "user", "content": results}
357
+
358
+
359
+ class BetaAsyncToolRunner(BaseAsyncToolRunner[BetaMessage]):
360
+ @override
361
+ async def __run__(self) -> AsyncIterator[BetaMessage]:
362
+ self._last_message = message = await self._client.beta.messages.create(**self._params, **self._options)
363
+ yield message
364
+ self._iteration_count += 1
365
+
366
+ while not self._should_stop():
367
+ response = await self.generate_tool_call_response()
368
+ if response is None:
369
+ log.debug("Tool call was not requested, exiting from tool runner loop.")
370
+ return
371
+
372
+ if not self._messages_modified:
373
+ self.append_messages(message, response)
374
+ self._iteration_count += 1
375
+ self._messages_modified = False
376
+ self._cached_tool_call_response = None
377
+ self._last_message = message = await self._client.beta.messages.create(**self._params, **self._options)
378
+ yield message
379
+
380
+
381
+ class BetaAsyncStreamingToolRunner(BaseAsyncToolRunner[BetaAsyncMessageStream]):
382
+ @override
383
+ async def __run__(self) -> AsyncIterator[BetaAsyncMessageStream]:
384
+ async with self._client.beta.messages.stream(**self._params, **self._options) as stream:
385
+ self._last_message = stream.get_final_message
386
+ yield stream
387
+ message = await stream.get_final_message()
388
+ self._iteration_count += 1
389
+
390
+ while not self._should_stop():
391
+ response = await self.generate_tool_call_response()
392
+ if response is None:
393
+ log.debug("Tool call was not requested, exiting from tool runner loop.")
394
+ return
395
+
396
+ if not self._messages_modified:
397
+ self.append_messages(message, response)
398
+ self._iteration_count += 1
399
+ self._messages_modified = False
400
+
401
+ async with self._client.beta.messages.stream(**self._params, **self._options) as stream:
402
+ self._last_message = stream.get_final_message
403
+ self._cached_tool_call_response = None
404
+ yield stream
405
+ message = await stream.get_final_message()