diff --git a/libs/partners/fireworks/langchain_fireworks/chat_models.py b/libs/partners/fireworks/langchain_fireworks/chat_models.py index 22a34bec14..78306dbb7a 100644 --- a/libs/partners/fireworks/langchain_fireworks/chat_models.py +++ b/libs/partners/fireworks/langchain_fireworks/chat_models.py @@ -222,6 +222,25 @@ def _allowed_content_part_keys() -> frozenset[str]: _ALLOWED_CONTENT_PART_KEYS: frozenset[str] = _allowed_content_part_keys() +_DROPPED_CONTENT_BLOCK_TYPES: frozenset[str] = frozenset( + { + "tool_use", + "thinking", + "reasoning", + "reasoning_content", + "function_call", + "code_interpreter_call", + } +) +"""Content block types the chat completions wire format does not carry. + +These arise from provider-specific or canonical v1 content (e.g. Anthropic +`tool_use`/`thinking` blocks, or a `reasoning` block on an AIMessage) that +reaches `_convert_message_to_dict` as conversation history. Fireworks rejects +them, so they are dropped rather than forwarded. +""" + + def _sanitize_chat_completions_content(content: Any) -> Any: """Strip non-wire keys from content blocks before serializing to Fireworks. @@ -292,13 +311,7 @@ def _format_message_content(content: Any) -> Any: for block in content: if isinstance(block, dict) and "type" in block: btype = block["type"] - if btype in ( - "tool_use", - "thinking", - "reasoning_content", - "function_call", - "code_interpreter_call", - ): + if btype in _DROPPED_CONTENT_BLOCK_TYPES: continue if is_data_content_block(block): formatted.append( diff --git a/libs/partners/fireworks/tests/unit_tests/test_chat_models.py b/libs/partners/fireworks/tests/unit_tests/test_chat_models.py index 448d2b6e0b..b665ef0e40 100644 --- a/libs/partners/fireworks/tests/unit_tests/test_chat_models.py +++ b/libs/partners/fireworks/tests/unit_tests/test_chat_models.py @@ -44,6 +44,7 @@ from langchain_core.messages import ( from langchain_fireworks import ChatFireworks from langchain_fireworks.chat_models import ( _ALLOWED_CONTENT_PART_KEYS, + _DROPPED_CONTENT_BLOCK_TYPES, FireworksContextOverflowError, _acompletion_with_retry, _completion_with_retry, @@ -402,16 +403,23 @@ def test_format_message_content_passes_through_existing_image_url() -> None: assert formatted == blocks -@pytest.mark.parametrize( - "btype", - [ +def test_dropped_content_block_types_membership() -> None: + """Pin the drop-list so a removal is a deliberate, visible change. + + The parametrized test below derives its cases from the constant, so it + tracks additions for free but cannot catch a deletion. + """ + assert { "tool_use", "thinking", + "reasoning", "reasoning_content", "function_call", "code_interpreter_call", - ], -) + } == _DROPPED_CONTENT_BLOCK_TYPES + + +@pytest.mark.parametrize("btype", sorted(_DROPPED_CONTENT_BLOCK_TYPES)) def test_format_message_content_drops_unsupported_block_types(btype: str) -> None: """Block types not part of the OpenAI chat completions wire format are stripped.""" blocks = [