fix(fireworks): bind_tools(strict: bool) and reasoning_content (#34343)

Extract strict from kwargs and pass it to convert_to_openai_tool when
converting tools. This ensures that when strict is provided, it's
properly used during tool conversion and removed from kwargs before
calling the parent bind method.

Also extract reasoning_content from API responses and store it in
additional_kwargs for AIMessage objects.

Fixes https://github.com/langchain-ai/langchain/issues/34341 and
https://github.com/langchain-ai/langchain/issues/34342

---------

Co-authored-by: Mason Daugherty <mason@langchain.dev>
This commit is contained in:
Dragos Bobolea
2025-12-27 10:42:06 +02:00
committed by GitHub
parent 5ef9f6e036
commit 6d447f89d9
3 changed files with 64 additions and 4 deletions

View File

@@ -114,8 +114,12 @@ def _convert_dict_to_message(_dict: Mapping[str, Any]) -> BaseMessage:
# Also Fireworks returns None for tool invocations
content = _dict.get("content", "") or ""
additional_kwargs: dict = {}
if reasoning_content := _dict.get("reasoning_content"):
additional_kwargs["reasoning_content"] = reasoning_content
if function_call := _dict.get("function_call"):
additional_kwargs["function_call"] = dict(function_call)
tool_calls = []
invalid_tool_calls = []
if raw_tool_calls := _dict.get("tool_calls"):
@@ -678,7 +682,10 @@ class ChatFireworks(BaseChatModel):
**kwargs: Any additional parameters to pass to
`langchain_fireworks.chat_models.ChatFireworks.bind`
""" # noqa: E501
formatted_tools = [convert_to_openai_tool(tool) for tool in tools]
strict = kwargs.pop("strict", None)
formatted_tools = [
convert_to_openai_tool(tool, strict=strict) for tool in tools
]
if tool_choice is not None and tool_choice:
if isinstance(tool_choice, str) and (
tool_choice not in ("auto", "any", "none")

View File

@@ -18,15 +18,30 @@ from langchain_fireworks import ChatFireworks
_MODEL = "accounts/fireworks/models/gpt-oss-120b"
def test_tool_choice_bool() -> None:
"""Test that tool choice is respected just passing in True."""
@pytest.mark.parametrize("strict", [None, True, False])
def test_tool_choice_bool(strict: bool | None) -> None: # noqa: FBT001
"""Test that tool choice is respected with different strict values."""
llm = ChatFireworks(model="fireworks/kimi-k2-instruct-0905")
class MyTool(BaseModel):
name: str
age: int
with_tool = llm.bind_tools([MyTool], tool_choice=True)
kwargs = {"tool_choice": True}
if strict is not None:
kwargs["strict"] = strict
with_tool = llm.bind_tools([MyTool], **kwargs)
# Verify that strict is correctly set in the tool definition
assert hasattr(with_tool, "kwargs")
tools = with_tool.kwargs.get("tools", [])
assert len(tools) == 1
tool_def = tools[0]
assert "function" in tool_def
if strict is None:
assert "strict" not in tool_def["function"]
else:
assert tool_def["function"].get("strict") is strict
resp = with_tool.invoke("Who was the 27 year old named Erick?")
assert isinstance(resp, AIMessage)

View File

@@ -0,0 +1,38 @@
"""Unit tests for ChatFireworks."""
from __future__ import annotations
from langchain_core.messages import AIMessage
from langchain_fireworks.chat_models import _convert_dict_to_message
def test_convert_dict_to_message_with_reasoning_content() -> None:
"""Test that reasoning_content is correctly extracted from API response."""
response_dict = {
"role": "assistant",
"content": "The answer is 42.",
"reasoning_content": "Let me think about this step by step...",
}
message = _convert_dict_to_message(response_dict)
assert isinstance(message, AIMessage)
assert message.content == "The answer is 42."
assert "reasoning_content" in message.additional_kwargs
expected_reasoning = "Let me think about this step by step..."
assert message.additional_kwargs["reasoning_content"] == expected_reasoning
def test_convert_dict_to_message_without_reasoning_content() -> None:
"""Test that messages without reasoning_content work correctly."""
response_dict = {
"role": "assistant",
"content": "The answer is 42.",
}
message = _convert_dict_to_message(response_dict)
assert isinstance(message, AIMessage)
assert message.content == "The answer is 42."
assert "reasoning_content" not in message.additional_kwargs