This commit is contained in:
Mrunmayee Daware 2026-10-21 02:36:04 +00:00 • committed by GitHub
commit e22824ffca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 99 additions and 15 deletions

View file

@ -28238,6 +28238,9 @@
{
"$ref": "#/components/schemas/CustomToolCallOutputItem"
},
{
"$ref": "#/components/schemas/ResponseReasoningItem"
},
{
"$ref": "#/components/schemas/OutputToolSearchCall"
}

View file

@ -28,8 +28,13 @@ from openai.types.chat.chat_completion_named_tool_choice_param import (
from openai.types.chat.chat_completion_named_tool_choice_param import (
Function as NamedToolChoiceFunction,
)
from openai.types.responses import ResponseFunctionToolCall, ResponseFunctionWebSearch
from openai.types.responses import (
ResponseFunctionToolCall,
ResponseFunctionWebSearch,
ResponseReasoningItem,
)
from openai.types.responses.response_create_params import ResponseInputParam
from openai.types.responses.response_reasoning_item import Content as ReasoningContent
from openai.types.responses.tool_choice_custom_param import ToolChoiceCustomParam
from openai.types.responses.tool_choice_function_param import ToolChoiceFunctionParam
from openai.types.responses.tool_param import FunctionToolParam
@ -3037,6 +3042,7 @@ class LiteLLMCompletionResponsesConfig:
| ResponseFunctionToolCall
| ResponseFunctionWebSearch
| CustomToolCallOutputItem
| ResponseReasoningItem
| OutputToolSearchCall
]:
responses_output: list[
@ -3047,6 +3053,7 @@ class LiteLLMCompletionResponsesConfig:
| ResponseFunctionToolCall
| ResponseFunctionWebSearch
| CustomToolCallOutputItem
| ResponseReasoningItem
| OutputToolSearchCall
] = []
@ -3124,7 +3131,7 @@ class LiteLLMCompletionResponsesConfig:
def _extract_reasoning_output_items(
chat_completion_response: ModelResponse,
choices: list[Choices],
) -> list[GenericResponseOutputItem]:
) -> list[ResponseReasoningItem]:
for choice in choices:
if hasattr(choice, "message") and choice.message:
message = choice.message
@ -3133,22 +3140,23 @@ class LiteLLMCompletionResponsesConfig:
if reasoning_content or encrypted_content:
# Only check the first choice for reasoning content
return [
GenericResponseOutputItem(
ResponseReasoningItem(
type="reasoning",
id=mint_reasoning_item_id(),
status=LiteLLMCompletionResponsesConfig._map_chat_completion_finish_reason_to_responses_status(
choice.finish_reason
),
role="assistant",
# summary is required by the SDK/spec; the bridge has
# no summarized reasoning, so it stays empty
summary=[],
content=[
OutputText(
type="output_text",
text=text,
annotations=[],
)
ReasoningContent(type="reasoning_text", text=text)
for text in (reasoning_content,)
if text
],
status=cast( # cast-ok: the map only returns completed/incomplete, both valid for a reasoning item's narrower status union
"Literal['in_progress', 'completed', 'incomplete']",
LiteLLMCompletionResponsesConfig._map_chat_completion_finish_reason_to_responses_status(
choice.finish_reason
),
),
encrypted_content=encrypted_content,
)
]

View file

@ -64,6 +64,7 @@ from openai.types.responses.response_create_params import (
)
from openai.types.responses.response_function_tool_call import ResponseFunctionToolCall
from openai.types.responses.response_function_web_search import ResponseFunctionWebSearch
from openai.types.responses.response_reasoning_item import ResponseReasoningItem
from pydantic import (
ConfigDict,
Discriminator,
@ -1487,6 +1488,7 @@ class ResponsesAPIResponse(BaseLiteLLMOpenAIResponseObject):
| ResponseFunctionToolCall
| ResponseFunctionWebSearch
| CustomToolCallOutputItem
| ResponseReasoningItem
| OutputToolSearchCall
]
)

View file

