Skip to content
Closed
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 @@ -10,6 +10,7 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
## [Unreleased]

### Fixed
- **v2 message handling**: `merge_consecutive_messages` no longer crashes with `AttributeError` when two consecutive same-role messages both have `content=None` (e.g. consecutive tool-call-only assistant turns in a replayed conversation history), affecting the OpenAI, Mistral, and Writer JSON-mode handlers.
- **v2 message handling**: Preserve caller-owned message lists and nested content across request preparation and retries for OpenAI-compatible, Cohere, Mistral, OpenRouter, Writer, and xAI handlers. ([#2417](https://github.com/567-labs/instructor/issues/2417), [#2428](https://github.com/567-labs/instructor/issues/2428))
- **v2 JSON extraction**: Prefer the final complete top-level JSON value in text responses and retain every JSON object when multiple objects arrive in one streaming chunk.
- **v2 schemas**: Treat fields with Pydantic `default_factory` values as optional in generated OpenAI tool schemas.
Expand Down
2 changes: 2 additions & 0 deletions instructor/v2/core/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ def merge_consecutive_messages(messages: list[dict[str, Any]]) -> list[dict[str,
for message in messages:
role = message.get("role", "user")
new_content = message.get("content", "")
if new_content is None:
new_content = ""
if not flat_string and isinstance(new_content, str):
new_content = [{"type": "text", "text": new_content}]

Expand Down
21 changes: 21 additions & 0 deletions tests/processing/test_message_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,27 @@ def test_multiple_consecutive(self):
assert result[2]["role"] == "user"
assert "I need help" in result[2]["content"]

def test_consecutive_none_content(self):
"""Consecutive same-role messages with content=None (e.g. tool-call-only
assistant turns) must not crash the merge."""
messages = [
{"role": "user", "content": "hi"},
{
"role": "assistant",
"content": None,
"tool_calls": [{"id": "call_1"}],
},
{
"role": "assistant",
"content": None,
"tool_calls": [{"id": "call_2"}],
},
]
result = merge_consecutive_messages(messages)
assert len(result) == 2
assert result[0]["role"] == "user"
assert result[1]["role"] == "assistant"


class TestGetMessageContent:
"""Test the get_message_content function."""
Expand Down