mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(openai/responses/guardrail_translation): fix basemodel import (#17977)
* fix(openai/responses/guardrail_translation): fix basemodel import ensures /v1/responses output is correctly processed by guardrail handler * fix: fix linting error * fix: fix linting errors
This commit is contained in:
parent
6ddc7875ea
commit
a3f5a94784
3 changed files with 120 additions and 7 deletions
|
|
@ -30,7 +30,7 @@ Output: response.output is List[GenericResponseOutputItem] where each has:
|
|||
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast
|
||||
|
||||
from openai import BaseModel
|
||||
from pydantic import BaseModel
|
||||
|
||||
from litellm._logging import verbose_proxy_logger
|
||||
from litellm.llms.base_llm.guardrail_translation.base_translation import BaseTranslation
|
||||
|
|
@ -424,6 +424,7 @@ class OpenAIResponsesHandler(BaseTranslation):
|
|||
|
||||
Override this method to customize text/image/tool extraction logic.
|
||||
"""
|
||||
|
||||
# Check if this is a tool call (OutputFunctionToolCall)
|
||||
if isinstance(output_item, OutputFunctionToolCall):
|
||||
if tool_calls_to_check is not None:
|
||||
|
|
|
|||
|
|
@ -810,7 +810,12 @@ def function_setup( # noqa: PLR0915
|
|||
or call_type == CallTypes.responses.value
|
||||
):
|
||||
# Handle both 'input' (standard Responses API) and 'messages' (Cursor chat format)
|
||||
messages = args[0] if len(args) > 0 else kwargs.get("input") or kwargs.get("messages", "default-message-value")
|
||||
messages = (
|
||||
args[0]
|
||||
if len(args) > 0
|
||||
else kwargs.get("input")
|
||||
or kwargs.get("messages", "default-message-value")
|
||||
)
|
||||
else:
|
||||
messages = "default-message-value"
|
||||
stream = False
|
||||
|
|
@ -6900,7 +6905,9 @@ def last_assistant_with_tool_calls_has_no_thinking_blocks(
|
|||
|
||||
# Check if it has thinking_blocks
|
||||
thinking_blocks = last_assistant_with_tools.get("thinking_blocks")
|
||||
return thinking_blocks is None or len(thinking_blocks) == 0
|
||||
return thinking_blocks is None or (
|
||||
hasattr(thinking_blocks, "__len__") and len(thinking_blocks) == 0
|
||||
)
|
||||
|
||||
|
||||
def add_dummy_tool(custom_llm_provider: str) -> List[ChatCompletionToolParam]:
|
||||
|
|
@ -7032,7 +7039,9 @@ def validate_chat_completion_user_messages(messages: List[AllMessageValues]):
|
|||
for item in user_content:
|
||||
if isinstance(item, dict):
|
||||
if item.get("type") not in ValidUserMessageContentTypes:
|
||||
raise Exception(f"invalid content type={item.get('type')}")
|
||||
raise Exception(
|
||||
f"invalid content type={item.get('type')}"
|
||||
)
|
||||
except Exception as e:
|
||||
if isinstance(e, KeyError):
|
||||
raise Exception(
|
||||
|
|
@ -7867,9 +7876,7 @@ class ProviderConfigManager:
|
|||
|
||||
return GeminiVideoConfig()
|
||||
elif LlmProviders.VERTEX_AI == provider:
|
||||
from litellm.llms.vertex_ai.videos.transformation import (
|
||||
VertexAIVideoConfig,
|
||||
)
|
||||
from litellm.llms.vertex_ai.videos.transformation import VertexAIVideoConfig
|
||||
|
||||
return VertexAIVideoConfig()
|
||||
elif LlmProviders.RUNWAYML == provider:
|
||||
|
|
|
|||
|
|
@ -716,3 +716,108 @@ class TestOpenAIResponsesHandlerToolCallExtraction:
|
|||
assert texts_to_check[0] == "I'll check the weather for you"
|
||||
assert len(tool_calls_to_check) == 1
|
||||
assert tool_calls_to_check[0]["function"]["name"] == "get_current_weather"
|
||||
|
||||
def test_extract_text_from_basemodel_instance(self):
|
||||
"""Test extracting text from GenericResponseOutputItem as BaseModel instance
|
||||
|
||||
This test verifies that _extract_output_text_and_images correctly handles
|
||||
GenericResponseOutputItem when passed as a Pydantic BaseModel instance
|
||||
(not as a dict). This addresses the issue where isinstance(output_item, BaseModel)
|
||||
was failing because the handler was importing BaseModel from openai instead of pydantic.
|
||||
"""
|
||||
handler = OpenAIResponsesHandler()
|
||||
|
||||
# Create a proper GenericResponseOutputItem instance (Pydantic BaseModel)
|
||||
output_item = GenericResponseOutputItem(
|
||||
type="message",
|
||||
id="msg_123",
|
||||
status="completed",
|
||||
role="assistant",
|
||||
content=[
|
||||
OutputText(
|
||||
type="output_text",
|
||||
text="Hi! My name is Ishaan.",
|
||||
annotations=[],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
texts_to_check: List[str] = []
|
||||
images_to_check: List[str] = []
|
||||
tool_calls_to_check: List[Any] = []
|
||||
task_mappings: List[Tuple[int, int]] = []
|
||||
|
||||
# Extract text from the BaseModel instance
|
||||
handler._extract_output_text_and_images(
|
||||
output_item=output_item,
|
||||
output_idx=0,
|
||||
texts_to_check=texts_to_check,
|
||||
images_to_check=images_to_check,
|
||||
task_mappings=task_mappings,
|
||||
tool_calls_to_check=tool_calls_to_check,
|
||||
)
|
||||
|
||||
# Verify text was extracted correctly
|
||||
assert len(texts_to_check) == 1
|
||||
assert texts_to_check[0] == "Hi! My name is Ishaan."
|
||||
assert len(task_mappings) == 1
|
||||
assert task_mappings[0] == (0, 0) # (output_idx, content_idx)
|
||||
assert len(tool_calls_to_check) == 0 # No tool calls in this output
|
||||
|
||||
def test_extract_text_from_basemodel_with_multiple_content_items(self):
|
||||
"""Test extracting multiple text items from GenericResponseOutputItem BaseModel
|
||||
|
||||
This test verifies that the handler correctly processes a BaseModel instance
|
||||
with multiple content items in the content array.
|
||||
"""
|
||||
handler = OpenAIResponsesHandler()
|
||||
|
||||
# Create GenericResponseOutputItem with multiple content items
|
||||
output_item = GenericResponseOutputItem(
|
||||
type="message",
|
||||
id="msg_456",
|
||||
status="completed",
|
||||
role="assistant",
|
||||
content=[
|
||||
OutputText(
|
||||
type="output_text",
|
||||
text="First paragraph.",
|
||||
annotations=[],
|
||||
),
|
||||
OutputText(
|
||||
type="output_text",
|
||||
text="Second paragraph.",
|
||||
annotations=[],
|
||||
),
|
||||
OutputText(
|
||||
type="output_text",
|
||||
text="Third paragraph.",
|
||||
annotations=[],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
texts_to_check: List[str] = []
|
||||
images_to_check: List[str] = []
|
||||
tool_calls_to_check: List[Any] = []
|
||||
task_mappings: List[Tuple[int, int]] = []
|
||||
|
||||
# Extract all text items
|
||||
handler._extract_output_text_and_images(
|
||||
output_item=output_item,
|
||||
output_idx=0,
|
||||
texts_to_check=texts_to_check,
|
||||
images_to_check=images_to_check,
|
||||
task_mappings=task_mappings,
|
||||
tool_calls_to_check=tool_calls_to_check,
|
||||
)
|
||||
|
||||
# Verify all text items were extracted
|
||||
assert len(texts_to_check) == 3
|
||||
assert texts_to_check[0] == "First paragraph."
|
||||
assert texts_to_check[1] == "Second paragraph."
|
||||
assert texts_to_check[2] == "Third paragraph."
|
||||
assert len(task_mappings) == 3
|
||||
assert task_mappings[0] == (0, 0)
|
||||
assert task_mappings[1] == (0, 1)
|
||||
assert task_mappings[2] == (0, 2)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue