mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
fix(anthropic): type safeguards and safeguard_results as the arrays Anthropic sends
Driving a real Claude Code 2.1.278 through the proxy, and a direct call to
api.anthropic.com, both show these two fields are JSON arrays on the wire rather
than objects. The request carries safeguards as
[{"type": "dangerous_tool_use", "classifier_context": {...}}] under beta
dangerous-tool-use-2026-09-03, and the 200 comes back with safeguard_results as
[{"type": "dangerous_tool_use", "status": {"type": "available", "tool_uses": {...}}}].
No runtime change: the request filter matches on TypedDict keys and never inspects
the value. The test fixtures move to the captured shapes so the regression tests
pin what the client and the provider actually exchange.
This commit is contained in:
parent
b59028d525
commit
1ac4d7ae04
4 changed files with 15 additions and 11 deletions
|
|
@ -411,7 +411,7 @@ class AnthropicMessagesRequestOptionalParams(TypedDict, total=False):
|
|||
output_config: AnthropicOutputConfig | None # Configuration for Claude's output behavior
|
||||
cache_control: dict[str, Any] | None # Automatic prompt caching
|
||||
reasoning_effort: str | None
|
||||
safeguards: ReadOnly[dict[str, object] | None]
|
||||
safeguards: ReadOnly[list[dict[str, object]] | None]
|
||||
|
||||
|
||||
class AnthropicMessagesRequest(AnthropicMessagesRequestOptionalParams, total=False):
|
||||
|
|
@ -531,7 +531,7 @@ class AnthropicStopDetails(TypedDict, total=False):
|
|||
class MessageDelta(TypedDict, total=False):
|
||||
stop_reason: str | None
|
||||
stop_details: ReadOnly[AnthropicStopDetails]
|
||||
safeguard_results: ReadOnly[dict[str, object]]
|
||||
safeguard_results: ReadOnly[list[dict[str, object]]]
|
||||
|
||||
|
||||
class ServerToolUsage(TypedDict, total=False):
|
||||
|
|
@ -602,7 +602,7 @@ class MessageChunk(TypedDict, total=False):
|
|||
stop_reason: str | None
|
||||
stop_sequence: str | None
|
||||
usage: UsageDelta
|
||||
safeguard_results: ReadOnly[dict[str, object]]
|
||||
safeguard_results: ReadOnly[list[dict[str, object]]]
|
||||
|
||||
|
||||
class MessageStartBlock(TypedDict):
|
||||
|
|
|
|||
|
|
@ -97,4 +97,4 @@ class AnthropicMessagesResponse(TypedDict, total=False):
|
|||
type: Literal["message"] | None
|
||||
usage: AnthropicUsage | None
|
||||
context_management: NotRequired[ContextManagementResponse]
|
||||
safeguard_results: NotRequired[ReadOnly[dict[str, object]]]
|
||||
safeguard_results: NotRequired[ReadOnly[list[dict[str, object]]]]
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ class TestOutputConfigStrippedFromCompletionKwargs:
|
|||
def test_safeguards_is_stripped_for_non_anthropic_target(self):
|
||||
extra_kwargs = {
|
||||
"custom_llm_provider": "azure",
|
||||
"safeguards": {"auto_mode": {"enabled": True, "version": "2026-09-01"}},
|
||||
"safeguards": [{"type": "dangerous_tool_use", "classifier_context": {"v": 1}}],
|
||||
}
|
||||
|
||||
result = _call_prepare(extra_kwargs=extra_kwargs)
|
||||
|
|
|
|||
|
|
@ -1442,10 +1442,12 @@ async def test_anthropic_messages_leaves_non_provider_failures_unmapped():
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_anthropic_messages_forwards_safeguards_and_unknown_beta_to_anthropic():
|
||||
"""Shapes are what Claude Code 2.1.278 sends and api.anthropic.com returns, captured 2026-09-21."""
|
||||
from litellm.llms.anthropic.experimental_pass_through.messages import handler
|
||||
|
||||
safeguards = {"auto_mode": {"enabled": True, "version": "2026-09-01"}}
|
||||
client_betas = "safeguards-2026-09-01,interleaved-thinking-2025-05-14"
|
||||
safeguards = [{"type": "dangerous_tool_use", "classifier_context": {"v": 1, "permission_mode": "auto"}}]
|
||||
client_betas = "dangerous-tool-use-2026-09-03,interleaved-thinking-2025-05-14"
|
||||
safeguard_results = [{"type": "dangerous_tool_use", "status": {"type": "available", "tool_uses": {}}}]
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
def upstream_records_the_request(request: httpx.Request) -> httpx.Response:
|
||||
|
|
@ -1462,7 +1464,7 @@ async def test_anthropic_messages_forwards_safeguards_and_unknown_beta_to_anthro
|
|||
"stop_reason": "end_turn",
|
||||
"stop_sequence": None,
|
||||
"usage": {"input_tokens": 1, "output_tokens": 1},
|
||||
"safeguard_results": {"verdict": "allow"},
|
||||
"safeguard_results": safeguard_results,
|
||||
},
|
||||
request=request,
|
||||
)
|
||||
|
|
@ -1483,15 +1485,17 @@ async def test_anthropic_messages_forwards_safeguards_and_unknown_beta_to_anthro
|
|||
|
||||
assert captured["body"]["safeguards"] == safeguards
|
||||
assert set(captured["anthropic-beta"].split(",")) == set(client_betas.split(","))
|
||||
assert response["safeguard_results"] == {"verdict": "allow"}
|
||||
assert response["safeguard_results"] == safeguard_results
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_anthropic_messages_streaming_forwards_safeguards_and_keeps_safeguard_results():
|
||||
"""Shapes are what Claude Code 2.1.278 sends and api.anthropic.com returns, captured 2026-09-21."""
|
||||
from litellm.llms.anthropic.experimental_pass_through.messages import handler
|
||||
|
||||
safeguards = {"auto_mode": {"enabled": True, "version": "2026-09-01"}}
|
||||
safeguard_results = {"verdict": "allow", "checks": ["shell_command"]}
|
||||
safeguards = [{"type": "dangerous_tool_use", "classifier_context": {"v": 1, "permission_mode": "auto"}}]
|
||||
tool_verdicts = {"toolu_01": {"type": "evaluated", "outcome": "not_flagged"}}
|
||||
safeguard_results = [{"type": "dangerous_tool_use", "status": {"type": "available", "tool_uses": tool_verdicts}}]
|
||||
captured: dict[str, object] = {}
|
||||
message_start = {
|
||||
"type": "message_start",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue