mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
Merge 95b0ca43bf into 9dda4d895f
This commit is contained in:
commit
7fcf92805b
2 changed files with 148 additions and 19 deletions
|
|
@ -676,7 +676,6 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
from openai.types.responses import (
|
||||
ResponseFunctionToolCall,
|
||||
ResponseOutputMessage,
|
||||
ResponseReasoningItem,
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
@ -691,7 +690,7 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
choices: Final[list[Choices]] = []
|
||||
index = 0
|
||||
reasoning_content: str | None = None
|
||||
pending_reasoning_item: _BuiltReasoningItem | None = None
|
||||
pending_reasoning_items: Final[list[_BuiltReasoningItem]] = [] # mutable-ok: accumulator
|
||||
|
||||
# Collect all tool calls to put them in a single choice
|
||||
# (Chat Completions API expects all tool calls in one message)
|
||||
|
|
@ -699,13 +698,13 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
tool_call_index = 0
|
||||
|
||||
for item in output_items:
|
||||
if isinstance(item, ResponseReasoningItem):
|
||||
pending_reasoning_item = _build_reasoning_item(
|
||||
item_id=item.id,
|
||||
encrypted_content=getattr(item, "encrypted_content", None),
|
||||
summary_raw=item.summary,
|
||||
)
|
||||
reasoning_content = " ".join(s["text"] for s in pending_reasoning_item["summary"] if s.get("text"))
|
||||
reasoning_item = _reasoning_item_from_output_item(item)
|
||||
if reasoning_item is not None:
|
||||
pending_reasoning_items.append(reasoning_item)
|
||||
summary_texts = [s["text"] for s in reasoning_item["summary"] if s.get("text")]
|
||||
if summary_texts:
|
||||
step_text = " ".join(summary_texts)
|
||||
reasoning_content = f"{reasoning_content} {step_text}".strip() if reasoning_content else step_text
|
||||
|
||||
elif isinstance(item, ResponseOutputMessage):
|
||||
for content in item.content:
|
||||
|
|
@ -720,10 +719,7 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
content=response_text if response_text else "",
|
||||
reasoning_content=reasoning_content,
|
||||
annotations=annotations,
|
||||
reasoning_items=cast(
|
||||
list[ChatCompletionReasoningItem] | None,
|
||||
([pending_reasoning_item] if pending_reasoning_item is not None else None),
|
||||
),
|
||||
reasoning_items=_as_chat_reasoning_items(pending_reasoning_items),
|
||||
)
|
||||
|
||||
choices.append(
|
||||
|
|
@ -735,7 +731,7 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
)
|
||||
|
||||
reasoning_content = None # flush
|
||||
pending_reasoning_item = None # flush
|
||||
pending_reasoning_items.clear() # flush
|
||||
index += 1
|
||||
|
||||
elif isinstance(item, ResponseFunctionToolCall):
|
||||
|
|
@ -780,6 +776,12 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
elif handle_raw_dict_callback is not None:
|
||||
choice, index = handle_raw_dict_callback(item=raw_item, index=index)
|
||||
if choice is not None:
|
||||
if pending_reasoning_items:
|
||||
choice.message.reasoning_items = _as_chat_reasoning_items(pending_reasoning_items)
|
||||
if reasoning_content:
|
||||
choice.message.reasoning_content = reasoning_content
|
||||
pending_reasoning_items.clear()
|
||||
reasoning_content = None
|
||||
choices.append(choice)
|
||||
else:
|
||||
pass # don't fail request if item in list is not supported
|
||||
|
|
@ -790,14 +792,11 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
content=None,
|
||||
tool_calls=accumulated_tool_calls,
|
||||
reasoning_content=reasoning_content,
|
||||
reasoning_items=cast(
|
||||
list[ChatCompletionReasoningItem] | None,
|
||||
([pending_reasoning_item] if pending_reasoning_item is not None else None),
|
||||
),
|
||||
reasoning_items=_as_chat_reasoning_items(pending_reasoning_items),
|
||||
)
|
||||
choices.append(Choices(message=msg, finish_reason="tool_calls", index=index))
|
||||
reasoning_content = None
|
||||
pending_reasoning_item = None
|
||||
pending_reasoning_items.clear()
|
||||
|
||||
return choices
|
||||
|
||||
|
|
|
|||
|
|
@ -4388,3 +4388,133 @@ def test_every_bridged_chunk_after_response_created_carries_the_served_service_t
|
|||
relayed = [iterator.chunk_parser(event).model_dump().get("service_tier") for event in events]
|
||||
|
||||
assert relayed == ["default"] * len(events), relayed
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"raw_output_items, expected_reasoning_ids, expected_summary_text, target_type",
|
||||
[
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"type": "reasoning",
|
||||
"id": "rs_1",
|
||||
"summary": [{"type": "summary_text", "text": "Step 1"}],
|
||||
"encrypted_content": "enc1",
|
||||
},
|
||||
{
|
||||
"type": "reasoning",
|
||||
"id": "rs_2",
|
||||
"summary": [{"type": "summary_text", "text": "Step 2"}],
|
||||
"encrypted_content": "enc2",
|
||||
},
|
||||
{
|
||||
"type": "function_call",
|
||||
"id": "fc_1",
|
||||
"call_id": "call_1",
|
||||
"name": "search",
|
||||
"arguments": "{}",
|
||||
"status": "completed",
|
||||
},
|
||||
],
|
||||
["rs_1", "rs_2"],
|
||||
"Step 1 Step 2",
|
||||
"tool_calls",
|
||||
id="multiple_reasoning_items_before_tool_call",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"type": "reasoning",
|
||||
"id": "rs_alpha",
|
||||
"summary": [{"type": "summary_text", "text": "Analysis"}],
|
||||
"encrypted_content": "enc_a",
|
||||
},
|
||||
{
|
||||
"type": "reasoning",
|
||||
"id": "rs_beta",
|
||||
"summary": [{"type": "summary_text", "text": "Conclusion"}],
|
||||
"encrypted_content": "enc_b",
|
||||
},
|
||||
],
|
||||
["rs_alpha", "rs_beta"],
|
||||
"Analysis Conclusion",
|
||||
"message",
|
||||
id="multiple_reasoning_items_before_output_message",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"type": "reasoning",
|
||||
"id": "rs_raw_1",
|
||||
"summary": [{"type": "summary_text", "text": "Raw Step 1"}],
|
||||
"encrypted_content": "enc_raw",
|
||||
},
|
||||
{
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [{"type": "output_text", "text": "Raw response text"}],
|
||||
},
|
||||
],
|
||||
["rs_raw_1"],
|
||||
"Raw Step 1",
|
||||
"raw_dict_message",
|
||||
id="reasoning_items_before_raw_dict_message",
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
{
|
||||
"type": "function_call",
|
||||
"id": "fc_solo",
|
||||
"call_id": "call_solo",
|
||||
"name": "lookup",
|
||||
"arguments": "{}",
|
||||
"status": "completed",
|
||||
}
|
||||
],
|
||||
None,
|
||||
None,
|
||||
"tool_calls",
|
||||
id="zero_reasoning_items_returns_none",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_convert_response_output_to_choices_preserves_all_reasoning_items(
|
||||
raw_output_items: list[dict[str, object]],
|
||||
expected_reasoning_ids: list[str] | None,
|
||||
expected_summary_text: str | None,
|
||||
target_type: str,
|
||||
) -> None:
|
||||
"""Verify non-streaming Responses API bridge retains all reasoning items across message and tool call turns."""
|
||||
from openai.types.responses import ResponseOutputMessage
|
||||
from openai.types.responses.response_output_message import ResponseOutputText
|
||||
|
||||
items_to_pass: Final[list[object]] = list(raw_output_items) # mutable-ok: fixture setup
|
||||
if target_type == "message":
|
||||
items_to_pass.append(
|
||||
ResponseOutputMessage(
|
||||
id="msg_1",
|
||||
role="assistant",
|
||||
status="completed",
|
||||
type="message",
|
||||
content=[ResponseOutputText(annotations=[], text="Done", type="output_text", logprobs=[])],
|
||||
)
|
||||
)
|
||||
|
||||
handler: Final = LiteLLMResponsesTransformationHandler()
|
||||
choices: Final = handler._convert_response_output_to_choices(
|
||||
items_to_pass,
|
||||
handle_raw_dict_callback=handler._handle_raw_dict_response_item,
|
||||
)
|
||||
|
||||
assert len(choices) == 1
|
||||
msg = choices[0].message
|
||||
reasoning_items = getattr(msg, "reasoning_items", None)
|
||||
reasoning_content = getattr(msg, "reasoning_content", None)
|
||||
if expected_reasoning_ids is None:
|
||||
assert reasoning_items is None
|
||||
assert reasoning_content is None
|
||||
else:
|
||||
assert reasoning_items is not None
|
||||
assert [item["id"] for item in reasoning_items] == expected_reasoning_ids
|
||||
assert reasoning_content == expected_summary_text
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue