This commit is contained in:
Inês Magessi 2026-08-27 15:42:22 -05:00 • committed by GitHub
commit aa81a60699
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 99 additions and 2 deletions

View file

@ -1476,9 +1476,18 @@ class LiteLLMCompletionResponsesConfig:
"image_url": {"url": image_url_val},
}
)
elif part_type in ("input_file", "file"):
file_item = LiteLLMCompletionResponsesConfig._transform_input_file_item_to_file_item(
part
)
if file_item.get("file"):
normalized_blocks.append(file_item)
# 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,91 @@ 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"
def test_function_call_output_input_file_without_data_is_skipped():
"""An input_file part with no file_id, file_url, or file_data carries no
payload — appending it as ``{"type": "file", "file": {}}`` would activate
the structured-content path and emit a hollow block that downstream
adapters (Gemini, Bedrock) reject or silently ignore. Skip it instead."""
out = LiteLLMCompletionResponsesConfig._transform_responses_api_tool_call_output_to_chat_completion_message(
tool_call_output={
"type": "function_call_output",
"call_id": "call_empty_file",
"output": [
{"type": "input_text", "text": "Just text here."},
{"type": "input_file", "filename": "missing.pdf"},
],
}
)
assert len(out) == 1
# No usable file payload -> fall back to the plain-string text path.
assert out[0]["content"] == "Just text here."