mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
fix(responses-adapter): skip null reasoning summary text and keep thinking-only assistant turns
This commit is contained in:
parent
68bda94995
commit
8a1fe281fd
4 changed files with 41 additions and 3 deletions
|
|
@ -428,12 +428,14 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
if role == "assistant":
|
||||
input_items.extend(_reasoning_input_items(msg))
|
||||
input_items.append(
|
||||
{
|
||||
{ # mutable-ok: API message payload
|
||||
"type": "message",
|
||||
"role": role,
|
||||
"content": self._convert_content_to_responses_format(content, cast(str, role)),
|
||||
}
|
||||
)
|
||||
elif role == "assistant":
|
||||
input_items.extend(_reasoning_input_items(msg))
|
||||
|
||||
return input_items, instructions
|
||||
|
||||
|
|
|
|||
|
|
@ -110,8 +110,8 @@ class LiteLLMAnthropicToResponsesAPIAdapter:
|
|||
def _summary_part_text(part: object) -> str:
|
||||
if isinstance(part, Mapping):
|
||||
mapping: Final = cast(Mapping[str, Any], part) # cast-ok: summary parts are untyped provider json
|
||||
return str(mapping.get("text", ""))
|
||||
return str(getattr(part, "text", ""))
|
||||
return str(mapping.get("text") or "")
|
||||
return str(getattr(part, "text", "") or "")
|
||||
|
||||
@classmethod
|
||||
def _thinking_blocks_from_reasoning_item(
|
||||
|
|
|
|||
|
|
@ -3824,6 +3824,28 @@ def test_assistant_thinking_blocks_become_a_reasoning_input_item():
|
|||
assert "id" not in reasoning_item
|
||||
|
||||
|
||||
def test_thinking_only_assistant_turn_still_sends_its_reasoning():
|
||||
"""An assistant turn can be pure reasoning, with no visible text and no tool call."""
|
||||
handler = LiteLLMResponsesTransformationHandler()
|
||||
messages = [
|
||||
{"role": "user", "content": "What is the weather in Denver?"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"thinking_blocks": [
|
||||
{"type": "thinking", "thinking": "August in Denver is dry.", "signature": "sig1"}
|
||||
],
|
||||
},
|
||||
{"role": "user", "content": "Why?"},
|
||||
]
|
||||
|
||||
input_items, _ = handler.convert_chat_completion_messages_to_responses_api(messages)
|
||||
|
||||
reasoning_items = [item for item in input_items if item.get("type") == "reasoning"]
|
||||
assert len(reasoning_items) == 1
|
||||
assert reasoning_items[0]["summary"] == [{"type": "summary_text", "text": "August in Denver is dry."}]
|
||||
|
||||
|
||||
def test_stored_reasoning_items_win_over_thinking_blocks():
|
||||
"""A minted reasoning id beats a re-derived one, so the two must not both be sent."""
|
||||
handler = LiteLLMResponsesTransformationHandler()
|
||||
|
|
|
|||
|
|
@ -1249,6 +1249,20 @@ class TestTranslateResponse:
|
|||
result: Any = _ADAPTER.translate_response(response)
|
||||
assert result["content"] == []
|
||||
|
||||
def test_null_summary_text_skipped_rather_than_stringified(self):
|
||||
"""A summary part whose text is null must not reach the client as the word "None"."""
|
||||
response = _make_mock_response(
|
||||
output=[
|
||||
{
|
||||
"type": "reasoning",
|
||||
"id": "rs_null_1",
|
||||
"summary": [{"type": "summary_text", "text": None}],
|
||||
}
|
||||
]
|
||||
)
|
||||
result: Any = _ADAPTER.translate_response(response)
|
||||
assert result["content"] == []
|
||||
|
||||
def test_reasoning_item_id_never_becomes_a_thinking_signature(self):
|
||||
"""Only Anthropic can sign a thinking block, so a stand-in signature is never invented."""
|
||||
reasoning = _make_reasoning_item(["Part one.", "Part two."], item_id="rs_abc123")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue