Files
567-labs--instructor/tests/test_anthropic_bedrock_caller.py
wehub-resource-sync 97e91a83f3
Ruff / Ruff (push) Waiting to run
Test / Core Tests (push) Waiting to run
Test / Offline Coverage Tests (Python 3.10) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.11) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.12) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.13) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.9) (push) Waiting to run
Test / Full Coverage (Python 3.11) (push) Waiting to run
Test / Core Provider Tests (OpenAI) (push) Blocked by required conditions
Test / Core Provider Tests (Anthropic) (push) Blocked by required conditions
Test / Core Provider Tests (Google) (push) Blocked by required conditions
Test / Core Provider Tests (Other) (push) Blocked by required conditions
Test / Anthropic Tests (push) Blocked by required conditions
Test / Gemini Tests (push) Blocked by required conditions
Test / Google GenAI Tests (push) Blocked by required conditions
Test / Vertex AI Tests (push) Blocked by required conditions
Test / OpenAI Tests (push) Blocked by required conditions
Test / Writer Tests (push) Blocked by required conditions
Test / Auto Client Tests (push) Blocked by required conditions
ty / type-check (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:38 +08:00

98 lines
3.4 KiB
Python

"""Test that reask_anthropic_tools excludes None fields when serializing content blocks.
AWS Bedrock does not populate the `caller` field on ToolUseBlock — it returns
caller=None. When reask_anthropic_tools called content.model_dump() the
resulting dict contained "caller": null, which the Anthropic API rejected with
HTTP 400.
GitHub Issue: https://github.com/instructor-ai/instructor/issues/2277
"""
import pytest
from pydantic import ValidationError, BaseModel, field_validator
anthropic = pytest.importorskip("anthropic", reason="anthropic package required")
def _make_validation_error() -> ValidationError:
class M(BaseModel):
v: int = 0
@field_validator("v")
@classmethod
def always_fail(cls, val: int) -> int:
raise ValueError(f"forced failure: {val}")
try:
M(v=1)
except ValidationError as exc:
return exc
raise AssertionError("unreachable") # pragma: no cover
def _build_bedrock_message() -> "anthropic.types.Message":
from anthropic.types import Message, ToolUseBlock, Usage
return Message(
id="msg_bedrock_test",
content=[
ToolUseBlock(
id="toolu_bedrock_abc",
input={"summary": "hello"},
name="MyExtraction",
type="tool_use",
caller=None, # Bedrock leaves this field unpopulated
)
],
model="anthropic.claude-sonnet-4-6",
role="assistant",
stop_reason="tool_use",
stop_sequence=None,
type="message",
usage=Usage(input_tokens=10, output_tokens=10),
)
class TestBedrockCallerNoneReask:
"""Regression tests for Bedrock caller=None serialisation bug."""
def test_reask_does_not_include_null_caller(self):
"""Assistant content dicts must not contain 'caller': None after reask."""
from instructor.v2.providers.anthropic.handlers import AnthropicToolsHandler
kwargs = {
"messages": [{"role": "user", "content": "extract something"}],
}
response = _build_bedrock_message()
exception = _make_validation_error()
result = AnthropicToolsHandler().handle_reask(kwargs, response, exception)
# Find the assistant turn that was appended
assistant_msgs = [m for m in result["messages"] if m.get("role") == "assistant"]
assert assistant_msgs, "reask must append an assistant message"
for block in assistant_msgs[0]["content"]:
assert "caller" not in block, (
f"'caller' field must be excluded when it is None, got: {block}"
)
def test_reask_tool_use_id_is_preserved(self):
"""The tool_use_id in the follow-up user message must match the block id."""
from instructor.v2.providers.anthropic.handlers import AnthropicToolsHandler
kwargs = {
"messages": [{"role": "user", "content": "extract something"}],
}
response = _build_bedrock_message()
exception = _make_validation_error()
result = AnthropicToolsHandler().handle_reask(kwargs, response, exception)
user_msgs = [m for m in result["messages"] if m.get("role") == "user"]
# Last user message is the tool_result reask
tool_result_msg = user_msgs[-1]
assert isinstance(tool_result_msg["content"], list)
assert tool_result_msg["content"][0]["tool_use_id"] == "toolu_bedrock_abc"