diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fcbd6f1b..1d9bb90df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html) ## [Unreleased] ### Fixed +- **v2 message handling**: Retain legacy `function_call` payloads in `dump_message` when the assistant message has empty or `None` content, so retry/reask flows no longer drop the model's prior function call. ([#2464](https://github.com/567-labs/instructor/issues/2464)) - **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. diff --git a/instructor/v2/core/messages.py b/instructor/v2/core/messages.py index 9a24c81ea..04e8ad594 100644 --- a/instructor/v2/core/messages.py +++ b/instructor/v2/core/messages.py @@ -45,11 +45,7 @@ def dump_message(message: ChatCompletionMessage) -> ChatCompletionMessageParam: } if hasattr(message, "tool_calls") and message.tool_calls is not None: ret["tool_calls"] = message.model_dump()["tool_calls"] - if ( - hasattr(message, "function_call") - and message.function_call is not None - and ret["content"] - ): + if hasattr(message, "function_call") and message.function_call is not None: if not isinstance(ret["content"], str): response_message = "" for content_message in ret["content"]: diff --git a/tests/coverage/test_core_helpers_coverage.py b/tests/coverage/test_core_helpers_coverage.py index fd99020ac..40559e8e1 100644 --- a/tests/coverage/test_core_helpers_coverage.py +++ b/tests/coverage/test_core_helpers_coverage.py @@ -115,6 +115,18 @@ def test_dump_message_appends_function_call_to_text_content() -> None: ) +def test_dump_message_retains_function_call_when_content_is_empty() -> None: + message = ChatCompletionMessage( + role="assistant", + content=None, + function_call=FunctionCall(name="lookup", arguments='{"id":7}'), + ) + + result = dump_message(message) + + assert result["content"] == json.dumps({"arguments": '{"id":7}', "name": "lookup"}) + + def test_merge_consecutive_messages_checks_tail_for_non_string_content() -> None: messages: list[dict[str, Any]] = [ {"role": "user", "content": f"line {index}"} for index in range(10)