anthropic 0.74.1__py3-none-any.whl → 0.76.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/_base_client.py +140 -11
- anthropic/_client.py +4 -12
- anthropic/_models.py +16 -1
- anthropic/_streaming.py +78 -76
- anthropic/_types.py +12 -2
- anthropic/_version.py +1 -1
- anthropic/lib/streaming/_beta_messages.py +1 -1
- anthropic/lib/tools/_beta_compaction_control.py +48 -0
- anthropic/lib/tools/_beta_functions.py +30 -8
- anthropic/lib/tools/_beta_runner.py +233 -34
- anthropic/resources/beta/messages/messages.py +134 -27
- anthropic/resources/messages/messages.py +4 -0
- anthropic/types/anthropic_beta_param.py +1 -0
- anthropic/types/beta/__init__.py +31 -0
- anthropic/types/beta/beta_code_execution_tool_20250522_param.py +9 -1
- anthropic/types/beta/beta_code_execution_tool_20250825_param.py +9 -1
- anthropic/types/beta/beta_container.py +4 -0
- anthropic/types/beta/beta_container_params.py +2 -0
- anthropic/types/beta/beta_container_upload_block.py +2 -0
- anthropic/types/beta/beta_container_upload_block_param.py +5 -0
- anthropic/types/beta/beta_content_block.py +2 -0
- anthropic/types/beta/beta_content_block_param.py +2 -0
- anthropic/types/beta/beta_direct_caller.py +13 -0
- anthropic/types/beta/beta_direct_caller_param.py +13 -0
- anthropic/types/beta/beta_mcp_tool_config_param.py +15 -0
- anthropic/types/beta/beta_mcp_tool_default_config_param.py +15 -0
- anthropic/types/beta/beta_mcp_toolset_param.py +34 -0
- anthropic/types/beta/beta_memory_tool_20250818_param.py +11 -1
- anthropic/types/beta/beta_output_config_param.py +13 -0
- anthropic/types/beta/beta_raw_content_block_start_event.py +2 -0
- anthropic/types/beta/beta_server_tool_caller.py +15 -0
- anthropic/types/beta/beta_server_tool_caller_param.py +15 -0
- anthropic/types/beta/beta_server_tool_use_block.py +20 -4
- anthropic/types/beta/beta_server_tool_use_block_param.py +19 -4
- anthropic/types/beta/beta_skill.py +2 -0
- anthropic/types/beta/beta_skill_params.py +2 -0
- anthropic/types/beta/beta_tool_bash_20241022_param.py +11 -1
- anthropic/types/beta/beta_tool_bash_20250124_param.py +11 -1
- anthropic/types/beta/beta_tool_choice_any_param.py +2 -0
- anthropic/types/beta/beta_tool_choice_auto_param.py +2 -0
- anthropic/types/beta/beta_tool_choice_none_param.py +2 -0
- anthropic/types/beta/beta_tool_choice_tool_param.py +2 -0
- anthropic/types/beta/beta_tool_computer_use_20241022_param.py +11 -1
- anthropic/types/beta/beta_tool_computer_use_20250124_param.py +11 -1
- anthropic/types/beta/beta_tool_computer_use_20251124_param.py +47 -0
- anthropic/types/beta/beta_tool_param.py +16 -1
- anthropic/types/beta/beta_tool_reference_block.py +13 -0
- anthropic/types/beta/beta_tool_reference_block_param.py +21 -0
- anthropic/types/beta/beta_tool_result_block_param.py +6 -1
- anthropic/types/beta/beta_tool_search_tool_bm25_20251119_param.py +33 -0
- anthropic/types/beta/beta_tool_search_tool_regex_20251119_param.py +33 -0
- anthropic/types/beta/beta_tool_search_tool_result_block.py +20 -0
- anthropic/types/beta/beta_tool_search_tool_result_block_param.py +25 -0
- anthropic/types/beta/beta_tool_search_tool_result_error.py +16 -0
- anthropic/types/beta/beta_tool_search_tool_result_error_param.py +13 -0
- anthropic/types/beta/beta_tool_search_tool_search_result_block.py +15 -0
- anthropic/types/beta/beta_tool_search_tool_search_result_block_param.py +16 -0
- anthropic/types/beta/beta_tool_text_editor_20241022_param.py +11 -1
- anthropic/types/beta/beta_tool_text_editor_20250124_param.py +11 -1
- anthropic/types/beta/beta_tool_text_editor_20250429_param.py +11 -1
- anthropic/types/beta/beta_tool_text_editor_20250728_param.py +11 -1
- anthropic/types/beta/beta_tool_union_param.py +8 -0
- anthropic/types/beta/beta_tool_use_block.py +11 -3
- anthropic/types/beta/beta_tool_use_block_param.py +10 -3
- anthropic/types/beta/beta_web_fetch_tool_20250910_param.py +9 -1
- anthropic/types/beta/beta_web_search_tool_20250305_param.py +14 -1
- anthropic/types/beta/message_count_tokens_params.py +15 -0
- anthropic/types/beta/message_create_params.py +7 -0
- anthropic/types/beta/messages/batch_create_params.py +4 -4
- anthropic/types/beta/messages/beta_message_batch_individual_response.py +4 -0
- anthropic/types/messages/batch_create_params.py +2 -2
- anthropic/types/messages/message_batch_individual_response.py +4 -0
- anthropic/types/model.py +2 -0
- anthropic/types/model_param.py +2 -0
- anthropic/types/tool_choice_any_param.py +2 -0
- anthropic/types/tool_choice_auto_param.py +2 -0
- anthropic/types/tool_choice_none_param.py +2 -0
- anthropic/types/tool_choice_tool_param.py +2 -0
- anthropic/types/tool_param.py +5 -0
- anthropic/types/web_search_tool_20250305_param.py +5 -0
- {anthropic-0.74.1.dist-info → anthropic-0.76.0.dist-info}/METADATA +4 -2
- {anthropic-0.74.1.dist-info → anthropic-0.76.0.dist-info}/RECORD +84 -64
- {anthropic-0.74.1.dist-info → anthropic-0.76.0.dist-info}/WHEEL +0 -0
- {anthropic-0.74.1.dist-info → anthropic-0.76.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,9 +14,9 @@ from ... import _compat
|
|
|
14
14
|
from ..._utils import is_dict
|
|
15
15
|
from ..._compat import cached_property
|
|
16
16
|
from ..._models import TypeAdapter
|
|
17
|
-
from ...types.beta import BetaToolUnionParam
|
|
17
|
+
from ...types.beta import BetaToolParam, BetaToolUnionParam
|
|
18
18
|
from ..._utils._utils import CallableT
|
|
19
|
-
from ...types.tool_param import
|
|
19
|
+
from ...types.tool_param import InputSchema
|
|
20
20
|
from ...types.beta.beta_tool_result_block_param import Content as BetaContent
|
|
21
21
|
|
|
22
22
|
log = logging.getLogger(__name__)
|
|
@@ -39,7 +39,10 @@ class BetaBuiltinFunctionTool(ABC):
|
|
|
39
39
|
|
|
40
40
|
@property
|
|
41
41
|
def name(self) -> str:
|
|
42
|
-
|
|
42
|
+
raw = self.to_dict()
|
|
43
|
+
if "mcp_server_name" in raw:
|
|
44
|
+
return raw["mcp_server_name"]
|
|
45
|
+
return raw["name"]
|
|
43
46
|
|
|
44
47
|
|
|
45
48
|
class BetaAsyncBuiltinFunctionTool(ABC):
|
|
@@ -51,7 +54,10 @@ class BetaAsyncBuiltinFunctionTool(ABC):
|
|
|
51
54
|
|
|
52
55
|
@property
|
|
53
56
|
def name(self) -> str:
|
|
54
|
-
|
|
57
|
+
raw = self.to_dict()
|
|
58
|
+
if "mcp_server_name" in raw:
|
|
59
|
+
return raw["mcp_server_name"]
|
|
60
|
+
return raw["name"]
|
|
55
61
|
|
|
56
62
|
|
|
57
63
|
class BaseFunctionTool(Generic[CallableT]):
|
|
@@ -72,6 +78,7 @@ class BaseFunctionTool(Generic[CallableT]):
|
|
|
72
78
|
name: str | None = None,
|
|
73
79
|
description: str | None = None,
|
|
74
80
|
input_schema: InputSchema | type[BaseModel] | None = None,
|
|
81
|
+
defer_loading: bool | None = None,
|
|
75
82
|
) -> None:
|
|
76
83
|
if _compat.PYDANTIC_V1:
|
|
77
84
|
raise RuntimeError("Tool functions are only supported with Pydantic v2")
|
|
@@ -79,6 +86,7 @@ class BaseFunctionTool(Generic[CallableT]):
|
|
|
79
86
|
self.func = func
|
|
80
87
|
self._func_with_validate = pydantic.validate_call(func)
|
|
81
88
|
self.name = name or func.__name__
|
|
89
|
+
self._defer_loading = defer_loading
|
|
82
90
|
|
|
83
91
|
self.description = description or self._get_description_from_docstring()
|
|
84
92
|
|
|
@@ -94,12 +102,15 @@ class BaseFunctionTool(Generic[CallableT]):
|
|
|
94
102
|
def __call__(self) -> CallableT:
|
|
95
103
|
return self.func
|
|
96
104
|
|
|
97
|
-
def to_dict(self) ->
|
|
98
|
-
|
|
105
|
+
def to_dict(self) -> BetaToolParam:
|
|
106
|
+
defn: BetaToolParam = {
|
|
99
107
|
"name": self.name,
|
|
100
108
|
"description": self.description,
|
|
101
109
|
"input_schema": self.input_schema,
|
|
102
110
|
}
|
|
111
|
+
if self._defer_loading is not None:
|
|
112
|
+
defn["defer_loading"] = self._defer_loading
|
|
113
|
+
return defn
|
|
103
114
|
|
|
104
115
|
@cached_property
|
|
105
116
|
def _parsed_docstring(self) -> docstring_parser.Docstring:
|
|
@@ -211,6 +222,7 @@ def beta_tool(
|
|
|
211
222
|
name: str | None = None,
|
|
212
223
|
description: str | None = None,
|
|
213
224
|
input_schema: InputSchema | type[BaseModel] | None = None,
|
|
225
|
+
defer_loading: bool | None = None,
|
|
214
226
|
) -> Callable[[FunctionT], BetaFunctionTool[FunctionT]]: ...
|
|
215
227
|
|
|
216
228
|
|
|
@@ -220,6 +232,7 @@ def beta_tool(
|
|
|
220
232
|
name: str | None = None,
|
|
221
233
|
description: str | None = None,
|
|
222
234
|
input_schema: InputSchema | type[BaseModel] | None = None,
|
|
235
|
+
defer_loading: bool | None = None,
|
|
223
236
|
) -> BetaFunctionTool[FunctionT] | Callable[[FunctionT], BetaFunctionTool[FunctionT]]:
|
|
224
237
|
"""Create a FunctionTool from a function with automatic schema inference.
|
|
225
238
|
|
|
@@ -239,11 +252,15 @@ def beta_tool(
|
|
|
239
252
|
|
|
240
253
|
if func is not None:
|
|
241
254
|
# @beta_tool called without parentheses
|
|
242
|
-
return BetaFunctionTool(
|
|
255
|
+
return BetaFunctionTool(
|
|
256
|
+
func=func, name=name, description=description, input_schema=input_schema, defer_loading=defer_loading
|
|
257
|
+
)
|
|
243
258
|
|
|
244
259
|
# @beta_tool()
|
|
245
260
|
def decorator(func: FunctionT) -> BetaFunctionTool[FunctionT]:
|
|
246
|
-
return BetaFunctionTool(
|
|
261
|
+
return BetaFunctionTool(
|
|
262
|
+
func=func, name=name, description=description, input_schema=input_schema, defer_loading=defer_loading
|
|
263
|
+
)
|
|
247
264
|
|
|
248
265
|
return decorator
|
|
249
266
|
|
|
@@ -259,6 +276,7 @@ def beta_async_tool(
|
|
|
259
276
|
name: str | None = None,
|
|
260
277
|
description: str | None = None,
|
|
261
278
|
input_schema: InputSchema | type[BaseModel] | None = None,
|
|
279
|
+
defer_loading: bool | None = None,
|
|
262
280
|
) -> BetaAsyncFunctionTool[AsyncFunctionT]: ...
|
|
263
281
|
|
|
264
282
|
|
|
@@ -268,6 +286,7 @@ def beta_async_tool(
|
|
|
268
286
|
name: str | None = None,
|
|
269
287
|
description: str | None = None,
|
|
270
288
|
input_schema: InputSchema | type[BaseModel] | None = None,
|
|
289
|
+
defer_loading: bool | None = None,
|
|
271
290
|
) -> Callable[[AsyncFunctionT], BetaAsyncFunctionTool[AsyncFunctionT]]: ...
|
|
272
291
|
|
|
273
292
|
|
|
@@ -277,6 +296,7 @@ def beta_async_tool(
|
|
|
277
296
|
name: str | None = None,
|
|
278
297
|
description: str | None = None,
|
|
279
298
|
input_schema: InputSchema | type[BaseModel] | None = None,
|
|
299
|
+
defer_loading: bool | None = None,
|
|
280
300
|
) -> BetaAsyncFunctionTool[AsyncFunctionT] | Callable[[AsyncFunctionT], BetaAsyncFunctionTool[AsyncFunctionT]]:
|
|
281
301
|
"""Create an AsyncFunctionTool from a function with automatic schema inference.
|
|
282
302
|
|
|
@@ -301,6 +321,7 @@ def beta_async_tool(
|
|
|
301
321
|
name=name,
|
|
302
322
|
description=description,
|
|
303
323
|
input_schema=input_schema,
|
|
324
|
+
defer_loading=defer_loading,
|
|
304
325
|
)
|
|
305
326
|
|
|
306
327
|
# @beta_async_tool()
|
|
@@ -310,6 +331,7 @@ def beta_async_tool(
|
|
|
310
331
|
name=name,
|
|
311
332
|
description=description,
|
|
312
333
|
input_schema=input_schema,
|
|
334
|
+
defer_loading=defer_loading,
|
|
313
335
|
)
|
|
314
336
|
|
|
315
337
|
return decorator
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import warnings
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
5
6
|
from typing import (
|
|
6
7
|
TYPE_CHECKING,
|
|
@@ -31,6 +32,7 @@ from ._beta_functions import (
|
|
|
31
32
|
BetaBuiltinFunctionTool,
|
|
32
33
|
BetaAsyncBuiltinFunctionTool,
|
|
33
34
|
)
|
|
35
|
+
from ._beta_compaction_control import DEFAULT_THRESHOLD, DEFAULT_SUMMARY_PROMPT, CompactionControl
|
|
34
36
|
from ..streaming._beta_messages import BetaMessageStream, BetaAsyncMessageStream
|
|
35
37
|
from ...types.beta.parsed_beta_message import ResponseFormatT, ParsedBetaMessage, ParsedBetaContentBlock
|
|
36
38
|
from ...types.beta.message_create_params import ParseMessageCreateParamsBase
|
|
@@ -66,6 +68,7 @@ class BaseToolRunner(Generic[AnyFunctionToolT, ResponseFormatT]):
|
|
|
66
68
|
options: RequestOptions,
|
|
67
69
|
tools: Iterable[AnyFunctionToolT],
|
|
68
70
|
max_iterations: int | None = None,
|
|
71
|
+
compaction_control: CompactionControl | None = None,
|
|
69
72
|
) -> None:
|
|
70
73
|
self._tools_by_name = {tool.name: tool for tool in tools}
|
|
71
74
|
self._params: ParseMessageCreateParamsBase[ResponseFormatT] = {
|
|
@@ -77,6 +80,7 @@ class BaseToolRunner(Generic[AnyFunctionToolT, ResponseFormatT]):
|
|
|
77
80
|
self._cached_tool_call_response: BetaMessageParam | None = None
|
|
78
81
|
self._max_iterations = max_iterations
|
|
79
82
|
self._iteration_count = 0
|
|
83
|
+
self._compaction_control = compaction_control
|
|
80
84
|
|
|
81
85
|
def set_messages_params(
|
|
82
86
|
self,
|
|
@@ -122,9 +126,17 @@ class BaseSyncToolRunner(BaseToolRunner[BetaRunnableTool, ResponseFormatT], Gene
|
|
|
122
126
|
tools: Iterable[BetaRunnableTool],
|
|
123
127
|
client: Anthropic,
|
|
124
128
|
max_iterations: int | None = None,
|
|
129
|
+
compaction_control: CompactionControl | None = None,
|
|
125
130
|
) -> None:
|
|
126
|
-
super().__init__(
|
|
131
|
+
super().__init__(
|
|
132
|
+
params=params,
|
|
133
|
+
options=options,
|
|
134
|
+
tools=tools,
|
|
135
|
+
max_iterations=max_iterations,
|
|
136
|
+
compaction_control=compaction_control,
|
|
137
|
+
)
|
|
127
138
|
self._client = client
|
|
139
|
+
|
|
128
140
|
self._iterator = self.__run__()
|
|
129
141
|
self._last_message: (
|
|
130
142
|
Callable[[], ParsedBetaMessage[ResponseFormatT]] | ParsedBetaMessage[ResponseFormatT] | None
|
|
@@ -143,31 +155,112 @@ class BaseSyncToolRunner(BaseToolRunner[BetaRunnableTool, ResponseFormatT], Gene
|
|
|
143
155
|
raise NotImplementedError()
|
|
144
156
|
yield # type: ignore[unreachable]
|
|
145
157
|
|
|
146
|
-
def
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
self.
|
|
158
|
+
def _check_and_compact(self) -> bool:
|
|
159
|
+
"""
|
|
160
|
+
Check token usage and compact messages if threshold exceeded.
|
|
161
|
+
Returns True if compaction was performed, False otherwise.
|
|
162
|
+
"""
|
|
163
|
+
if self._compaction_control is None or not self._compaction_control["enabled"]:
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
message = self._get_last_message()
|
|
167
|
+
tokens_used = 0
|
|
168
|
+
if message is not None:
|
|
169
|
+
total_input_tokens = (
|
|
170
|
+
message.usage.input_tokens
|
|
171
|
+
+ (message.usage.cache_creation_input_tokens or 0)
|
|
172
|
+
+ (message.usage.cache_read_input_tokens or 0)
|
|
173
|
+
)
|
|
174
|
+
tokens_used = total_input_tokens + message.usage.output_tokens
|
|
175
|
+
|
|
176
|
+
threshold = self._compaction_control.get("context_token_threshold", DEFAULT_THRESHOLD)
|
|
177
|
+
|
|
178
|
+
if tokens_used < threshold:
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
# Perform compaction
|
|
182
|
+
log.info(f"Token usage {tokens_used} has exceeded the threshold of {threshold}. Performing compaction.")
|
|
183
|
+
|
|
184
|
+
model = self._compaction_control.get("model", self._params["model"])
|
|
185
|
+
|
|
186
|
+
messages = list(self._params["messages"])
|
|
187
|
+
|
|
188
|
+
if messages[-1]["role"] == "assistant":
|
|
189
|
+
# Remove tool_use blocks from the last message to avoid 400 error
|
|
190
|
+
# (tool_use requires tool_result, which we don't have yet)
|
|
191
|
+
non_tool_blocks = [
|
|
192
|
+
block
|
|
193
|
+
for block in messages[-1]["content"]
|
|
194
|
+
if isinstance(block, dict) and block.get("type") != "tool_use"
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
if non_tool_blocks:
|
|
198
|
+
messages[-1]["content"] = non_tool_blocks
|
|
199
|
+
else:
|
|
200
|
+
messages.pop()
|
|
201
|
+
|
|
202
|
+
messages = [
|
|
203
|
+
*messages,
|
|
204
|
+
BetaMessageParam(
|
|
205
|
+
role="user",
|
|
206
|
+
content=self._compaction_control.get("summary_prompt", DEFAULT_SUMMARY_PROMPT),
|
|
207
|
+
),
|
|
208
|
+
]
|
|
152
209
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
210
|
+
response = self._client.beta.messages.create(
|
|
211
|
+
model=model,
|
|
212
|
+
messages=messages,
|
|
213
|
+
max_tokens=self._params["max_tokens"],
|
|
214
|
+
extra_headers={"X-Stainless-Helper": "compaction"},
|
|
215
|
+
)
|
|
158
216
|
|
|
159
|
-
|
|
160
|
-
self.append_messages(message, response)
|
|
217
|
+
log.info(f"Compaction complete. New token usage: {response.usage.output_tokens}")
|
|
161
218
|
|
|
162
|
-
|
|
163
|
-
self._messages_modified = False
|
|
164
|
-
self._cached_tool_call_response = None
|
|
219
|
+
first_content = list(response.content)[0]
|
|
165
220
|
|
|
221
|
+
if first_content.type != "text":
|
|
222
|
+
raise ValueError("Compaction response content is not of type 'text'")
|
|
223
|
+
|
|
224
|
+
self.set_messages_params(
|
|
225
|
+
lambda params: {
|
|
226
|
+
**params,
|
|
227
|
+
"messages": [
|
|
228
|
+
{
|
|
229
|
+
"role": "user",
|
|
230
|
+
"content": [
|
|
231
|
+
{
|
|
232
|
+
"type": "text",
|
|
233
|
+
"text": first_content.text,
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
}
|
|
237
|
+
],
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
return True
|
|
241
|
+
|
|
242
|
+
def __run__(self) -> Iterator[RunnerItemT]:
|
|
243
|
+
while not self._should_stop():
|
|
166
244
|
with self._handle_request() as item:
|
|
167
245
|
yield item
|
|
168
246
|
message = self._get_last_message()
|
|
169
247
|
assert message is not None
|
|
170
248
|
|
|
249
|
+
self._iteration_count += 1
|
|
250
|
+
|
|
251
|
+
# If the compaction was performed, skip tool call generation this iteration
|
|
252
|
+
if not self._check_and_compact():
|
|
253
|
+
response = self.generate_tool_call_response()
|
|
254
|
+
if response is None:
|
|
255
|
+
log.debug("Tool call was not requested, exiting from tool runner loop.")
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
if not self._messages_modified:
|
|
259
|
+
self.append_messages(message, response)
|
|
260
|
+
|
|
261
|
+
self._messages_modified = False
|
|
262
|
+
self._cached_tool_call_response = None
|
|
263
|
+
|
|
171
264
|
def until_done(self) -> ParsedBetaMessage[ResponseFormatT]:
|
|
172
265
|
"""
|
|
173
266
|
Consumes the tool runner stream and returns the last message if it has not been consumed yet.
|
|
@@ -206,6 +299,14 @@ class BaseSyncToolRunner(BaseToolRunner[BetaRunnableTool, ResponseFormatT], Gene
|
|
|
206
299
|
for tool_use in tool_use_blocks:
|
|
207
300
|
tool = self._tools_by_name.get(tool_use.name)
|
|
208
301
|
if tool is None:
|
|
302
|
+
warnings.warn(
|
|
303
|
+
f"Tool '{tool_use.name}' not found in tool runner. "
|
|
304
|
+
f"Available tools: {list(self._tools_by_name.keys())}. "
|
|
305
|
+
f"If using a raw tool definition, handle the tool call manually and use `append_messages()` to add the result. "
|
|
306
|
+
f"Otherwise, pass the tool using `beta_tool(func)` or a `@beta_tool` decorated function.",
|
|
307
|
+
UserWarning,
|
|
308
|
+
stacklevel=3,
|
|
309
|
+
)
|
|
209
310
|
results.append(
|
|
210
311
|
{
|
|
211
312
|
"type": "tool_result",
|
|
@@ -274,9 +375,17 @@ class BaseAsyncToolRunner(
|
|
|
274
375
|
tools: Iterable[BetaAsyncRunnableTool],
|
|
275
376
|
client: AsyncAnthropic,
|
|
276
377
|
max_iterations: int | None = None,
|
|
378
|
+
compaction_control: CompactionControl | None = None,
|
|
277
379
|
) -> None:
|
|
278
|
-
super().__init__(
|
|
380
|
+
super().__init__(
|
|
381
|
+
params=params,
|
|
382
|
+
options=options,
|
|
383
|
+
tools=tools,
|
|
384
|
+
max_iterations=max_iterations,
|
|
385
|
+
compaction_control=compaction_control,
|
|
386
|
+
)
|
|
279
387
|
self._client = client
|
|
388
|
+
|
|
280
389
|
self._iterator = self.__run__()
|
|
281
390
|
self._last_message: (
|
|
282
391
|
Callable[[], Coroutine[None, None, ParsedBetaMessage[ResponseFormatT]]]
|
|
@@ -297,30 +406,112 @@ class BaseAsyncToolRunner(
|
|
|
297
406
|
raise NotImplementedError()
|
|
298
407
|
yield # type: ignore[unreachable]
|
|
299
408
|
|
|
300
|
-
async def
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
self.
|
|
409
|
+
async def _check_and_compact(self) -> bool:
|
|
410
|
+
"""
|
|
411
|
+
Check token usage and compact messages if threshold exceeded.
|
|
412
|
+
Returns True if compaction was performed, False otherwise.
|
|
413
|
+
"""
|
|
414
|
+
if self._compaction_control is None or not self._compaction_control["enabled"]:
|
|
415
|
+
return False
|
|
416
|
+
|
|
417
|
+
message = await self._get_last_message()
|
|
418
|
+
tokens_used = 0
|
|
419
|
+
if message is not None:
|
|
420
|
+
total_input_tokens = (
|
|
421
|
+
message.usage.input_tokens
|
|
422
|
+
+ (message.usage.cache_creation_input_tokens or 0)
|
|
423
|
+
+ (message.usage.cache_read_input_tokens or 0)
|
|
424
|
+
)
|
|
425
|
+
tokens_used = total_input_tokens + message.usage.output_tokens
|
|
426
|
+
|
|
427
|
+
threshold = self._compaction_control.get("context_token_threshold", DEFAULT_THRESHOLD)
|
|
428
|
+
|
|
429
|
+
if tokens_used < threshold:
|
|
430
|
+
return False
|
|
431
|
+
|
|
432
|
+
# Perform compaction
|
|
433
|
+
log.info(f"Token usage {tokens_used} has exceeded the threshold of {threshold}. Performing compaction.")
|
|
434
|
+
|
|
435
|
+
model = self._compaction_control.get("model", self._params["model"])
|
|
436
|
+
|
|
437
|
+
messages = list(self._params["messages"])
|
|
438
|
+
|
|
439
|
+
if messages[-1]["role"] == "assistant":
|
|
440
|
+
# Remove tool_use blocks from the last message to avoid 400 error
|
|
441
|
+
# (tool_use requires tool_result, which we don't have yet)
|
|
442
|
+
non_tool_blocks = [
|
|
443
|
+
block
|
|
444
|
+
for block in messages[-1]["content"]
|
|
445
|
+
if isinstance(block, dict) and block.get("type") != "tool_use"
|
|
446
|
+
]
|
|
447
|
+
|
|
448
|
+
if non_tool_blocks:
|
|
449
|
+
messages[-1]["content"] = non_tool_blocks
|
|
450
|
+
else:
|
|
451
|
+
messages.pop()
|
|
452
|
+
|
|
453
|
+
messages = [
|
|
454
|
+
*self._params["messages"],
|
|
455
|
+
BetaMessageParam(
|
|
456
|
+
role="user",
|
|
457
|
+
content=self._compaction_control.get("summary_prompt", DEFAULT_SUMMARY_PROMPT),
|
|
458
|
+
),
|
|
459
|
+
]
|
|
306
460
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
461
|
+
response = await self._client.beta.messages.create(
|
|
462
|
+
model=model,
|
|
463
|
+
messages=messages,
|
|
464
|
+
max_tokens=self._params["max_tokens"],
|
|
465
|
+
extra_headers={"X-Stainless-Helper": "compaction"},
|
|
466
|
+
)
|
|
312
467
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
468
|
+
log.info(f"Compaction complete. New token usage: {response.usage.output_tokens}")
|
|
469
|
+
|
|
470
|
+
first_content = list(response.content)[0]
|
|
471
|
+
|
|
472
|
+
if first_content.type != "text":
|
|
473
|
+
raise ValueError("Compaction response content is not of type 'text'")
|
|
474
|
+
|
|
475
|
+
self.set_messages_params(
|
|
476
|
+
lambda params: {
|
|
477
|
+
**params,
|
|
478
|
+
"messages": [
|
|
479
|
+
{
|
|
480
|
+
"role": "user",
|
|
481
|
+
"content": [
|
|
482
|
+
{
|
|
483
|
+
"type": "text",
|
|
484
|
+
"text": first_content.text,
|
|
485
|
+
}
|
|
486
|
+
],
|
|
487
|
+
}
|
|
488
|
+
],
|
|
489
|
+
}
|
|
490
|
+
)
|
|
491
|
+
return True
|
|
318
492
|
|
|
493
|
+
async def __run__(self) -> AsyncIterator[RunnerItemT]:
|
|
494
|
+
while not self._should_stop():
|
|
319
495
|
async with self._handle_request() as item:
|
|
320
496
|
yield item
|
|
321
497
|
message = await self._get_last_message()
|
|
322
498
|
assert message is not None
|
|
323
499
|
|
|
500
|
+
self._iteration_count += 1
|
|
501
|
+
|
|
502
|
+
# If the compaction was performed, skip tool call generation this iteration
|
|
503
|
+
if not await self._check_and_compact():
|
|
504
|
+
response = await self.generate_tool_call_response()
|
|
505
|
+
if response is None:
|
|
506
|
+
log.debug("Tool call was not requested, exiting from tool runner loop.")
|
|
507
|
+
return
|
|
508
|
+
|
|
509
|
+
if not self._messages_modified:
|
|
510
|
+
self.append_messages(message, response)
|
|
511
|
+
|
|
512
|
+
self._messages_modified = False
|
|
513
|
+
self._cached_tool_call_response = None
|
|
514
|
+
|
|
324
515
|
async def until_done(self) -> ParsedBetaMessage[ResponseFormatT]:
|
|
325
516
|
"""
|
|
326
517
|
Consumes the tool runner stream and returns the last message if it has not been consumed yet.
|
|
@@ -372,6 +563,14 @@ class BaseAsyncToolRunner(
|
|
|
372
563
|
for tool_use in tool_use_blocks:
|
|
373
564
|
tool = self._tools_by_name.get(tool_use.name)
|
|
374
565
|
if tool is None:
|
|
566
|
+
warnings.warn(
|
|
567
|
+
f"Tool '{tool_use.name}' not found in tool runner. "
|
|
568
|
+
f"Available tools: {list(self._tools_by_name.keys())}. "
|
|
569
|
+
f"If using a raw tool definition, handle the tool call manually and use `append_messages()` to add the result. "
|
|
570
|
+
f"Otherwise, pass the tool using `beta_async_tool(func)` or a `@beta_async_tool` decorated function.",
|
|
571
|
+
UserWarning,
|
|
572
|
+
stacklevel=3,
|
|
573
|
+
)
|
|
375
574
|
results.append(
|
|
376
575
|
{
|
|
377
576
|
"type": "tool_result",
|