fix(responses): preserve input_file in function_call_output during chat lowering

closes #28232
This commit is contained in:
Ines Magessi 2026-05-19 10:38:45 +02:00
parent cff3e0b75e
commit 7ce0bf2c7b
2 changed files with 78 additions and 2 deletions

View file

@ -1085,9 +1085,18 @@ class LiteLLMCompletionResponsesConfig:
"image_url": {"url": image_url_val},
}
)
elif part_type in ("input_file", "file"):
normalized_blocks.append(
LiteLLMCompletionResponsesConfig._transform_input_file_item_to_file_item(
part
)
)
# Prefer structured blocks if we have images; otherwise return a string.
if any(b.get("type") == "image_url" for b in normalized_blocks):
# Prefer structured blocks if we have any non-text part
# (images or files); otherwise return a string.
if any(
b.get("type") in ("image_url", "file") for b in normalized_blocks
):
# Ensure we include any accumulated text as text blocks too
return normalized_blocks
if text_acc:

View file

@ -40,3 +40,70 @@ def test_function_call_output_string_passthrough():
)
assert len(out) == 1
assert out[0]["content"] == '{"ok":true}'
def test_function_call_output_input_file_is_preserved_as_file_block():
"""A PDF returned from a tool call (input_file part inside
function_call_output.output) must survive the lowering to a chat-completions
tool message as a structured file content block. Previously the file part
was silently dropped and only the input_text sibling reached the model."""
pdf_data_url = "data:application/pdf;base64,JVBERi0xLjQKJfb=="
out = LiteLLMCompletionResponsesConfig._transform_responses_api_tool_call_output_to_chat_completion_message(
tool_call_output={
"type": "function_call_output",
"call_id": "call_pdf",
"output": [
{"type": "input_text", "text": "Here is the PDF."},
{
"type": "input_file",
"file_data": pdf_data_url,
"filename": "test.pdf",
},
],
}
)
assert len(out) == 1
msg = out[0]
assert msg["role"] == "tool"
assert msg["tool_call_id"] == "call_pdf"
content = msg["content"]
assert isinstance(content, list), (
"expected structured content list when a file part is present, "
f"got {type(content).__name__}: {content!r}"
)
text_blocks = [b for b in content if b.get("type") == "text"]
file_blocks = [b for b in content if b.get("type") == "file"]
assert any(b.get("text") == "Here is the PDF." for b in text_blocks)
assert len(file_blocks) == 1
assert file_blocks[0]["file"]["file_data"] == pdf_data_url
def test_function_call_output_input_file_with_file_id_is_preserved():
"""Same as above but for the file_id form (no inline bytes)."""
out = LiteLLMCompletionResponsesConfig._transform_responses_api_tool_call_output_to_chat_completion_message(
tool_call_output={
"type": "function_call_output",
"call_id": "call_pdf_id",
"output": [
{"type": "input_text", "text": "See attached."},
{
"type": "input_file",
"file_id": "file-abc123",
"filename": "report.pdf",
},
],
}
)
assert len(out) == 1
content = out[0]["content"]
assert isinstance(content, list)
file_blocks = [b for b in content if b.get("type") == "file"]
assert len(file_blocks) == 1
assert file_blocks[0]["file"]["file_id"] == "file-abc123"