@ -307,9 +307,10 @@ class TestLiteLLMCompletionResponsesConfig:
if hasattr(reasoning_item, "id") and reasoning_item.id:
assert reasoning_item.id.startswith("rs_"), f"Expected ID to start with 'rs_', got: {reasoning_item.id}"
assert reasoning_item.status == "completed"
assert reasoning_item.role == "assistant"
assert reasoning_item.summary == []
assert not hasattr(reasoning_item, "role")
assert len(reasoning_item.content) == 1
assert reasoning_item.content[0].type == "output_text"
assert reasoning_item.content[0].type == "reasoning_text"
assert "step by step" in reasoning_item.content[0].text
assert "42" in reasoning_item.content[0].text
@ -319,6 +320,76 @@ class TestLiteLLMCompletionResponsesConfig:
message_item = message_items[0]
assert message_item.content[0].text == "The answer is 42."
def test_reasoning_output_items_validate_against_openai_sdk(self):
"""Bridged reasoning items must carry a summary array and reasoning_text
content parts so ResponseReasoningItem accepts them (issue #45552)."""
from openai.types.responses import ResponseReasoningItem
chat_completion_response = ModelResponse(
id="test-response-id",
created=1234567890,
model="test-model",
object="chat.completion",
choices=[
Choices(
finish_reason="stop",
index=0,
message=Message(
content="The answer is 42.",
role="assistant",
reasoning_content="Thinking about the question.",
),
)
],
)
responses_api_response = LiteLLMCompletionResponsesConfig.transform_chat_completion_response_to_responses_api_response(
request_input="What is the meaning of life?",
responses_api_request={},
chat_completion_response=chat_completion_response,
)
reasoning_items = [
item for item in responses_api_response.output if item.type == "reasoning"
]
assert len(reasoning_items) == 1
dumped = reasoning_items[0].model_dump(mode="json")
assert dumped["summary"] == []
assert dumped["content"][0]["type"] == "reasoning_text"
assert "role" not in dumped
assert "phase" not in dumped
validated = ResponseReasoningItem.model_validate(dumped)
assert validated.summary == []
def test_reasoning_output_item_without_text_still_validates(self):
"""Encrypted-only reasoning (empty content) must still carry summary: [] and
validate against the SDK (issue #45552, display: omitted case)."""
from openai.types.responses import ResponseReasoningItem
message = Message(role="assistant", content="apple")
message.thinking_blocks = [
{"type": "thinking", "thinking": "", "signature": "sig"}
]
choice = Choices(index=0, finish_reason="stop", message=message)
items = LiteLLMCompletionResponsesConfig._extract_reasoning_output_items(
chat_completion_response=ModelResponse(
id="test-response-id",
created=1234567890,
model="test-model",
object="chat.completion",
),
choices=[choice],
)
assert len(items) == 1
dumped = items[0].model_dump(mode="json")
assert dumped["summary"] == []
assert dumped["content"] == []
ResponseReasoningItem.model_validate(dumped)
def test_transform_chat_completion_response_without_reasoning_content(self):
"""Test that transformation works normally when no reasoning content is present"""
# Setup

View file

@ -44279,7 +44279,7 @@ export interface components {
/** Output */
output: (components["schemas"]["ResponseOutputMessage"] | components["schemas"]["ResponseFileSearchToolCall"] | components["schemas"]["ResponseFunctionToolCall"] | components["schemas"]["ResponseFunctionToolCallOutputItem"] | components["schemas"]["ResponseFunctionWebSearch"] | components["schemas"]["ResponseComputerToolCall"] | components["schemas"]["ResponseComputerToolCallOutputItem"] | components["schemas"]["ResponseReasoningItem"] | components["schemas"]["ResponseToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItem"] | components["schemas"]["ResponseCompactionItem"] | components["schemas"]["ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCall"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ResponseFunctionShellToolCall"] | components["schemas"]["ResponseFunctionShellToolCallOutput"] | components["schemas"]["ResponseApplyPatchToolCall"] | components["schemas"]["ResponseApplyPatchToolCallOutput"] | components["schemas"]["McpCall"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["McpApprovalResponse"] | components["schemas"]["ResponseCustomToolCall"] | components["schemas"]["ResponseCustomToolCallOutputItem"] | {
[key: string]: unknown;
})[] | (components["schemas"]["GenericResponseOutputItem"] | components["schemas"]["OutputCodeInterpreterCall"] | components["schemas"]["OutputFunctionToolCall"] | components["schemas"]["OutputImageGenerationCall"] | components["schemas"]["ResponseFunctionToolCall"] | components["schemas"]["ResponseFunctionWebSearch"] | components["schemas"]["CustomToolCallOutputItem"] | components["schemas"]["OutputToolSearchCall"])[];
})[] | (components["schemas"]["GenericResponseOutputItem"] | components["schemas"]["OutputCodeInterpreterCall"] | components["schemas"]["OutputFunctionToolCall"] | components["schemas"]["OutputImageGenerationCall"] | components["schemas"]["ResponseFunctionToolCall"] | components["schemas"]["ResponseFunctionWebSearch"] | components["schemas"]["CustomToolCallOutputItem"] | components["schemas"]["ResponseReasoningItem"] | components["schemas"]["OutputToolSearchCall"])[];
/** Parallel Tool Calls */
parallel_tool_calls?: boolean | null;
/** Previous Response Id */