diff --git a/libs/core/langchain_core/language_models/chat_models.py b/libs/core/langchain_core/language_models/chat_models.py index a7e8271f23b..8203b9ac067 100644 --- a/libs/core/langchain_core/language_models/chat_models.py +++ b/libs/core/langchain_core/language_models/chat_models.py @@ -48,7 +48,6 @@ from langchain_core.messages import ( message_chunk_to_message, ) from langchain_core.messages.block_translators.openai import ( - convert_to_openai_data_block, convert_to_openai_image_block, ) from langchain_core.output_parsers.openai_tools import ( @@ -149,16 +148,18 @@ def _format_for_tracing(messages: list[BaseMessage]) -> list[BaseMessage]: block.get("type") == "file" and is_data_content_block(block) # v0 (image/audio/file) or v1 and "base64" in block - # Narrows to old Base64ContentBlock or new FileContentBlock + # Backward compat: convert v1 base64 blocks to v0 ): if message_to_trace is message: # Shallow copy message_to_trace = message.model_copy() message_to_trace.content = list(message_to_trace.content) - message_to_trace.content[idx] = convert_to_openai_data_block( # type: ignore[index] - block - ) + message_to_trace.content[idx] = { # type: ignore[index] + **{k: v for k, v in block.items() if k != "base64"}, + "data": block["base64"], + "source_type": "base64", + } elif len(block) == 1 and "type" not in block: # Tracing assumes all content blocks have a "type" key. Here # we add this key if it is missing, and there's an obvious diff --git a/libs/core/tests/unit_tests/language_models/chat_models/test_base.py b/libs/core/tests/unit_tests/language_models/chat_models/test_base.py index 8ef536f7260..b80b5ce932a 100644 --- a/libs/core/tests/unit_tests/language_models/chat_models/test_base.py +++ b/libs/core/tests/unit_tests/language_models/chat_models/test_base.py @@ -1,6 +1,7 @@ """Test base chat model.""" import uuid +import warnings from collections.abc import AsyncIterator, Iterator from typing import TYPE_CHECKING, Any, Literal, Optional, Union @@ -505,6 +506,45 @@ def test_trace_images_in_openai_format() -> None: ] +def test_trace_pdfs() -> None: + # For backward compat + llm = ParrotFakeChatModel() + messages = [ + { + "role": "user", + "content": [ + { + "type": "file", + "mime_type": "application/pdf", + "base64": "", + } + ], + } + ] + tracer = FakeChatModelStartTracer() + + with warnings.catch_warnings(): + warnings.simplefilter("error") + llm.invoke(messages, config={"callbacks": [tracer]}) + + assert tracer.messages == [ + [ + [ + HumanMessage( + content=[ + { + "type": "file", + "mime_type": "application/pdf", + "source_type": "base64", + "data": "", + } + ] + ) + ] + ] + ] + + def test_content_block_transformation_v0_to_v1_image() -> None: """Test that v0 format image content blocks are transformed to v1 format.""" # Create a message with v0 format image content diff --git a/libs/partners/openai/tests/unit_tests/chat_models/test_base.py b/libs/partners/openai/tests/unit_tests/chat_models/test_base.py index ffee5318ed0..daefc7a0352 100644 --- a/libs/partners/openai/tests/unit_tests/chat_models/test_base.py +++ b/libs/partners/openai/tests/unit_tests/chat_models/test_base.py @@ -789,6 +789,22 @@ def test_format_message_content() -> None: for content in contents: assert expected == _format_message_content([content]) + # Test warn if PDF is missing a filename + pdf_block = { + "type": "file", + "base64": "", + "mime_type": "application/pdf", + } + expected = [ + # N.B. this format is invalid for OpenAI + { + "type": "file", + "file": {"file_data": "data:application/pdf;base64,"}, + } + ] + with pytest.warns(match="filename"): + assert expected == _format_message_content([pdf_block]) + contents = [ {"type": "file", "source_type": "id", "id": "file-abc123"}, {"type": "file", "file_id": "file-abc123"},