From 00a7de0171e6994b627d37b6d95fe5252d02fab6 Mon Sep 17 00:00:00 2001 From: Xubin Ren <52506698+Re-bin@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:10:03 +0800 Subject: [PATCH] fix: stringify Anthropic typeless blocks as JSON --- nanobot/providers/anthropic_provider.py | 15 +++++++++++++-- tests/providers/test_anthropic_tool_result.py | 13 +++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/nanobot/providers/anthropic_provider.py b/nanobot/providers/anthropic_provider.py index c52600f6..aa6abc0b 100644 --- a/nanobot/providers/anthropic_provider.py +++ b/nanobot/providers/anthropic_provider.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio import hashlib +import json import re import secrets import string @@ -280,7 +281,10 @@ class AnthropicProvider(LLMProvider): # Anthropic requires every content block to declare a "type". # A tool that returned a bare dict lands here; coerce it to # a text block instead of emitting one that the API rejects. - blocks.append({"type": "text", "text": str(item)}) + blocks.append({ + "type": "text", + "text": AnthropicProvider._stringify_typeless_block(item), + }) else: blocks.append(item) else: @@ -324,11 +328,18 @@ class AnthropicProvider(LLMProvider): # A tool that returned a bare dict (or a list of dicts) lands # here; coerce it to a text block instead of emitting a block # the API rejects with "content.0.type: Field required". - result.append({"type": "text", "text": str(item)}) + result.append({ + "type": "text", + "text": AnthropicProvider._stringify_typeless_block(item), + }) continue result.append(item) return result or "(empty)" + @staticmethod + def _stringify_typeless_block(block: dict[str, Any]) -> str: + return json.dumps(block, ensure_ascii=False, sort_keys=True, default=str) + @staticmethod def _convert_image_block(block: dict[str, Any]) -> dict[str, Any] | None: """Convert OpenAI image_url block to Anthropic image block.""" diff --git a/tests/providers/test_anthropic_tool_result.py b/tests/providers/test_anthropic_tool_result.py index 9cec6769..b08a18fd 100644 --- a/tests/providers/test_anthropic_tool_result.py +++ b/tests/providers/test_anthropic_tool_result.py @@ -70,7 +70,7 @@ def test_convert_user_content_coerces_typeless_dict(): {"foo": "bar"}, {"type": "text", "text": "ok"}, ]) - assert result[0] == {"type": "text", "text": str({"foo": "bar"})} + assert result[0] == {"type": "text", "text": '{"foo": "bar"}'} assert result[1] == {"type": "text", "text": "ok"} @@ -81,7 +81,16 @@ def test_convert_user_content_coerces_mixed_typeless(): {"key": "val"}, ]) assert result[0] == {"type": "text", "text": "42"} - assert result[1] == {"type": "text", "text": str({"key": "val"})} + assert result[1] == {"type": "text", "text": '{"key": "val"}'} + + +def test_assistant_blocks_coerce_typeless_dict_to_json_text(): + blocks = AnthropicProvider._assistant_blocks({ + "role": "assistant", + "content": [{"answer": "ok", "count": 2}], + }) + + assert blocks == [{"type": "text", "text": '{"answer": "ok", "count": 2}'}] def test_convert_assistant_message_repairs_history_tool_arguments():