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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `Chat.export()` and `interpolate_file()` now always read and write files as UTF-8. Previously they relied on the platform's default locale encoding, so on a non-UTF-8 locale `export()` could raise `UnicodeEncodeError` and `interpolate_file()` could silently return a corrupted (mojibake) prompt instead of the file's contents. (#437)
* `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)
* Providers built on the Chat Completions API (`ChatOpenAICompletions()`, `ChatGroq()`, `ChatOpenRouter()`, `ChatDeepSeek()`, etc.) now put the tool results of a turn ahead of the user's text in the same turn, as the API requires. Resuming a chat that ended with an unanswered tool request (or any turn that mixes tool results and new text) sent the user message first, which these APIs reject because a tool message must directly follow the assistant message that requested it. (#476)
* `ChatMistral()` now reports `"context_window"` as the finish reason when a response stops with Mistral's `model_length` (the request reached the model's context length), instead of passing the raw string through. As for other providers, this makes chatlas warn about the truncated response (or raise an error when a data model was requested). (#477)


## [0.23.0] - 2026-09-04
Expand Down
3 changes: 3 additions & 0 deletions chatlas/_provider_openai_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ def ChatOpenAICompletions(
"tool_calls": "tool_use",
"length": "max_tokens",
"content_filter": "content_filter",
# Mistral: the request ran into the model's context length
# https://docs.mistral.ai/api/endpoint/chat
"model_length": "context_window",
}


Expand Down
23 changes: 22 additions & 1 deletion tests/test_provider_openai_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def test_normalize_finish_reason_maps_known_reasons():
assert completions_normalize_finish_reason("length") == "max_tokens"
assert completions_normalize_finish_reason("content_filter") == "content_filter"
assert completions_normalize_finish_reason("tool_calls") == "tool_use"
assert completions_normalize_finish_reason("model_length") == "context_window"


def test_normalize_finish_reason_passes_through_unknown():
Expand Down Expand Up @@ -597,4 +598,24 @@ def test_load_tool_request_args_error_names_the_arguments():
assert "(get_weather)" in message
assert '\'{"city": "Paris"\'' in message
assert "{args}" not in message
assert 'input arguments: \'{"city": "Paris"\'. This can happen' in message
assert 'input arguments: \'{"city": "Paris"\'. This can happen' in message


def test_model_length_finish_reason_warns_about_the_context_window():
completion = ChatCompletion.construct(
id="x",
choices=[
{
"index": 0,
"finish_reason": "model_length",
"message": {"role": "assistant", "content": "partial"},
}
],
)
provider = OpenAICompletionsProvider(api_key="key", model="m")

turn = provider.value_turn(completion, has_data_model=False)
assert turn.finish_reason == "context_window"

with pytest.raises(ValueError, match="context window"):
provider.value_turn(completion, has_data_model=True)
Loading