Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Anthropic-backed providers (`ChatAnthropic()`, `ChatPosit()`, `ChatBedrock()`, etc.) no longer fail with `Invalid signature in thinking block` when the conversation history contains reasoning from a non-Claude model (e.g., after switching `chat.model` across families); such thinking is now replayed as plain text so the model can still see it.
* `content_image_file()` no longer fails with `ValueError: unknown file extension` when `resize` uses the `!` (ignore aspect ratio) flag on an image larger than the requested box, e.g. `resize="200x200!"`. (#433)
* `params(top_k=)` is no longer sent as `top_logprobs` for OpenAI-based providers (the two are unrelated; OpenAI has no `top_k` sampling parameter). `top_k` is now dropped with the standard unsupported-parameter warning. (#412)
* `ChatAnthropic()` no longer drops assistant turns that have no content; doing so could produce two consecutive user messages, violating the API's user/assistant alternation requirement. A `"[empty string]"` placeholder is sent instead, matching how empty text content is already normalized. (#416)


## [0.23.0] - 2026-09-04
Expand All @@ -35,7 +36,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `ChatBedrock()` now defaults `base_url` to the official AWS SDKs' endpoint override environment variables when set: `AWS_ENDPOINT_URL_BEDROCK_RUNTIME` for `api="converse"`, and `AWS_ENDPOINT_URL_BEDROCK_MANTLE` for `api="messages"` and `api="responses"`. Similarly, `ChatAnthropic()` respects the `ANTHROPIC_BASE_URL` environment variable (via the anthropic SDK). Setting these variables is enough to route requests through a proxy or gateway, so you don't have to pass `base_url` on every call.

### Bug fixes

* `ChatBedrock()`'s default model (previously `"us.anthropic.claude-sonnet-4-6"`) is now `"us.anthropic.claude-sonnet-5"`, which mantle's `api="messages"` endpoint actually serves. Separately, the cross-region inference prefix (e.g. `"us."`) is now stripped from the model id sent in requests to `api="messages"` and `api="responses"`, since mantle rejects it even though Converse requires it. Previously, a mantle-only model with a cross-region prefix (e.g. `model="us.openai.gpt-5.4"`) would 404. (#411)
* `ChatDatabricks()` no longer drops the assistant's reply from the conversation when a GPT-OSS endpoint streams typed content. The typed part array was merged into the accumulated completion before it was normalized, so every later text delta was appended to it one character at a time and the finished turn came back empty. (#409)
* `.to_solver()` no longer corrupts the system prompt or the prior turns it reads out of Inspect AI's message state. The system prompt was being set to the `repr()` of the `ChatMessageSystem` object rather than its text, and message content arriving in Inspect AI's `str` form (rather than as a list of `Content`) was iterated one character at a time. (#407)
Expand Down
10 changes: 6 additions & 4 deletions chatlas/_provider_anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -891,17 +891,19 @@ def _as_message_params(self, turns: Sequence[Turn]) -> list["MessageParam"]:
if not isinstance(turn, (UserTurn, AssistantTurn)):
raise ValueError(f"Unknown role {turn.role}")

content = [
content: list[ContentBlockParam] = [
self._as_content_block(self._as_replayable_content(c))
for c in turn.contents
if not isinstance(c, PROVIDER_ANNOTATION_TYPES)
or anthropic_replayable(c)
]

# Drop empty assistant turns to avoid an API error
# (all messages must have non-empty content)
# Dropping an empty assistant turn could produce two consecutive
# user messages, violating the API's alternation requirement.
if turn.role == "assistant" and len(content) == 0:
continue
content = [
cast("TextBlockParam", {"type": "text", "text": "[empty string]"})
]

# Add cache control to the last content block in the last turn
# https://docs.claude.com/en/docs/build-with-claude/prompt-caching#how-automatic-prefix-checking-works
Expand Down
13 changes: 6 additions & 7 deletions tests/test_provider_anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,24 +928,23 @@ def test_anthropic_list_models():
assert_list_models(chat_func)


def test_anthropic_removes_empty_assistant_turns():
"""Test that empty assistant turns are dropped to avoid API errors."""
def test_anthropic_empty_assistant_turn_placeholder():
"""Empty assistant turns get a placeholder instead of being dropped (#416)."""
chat = chat_func()
chat.set_turns(
[
UserTurn("Don't say anything"),
AssistantTurn([]),
UserTurn("What did I just say?"),
]
)

# Get the message params that would be sent to the API
provider = cast(AnthropicProvider, chat.provider)
turns_json = provider._as_message_params(chat.get_turns())

# Should only have the user turn, not the empty assistant turn
assert len(turns_json) == 1
assert turns_json[0]["role"] == "user"
assert turns_json[0]["content"][0]["text"] == "Don't say anything" # type: ignore
assert [m["role"] for m in turns_json] == ["user", "assistant", "user"]
assert turns_json[1]["content"] == [{"type": "text", "text": "[empty string]"}]
assert turns_json[2]["content"][0]["text"] == "What did I just say?" # type: ignore


@pytest.mark.vcr
Expand Down
Loading