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.
- anthropic/__init__.py +3 -0
- anthropic/_base_client.py +3 -3
- anthropic/_compat.py +48 -48
- anthropic/_models.py +54 -45
- anthropic/_utils/__init__.py +8 -2
- anthropic/_utils/_compat.py +45 -0
- anthropic/_utils/_datetime_parse.py +136 -0
- anthropic/_utils/_transform.py +5 -1
- anthropic/_utils/_typing.py +1 -1
- anthropic/_utils/_utils.py +0 -1
- anthropic/_version.py +1 -1
- anthropic/lib/tools/__init__.py +20 -0
- anthropic/lib/tools/_beta_functions.py +289 -0
- anthropic/lib/tools/_beta_runner.py +405 -0
- anthropic/resources/beta/messages/messages.py +370 -1
- anthropic/types/beta/__init__.py +14 -0
- anthropic/types/beta/beta_base64_pdf_source.py +15 -0
- anthropic/types/beta/beta_citation_config.py +9 -0
- anthropic/types/beta/beta_content_block.py +2 -0
- anthropic/types/beta/beta_content_block_param.py +4 -0
- anthropic/types/beta/beta_document_block.py +26 -0
- anthropic/types/beta/beta_plain_text_source.py +15 -0
- anthropic/types/beta/beta_raw_content_block_start_event.py +2 -0
- anthropic/types/beta/beta_request_document_block_param.py +1 -1
- anthropic/types/beta/beta_server_tool_usage.py +3 -0
- anthropic/types/beta/beta_server_tool_use_block.py +1 -1
- anthropic/types/beta/beta_server_tool_use_block_param.py +3 -1
- anthropic/types/beta/beta_tool_union_param.py +2 -0
- anthropic/types/beta/beta_web_fetch_block.py +21 -0
- anthropic/types/beta/beta_web_fetch_block_param.py +22 -0
- anthropic/types/beta/beta_web_fetch_tool_20250910_param.py +46 -0
- anthropic/types/beta/beta_web_fetch_tool_result_block.py +20 -0
- anthropic/types/beta/beta_web_fetch_tool_result_block_param.py +25 -0
- anthropic/types/beta/beta_web_fetch_tool_result_error_block.py +14 -0
- anthropic/types/beta/beta_web_fetch_tool_result_error_block_param.py +15 -0
- anthropic/types/beta/beta_web_fetch_tool_result_error_code.py +16 -0
- anthropic/types/beta/message_count_tokens_params.py +2 -0
- anthropic/types/document_block_param.py +1 -1
- {anthropic-0.66.0.dist-info → anthropic-0.68.0.dist-info}/METADATA +51 -1
- {anthropic-0.66.0.dist-info → anthropic-0.68.0.dist-info}/RECORD +42 -25
- {anthropic-0.66.0.dist-info → anthropic-0.68.0.dist-info}/WHEEL +0 -0
- {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()
|