mirror of
https://github.com/hwchase17/langchain.git
synced 2026-05-17 13:00:49 +00:00
fix(fireworks): strip non-wire keys from ToolMessage text content blocks (#37187)
Fireworks's chat completions endpoint rejects unknown fields on tool message content blocks — specifically the `id` key that LangChain auto-generates on `TextContentBlock`. Add `_sanitize_chat_completions_content` to strip those extra keys before the payload hits the wire, preventing `Extra inputs are not permitted` errors on tool message round-trips.
This commit is contained in:
@@ -170,6 +170,25 @@ def _convert_dict_to_message(_dict: Mapping[str, Any]) -> BaseMessage:
|
||||
return ChatMessage(content=_dict.get("content", ""), role=role or "")
|
||||
|
||||
|
||||
def _sanitize_chat_completions_content(content: Any) -> Any:
|
||||
"""Strip non-wire keys from text content blocks.
|
||||
|
||||
Fireworks's chat completions endpoint rejects unknown fields on tool
|
||||
message content blocks (e.g. the `id` that LangChain auto-generates on
|
||||
`TextContentBlock`). For list content, keep only `type` and `text` on
|
||||
text blocks; pass other blocks and non-list content through unchanged.
|
||||
"""
|
||||
if not isinstance(content, list):
|
||||
return content
|
||||
sanitized: list[Any] = []
|
||||
for block in content:
|
||||
if isinstance(block, dict) and block.get("type") == "text" and "text" in block:
|
||||
sanitized.append({"type": "text", "text": block["text"]})
|
||||
else:
|
||||
sanitized.append(block)
|
||||
return sanitized
|
||||
|
||||
|
||||
def _format_message_content(content: Any) -> Any:
|
||||
"""Format message content for the Fireworks chat completions wire format.
|
||||
|
||||
@@ -296,7 +315,9 @@ def _convert_message_to_dict(message: BaseMessage) -> dict:
|
||||
elif isinstance(message, ToolMessage):
|
||||
message_dict = {
|
||||
"role": "tool",
|
||||
"content": _format_message_content(message.content),
|
||||
"content": _sanitize_chat_completions_content(
|
||||
_format_message_content(message.content)
|
||||
),
|
||||
"tool_call_id": message.tool_call_id,
|
||||
}
|
||||
else:
|
||||
|
||||
@@ -31,6 +31,7 @@ from langchain_fireworks.chat_models import (
|
||||
_convert_dict_to_message,
|
||||
_convert_message_to_dict,
|
||||
_format_message_content,
|
||||
_sanitize_chat_completions_content,
|
||||
_usage_to_metadata,
|
||||
)
|
||||
|
||||
@@ -106,6 +107,32 @@ def test_format_message_content_passthrough_string() -> None:
|
||||
assert _format_message_content("hello") == "hello"
|
||||
|
||||
|
||||
def test_sanitize_chat_completions_text_blocks_strips_id() -> None:
|
||||
"""LangChain auto-generated `id` on text blocks must not reach the wire.
|
||||
|
||||
Fireworks's chat completions schema rejects unknown keys on tool message
|
||||
content blocks (`Extra inputs are not permitted, ... [0].id`).
|
||||
"""
|
||||
message = ToolMessage(
|
||||
content=[{"type": "text", "text": "foo", "id": "lc_abc123"}],
|
||||
tool_call_id="def456",
|
||||
)
|
||||
assert _convert_message_to_dict(message) == {
|
||||
"role": "tool",
|
||||
"content": [{"type": "text", "text": "foo"}],
|
||||
"tool_call_id": "def456",
|
||||
}
|
||||
|
||||
|
||||
def test_sanitize_chat_completions_content_passthrough_string() -> None:
|
||||
assert _sanitize_chat_completions_content("hello") == "hello"
|
||||
|
||||
|
||||
def test_sanitize_chat_completions_content_passthrough_non_text_block() -> None:
|
||||
blocks = [{"type": "image_url", "image_url": {"url": "https://x/y.png"}}]
|
||||
assert _sanitize_chat_completions_content(blocks) == blocks
|
||||
|
||||
|
||||
def test_format_message_content_translates_v1_image_block() -> None:
|
||||
"""Canonical v1 image block is translated to OpenAI image_url + data URI."""
|
||||
blocks = [{"type": "image", "base64": "abc", "mime_type": "image/png"}]
|
||||
|
||||
Reference in New Issue
Block a user