From 02f0b8ce706620f9b6485be871e721a86f912380 Mon Sep 17 00:00:00 2001 From: Scott Boudreaux <121303252+Scottcjn@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:21:49 -0500 Subject: [PATCH 1/3] fix: add helpful error message for malformed tool JSON in non-beta streaming The beta path (_beta_messages.py) wraps from_json() with a try-except that provides an actionable error message including the raw JSON. The non-beta path (_messages.py) was missing this wrapper, causing raw ValueError with no context ("expected ident at line 1 column 11"). Fixes #1265 --- src/anthropic/lib/streaming/_messages.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/anthropic/lib/streaming/_messages.py b/src/anthropic/lib/streaming/_messages.py index b6b5f538..b09eaac0 100644 --- a/src/anthropic/lib/streaming/_messages.py +++ b/src/anthropic/lib/streaming/_messages.py @@ -477,7 +477,14 @@ def accumulate_event( json_buf += bytes(event.delta.partial_json, "utf-8") if json_buf: - content.input = from_json(json_buf, partial_mode=True) + try: + content.input = from_json(json_buf, partial_mode=True) + except ValueError as e: + raise ValueError( + f"Unable to parse tool parameter JSON from model. " + f"Please retry your request or adjust your prompt. " + f"Error: {e}. JSON: {json_buf.decode('utf-8')}" + ) from e setattr(content, JSON_BUF_PROPERTY, json_buf) elif event.delta.type == "citations_delta": From b549b8b154f5037040c68b4cbba78c8da6bf42af Mon Sep 17 00:00:00 2001 From: Scott Boudreaux <121303252+Scottcjn@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:22:19 -0500 Subject: [PATCH 2/3] fix: correct BetaAsyncAbstractMemoryTool docstring example The docstring example was copy-pasted from the sync class without updating: wrong base class name, sync methods instead of async, Anthropic() instead of AsyncAnthropic(), missing await. Fixes #1290 --- src/anthropic/lib/tools/_beta_builtin_memory_tool.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/anthropic/lib/tools/_beta_builtin_memory_tool.py b/src/anthropic/lib/tools/_beta_builtin_memory_tool.py index c58d54a2..caeb80a9 100644 --- a/src/anthropic/lib/tools/_beta_builtin_memory_tool.py +++ b/src/anthropic/lib/tools/_beta_builtin_memory_tool.py @@ -166,21 +166,21 @@ class BetaAsyncAbstractMemoryTool(BetaAsyncBuiltinFunctionTool): Example usage: ```py - class MyMemoryTool(BetaAbstractMemoryTool): - def view(self, command: BetaMemoryTool20250818ViewCommand) -> BetaFunctionToolResultType: + class MyMemoryTool(BetaAsyncAbstractMemoryTool): + async def view(self, command: BetaMemoryTool20250818ViewCommand) -> BetaFunctionToolResultType: ... return "view result" - def create(self, command: BetaMemoryTool20250818CreateCommand) -> BetaFunctionToolResultType: + async def create(self, command: BetaMemoryTool20250818CreateCommand) -> BetaFunctionToolResultType: ... return "created successfully" # ... implement other abstract methods - client = Anthropic() + client = AsyncAnthropic() memory_tool = MyMemoryTool() - message = client.beta.messages.run_tools( + message = await client.beta.messages.run_tools( model="claude-sonnet-4-5", messages=[{"role": "user", "content": "Remember that I like coffee"}], tools=[memory_tool], From e6c07c0cdc088abe7891995f589a1ccda2f7b144 Mon Sep 17 00:00:00 2001 From: Scott Boudreaux <121303252+Scottcjn@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:23:55 -0500 Subject: [PATCH 3/3] fix: recurse into tuples in deepcopy_minimal to prevent mutation deepcopy_minimal only copied dicts and lists, leaving tuples as-is. When a FileTypes tuple like (name, content, mime, headers) was passed to files.beta.upload, the headers Mapping inside the tuple could be mutated in-place, corrupting the caller's data. Fixes #1202 --- src/anthropic/_utils/_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/anthropic/_utils/_utils.py b/src/anthropic/_utils/_utils.py index eec7f4a1..f112e7c7 100644 --- a/src/anthropic/_utils/_utils.py +++ b/src/anthropic/_utils/_utils.py @@ -181,6 +181,7 @@ def deepcopy_minimal(item: _T) -> _T: - mappings, e.g. `dict` - list + - tuple (recursed but preserved as tuple) This is done for performance reasons. """ @@ -188,6 +189,8 @@ def deepcopy_minimal(item: _T) -> _T: return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()}) if is_list(item): return cast(_T, [deepcopy_minimal(entry) for entry in item]) + if isinstance(item, tuple): + return cast(_T, tuple(deepcopy_minimal(entry) for entry in item)) return item