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 @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Bug fixes

* Streaming from `ChatOpenAICompletions()` (and the providers built on it, such as `ChatOpenRouter()` and `ChatGroq()`) no longer drops a chunk's text when the same chunk carries an empty `reasoning` or `reasoning_content` field, as some OpenAI-compatible servers send.
* `ChatOllama()` can now be created without a `model`, so `ChatOllama().list_models()` works. Sending a message without a model still raises an error that lists the available models. (#166)
* `ChatGoogle()` now reports `finish_reason == "content_filter"` for Gemini's `IMAGE_SAFETY`, `IMAGE_PROHIBITED_CONTENT`, `IMAGE_RECITATION` and `LANGUAGE` finish reasons, instead of passing the raw Gemini string through, and reports `"tool_use"` (rather than `"success"`) when a turn ends with a tool request, as `ChatOpenAI()` does. (#473)
* The error raised for a tool request with invalid JSON arguments from `ChatOpenAI()` and `ChatOpenAICompletions()` (and the providers built on them) now shows the offending arguments, instead of the literal text `{args}`.
Expand Down
2 changes: 1 addition & 1 deletion chatlas/_provider_openai_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ def stream_content(self, chunk, completion, turns=()) -> list[Content]:
reasoning = getattr(delta, "reasoning", None) or getattr(
delta, "reasoning_content", None
)
if reasoning is not None:
if reasoning:
return [ContentThinkingDelta(thinking=reasoning)]

text = delta.content
Expand Down
27 changes: 27 additions & 0 deletions tests/test_provider_openai_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,33 @@ def __init__(self, choices):
assert result == [ContentText(text="hello")]


def test_stream_content_empty_reasoning_keeps_text():
"""Some backends send `reasoning_content: ""` alongside the text of a chunk."""
from openai.types.chat import ChatCompletionChunk

provider = OpenAICompletionsProvider(model="test")

def chunk(**delta):
return ChatCompletionChunk.model_validate(
{
"id": "1",
"object": "chat.completion.chunk",
"created": 0,
"model": "test",
"choices": [{"index": 0, "delta": delta, "finish_reason": None}],
}
)

result = provider.stream_content(chunk(content="Hello", reasoning_content=""), None)
assert result == [ContentText(text="Hello")]

result = provider.stream_content(chunk(content="Hello", reasoning=""), None)
assert result == [ContentText(text="Hello")]

# A chunk with neither reasoning nor text adds nothing.
assert provider.stream_content(chunk(reasoning_content=""), None) == []


def test_response_as_turn_extracts_reasoning_content():
from unittest.mock import Mock

Expand Down
Loading